#!/usr/bin/env python # coding: utf-8 #
# # ## Открытый курс по машинному обучению #
Автор материала: Измайлов Константин Константинович (@Izmajlovkonstantin). # ##
Отбор признаков с помощью алгоритма Boruta или как приручить лесного демона.
# # ### 1. Введение # Решая ту или иную задачу машинного обучения не стоит забывать одно простое правило: "Garbage in - garbage out" , означающее, что чем "зашумленнее" были поданы данные на входе, тем хуже будет решение задачи. # # Корректный отбор признаков - один из важнейших этапов предобработки данных. Особенно критичен отбор признаков при решении задач в индустрии, когда датасаентист встречается с большим набором фичей, многие из которых не имеют отношения к решаемой проблеме. # # Отбор признаков обеспечаивает следующие преимущества: # # # # Существуют три основных класса алгоритмов отбора признаков: #
    #
  1. Фильтры - применяются до классификации, не зависят от алгоритма самой модели. Признаки отбираются, как правило, основываясь на оценках статистических тестов (корреляци Пирсона, LDA, ANOVA и т.д.); #
  2. Встроенные методы - выполняют отбор признаков неотделимо от процесса обучения модели (наприм. - Lasso-регрессия); #
  3. Оберточные методы - используют информацию о важности признаков, полученную от алгоритмов обучения, и затем находят сложные зависимости между ними. #
# Схематичное изображение оберточного метода. # # Одним из таких оберточных методов, который будет рассмотрен в данном туториале, будет алгоритм Boruta , названный в честь лесного славянского демона, реализующий адаптационный алгоритм для модели случайного леса (Kursa, Rudnicki, 2010). # ### 2. Алгоритм Boruta # Основная идея алгоритма заключается в сравнении исходных признаков с их теневыми копиями (англ. - shadow features) - признаками, полученными с помощью случайного перемешивания значений исходных признаков между строками. Соответственно, признаки, которые мало чем отличаются от теневых будут совершенно не важны для модели. # Рассмотрим алгоритм пошагово: #
    #
  1. Добавить в исходный набор данных теневые копии всех признаков (обычно добавляется по 5 теневых признаков для каждого признака исходного набора данных, вне зависимости от их общего числа); #
  2. Обучить несколько раз алгоритм (в данном случае - случайный лес) на новом наборе данных; #
  3. Рассчитать важность всех признаков на каждой итерации алгоритма. # Важность признаков рассчитывается как дополнительная ошибки регрессии, вызванная исключением этого признака из модели. Среднее $μ$ этой дополнительной ошибки и его стандартное отклонение $σ$ рассчитываются по всем деревьям в лесу, которые используют оцениваемый признак для прогнозирования. Мера $Z$ каждого признака рассчитывается как $\dfrac{μ}{σ}$. Данная оценка не может использоваться напрямую для определения важности каждого из признаков модели в отдельности, так как не имеет нормального распределения. #
    #
  4. Для каждого из признаков с помощью биноминального распределения подсчитывается, какая вероятность того, что $Z$-мера исходного признака будет выше $Z$-меры всех его теневых признаков. Рассчитывается уровень значимости $p$ с поправкой на множественную проверку гипотез (для алгоритма Boruta используется достаточно консервативная коррекция Бонфферони), так как такое сравнение делается для тысячи итераций; #
  5. Признаки, у которых веротяность $Z$-меры теневых признаков статистически значима выше вероятности $Z$-меры исходного признака считаются неважными и выбрасываются из обучаемого набора данных (зачастую со своими теневыми копиями); #
  6. Процедура повторяется до тех пор, пока не будет достигнут заданный набор итераций или всем признаком не будет проставлен признак важности. #
# ### 3. Интерфейс алгоритма # Изначально алгоритм был реализован на R, а уже затем перенесен на Python с некоторыми изменениями: # # Параметры алгоритма: # estimator : object # n_estimators : int or string, default = 1000 # perc : int, default = 100 # alpha : float, default = 0.05 # two_step : Boolean, default = True # max_iter : int, default = 100 # verbose : int, default=0 # # Более подробно о параметрах и атрибутах можно прочитать [тут](https://github.com/scikit-learn-contrib/boruta_py). # ### 4. Пример реализации # Применять алгоритм Boruta будем на публичном датасете Breast Cancer Wisconsin (Diagnostic) Data Set представляющий собой проблему классификации, где по характеристикам опухоли в женской груди необходимо определить, является ли опухоль доброкачественной (benign) либо злокачественной (malignant). # In[1]: # импортируем необходимые библиотеки import warnings warnings.filterwarnings('ignore') import pandas as pd from sklearn.ensemble import RandomForestClassifier from boruta import BorutaPy import numpy as np from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score #загружаем сам датасет from sklearn.datasets import load_breast_cancer data = load_breast_cancer() df = pd.DataFrame(np.c_[data['data'], data['target']], columns= np.append(data['feature_names'], ['target'])) # In[2]: df.head() # Датасет состоит из 30 признаков и одного целевого признака. О значении большинства признаков остается лишь догадываться, тем более определить их значимость, датасаентисту, не разбирающемуся в медицине, не представляется возможным. # In[3]: print('Доля меток с злокачественной опухолью - {}.'.format(np.round(df[df.target == 1].shape[0]/df.shape[0],2))) # Валидировать алгоритм будем на 3 фолдах, в качестве метрики будем использовать ROC AUC score. # In[4]: X = df.iloc[:,:-1] y = df['target'] def validation(ensemble, X, y, n_folds = 3): rocs = [] skf = StratifiedKFold(n_splits=n_folds) for train, test in skf.split(X, y): X_train, X_test = X.loc[train,:], X.loc[test,:] y_train, y_test = y[train],y[test] ensemble.fit(X_train,y_train) rocs.append(roc_auc_score(y_test,ensemble.predict_proba(X_test)[:,1])) return np.mean(rocs) # Воспользуемся коробочным решением алгоритма и посмотрим на результат. # In[5]: clf = RandomForestClassifier(n_estimators=500,max_depth=3, random_state=10) print('ROC AUC SCORE - {}.'.format(validation(clf, X, y))) # Коробочное решение алгоритма справляется уже неплохо, попробуем призвать нашего лесного демона, может, он сможет улучшить наш алгоритм. При этом, попробуем добавить в данные немного шума, посмотрим, сможет ли Boruta найти их. # In[6]: X_with_noise = X.copy() X_with_noise['random_noise_0'] = np.random.random( X_with_noise.shape[0]) X_with_noise['random_noise_1'] = np.random.random( X_with_noise.shape[0]) X_with_noise['random_noise_2'] = np.random.random( X_with_noise.shape[0]) X_with_noise['random_noise_3'] = np.random.random( X_with_noise.shape[0]) feat_selector = BorutaPy(clf, n_estimators='auto', verbose=2, random_state=17, max_iter=50) feat_selector.fit(X_with_noise.values, y) # Уже на 8 итерации Boruta определил 4 добавленных шумовых признака. В том числе, для некоторых признаков исходного датасета была поставлена метка Tentative, то есть алгоритм не совсем уверен, являются ли данные метки важными для предсказания или нет. # # Определим, что это за признаки и попробуем обучить алгоритм без них. # In[7]: # Неважные для обучения признаки X_with_noise.columns[np.where(feat_selector.ranking_ !=1)] # In[8]: X_important = X.iloc[:,np.where(feat_selector.ranking_ ==1)[0]] clf = RandomForestClassifier(n_estimators=500,max_depth=3, random_state=10) print('ROC AUC SCORE - {}.'.format(validation(clf, X_important, y))) # Качество алгоритма улучшилось! А значит, наш лесной демон выполнил свою работу и может отправляться обратно в лес. # ### 5. Заключение # Хоть и в данном примере Boruta отработал на отлично, и с его помощью удалось улучшить качество алгоритма, но это не всегда так. В любом случае, попробовать использовать алгоритм стоит. # # Стоит отметить, что у алгоритма есть небольшой баг, который автор еще не устранил: # в том случае, если по завершению всех итераций алгоритма Boruta не нашёл ни одного неважного признака (Rejected = 0) вылетает ошибка "iteration over a 0-d array". До выхода нового апдейта модуля для корректной работы алгоритма можно добавлять колонку констант, которая будет наверняка принята алгоритмом как rejected и не спровоцирует вышеобознаечнную ошибку. # # По личному опыту скажу, что Boruta хорошо заходит в задачах с генерацией большого количества искусственных признаков: в задачах кредитного скоринга, но не стоит забывать, что работа алгоритма может занять много времени в зависимости от объема данных. # # Также в сети появилась новая улучшенная версия Boruta, реализованная на всеми любимом XGBoost. Прочитать про нее можно [тут](https://github.com/chasedehan/BoostARoota). # # На последок, желаю всем успехов в тренировки своих демонов, надеюсь данный туториал был вам полезен! #