Quick check with random forest

быстрая проверка на наличие зависимости признаков алгоритмом случайного леса (классификация) by Grossmend (Апрель 23, 2019 в 15:22)

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

Итак, поехали

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
Загрузка данных

from sklearn.model_selection import train_test_split
# загрузка данных для классификации
path_file = 'example_data.xlsx'
sheet_name = 'example_load'
data = pd.read_excel(path_file, sheet_name=sheet_name)
print(data.shape)
Responsive image

# просмотр столбцов
data.columns
Responsive image

# проверка на необходимые столбцы
if not set(['idx','label']).issubset(data.columns):
    raise Exception('не хватает необходимых столбцов')

# проверка на уникальность индекса
if not data['idx'].is_unique:
    raise Exception('индексы должны быть уникальными')
Тут мы загружаем excel-файл. Два необходимых столбца это "idx" и "label".
idx - столбец индексов данных
label - целевые значения классификации

Тут для примера используется избитый, обработанный набор данных о пассажирах Титаника. Такие признаки как "Embarked", "Cabin" должны обрабатываться методом "one-hot encoding" (прямого кодирования). Но в данной статье не суть, нам просто нужны "понятные" глазу признаки.

# присвоение индекса
if 'idx' in data:
    if not data['idx'].is_unique:
        raise Exception('входные индексы имеют дубликаты')
    data.set_index(['idx'], inplace=True)
# смотрим на данные
print('размерность:', data.shape)
data.head(4)
Responsive image

Предобработка данных

from sklearn.preprocessing import MinMaxScaler, StandardScaler


# # масштабирование данных - нормализация (минимакс)
# scaler = MinMaxScaler(feature_range=(0.1, 0.9))
# scaled_features = scaler.fit_transform(data[data.columns.difference(['label'])].astype('float64'))
# scaled_data = pd.DataFrame(data=scaled_features, index=data.index, columns=data.drop(['label'], axis=1).columns)
# scaled_data['label'] = data['label']
# scaled_data.head(4)

# # масштабирование данных - стандартизация
# scaler = StandardScaler()
# scaled_features = scaler.fit_transform(data[data.columns.difference(['label'])].astype('float64'))
# scaled_data = pd.DataFrame(data=scaled_features, index=data.index, columns=data.drop(['label'], axis=1).columns)
# scaled_data['label'] = data['label']
# scaled_data.head(4)

# оставить без масштабирования
scaled_data = data.copy()
scaled_data.head(4)
При необходимости задайте масштабирование признаков, хотя случайный лес один из немногих алгоритмов, который не требует обязательного масштабирования признаков.

# замена пустых значений
scaled_data.fillna(value=0, inplace=True)
Тут можно описать свои собственные методы заполнения пропущенных значений (в зависимости от конкретной задачи)

Подготовка данных к ML

from sklearn.model_selection import train_test_split
X_data = scaled_data.drop('label', axis=1).select_dtypes(include=[np.number])
y_data = scaled_data['label'].astype('int32')

print('size train data:', X_data.shape)
print('size train labels:', y_data.shape)
Responsive image

# разбивка на тестовые и тренировочные данные
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size=0.2)
Модель - Random forest

from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.metrics import accuracy_score

# модель случайный лес
from sklearn.ensemble import RandomForestClassifier
# случайный лес
rf = RandomForestClassifier(max_features='auto', random_state=1, n_jobs=-1, n_estimators=10)
# посмотрим на примерный результат, без поиска лучших гиперпараметров
print(np.mean(cross_val_score(rf, X_data, y_data, cv=5)))
Responsive image

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

# оптимизируемые параметры (по каким будет идти перебор)
# для каждой задачи нужно проставлять индивидуально
rf_params = {'criterion' : ['entropy'],
             'min_samples_leaf' : [1,2,3],
             'min_samples_split' : [5,15,50,100],
             'n_estimators': [10,25,50,75,100]}
rf_params - словарь подбираемых гиперпараметров

Среди данных гиперпараметров алгоритм будет искать лучшее решение, методом полного перебора

В боевых задачах чаще всего намного больше образцов, также как и их признаков, поэтому можно применить вместо полного перебора RandomizedSearchCV, после него уже применять GridSearchCV

# объект для поиска наилучших параметров
rf_grid = GridSearchCV(rf,
                       rf_params,
                       cv=5,
                       n_jobs=-1,
                       verbose=True)
Поиск лучших гиперпараметров

# поиск наилучших параметров для модели
rf_grid.fit(X_train, y_train);
Responsive image

# просмотр лучших параметров
rf_grid.best_params_
Responsive image

Если какое-либо оптимизируемое значение стоит на границе словаря подбора гиперпараметров, то стоит пересмотреть словарь, "со сдвигом", потому как потенциально можно получить решение лучше

# просмотр лучшего результата
rf_grid.best_score_
Responsive image

Можно посмотреть различные комбинации значений гиперпараметров и средние оценки, по перекрестной проверке:

# просмотр комбинаций гиперпараметров с оценками
means = rf_grid.cv_results_['mean_test_score']
stds = rf_grid.cv_results_['std_test_score']
for mean, std, params in zip(means, stds, rf_grid.cv_results_['params']):
    print("%0.3f (+/-%0.03f) for %r" % (mean, std * 2, params))
plt.subplots(figsize=(9,5))
plt.plot(np.sort(rf_grid.cv_results_['mean_test_score']));
plt.grid(alpha=0.4)
plt.ylabel('точность модели');
plt.xlabel('модели с разными параметрами');
plt.title('Точность модели от оптимизируемых гиперпараметров');
Responsive image

Responsive image

Создаем модель с оптимизированными гиперпараметрами

# (обычно я выбираю те гиперпараметры, где проходит зеленая линия, см. выше)
# отбираем лучшую модель 
best_model = rf_grid.best_estimator_
# обучаем модель на всех данных
best_model.fit(X_train, y_train)
Responsive image

# смотрим на результативность на тестовых данных
best_model.score(X_test, y_test)
Responsive image

# сформируем важности признаков
imp_df = pd.DataFrame(best_model.feature_importances_,
                      columns=['Feature_Imp'],
                      index=X_train.columns)
imp_df.sort_values(by=['Feature_Imp'], ascending=False, inplace=True)
print(imp_df)
Responsive image

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

обновлено: Январь 12, 2020 в 17:19


Количество комментариев: 0

Комментариев нет



Добавить комментарий: