Сквозное упражнение по классификации машинного обучения с учителем на Python с использованием конвейеров и перекрестной проверки поиска по сетке

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

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

Мы будем использовать данные, предоставленные Opportunity Insights, группой исследователей и аналитиков из Гарвардского университета. Я использую данные из двух разных наборов данных из их проекта под названием Атлас возможностей: отображение детских корней социальной мобильности, которые я настоятельно рекомендую проверить тем, кто интересуется этой темой.

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

Давайте начнем!

1. Подготовьте данные

Все мы, специалисты по данным, знаем, что данные в большинстве случаев беспорядочные!

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

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

В качестве самого первого шага мне нравится загружать все библиотеки, которые мне понадобятся, в самом начале моей записной книжки. Мы будем использовать Pandas, Matplotlib и Seaborn для некоторых визуализаций и в значительной степени полагаться на несколько библиотек Scikit-Learn, которые очень полезны и покрывают большинство наших потребностей в машинном обучении.

У нас есть набор данных разумного размера для проекта машинного обучения, содержащий более 74 тыс. Строк и 38 столбцов, в основном числовых. Полное описание всех столбцов можно найти в файле READ ME для Характеристики района по переписи, но большинство из них не требует пояснений. Давайте посмотрим на все столбцы. Мы могли бы использовать df.info() для одновременного просмотра типов данных и нулевых значений, но мне нравится проверять типы данных отдельно с помощью df.dtypes, а затем проверять процент NaN в каждом столбце с помощью простого вычисления:

Каждая строка идентифицируется переписным участком, округом и штатом, поэтому, чтобы сделать идентификатор уникальным, мы объединим эти три в уникальный столбец «ID». Мы также удалим столбцы с высоким процентом NaN.

Затем мы хотим взглянуть на основную статистику наших числовых данных. Поскольку у нас 38 столбцов, pandas не будет отображать их все, если я просто вызову df.describe(). Есть несколько способов обойти это и получить возможность визуализировать статистику всех столбцов, но я лично считаю, что самый простой способ - просто разделить фрейм данных и вызвать метод описания дважды (или больше).

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

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

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

У нас все еще есть до 3,6% нулевых значений в некоторых из наших столбцов, но поскольку каждая строка представляет одну конкретную географическую область в США, вместо того, чтобы отбрасывать их, я заменю их медианным значением столбца.

Наконец, мы посмотрим на корреляции, а также на баланс нашего целевого результата.

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

Разделите данные на наборы для обучения, проверки и тестирования

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

Отлично, теперь у нас есть три набора данных со сбалансированными результатами, готовые к обучению!

2. Подбирайте, оценивайте и настраивайте модели

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

Выбор модели

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

Вот наши результаты:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=None, n_neighbors=5, p=2,
           weights='uniform')
model score: 0.8420469083155651
----------------
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
  kernel='rbf', max_iter=-1, probability=False, random_state=None,
  shrinking=True, tol=0.001, verbose=False)
model score: 0.8640511727078891
----------------
LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0)
model score: 0.8436673773987207
----------------
NuSVC(cache_size=200, class_weight=None, coef0=0.0,
   decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
   kernel='rbf', max_iter=-1, nu=0.5, probability=False, random_state=None,
   shrinking=True, tol=0.001, verbose=False)
model score: 0.8354797441364605
----------------
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
model score: 0.8001705756929638
----------------
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=None,
            oob_score=False, random_state=None, verbose=0,
            warm_start=False)
model score: 0.8484434968017057
----------------
AdaBoostClassifier(algorithm='SAMME.R', base_estimator=None,
          learning_rate=1.0, n_estimators=50, random_state=None)
model score: 0.8504904051172708
----------------
GradientBoostingClassifier(criterion='friedman_mse', init=None,
              learning_rate=0.1, loss='deviance', max_depth=3,
              max_features=None, max_leaf_nodes=None,
              min_impurity_decrease=0.0, min_impurity_split=None,
              min_samples_leaf=1, min_samples_split=2,
              min_weight_fraction_leaf=0.0, n_estimators=100,
              n_iter_no_change=None, presort='auto', random_state=None,
              subsample=1.0, tol=0.0001, validation_fraction=0.1,
              verbose=0, warm_start=False)
model score: 0.8581663113006397
----------------
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
       colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0,
       max_depth=3, min_child_weight=1, missing=None, n_estimators=100,
       n_jobs=1, nthread=None, objective='binary:logistic', random_state=0,
       reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
       silent=True, subsample=1)
model score: 0.8575692963752666
----------------

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

Настройка гиперпараметров с помощью конвейера и поиска по сетке резюме

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

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

Теперь мы можем получить доступ к лучшим параметрам, найденным с помощью grid_svm.best_params_. В нашей модели лучшие значения составили C: 10, gamma: 0.01.

Оценка модели

Давайте проверим оценку точности нашей модели с этими лучшими параметрами как для данных поезда, так и для данных проверки.

Мы получили оценку 0,8749173721133549 для данных поезда и оценку 0,8622601279317698 для данных проверки. Это показывает нам, что наша модель не переоснащается, и что наша классификация моделей немного превышает 86%.

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

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

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

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

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

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

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