Мои личные заметки из класса машинного обучения. Эти примечания будут и дальше обновляться и улучшаться по мере того, как я продолжаю просматривать курс, чтобы по-настоящему понять его. Большое спасибо Джереми и Рэйчел, которые дали мне возможность учиться.

Уроки: 12345678910 11 12

Перед тем, как начать, вопрос: Можем ли мы суммировать взаимосвязь между гиперпараметрами случайного леса и их влиянием на переобучение, коллинеарность и т. д. [1:51] Абсолютно. Возвращаясь к тетрадке урока 1.

Интересующие гиперпараметры:

1. set_rf_samples

  • Определяет количество строк в каждом дереве. Поэтому, прежде чем мы начнем новое дерево, мы либо загружаем образец (т.е. выборку с заменой из всего этого), либо извлекаем подвыборку с меньшим количеством строк, а затем строим дерево оттуда.
  • Шаг 1: у нас есть весь большой набор данных, мы случайным образом извлекаем из него несколько строк и превращаем их в меньший набор данных. Из этого мы строим дерево.

  • Предполагая, что дерево остается сбалансированным, когда мы его выращиваем, сколько слоев будет в глубине этого дерева (при условии, что мы выращиваем его до тех пор, пока каждый лист не станет равным единице)? log2 (20000). Глубина дерева на самом деле не сильно меняется в зависимости от количества выборок, потому что она связана с журналом размера.
  • Когда мы спустимся вниз, сколько будет листовых узлов? 20К. У нас есть линейная зависимость между количеством листовых узлов и размером выборки. Поэтому, когда вы уменьшаете размер выборки, остается меньше окончательных решений. Следовательно, дерево будет менее богатым с точки зрения того, что оно может предсказывать, потому что оно принимает меньше разных индивидуальных решений, а также делает меньше бинарных выборов, чтобы добраться до этих решений.
  • Установка более низких значений выборки RF будет означать, что вы меньше переобучаете, но это также означает, что у вас будет менее точная индивидуальная модель дерева. Брейман, изобретатель случайного леса, описал это так: вы пытаетесь сделать две вещи, когда строите модель с мешком. Во-первых, каждое отдельное дерево / средство оценки должно быть максимально точным (поэтому каждая модель является сильной прогностической моделью). Но что касается оценок, корреляция между ними настолько низкая, насколько это возможно, поэтому, когда вы усредняете их вместе, вы получаете нечто обобщающее. Уменьшая число set_rf_samples, мы фактически уменьшаем мощность оценщика и увеличиваем корреляцию - приведет ли это к лучшему или худшему результату набора валидации для вас? Это зависит. Это своего рода компромисс, который вам нужно найти, когда вы создаете модели машинного обучения.

Вопрос о oob=True [6:46]. Все, что делает oob=True, это говорит о том, какая у вас подвыборка (это может быть образец начальной загрузки или подвыборка), берет все остальные строки (для каждого дерева), помещает их в другой набор данных и вычисляет ошибку по ним. Так что на самом деле это никак не влияет на тренировку. Это просто дает вам дополнительную метрику, которая является ошибкой OOB. Так что, если у вас нет набора для проверки, это позволяет вам бесплатно получить своего рода набор для квази-проверки.

Вопрос: Если я не сделаю set_rf_samples, как это будет называться? [7:55] По умолчанию, если вы скажете reset_rf_samples, это заставит его выполнить загрузку, поэтому он будет производить выборку нового набора данных такого же размера, как исходный, но с заменой.

Второе преимущество set_rf_samples в том, что вы можете бегать быстрее [8:28]. В частности, если вы работаете с действительно большим набором данных, например с сотней миллионов строк, будет невозможно запустить его для всего набора данных. Таким образом, вам придется либо самому выбрать подвыборку, прежде чем начать, либо вы set_rf_samples.

2. min_samples_leaf [8:48]

Раньше мы предполагали, что min_samples_leaf=1, если установлено значение 2, новая глубина дерева будет log2(20000)-1. Каждый раз, когда мы удваиваем min_samples_leaf, мы удаляем один слой из дерева и вдвое уменьшаем количество листовых узлов (т.е. 10k). Результатом увеличения min_samples_leaf является то, что теперь каждый из наших листовых узлов имеет более одного элемента, поэтому мы собираемся получить более стабильное среднее значение, которое мы вычисляем в каждом дереве. У нас немного меньше глубины (т.е. нам нужно принимать меньше решений) и меньше количество листовых узлов. Итак, опять же, мы ожидаем, что результатом этого узла будет то, что каждый оценщик будет менее предсказуемым, но оценщики также будут менее коррелированными. Так что это может помочь нам избежать переобучения.

Вопрос: я не уверен, будет ли каждый листовой узел иметь ровно два узла [10:33]. Нет, не обязательно их ровно два. Примером неравномерного разделения, такого как листовой узел, содержащий 100 элементов, является случай, когда все они одинаковы с точки зрения зависимой переменной (предположим, что любая из них, но гораздо более вероятно, будет зависимой). Итак, если вы дойдете до листового узла, где каждый из них имеет одинаковую цену на аукционе, или в классификации каждый из них является собакой, то вы не можете сделать никакого разделения, которое улучшило бы вашу информацию. Помните, что информация - это термин, который мы используем в общем смысле в случайном лесу, чтобы описать разницу в дополнительной информации, которую мы создаем из разбиения, и насколько мы улучшаем модель. Таким образом, вы часто будете видеть это слово прирост информации, которое означает, насколько улучшилась модель, добавив дополнительную точку разделения, и она может быть основана на RMSE или кросс-энтропии, или насколько отличается от стандартных отклонений и т. Д.

Так что это второе, что мы можем сделать. Это ускорит наше обучение, потому что у него на один набор решений меньше. Несмотря на то, что на один набор решений меньше, в этих решениях содержится столько же данных, сколько в предыдущем наборе. Таким образом, каждый слой дерева может занимать вдвое больше, чем предыдущий слой. Так что это определенно может ускорить обучение и лучше обобщить.

3. max_features [12:22]

При каждом разбиении он будет произвольно выбирать столбцы (в отличие от set_rf_samples выбора подмножества строк для каждого дерева). Звучит как небольшая разница, но на самом деле это совсем другой взгляд на это. Мы делаем set_rf_samples, поэтому мы извлекаем наш дополнительный образец или образец начальной загрузки, который сохраняется для всего дерева, и у нас там есть все столбцы. При использовании max_features=0.5 в каждом разделе мы выбираем другую половину функций. Причина, по которой мы это делаем, заключается в том, что мы хотим, чтобы деревья были как можно более богатыми. В частности, если вы делали только небольшое количество деревьев (например, 10 деревьев) и выбрали один и тот же набор столбцов на всем протяжении дерева, вы не получите особого разнообразия в том, какие вещи он может найти. Таким образом, по крайней мере в теории, кажется, что это что-то, что даст нам лучший набор деревьев, выбирая разные случайные подмножества функций в каждой точке принятия решения.

Общий эффект max_features такой же - это будет означать, что каждое отдельное дерево, вероятно, будет менее точным, но деревья будут более разнообразными. В частности, здесь это может быть критичным, потому что представьте, что у вас есть одна функция, которая является суперпрогнозной. Это настолько предсказуемо, что каждая случайная подвыборка, которую вы просматриваете, всегда начинается с разбиения по одному и тому же признаку, тогда деревья будут очень похожими в том смысле, что все они имеют одинаковое начальное разбиение. Но могут быть и другие интересные начальные расщепления, потому что они создают разные взаимодействия переменных. Таким образом, к половине времени, когда эта функция не будет доступна даже наверху дерева, по крайней мере, половина дерева будет иметь другое начальное разделение. Это определенно может дать нам больше вариантов и, следовательно, может помочь нам создать более обобщенные деревья, которые имеют меньшую корреляцию друг с другом, даже если отдельные деревья, вероятно, не будут такими предсказуемыми.

На практике, когда вы добавляете больше деревьев, если у вас max_features=None, он будет использовать все функции каждый раз. Тогда с очень небольшим количеством деревьев это все еще может дать вам довольно хорошую ошибку. Но по мере того, как вы создаете больше деревьев, это не поможет, потому что все они очень похожи, поскольку все они пробуют каждую переменную. Где еще, если вы скажете max_features=sqrt или log2, тогда, когда мы добавим больше оценок, мы увидим улучшения, так что между этими двумя есть интересное взаимодействие. Таблица выше взята из документации scikit-learn.

4. Вещи, которые совершенно не влияют на нашу тренировку [16:32]

n_jobs: просто указывает, на скольких процессорах или ядрах мы работаем, что позволяет ускорить работу до определенного момента. Вообще говоря, делая это больше 8 или около того, они могут иметь убывающую прибыль. -1 говорит, что нужно использовать все свои ядра. Кажется странным, что по умолчанию используется одно ядро. Вы определенно получите большую производительность, используя больше ядер, потому что в настоящее время у всех вас есть компьютеры с более чем одним ядром.

oob_score=True: Это просто позволяет нам видеть оценку OOB. Если у вас был set_rf_samples довольно маленький по сравнению с большим набором данных, вычисление OOB потребует вечности. Надеюсь, в какой-то момент мы сможем исправить библиотеку, чтобы этого не произошло. Нет причин, по которым должно быть так, но прямо сейчас библиотека работает именно так.

Так что это наши ключевые базовые параметры, которые мы можем изменить [17:38]. Есть еще кое-что, что вы можете увидеть в документах или shift+tab, чтобы ознакомиться с ними, но те, которые вы видели, - это те, с которыми я считаю полезными поиграть, так что не стесняйтесь играть и с другими. В целом, эти значения работают хорошо:

max_features: Нет, 0,5, sqrt, log2

min_samples_leaf: 1, 3, 5, 10, 25, 100… По мере того, как вы увеличиваете, если вы замечаете, что к тому времени, когда вы дойдете до 10, уже становится хуже, тогда нет смысла идти дальше. Если вы дойдете до 100, но все равно будет лучше, вы можете продолжать попытки.

Интерпретация случайного леса [18:50]

Интерпретация случайного леса - это то, что вы можете использовать для создания действительно крутых ядер Kaggle. Уверенность, основанная на вариативности дерева, - это то, чего больше нигде не существует. Важность функций определенно имеет значение, и это уже присутствует во многих ядрах Kaggle. Если вы смотрите на соревнование или набор данных, где никто не указывал на важность функций, то, если вы сделаете это первым, всегда выиграете много голосов, потому что самое важное - это то, какие функции важны.

Уверенность, основанная на древесной дисперсии [20:43]

Как я уже упоминал, когда мы выполняем интерпретацию модели, я склоняюсь к некоторому подмножеству - чему-то достаточно маленькому, чтобы я мог запустить модель менее чем за 10 секунд, потому что нет смысла запускать сверхточную модель. Пятидесяти тысяч более чем достаточно, чтобы каждый раз, когда вы запускаете интерпретацию, вы получаете те же результаты, и если это так, то вы уже используете достаточно данных.

set_rf_samples(50000)
m = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, 
        max_features=0.5, n_jobs=-1, oob_score=True)
m.fit(X_train, y_train)
print_score(m)

Важность функции [21:14]

Мы узнали, что это работает, случайным образом перетасовывая столбец, каждый столбец по одному, а затем наблюдая, насколько точна предварительно обученная модель, когда вы передаете ее во все данные, как и раньше, но с перетасовкой одного столбца.

Некоторые вопросы, которые я получил после занятий, напомнили мне, что очень легко недооценить силу и магию этого подхода. Чтобы объяснить, я упомяну пару вопросов, которые я слышал.

Один вопрос заключался в том, «что, если бы мы просто брали по одному столбцу за раз и создавали дерево только по этому столбцу». Затем мы увидим, какое дерево столбца является наиболее предсказуемым. Почему это может ввести в заблуждение относительно важности функции? Мы потеряем взаимодействие между функциями. Если мы просто перемешаем их, это добавит случайности, и мы сможем фиксировать взаимодействия и важность функции. Этот вопрос взаимодействия - немаловажная деталь. Это очень важно. Подумайте об этом наборе данных о бульдозерах, где, например, есть одно поле с названием «год изготовления» и другое поле с названием «дата продажи». Если задуматься, становится совершенно очевидно, что важна комбинация этих двух факторов. Другими словами, разница между ними заключается в том, сколько лет было тому оборудованию, когда оно было продано. Так что, если мы включим только одну из них, мы сильно недооценим, насколько важна эта функция. Но вот действительно важный момент. Практически всегда можно создать простую логистическую регрессию, которая не хуже любого случайного леса, если вы заранее точно знаете, какие переменные вам нужны, как именно они взаимодействуют и как именно их нужно преобразовать. В этом случае, например, мы могли бы создать новое поле, которое было бы равно году продажи минус год изготовления, и мы могли бы скормить его модели и получить это взаимодействие для нас. Но дело в том, что мы этого никогда не узнаем. Вы можете догадаться об этом - я думаю, что некоторые из этих вещей взаимодействуют таким образом, и я думаю, что нам нужно взять журнал и так далее. Но правда в том, что в том, как устроен мир, в причинных структурах есть множество вещей, взаимодействующих множеством тонких способов. Вот почему использование деревьев, будь то машины для повышения градиента или случайные леса, работает так хорошо.

Комментарий Терренса: Одна вещь, которая укусила меня много лет назад, заключалась в том, что я пытался делать одну переменную за раз, думая: Ну ладно, я выясню, какая из них больше всего коррелирует с зависимой переменной [24: 45 ]. Но чего он не разделяет, так это того, что если все переменные в основном копируются в одну и ту же переменную, тогда все они будут казаться одинаково важными, но на самом деле это всего лишь один фактор.

Это верно и здесь. Если бы столбец отображался дважды, перетасовка этого столбца не сильно ухудшила бы модель. Если вы подумаете о том, как он построен, особенно если бы у нас было max_features=0.5, в некоторых случаях мы получим версию A столбца, в некоторых случаях мы получим версию B столбца. Таким образом, в половине случаев перетасовка версии A столбца сделает дерево немного хуже, в половине случаев оно сделает столбец B немного хуже, и, таким образом, будет показано, что обе эти функции несколько важны. И он разделит важность между двумя функциями. Вот почему «коллинеарность» (я пишу коллинеарность, но это означает, что они линейно связаны, так что это не совсем правильно) - но именно поэтому наличие двух переменных, которые тесно связаны друг с другом, или нескольких переменных, которые тесно связаны друг к другу означает, что вы часто недооцениваете их важность, используя эту технику случайного леса.

Вопрос: После того, как мы перемешали и получили новую модель, какие именно единицы имеют такую ​​важность? Это изменение в R² [26:26]? Это зависит от библиотеки, которую мы используем. Так что единицы вроде… я никогда о них не думаю. Я просто знаю, что в этой конкретной библиотеке я бы предпочел использовать 0,005. Но все, что меня действительно интересует, - это эта картинка (важность функции упорядочена для каждой переменной):

Затем увеличьте масштаб, превратив его в гистограмму, а затем найдите, где он становится плоским (~ 0,005).

Поэтому я удалил их на этом этапе и проверил, не ухудшилась ли оценка валидации.

to_keep = fi[fi.imp>0.005].cols; len(to_keep)

Если стало хуже, я просто немного уменьшу отсечку, пока не станет хуже. Так что единицы измерения этого не имеют большого значения. Кстати, позже мы узнаем о втором способе определения важности переменной.

Какова цель их удаления [27:42]? Посмотрев на наш график важности характеристик, мы видим, что единицы меньше 0,005 - это длинный скучный хвост. Итак, я сказал, что давайте просто попробуем захватить столбцы, в которых он больше 0,005, создать новый фрейм данных с именем df_keep, который равен df_train только с этими сохраненными столбцами, создать новые наборы для обучения и проверки только с этими столбцами, создать новый случайный лес, и посмотрите, как оценивается набор для проверки. И набор проверки RMSE изменился, и они стали немного лучше. Так что, если они примерно такие же или немного лучше, то, на мой взгляд, это такая же хорошая модель, но теперь она проще.

Поэтому, когда я переделываю важность функции, коллинеарность уменьшается. В этом случае я увидел, что производство того года пошло от того, чтобы быть немного лучше, чем следующая лучшая вещь (соединительная система), но теперь это намного лучше. Так что, похоже, это определенно изменило важность этих функций и, надеюсь, дало мне больше информации об этом.

Вопрос: Как это помогло нашей модели [29:30]? Мы собираемся сейчас углубиться в это. По сути, это говорит нам, что, например, если мы ищем, как мы поступаем с отсутствующим значением, есть ли в данных шум, если это категориальная переменная с высокой мощностью - все это разные шаги, которые мы предпримем. Так, например, если это была категориальная переменная с высокой мощностью, которая изначально была строкой, может быть, fiProductClassDesc в приведенном выше случае, я помню, что одна из тех, на которые мы смотрели на днях, сначала была типом транспортного средства, а затем дефисом, а затем размер автомобиля. Мы могли бы посмотреть на это и сказать: «Хорошо, это была важная колонка. Давайте попробуем разделить его на две части по дефису, а затем возьмем тот бит, который соответствует его размеру, проанализируем его и преобразуем в целое число ". Мы можем попробовать заняться разработкой функций. По сути, пока вы не узнаете, какие из них важны, вы не знаете, на чем сосредоточить время разработки этой функции. Вы можете поговорить со своим клиентом или людьми, ответственными за создание этих данных. Если бы вы на самом деле работали в компании, производящей бульдозерные аукционы, вы могли бы сейчас пойти к настоящим аукционистам и сказать: «Я действительно удивлен, что система сцепки, похоже, так сильно влияет на решения людей о ценообразовании. Как вы думаете, почему? и они могут сказать вам: «О, это на самом деле потому, что только эти классы транспортных средств имеют системы сцепления или только этот производитель имеет системы сцепления. Так что, откровенно говоря, это на самом деле говорит вам не о системах сопряжения, а о другом. Ой, это напомнило мне, это еще кое-что, что мы на самом деле измерили. Он находится в этом другом CSV-файле. Я тебе его принесу. Так вы сможете сосредоточить свое внимание.

Вопрос. Как вы знаете, в эти выходные у меня была небольшая забавная задача. Я ввел пару сумасшедших вычислений в свой случайный лес, и внезапно они стали похожи на боже мой, это самые важные переменные, когда-либо подавляющие все остальные. Но затем я получил ужасную оценку, а затем потому, что теперь, когда я думаю, что мои оценки вычислены правильно, я заметил, что важность взлетела до небес, но набор проверки все еще был плохим или стал еще хуже. Это потому, что каким-то образом эти вычисления позволяют обучению почти как карта идентификаторов, точно такой, каким должен был быть ответ для обучения, но, конечно, это не распространяется на набор проверки. Это то, что я наблюдал [31:33]? Есть две причины, по которым ваш результат проверки может быть не очень высоким.

Итак, мы получили эти пять чисел: RMSE обучения, проверки, R² обучения, проверки и R2 OOB. На то есть две причины, и на самом деле в этом соревновании Kaggle нас действительно волнует RMSE набора для проверки, если мы создали хороший набор для проверки. В случае с Терренсом он говорит, что RMSE валидации ухудшился, когда я провел некоторую разработку функций. Это почему? Есть две возможные причины.

  • Причина первая в том, что вы переобучаете. Если вы переобучаете, то ваш OOB также ухудшится. Если вы делаете огромный набор данных с маленьким set_rf_samples, поэтому вы не можете использовать OOB, тогда вместо этого создайте второй набор проверки, который представляет собой случайную выборку, и сделайте это. Другими словами, если ваш OOB или набор для проверки случайной выборки стали намного хуже, значит, вы переобучаете. Я думаю, что в твоем случае, Терренс, это маловероятно, потому что случайные леса не так уж и подходят. Очень сложно заставить их так сильно переобучиться, если вы не используете какие-то действительно странные параметры, например, только один оценщик. Как только у нас будет десять деревьев, должно быть достаточно вариаций, которые вы определенно сможете переоснастить, но не настолько, чтобы вы не испортили свой результат валидации, добавив переменную. Так что, я думаю, вы обнаружите, что это, вероятно, не так, но это легко проверить. А если это не так, то вы увидите, что ваш показатель OOB или ваш показатель проверки на случайной выборке не стал намного хуже.
  • Вторая причина, по которой ваша оценка валидации может ухудшиться, если ваша оценка OOB не стала хуже, вы не переобучаете, но ваша оценка валидации ухудшилась, что означает, что вы делаете что-то, что верно в обучающем наборе, но не верно в набор проверки. Таким образом, это может произойти только в том случае, если ваш набор для проверки не является случайной выборкой. Например, в конкурсе бульдозеров или в конкурсе продуктовых магазинов мы намеренно создали набор для проверки для другого диапазона дат - для последних двух недель. Так что, если что-то другое произошло за последние две недели по сравнению с предыдущими неделями, вы можете полностью нарушить свой набор проверки. Например, если был какой-то уникальный идентификатор, который отличается в двух периодах дат, вы могли бы научиться идентифицировать вещи, используя этот идентификатор в обучающем наборе. Но тогда за последние две недели может быть совершенно другой набор идентификаторов или другой набор поведения, это может стать намного хуже. Однако то, что вы описываете, не является обычным явлением. Поэтому я немного скептически настроен - это может быть ошибка, но, надеюсь, теперь есть достаточно вещей, которые вы можете использовать, чтобы выяснить, является ли это ошибкой. Нам будет интересно услышать то, что вы узнали.

Линейная регрессия, логистическая регрессия [36:01]

Это важность функции. Я хотел бы сравнить это с тем, как важность функций обычно определяется в промышленности и академических сообществах за пределами машинного обучения, например, в психологии, экономике и т. Д. Вообще говоря, люди в такой среде склонны использовать какую-то линейную регрессию, логистическую регрессию, общие линейные модели. Они начинают со своего набора данных и говорят, что я собираюсь предположить, что я знаю тип параметрической связи между моими независимыми переменными и моей зависимой переменной. Итак, я собираюсь предположить, что это линейная связь или линейная связь с функцией связи, такой как сигмоид, для создания логистической регрессии. Предполагая, что я это уже знаю, теперь я могу записать это в виде уравнения. Итак, если у вас есть x1, x2 и так далее.

Я могу сказать, что мои значения y равны ax1 + bx2 = y, поэтому я могу достаточно легко определить важность функции, просто взглянув на эти коэффициенты и увидев, какой из них самый высокий, особенно если у вас сначала нормализовал данные. Есть очень распространенный трюк, заключающийся в том, что это каким-то образом более точный, более чистый, в каком-то смысле лучший способ определения важности функций, но это не может быть дальше от истины. Если вы подумаете об этом, если вам не хватало взаимодействия, если вам не хватало преобразования, которое вам было нужно, или если у вас есть какой-либо способ быть чем-то менее чем на 100% идеальным во всей вашей предварительной обработке, так что ваша модель является абсолютной верная истинность ситуации - если у вас нет всего этого правильного, тогда ваши коэффициенты неверны. Ваши коэффициенты говорят вам: «В вашей совершенно неправильной модели эти вещи настолько важны», что по сути бессмысленно. В другом случае важность функции случайного леса говорит вам в этом чрезвычайно высоком параметре, очень гибкой функциональной форме с небольшими статистическими допущениями, если они вообще есть, - это важность вашей функции. Поэтому я был бы очень осторожен.

Опять же, я не могу достаточно подчеркнуть это, когда вы покидаете эту программу, вы гораздо чаще будете видеть, как люди говорят о коэффициентах логистической регрессии, чем вы увидите, как они говорят о важности случайных переменных леса. И каждый раз, когда вы видите, что это происходит, вы должны очень скептически относиться к тому, что вы видите. Каждый раз, когда вы читаете статью по экономике или психологии, или отдел маркетинга говорит вам, что эта регрессия или что-то еще, все эти коэффициенты будут сильно искажены какими-либо проблемами в модели. Кроме того, если они сделали так много предварительной обработки, что на самом деле модель довольно точна, то теперь вы смотрите на коэффициенты, которые будут похожи на коэффициент некоторого главного компонента из PCA или коэффициент некоторого расстояния от некоторого кластера. или что-то. В любом случае, их очень-очень сложно интерпретировать. Это не фактические переменные. Так что это своего рода два варианта, которые я видел, когда люди пытаются использовать классические статистические методы для определения эквивалента важности переменной. Я думаю, что ситуация начинает медленно меняться. Есть некоторые области, которые начинают понимать, что это совершенно неправильный способ делать что-то. Но с тех пор, как появились случайные леса, прошло почти 20 лет, так что это занимает много времени. Люди говорят, что единственный путь, по которому знания действительно развиваются, - это когда умирает предыдущее поколение, и это отчасти правда. В частности, академики, они делают карьеру, преуспев в определенных делах, и часто люди не замечают, что это уже не лучший способ делать что-то до тех пор, пока не придет следующее поколение. Думаю, вот что здесь произошло.

Теперь у нас есть модель, которая на самом деле не лучше прогнозирующей точности, но мы хорошо понимаем, что есть четыре основных важных вещи [40:38]: YearMade, Coupler_System, ProductSize, fiProductClassDesc.

Одно горячее кодирование [41:00]

Однако есть еще кое-что, что мы можем сделать, а именно что-то, что называется одним горячим кодированием. Итак, мы подошли к тому месту, где мы говорили о категориальной переменной. Помните, категориальная переменная, допустим, у нас есть строка high, low, medium (порядок, который мы получили, был довольно странным - по умолчанию в алфавитном порядке). Итак, мы сопоставили его с 0, 1, 2. К тому времени, когда он попадает в наш фрейм данных, теперь это число, поэтому случайный лес не знает, что это была изначально категория - это просто число. Итак, когда создается случайный лес, он в основном говорит: «О, больше 1» или «нет». Или это больше нуля, или нет. По сути, это два возможных решения, которые он мог бы принять. Для чего-то с 5 или 6 диапазонами может оказаться, что на самом деле интересен только один из уровней категории. Возможно, единственное, что имело значение, - это неизвестно. Возможно, незнание его размера как-то влияет на цену. Так что, если мы хотели иметь возможность распознать это, и особенно если так получилось, что способ, которым были закодированы числа, был неизвестен, оказался посередине, тогда потребуется два разделения, чтобы добраться до точки, где мы можем видеть что на самом деле это неизвестно, что имеет значение. Так что это немного неэффективно, и мы тратим зря вычисления дерева. Расточительное вычисление дерева имеет значение, потому что каждый раз, когда мы выполняем разбиение, мы уменьшаем вдвое объем данных, по крайней мере, для того, чтобы провести дополнительный анализ. Таким образом, наше дерево станет менее богатым и менее эффективным, если мы не будем предоставлять данные таким образом, чтобы оно было удобно для выполнения необходимой работы.

Вместо этого мы могли бы создать 6 столбцов для каждой категории, и каждый столбец содержал бы единицы и нули. После добавления 6 дополнительных столбцов к нашему набору данных случайный лес теперь может выбрать один из них и сказать: о, давайте взглянем на is_unknown. Я могу подобрать один вариант: 1 против 0. Давайте посмотрим, что из этого получится. Таким образом, теперь у него есть возможность за один шаг вывести один уровень категории, и этот вид кодирования называется горячим кодированием. Для многих типов моделей машинного обучения нечто подобное необходимо. Если вы выполняете логистическую регрессию, вы не можете ввести категориальную переменную, которая идет через пять, потому что явно не существует письменной линейной связи между этим и чем-либо. Итак, одно горячее кодирование, многие люди ошибочно полагают, что для всего машинного обучения требуется одно горячее кодирование. Но в этом случае я собираюсь показать вам, как мы могли бы использовать его по желанию, и посмотрю, может ли он иногда улучшать ситуацию.

Вопрос: если у нас есть шесть категорий, как в этом случае, возникнут ли проблемы с добавлением столбца для каждой из категорий? В линейной регрессии, если есть шесть категорий, мы должны делать это только для пяти из них [45:17]. Вы, конечно, можете сказать, что давайте не будем беспокоиться о добавлении is_medium, потому что мы можем вывести его из других пяти. Я бы посоветовал включить его в любом случае, потому что в противном случае случайный лес должен принять пять решений, чтобы добраться до этой точки. Причина, по которой вам не нужно включать его в линейные модели, заключается в том, что линейные модели ненавидят коллинеарность, но нас это не волнует.

Таким образом, мы можем достаточно легко выполнить одно горячее кодирование, и способ, которым мы это делаем, заключается в том, что мы передаем один дополнительный параметр в proc_df, который является максимальным количеством категорий (max_n_cat). Итак, если мы скажем, что их семь, то все, что меньше семи уровней, будет превращено в набор столбцов с горячим кодированием.

df_trn2, y_trn, nas = proc_df(df_raw, 'SalePrice', max_n_cat=7) X_train, X_valid = split_vals(df_trn2, n_trn)  
m = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, 
       max_features=0.6, n_jobs=-1, oob_score=True) 
m.fit(X_train, y_train) 
print_score(m)
[0.2132925755978791, 0.25212838463780185, 0.90966193351324276, 0.88647501408921581, 0.89194147155121262]

Как будто у почтового индекса более шести уровней, так что оставим это число. Вообще говоря, вы, вероятно, не захотите использовать одно горячее кодирование почтового индекса, потому что это просто создаст массу данных, проблемы с памятью, проблемы с вычислениями и так далее. Так что это еще один параметр, с которым вы можете поиграть.

Итак, если я попробую, запустив случайный лес как обычно, вы увидите, что происходит с R² набора проверки и с RMSE набора проверки. В этом случае я обнаружил, что стало немного хуже. Это не всегда так, и это будет зависеть от вашего набора данных. Это зависит от того, есть ли у вас набор данных, в котором отдельные категории имеют тенденцию быть довольно важными, или нет. В данном конкретном случае это не сделало его более предсказуемым. Однако то, что он сделал, это то, что теперь у нас есть другие функции. proc_df помещает имя переменной, подчеркивание и имя уровня. Итак, что интересно, выяснилось, что раньше в нем говорилось, что ограждение было в некоторой степени важным. Когда мы делаем это как одно горячее кодирование, на самом деле говорится, что Enclosure_EROPS w AC - самое важное. Так что, по крайней мере, с целью интерпретации вашей модели вы всегда должны пробовать одну горячую кодировку нескольких ваших переменных. Я часто нахожу где-то около 6 или 7 неплохих. Вы можете попробовать сделать это число как можно большим, чтобы вычисление не занимало вечность, а важность функции не включала в себя действительно крошечные уровни, которые не представляют интереса. Это зависит от вас, но в данном случае мне это показалось очень интересным. Это ясно говорит мне, что мне нужно узнать, что такое Enclosure_EROPS w AC и почему это важно, потому что сейчас это ничего не значит для меня, но это самая важная вещь. Так что я должен это выяснить.

Вопрос: Вы можете объяснить, как работает изменение максимального количества категорий? Потому что мне кажется, что есть пять категорий или шесть категорий [49:15]. Все, что он делает, - это, например, столбец с названием почтовый индекс, диапазон использования и пол. Скажем, почтовый индекс имеет 5000 уровней. Количество уровней в категории мы называем ее мощностью. Таким образом, он имеет мощность 5000. Диапазон использования может иметь количество элементов равное шести. У секса два элемента. Итак, когда proc_df проходит и говорит: Хорошо, это категориальная переменная, следует ли мне сразу ее кодировать? Он сравнивает число элементов с max_n_cat и говорит, что 5000 больше семи, поэтому я не использую горячее кодирование. Затем он переходит в полосу использования - 6 меньше 7, поэтому я делаю одно горячее кодирование. Это касается секса, а 2 меньше 7, так что одно горячее кодирование тоже. Таким образом, он просто говорит для каждой переменной, как я решаю, кодировать ее или нет. Как только мы решаем выполнить горячее кодирование, исходная переменная не сохраняется.

Если вы действительно пытались превратить свои порядковые переменные в правильные порядковые номера, использование proc_df может это разрушить. Простой способ избежать этого - если мы знаем, что всегда хотим использовать коды для диапазона использования, вы можете просто заменить его:

Теперь это целое число. Так что это никогда не изменится.

Удаление избыточных функций [54:57]

Мы уже видели, как переменные, которые в основном измеряют одно и то же, могут сбивать с толку нашу важность переменных. Они также могут сделать наш случайный лес немного менее хорошим, потому что для этого требуется больше вычислений, и есть больше столбцов для проверки. Итак, мы собираемся проделать еще немного работы, чтобы попытаться удалить избыточные функции. Я делаю это так, как называется «дендрограмма». И это своего рода иерархическая кластеризация.

Кластерный анализ - это то, где вы пытаетесь посмотреть на объекты, они могут быть либо строками в наборе данных, либо столбцами, и найти, какие из них похожи друг на друга. Часто вы увидите, как люди особенно говорят о кластерном анализе, они обычно ссылаются на строки данных, говорят: «Давайте построим график» и найдем кластеры. Распространенный тип кластерного анализа, если позволяет время, мы можем поговорить об этом более подробно, называется k-средними. В основном это когда вы предполагаете, что у вас вообще нет меток, и вы берете пару точек данных наугад, и постепенно находите те, которые находятся рядом с ними, перемещаете их все ближе и ближе к центроидам, и вы повторяете это снова и снова. Это итеративный подход: вы сообщаете ему, сколько кластеров вы хотите, и он сообщит вам, где, по его мнению, находятся классы.

Действительно малоиспользуемый метод (20 или 30 лет назад он был намного популярнее, чем сегодня) - это иерархическая кластеризация, также известная как агломерированная кластеризация. В иерархической или агломерированной кластеризации мы смотрим на каждую пару объектов и говорим, какие два объекта являются ближайшими. Затем мы берем ближайшую пару, удаляем их и заменяем их средней точкой. Затем повторяйте это снова и снова. Поскольку мы удаляем точки и заменяем их их средними значениями, вы постепенно уменьшаете количество точек путем попарного объединения. Круто то, что это можно построить.

from scipy.cluster import hierarchy as hc
corr = np.round(scipy.stats.spearmanr(df_keep).correlation, 4)
corr_condensed = hc.distance.squareform(1-corr)
z = hc.linkage(corr_condensed, method='average')
fig = plt.figure(figsize=(16,10))
dendrogram = hc.dendrogram(z, labels=df_keep.columns, 
      orientation='left', leaf_font_size=16)
plt.show()

Вот так. Вместо того, чтобы смотреть на точки, вы смотрите на переменные, и мы можем увидеть, какие две переменные наиболее похожи. saleYear и saleElapsed очень похожи. Итак, по горизонтальной оси здесь показано, насколько похожи две сравниваемые точки. Если они ближе правее, значит, они очень похожи. Итак, saleYear и saleElapsed были объединены, и они были очень похожи.

В данном случае я на самом деле использовал R. Спирмена. Вы, ребята, уже знакомы с коэффициентами корреляции? Таким образом, корреляция почти такая же, как и у R², но это между двумя переменными, а не между переменной и ее предсказанием. Проблема с нормальной корреляцией в том, что если у вас есть данные, которые выглядят так, вы можете провести корреляцию и получить хороший результат.

Но если у вас есть данные, которые выглядят так, и вы пытаетесь провести корреляцию (предполагая линейность), это не очень хорошо.

Итак, есть вещь, называемая ранговой корреляцией, которая представляет собой действительно простую идею. Замените каждую точку ее рангом.

Слева направо мы ранжируем от 1, 2,… 6. Затем вы проделаете то же самое с осью Y. Затем вы создаете новый график, на котором вы наносите не данные, а ранжирование данных. Если вы думаете об этом, ранг этого набора данных будет выглядеть как точная линия, потому что каждый раз, когда что-то было больше на оси x, оно также было больше на оси y. Итак, если мы сделаем корреляцию по рангу, это называется ранговой корреляцией.

Поскольку мы хотим найти столбцы, которые похожи так, чтобы случайный лес нашел их похожими (случайные леса не заботятся о линейности, они просто заботятся о порядке), поэтому ранговая корреляция - это правильный способ думать об этом [ 1:00:05]. Итак, R Спирмена - это название наиболее распространенной ранговой корреляции. Но вы можете буквально заменить данные на их ранг и отбросить их на обычную корреляцию, и вы получите в основном тот же ответ. Единственная разница в том, как обрабатываются связи, что является довольно незначительной проблемой.

Когда у нас есть корреляционная матрица, в основном вы делаете несколько стандартных шагов, чтобы превратить ее в дендрограмму, которую я должен искать в stackoverflow каждый раз, когда я это делаю. Вы в основном превращаете это в матрицу расстояний, а затем создаете что-то, что сообщает вам, какие вещи иерархически связаны друг с другом. Итак, это три стандартных шага, которые вам всегда нужно делать для создания дендрограммы:

corr_condensed = hc.distance.squareform(1-corr)
z = hc.linkage(corr_condensed, method='average')
dendrogram = hc.dendrogram(z, labels=df_keep.columns, 
      orientation='left', leaf_font_size=16)

Затем вы можете построить график [1:01:30]. saleYear и saleElapsed измеряют в основном одно и то же (по крайней мере, с точки зрения ранга), что неудивительно, потому что saleElapsed - это количество дней с первого дня в моем наборе данных, поэтому очевидно, что эти два почти полностью коррелированы. Grouser_Tracks, Hidraulics_Flow и Coupler_System, похоже, измеряют одно и то же. Это интересно, потому что помните, Coupler_System он сказал, что это было очень важно. Таким образом, это скорее подтверждает нашу гипотезу, что не имеет никакого отношения к тому, является ли это система сцепки, но имеет ли это какой-либо тип транспортного средства, имеющий такие особенности. ProductGroup и ProductGroupDesc, похоже, измеряют одно и то же, как и fiBaseModel и fiModelDesc. Как только мы пройдем через это, все внезапно станет еще дальше, так что я, вероятно, не буду об этом беспокоиться. Итак, мы собираемся рассмотреть эти четыре группы, которые очень похожи.

Если вы просто хотите узнать, насколько эта штука похожа на эту, лучше всего взглянуть на корреляционную матрицу Спирмена R [1:03:43]. Здесь не используется случайный лес. Измерение расстояния полностью основано на ранговой корреляции.

Затем я беру эти группы и создаю небольшую функцию get_oob (получение очков вне диапазона) [1:04:29]. Он создает случайный лес для некоторого фрейма данных. Я убеждаюсь, что взял этот фрейм данных и разделил его на набор для обучения и проверки, а затем вызываю fit и возвращаю оценку OOB.

def get_oob(df):
    m = RandomForestRegressor(n_estimators=30, min_samples_leaf=5, 
           max_features=0.6, n_jobs=-1, oob_score=True)
    x, _ = split_vals(df, n_trn)
    m.fit(x, y_train)
    return m.oob_score_

В основном то, что я собираюсь сделать, - это попытаться удалить каждую из этих 9 или около того переменных по одной и посмотреть, какие из них я могу удалить, и это не ухудшит оценку OOB.

get_oob(df_keep)
0.89019425494301454

И каждый раз, когда я запускаю это, я получаю немного разные результаты, так что на самом деле похоже, что в последний раз у меня было 6 вещей, а не 9. Итак, вы можете видеть, я просто провожу цикл по каждой из вещей, о которых я думаю, может быть, я смогу избавиться от этого, потому что это избыточно, и я распечатываю имя столбца и оценку OOB модели, которая обучается после того, как отбросила это. столбец.

for c in ('saleYear', 'saleElapsed', 'fiModelDesc', 'fiBaseModel', 
          'Grouser_Tracks', 'Coupler_System'):
    print(c, get_oob(df_keep.drop(c, axis=1)))

Оценка OOB для всего моего фрейма данных составляет 0,89, а затем после отбрасывания каждой из этих вещей практически ни одна из них не стала намного хуже. saleElapsed становится немного хуже, чем saleYear. Берт, похоже, почти все остальное, я могу отказаться только от проблемы с третьим десятичным знаком. Поэтому очевидно, что вам нужно запомнить дендрограмму. Возьмем fiModelDesc и fiBaseModel, они очень похожи друг на друга. Это говорит не о том, что я могу избавиться от них обоих, я могу избавиться от одного из них, потому что они, по сути, измеряют одно и то же.

saleYear 0.889037446375
saleElapsed 0.886210803445
fiModelDesc 0.888540591321
fiBaseModel 0.88893958239
Grouser_Tracks 0.890385236272
Coupler_System 0.889601052658

Тогда я пробую. Попробуем избавиться от по одному в каждой группе:

to_drop = ['saleYear', 'fiBaseModel', 'Grouser_Tracks']
get_oob(df_keep.drop(to_drop, axis=1))
0.88858458047200739

Мы перешли с 0,890 на 0,888, опять же, это так близко, что теряет смысл. Так что звучит хорошо. Чем проще, тем лучше. Итак, я собираюсь удалить эти столбцы из своего фрейма данных, а затем снова попробовать запустить полную модель.

df_keep.drop(to_drop, axis=1, inplace=True)
X_train, X_valid = split_vals(df_keep, n_trn)
np.save('tmp/keep_cols.npy', np.array(df_keep.columns))
keep_cols = np.load('tmp/keep_cols.npy')
df_keep = df_trn[keep_cols]

reset_rf_samples означает, что я использую весь свой загрузочный образец. С 40 оценщиками мы получили 0,907.

reset_rf_samples()
m = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, max_features=0.5, n_jobs=-1, oob_score=True)
m.fit(X_train, y_train)
print_score(m)
[0.12615142089579687, 0.22781819082173235, 0.96677727309424211, 0.90731173105384466, 0.9084359846323049]

Итак, у меня есть модель, которая меньше и проще, и я получаю за нее хорошие оценки. Итак, на данный момент я избавился от максимально возможного количества столбцов (те, которые либо не имели хорошей важности функции, либо были сильно связаны с другими переменными, и модель не стала значительно хуже. когда я их удалил).

Частичная зависимость [1:07:34]

Итак, теперь я нахожусь в той точке, где я хочу попытаться лучше понять свои данные, воспользовавшись преимуществами модели. И мы собираемся использовать так называемую частичную зависимость. Опять же, это то, что вы можете использовать в ядре Kaggle, и многие люди оценят это, потому что почти никто не знает о частичной зависимости, а это очень и очень мощный метод. Что мы собираемся сделать, так это выяснить, для важных функций, как они соотносятся с зависимой переменной. Давайте посмотрим.

from pdpbox import pdp
from plotnine import *

Опять же, поскольку мы выполняем интерпретацию, мы установим set_rf_samples на 50 000, чтобы все выполнялось быстро.

set_rf_samples(50000)

Мы возьмем наш фрейм данных, мы определим важность нашей функции и заметим, что мы используем max_n_cat, потому что на самом деле мне очень интересно видеть отдельные уровни для интерпретации.

df_trn2, y_trn, nas = proc_df(df_raw, 'SalePrice', max_n_cat=7)
X_train, X_valid = split_vals(df_trn2, n_trn)
m = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, 
       max_features=0.6, n_jobs=-1)
m.fit(X_train, y_train);

Вот 10 лучших:

plot_fi(rf_feat_importance(m, df_trn2)[:10]);

Давайте попробуем узнать больше об этих 10 лучших. YearMade - второй по важности. Итак, одна очевидная вещь, которую мы могли бы сделать, - это построить YearMade против saleElapsed, потому что, как мы уже говорили, кажется логичным, что они оба важны, но очень вероятно, что они объединены вместе, чтобы определить, сколько лет было продукту. когда было продано. Итак, мы могли бы попытаться сопоставить YearMade против saleElapsed, чтобы увидеть, как они соотносятся друг с другом.

df_raw.plot('YearMade', 'saleElapsed', 'scatter', alpha=0.01, figsize=(10,8));

И когда мы это делаем, мы получаем очень уродливый график [1:09:08]. Это показывает нам, что у YearMade на самом деле есть целая куча, которая составляет тысячу. Ясно, что именно здесь я хотел бы вернуться к клиенту и сказать: Хорошо, я предполагаю, что эти бульдозеры на самом деле не были произведены в 1000 году, и они, вероятно, сказали бы мне:« О да, это те, где мы не знаю, где это было сделано . Может быть, до 1986 года мы этого не отслеживали или, может быть, вещи, которые продаются в Иллинойсе, у нас нет этих данных и т. Д. - они скажут нам какую-то причину. Итак, чтобы лучше понять этот сюжет, я просто уберу их из этого раздела анализа, посвященного интерпретации. Мы просто возьмем то, где YearMade больше 1930.

x_all = get_sample(df_raw[df_raw.YearMade>1930], 500)
ggplot(x_all, aes('YearMade', 'SalePrice'))+stat_smooth(se=True, 
       method='loess')

Теперь посмотрим на взаимосвязь между YearMade и SalePrice. Есть действительно отличный пакет под названием ggplot. ggplot изначально был пакетом R (GG означает Грамматика графики). Грамматика графики - это очень мощный способ мышления о том, как создавать диаграммы очень гибким способом. Я не собираюсь много говорить об этом на этом занятии. В Интернете доступно много информации. Но я определенно рекомендую его как отличный пакет для использования. ggplot который вы можете pip установить, он уже является частью среды fast.ai. ggplot в Python имеет в основном те же параметры и API, что и версия R. Версия R гораздо лучше документирована, поэтому вы должны прочитать ее документацию, чтобы узнать, как ее использовать. Но в основном вы говорите: «Хорошо, я хочу создать график для этого фрейма данных (x_all). Когда вы создаете графики, большинство используемых вами наборов данных будут слишком большими для построения. Например, если вы сделаете диаграмму рассеяния, она создаст столько точек, что это просто большой беспорядок, и это займет целую вечность. Помните, что когда вы рисуете что-то, вы смотрите на это, поэтому нет смысла строить что-то с сотней миллионов выборок, когда, если вы использовали только сто тысяч, они будут идентичны пикселям. Вот почему я сначала звоню get_sample. get_sample просто берет случайную выборку.

ggplot(x_all, aes('YearMade', 'SalePrice'))+stat_smooth(se=True, 
       method='loess')

Итак, я просто возьму 500 точек из своего фрейма данных и построю график YearMade против SalePrice. aes означает «эстетика» - это основной способ настройки столбцов в ggplot. Еще есть странная вещь в ggplot, где «+» означает добавление элементов диаграммы. Итак, я собираюсь добавить более плавный. Часто вы обнаруживаете, что на диаграмме рассеяния очень трудно увидеть, что происходит, из-за слишком большой случайности. Или, иначе, сглаживание в основном создает небольшую линейную регрессию для каждого небольшого подмножества графика. Таким образом, он соединяется и позволяет увидеть красивую плавную кривую. Это основной способ рассмотрения одномерных отношений. Добавление стандартной ошибки, равной истине (se=True), также показывает мне доверительный интервал этого сглаживания. loess означает локально взвешенную регрессию, которая представляет собой идею выполнения множества небольших мини-регрессий.

Итак, мы можем видеть здесь [1:12:48], что отношения между YearMade и SalePrice повсюду, что не совсем то, чего мы ожидали. Я ожидал, что те вещи, которые продаются совсем недавно, вероятно, будут дороже из-за инфляции, и это более современные модели. Проблема в том, что когда вы смотрите на однофакторные отношения, подобные этой, возникает много коллинеарности - множество взаимодействий, которые теряются. Например, почему упала цена? Неужели это действительно потому, что вещи, сделанные между 1991 и 1997 годами, менее ценны? Или это на самом деле потому, что большинство из них было продано в то время, и, возможно, тогда была рецессия? Или, может быть, это было потому, что продукты, продаваемые в то время, намного больше людей покупали типы автомобилей, которые были менее дорогими? Для этого есть множество причин. Итак, снова, как специалисты по данным, вы будете постоянно видеть, что в компаниях, к которым вы присоединяетесь, люди будут приходить к вам с такими одномерными диаграммами, где они говорят: О, черт возьми, наши продажи в Чикаго исчезли. Их по-настоящему обманули . или люди больше не нажимают на это добавление, и они покажут вам диаграмму, которая выглядит так, и спросят, что произошло. В большинстве случаев ответ на вопрос что случилось? Состоит в том, что происходит что-то еще. Так, например, на самом деле в Чикаго на прошлой неделе мы проводили новую рекламную акцию, и поэтому наши доходы упали - это не потому, что люди больше не покупают вещи в Чикаго; цены были ниже .

Итак, что мы действительно хотим сделать, так это сказать: Ну, какова связь между SalePrice и YearMade при прочих равных условиях. «При прочих равных условиях в основном означает, что если бы мы продали что-то в 1990 г. по сравнению с 1980 г., и это было бы точно так же одному и тому же человеку на одном аукционе и т. Д., Какова была бы разница в цене? Для этого мы создаем так называемый график частичной зависимости [1:15:02].

x = get_sample(X_train[X_train.YearMade>1930], 500)

Есть действительно хорошая библиотека, о которой никто не слышал, называется pdp, которая строит эти частичные графики зависимости, и вот что происходит. У нас есть выборка из 500 точек данных, и мы собираемся сделать что-то действительно интересное. Мы собираемся взять каждый из этих пятисот случайно выбранных аукционов и составить из него небольшой набор данных.

Вот наш набор данных из 500 аукционов, а вот наши столбцы, одна из которых - это то, что нас интересует - YearMade. Теперь мы собираемся попытаться создать диаграмму, где мы скажем, при прочих равных в 1960 году, сколько вещи стоили на аукционах? Мы собираемся сделать это следующим образом: заменим столбец YearMade на столбец 1960. Мы собираемся копировать значение 1960 снова и снова полностью вниз. Теперь в каждой строке указан год 1960, и все остальные данные будут точно такими же. Мы собираемся взять наш случайный лес, мы собираемся пропустить все это через наш случайный лес, чтобы предсказать цену продажи. Это скажет нам обо всем, что было продано с аукциона, сколько, по нашему мнению, было бы продано, если бы эта вещь была сделана в 1960 году. И это то, что мы собираемся построить справа.

И мы собираемся сделать то же самое в 1961 году.

Вопрос: Для ясности, мы уже поместили случайный лес, а затем мы просто проводим новый год и смотрим, какой он определяет цену [1:17:10]? Да, так что это очень похоже на то, как мы указали важность. Но вместо того, чтобы случайным образом перемешивать столбец, мы собираемся заменить столбец постоянным значением. Случайное перемешивание столбца показывает, насколько он точен, если вы больше не используете этот столбец. Заменив весь столбец на постоянные оценки для нас, за сколько мы продали бы этот продукт на этом аукционе в тот день в том месте, если бы этот продукт был произведен в 1961 году. Затем мы берем среднее значение всех продажных цен, которые мы вычислить из этого случайного леса. Мы делаем это в 1961 году и получаем это значение:

def plot_pdp(feat, clusters=None, feat_name=None):
    feat_name = feat_name or feat
    p = pdp.pdp_isolate(m, x, feat)
    return pdp.pdp_plot(p, feat_name, plot_lines=True, 
                        cluster=clusters is not None, 
                        n_cluster_centers=clusters)
plot_pdp('YearMade')

Итак, график частичной зависимости (PDP) здесь показывает нам, что каждая из этих голубых линий на самом деле показывает нам все 500 линий [1:18:01]. Итак, для строки номер 1 в нашем наборе данных, если мы продали ее в 1960 году, мы собираемся проиндексировать ее до нуля, поэтому назовем это нулем. Если бы мы продали его в 1970 году на этом конкретном аукционе, он был бы здесь и т. Д. Мы фактически строим все 500 прогнозов того, сколько стоил бы каждый из этих 500 аукционов, если бы мы заменили его YearMade каждым из этих различных значений. Тогда эта темная линия - среднее. Таким образом, это говорит нам, сколько мы в среднем продали бы на всех этих аукционах, если бы все эти продукты были действительно произведены в 1985, 1990, 1993 и т. Д. Итак, вы можете видеть, что здесь произошло, по крайней мере, в тот период, когда мы иметь разумный объем данных, с 1990 года, это в основном полностью прямая линия - это то, что вы бы исключили. Потому что, если бы он был продан в тот же день, и это был трактор того же типа, проданный одному и тому же человеку на одном аукционе, то можно было бы ожидать, что более свежие автомобили будут дороже из-за инфляции, и они новее. Вы ожидаете, что эта связь будет примерно линейной, и это именно то, что мы находим. Устранение всех этих внешних факторов часто позволяет нам гораздо яснее увидеть истину.

Этот график частичной зависимости - это то, что использует случайный лес, чтобы дать нам более ясную интерпретацию того, что происходит в наших данных [1:20:02]. Шаги были:

  1. Прежде всего посмотрите на важность будущего, чтобы понять, какие вещи, по нашему мнению, нас волнуют.
  2. Затем использовать график частичной зависимости, чтобы рассказать нам, что происходит в среднем.

Есть еще одна интересная вещь, которую мы можем сделать с PDP, - это использовать кластеры. Кластеры используют кластерный анализ, чтобы посмотреть на каждую из 500 строк и сказать, перемещаются ли некоторые из этих 500 строк одинаково. Мы могли видеть, что кажется, что есть целое множество рядов, которые сначала идут вниз, а затем вверх, и кажется, что есть группа рядов, которые поднимаются вверх, а затем сходятся. Похоже, что скрываются какие-то разные типы поведения, и вот результат этого кластерного анализа:

plot_pdp('YearMade', clusters=5) 

Мы по-прежнему получаем то же среднее значение, но в нем говорится, что мы видим пять наиболее распространенных форм. И вот здесь вы можете пойти и сказать: «Хорошо, похоже, что на некоторые виды транспортных средств после 1990 года они будут стоить довольно стабильно». До этого они были довольно линейными. Некоторые другие виды транспортных средств были прямо противоположными, поэтому разные виды транспортных средств имеют разные формы. Итак, это то, во что вы могли бы покопаться.

Вопрос: Что мы будем делать с этой информацией [1:21:40]? Цель интерпретации - узнать о наборе данных, и почему вы хотите узнать о наборе данных? Потому что вы хотите что-то с этим сделать. Так что в данном случае это не так уж важно, если вы пытаетесь выиграть соревнование Kaggle - это может быть немного похоже на то, что некоторые из этих идей могут заставить вас осознать, что я могу преобразовать эту переменную или создать это взаимодействие и т. Д. Очевидно, что важность функции очень важен для соревнований Kaggle. Но это гораздо больше для реальной жизни. Это когда вы разговариваете с кем-то и говорите им: Хорошо, те графики, которые вы мне показывали, которые на самом деле говорят о таком падении цен, основанном на товарах, произведенных в период с 1990 по 1997 год. В самом деле. На самом деле они росли, и в то время происходило что-то еще . По сути, это то, что позволяет вам сказать, какой бы результат я ни пытался добиться в своем бизнесе, это то, как что-то движет им. Так что если я смотрю на рекламные технологии, то, что вызывает клики, я на самом деле копаюсь, чтобы сказать "хорошо", на самом деле это то, как приводятся клики. Фактически это переменная, которая движет им. Вот как это связано. Поэтому мы должны изменить свое поведение таким образом. Это действительно цель любой модели. Я предполагаю, что есть две возможные цели: одна цель модели - просто получить прогнозы, например, если вы занимаетесь торговлей хедж-фондами, вы, вероятно, захотите знать, какой будет цена этого капитала. Если вы занимаетесь страхованием, вы, вероятно, просто хотите знать, сколько претензий будет у этого парня. Но, вероятно, большую часть времени вы на самом деле пытаетесь что-то изменить в том, как вы ведете бизнес - как вы занимаетесь маркетингом, как вы занимаетесь логистикой, поэтому вас действительно волнует, как эти вещи связаны друг с другом.

Вопрос: не могли бы вы еще раз объяснить, почему падение не означает того, что мы думали [1:23:36]? да. Так что это классический скучный одномерный сюжет. Итак, мы просто берем все точки, все варианты, строим график YearMade против SalePrice, и мы просто подбираем приблизительное среднее значение по ним. Это правда, что продукты, произведенные в период с 1992 по 1997 год, в среднем по нашему набору данных, продаются по более низкой цене. Очень часто в бизнесе можно услышать, как кто-то смотрит на что-то подобное и говорит, например, мы должны прекратить продавать с аукциона оборудование, произведенное в те годы, потому что мы получаем меньше денег. Но если правда в том, что в те годы люди просто производили больше небольшого промышленного оборудования, которое, как можно было бы ожидать, будет продаваться по более низкой цене, и на самом деле наша прибыль от этого, например, столь же высока. Или дело не в том, что вещи, сделанные в те годы, теперь стали бы дешевле, а в том, что когда мы продавали вещи в те годы, они были дешевле, потому что шла рецессия. Если вы действительно пытаетесь предпринять какие-то действия на основе этого, вас, вероятно, заботит не только то, что вещи, сделанные в те годы, в среднем дешевле, но как это влияет сегодня. Итак, подход PDP, когда мы на самом деле говорим: давайте попробуем удалить все эти внешние эффекты. Итак, если что-то продается в один и тот же день одному и тому же человеку с таким же автомобилем, то на самом деле, как год влияет на цену. Это в основном говорит, например, что если я решаю, что купить на аукционе, то это говорит мне, что покупка более свежего автомобиля в среднем действительно дает вам больше денег, что не то, что сказал наивный одномерный сюжет.

Комментарий. Бульдозеры, выпущенные в 2010 году, вероятно, не близки к типу бульдозеров, выпущенных в 1960 году. Если вы возьмете что-то совсем другое, например, бульдозер 2010 года, а затем попытаетесь просто отбросьте его, чтобы сказать о, если бы это было сделано в 1960 году, что может привести к плохому предсказанию в какой-то момент, потому что это так далеко за пределами обучающей выборки [1:26:12]. Абсолютно. Неплохо подмечено. Однако это ограничение, если у вас есть точка данных, которая находится в той части пространства, которую он раньше не видел, например, возможно, люди не ставили кондиционеры в бульдозеры в 1960 году, и вы говорите, сколько бы этот бульдозер с кондиционером можно было бы купить в 1960 году, у вас нет никакой информации, чтобы знать об этом. Это все еще лучший из известных мне методов, но он не идеален. И вы как бы надеетесь, что деревья все же откроют какую-то полезную истину, даже если раньше они не видели такой комбинации функций. Но да, об этом нужно знать.

feats = ['saleElapsed', 'YearMade']
p = pdp.pdp_interact(m, x, feats)
pdp.pdp_interact_plot(p, feats)

Вы также можете сделать то же самое в сюжете взаимодействия PDP [1:27:36]. И сюжет взаимодействия PDP, который я и пытаюсь понять, - это то, как saleElapsed и YearMade вместе влияют на цену. Если я построю график взаимодействия PDP, он покажет мне, что количество продаж соответствует цене, год выпуска - цене, а комбинация - цене. Помните, что это всегда журнал цены. Вот почему эти цены выглядят странно. Вы можете видеть, что комбинация saleElapsed и YearMade соответствует вашим ожиданиям: самые высокие цены - это те цены, которые меньше всего прошли и произведены за последний год. В правом верхнем углу показано одномерное отношение между saleElapsed и ценой, в нижнем левом углу - одномерное соотношение между YearMade и ценой, а в правом нижнем углу - их комбинация. Достаточно ясно увидеть, что эти две вещи вместе влияют на цену. Вы также можете видеть, что это не простые диагональные линии, поэтому происходит некоторое интересное взаимодействие. Глядя на эти графики, этого достаточно, чтобы заставить меня подумать: о, возможно, нам стоит ввести какой-то термин взаимодействия и посмотреть, что произойдет. Так что давайте вернемся к этому через минуту, но давайте взглянем еще на парочку.

Помните, что в этом случае мы сделали горячее кодирование - еще вверху мы сказали max_n_cat=7 [1:29:18]. Итак, у нас есть такие вещи, как Enclosure_EROPS w AC. Поэтому, если у вас есть переменные с горячим кодированием, вы можете передать их массив plot_pdp, и он будет рассматривать их как категорию.

Итак, в этом случае я собираюсь создать график PDP этих трех категорий и назову его «Вложение».

plot_pdp(['Enclosure_EROPS w AC', 'Enclosure_EROPS', 
         'Enclosure_OROPS'], 5, 'Enclosure')

Я вижу здесь, что Enclosure_EROPS w AC в среднем дороже, чем Enclosure_EROPS или Enclosure_OROPS. На самом деле, похоже, что последние два очень похожи, иначе Enclosure_EROPS w AC выше. Так что на данном этапе я, вероятно, склонен зайти в Google и набрать «erops orops», узнать, что это за штуки, и поехали.

Получается, что EROPS представляет собой закрытую конструкцию, защищающую от опрокидывания, и поэтому оказывается, что если ваш бульдозер полностью закрыт, то при желании вы также можете получить кондиционер. На самом деле эта штука говорит нам, есть ли в ней кондиционер. Если это открытая конструкция, то, очевидно, у вас вообще нет кондиционера. Вот что это за три уровня. Итак, теперь мы узнали, что при прочих равных условиях, тот же бульдозер, проданный в одно и то же время, построенный в то же время, проданный одному и тому же человеку, будет немного дороже, если у него есть кондиционер, чем если бы он нет. Итак, мы снова получаем эту прекрасную способность интерпретации. Теперь, когда я потратил некоторое время на этот набор данных, я определенно заметил, что, зная, что это самая важная вещь, вы заметили, что сейчас гораздо больше бульдозеров с кондиционером, чем раньше, и поэтому определенно существует взаимодействие между дата и это.

df_raw.YearMade[df_raw.YearMade<1950] = 1950
df_keep['age'] = df_raw['age'] = df_raw.saleYear-df_raw.YearMade
X_train, X_valid = split_vals(df_keep, n_trn)
m = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, 
                          max_features=0.6, n_jobs=-1)
m.fit(X_train, y_train)
plot_fi(rf_feat_importance(m, df_keep));

Основываясь на более раннем анализе взаимодействия, я попытался, прежде всего, установить все до 1950–1950, потому что это, кажется, какое-то пропущенное значение [1:31:25]. Я установил age равным saleYear - YearMade. Затем я попытался запустить на этом случайный лес. В самом деле, age сейчас самая важная вещь, saleElapsed здесь далеко, YearMade снова здесь. Итак, мы использовали это, чтобы найти взаимодействие. Но помните, конечно, случайный лес может создать взаимодействие за счет наличия нескольких точек разделения, поэтому мы не должны предполагать, что на самом деле это будет лучший результат. И на практике, когда я посмотрел на свой результат и RMSE, я обнаружил, что добавление возраста было немного хуже. Об этом мы поговорим позже, возможно, на следующем уроке.

Интерпретатор дерева [1:32:34]

И последнее - интерпретатор дерева. Это тоже относится к категории вещей, о существовании которых большинство людей не подозревает, но это очень важно. Почти бессмысленно для соревнований Kaggle, но очень важно для реальной жизни. Вот идея. Допустим, вы страховая компания, и кто-то звонит, вы даете ему цитату, и они говорят: «О, это на 500 долларов больше, чем в прошлом году. Почему?" В общем, вы сделали прогноз на основе какой-то модели, и кто-то спрашивает, почему. Здесь мы используем метод, называемый интерпретатором дерева. Что делает древовидный интерпретатор, так это то, что он позволяет нам взять конкретную строку.

from treeinterpreter import treeinterpreter as ti
df_train, df_valid = split_vals(df_raw[df_keep.columns], n_trn)

Итак, в этом случае мы собираемся выбрать строку с нулевым номером.

row = X_valid.values[None,0]; row

Вот все столбцы в нулевой строке.

Что я могу сделать с интерпретатором дерева, так это то, что я могу пойти ti.predict, передать свой случайный лес и мою строку (так что это будет похоже на информацию о страховке этого конкретного клиента или, в данном случае, на этот конкретный аукцион). И это вернет мне три вещи:

  • prediction: Прогноз из случайного леса
  • bias: средняя цена продажи по всему исходному набору данных.
  • contributions: столбец и значение для разделения (т. Е. Предиктор), а также степень изменения прогнозируемого значения.
prediction, bias, contributions = ti.predict(m, row)

Вы можете думать об этом так [1:34:51]. Для всего набора данных средняя продажная цена журнала составляла 102. Набор данных для тех, у кого было Coupler_system ≤ 0.5, имел в среднем 10,3. Набор данных для Coupler_system ≤ 0.5 и Enclosure ≤ 2.0 был 9,9, а затем, в конце концов, мы поднялись здесь, а также с ModelID ≤ 4573.0, это 10,2. Итак, вы можете спросить, хорошо, почему мы предсказали 10,2 для этой конкретной строки?

Это потому, что мы начали с 10.19:

  • Поскольку система сопряжения была меньше 0,3, мы добавили к ней около 0,2 (таким образом, мы перешли с 10,19 на 10,34).
  • Поскольку вложение было меньше 2, мы вычли около 0,4.
  • Затем, поскольку ID модели был меньше 4573, мы добавили около 0,7

Итак, с помощью одного дерева вы можете понять, почему мы предсказали 10.2. В каждой из этих точек принятия решения мы добавляем или вычитаем немного от значения. Тогда мы могли бы сделать это для всех деревьев, а затем взять среднее значение. Итак, каждый раз, когда мы видим ограждение, мы увеличивали или уменьшали стоимость и на сколько? Каждый раз, когда мы видим идентификатор модели, мы увеличивали или уменьшали значение и на сколько? Мы могли бы взять среднее из всех этих значений, и в итоге получилась вещь под названием contributions.

prediction[0], bias[0]
(9.1909688098736275, 10.10606580677884)
idxs = np.argsort(contributions[0])
[o for o in zip(df_keep.columns[idxs], df_valid.iloc[0][idxs], contributions[0][idxs])]

Итак, вот все наши предсказатели и значение каждого [1:37:54].

[('ProductSize', 'Mini', -0.54680742853695008),
 ('age', 11, -0.12507089451852943),
 ('fiProductClassDesc',
  'Hydraulic Excavator, Track - 3.0 to 4.0 Metric Tons',
  -0.11143111128570773),
 ('fiModelDesc', 'KX1212', -0.065155113754146801),
 ('fiSecondaryDesc', nan, -0.055237427792181749),
 ('Enclosure', 'EROPS', -0.050467175593900217),
 ('fiModelDescriptor', nan, -0.042354676935508852),
 ('saleElapsed', 7912, -0.019642242073500914),
 ('saleDay', 16, -0.012812993479652724),
 ('Tire_Size', nan, -0.0029687660942271598),
 ('SalesID', 4364751, -0.0010443985823001434),
 ('saleDayofyear', 259, -0.00086540581130196688),
 ('Drive_System', nan, 0.0015385818526195915),
 ('Hydraulics', 'Standard', 0.0022411701338458821),
 ('state', 'Ohio', 0.0037587658190299409),
 ('ProductGroupDesc', 'Track Excavators', 0.0067688906745931197),
 ('ProductGroup', 'TEX', 0.014654732626326661),
 ('MachineID', 2300944, 0.015578052196894499),
 ('Hydraulics_Flow', nan, 0.028973749866174004),
 ('ModelID', 665, 0.038307429579276284),
 ('Coupler_System', nan, 0.052509808150765114),
 ('YearMade', 1999, 0.071829996446492878)]

При сортировке видео возникла проблема, поскольку сортировка по индексу не использовалась, но приведенный выше пример является исправленной версией.

contributions[0].sum()
-0.7383536391949419

Итак, есть вещь, называемая смещением, и смещение - это просто среднее значение до того, как мы начнем делать какие-либо сплиты [1:39:03]. Если вы начнете со среднего логарифма стоимости, а затем мы спустились по каждому дереву, и каждый раз, когда мы видели YearMade, мы оказывали какое-то влияние, соединительную систему - какое-то, размер продукта - какое-то и так далее.

Мы можем вернуться к интерпретатору дерева в следующий раз, но основная идея (это последний из наших ключевых моментов интерпретации) заключается в том, что нам нужна некоторая способность не только рассказывать нам о модели в целом и о том, как она работает в среднем, но и чтобы посмотреть, как модель делает прогноз для отдельной строки. И вот что мы здесь делаем.

Уроки: 12345678910 11 12