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

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

Почему выбор функций?

Выбор признаков — это процесс сокращения количества переменных, используемых в модели. У него есть несколько преимуществ на стороне ML:

  1. Улучшает производительность
  2. Уменьшает переоснащение
  3. Сокращает время обучения.

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

Случайные леса и выбор признаков

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

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

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

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

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

Здесь вы можете найти набор данных и описание переменной.

Код

1. Импортировать библиотеки

import pandas as pd
from sklearn.preprocessing import StandardScaler 
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import balanced_accuracy_score
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV

2. Чтение набора данных

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

df = pd.read_csv('salary.csv')
df

3. Разработка функций

Горячее кодирование является подходящей стратегией для преобразования категориальных признаков в числовые.

# Divide categorical and numerical features
numerical_variables = ["age", "fnlwgt", "education-num", "capital-gain", "capital-loss", "hours-per-week"]
categorical_variables = ["workclass", "education", "marital-status", "occupation", "relationship", "race", "native-country","sex", "salary"]
# One-hot encoding of categorical variables
one_categorical_variables = pd.get_dummies(df[categorical_variables], drop_first= True)
# Create a dataset with only numerical features
df_features = pd.concat([df[numerical_variables], one_categorical_variables], axis = 1)

4. Создание обучающих и тестовых наборов

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

y = df_features.pop('salary_ >50K')
X_train, X_test, y_train, y_test = train_test_split(df_features.fillna(0.), y , test_size=0.3, random_state=42)

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

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train[numerical_variables])
X_test_scaled = scaler.transform(X_test [numerical_variables])

Объединяйте функции с горячим кодированием и масштабированием.

X_train_final = pd.concat([pd.DataFrame(X_train_scaled, columns=numerical_variables).reset_index(drop=True), X_train[one_categorical_variables.columns[:-1]].reset_index(drop=True)], axis = 1)
X_test_final = pd.concat([pd.DataFrame(X_test_scaled, columns=numerical_variables).reset_index(drop=True), X_test[one_categorical_variables.columns[:-1]].reset_index(drop=True)], axis = 1)

5. Выбор функции

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

rf = RandomForestClassifier(random_state=42, n_jobs=-1, n_estimators=1000, max_depth=5)
rf.fit(X_train_final, y_train)

Мы можем изучить важность функции с помощью простого сюжета.

figure(figsize=(10, 10))
feat_importances = pd.Series(rf.feature_importances_, index=df_features.columns)
most_important_features = feat_importances.nlargest(10).index.tolist()
feat_importances.nlargest(40).plot(kind='barh')

Функции ранжируются от наименее важных к наиболее важным. Сумма их важности равна 1. Следовательно, также можно вычислить их относительную важность.

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

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

6. Результаты

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

Сетка параметров содержит набор гиперпараметров, которые необходимо оптимизировать.

params = [{
         'max_depth': [8,9,10,11,12,13,14,15],
         'n_estimators': [1,10,100,200,400,1000]}]

Фрагменты кода ниже соответствуют двум случайным лесам. Первый без выбора признаков, второй с ним.

Модель, использующая все функции, имеет хорошую производительность, но оснащена многими функциями.

forest_cv_feat_sel = GridSearchCV(RandomForestClassifier(random_state=42, n_jobs=-1),
                      param_grid=params,
                      scoring='balanced_accuracy',
                      cv=3,
                      n_jobs = -1)
forest_cv_feat_sel.fit(X_train_final, y_train)
print(f"balanced_accuracy_score : {balanced_accuracy_score(list(y_train),list(forest_cv_feat_sel.predict(X_train_final)))}")
print(f"balanced_accuracy_score : {balanced_accuracy_score(list(y_test), list(forest_cv_feat_sel.predict(X_test_final)))}")

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

forest_cv = GridSearchCV(RandomForestClassifier(random_state=42, n_jobs=-1),
                      param_grid=params,
                      scoring='balanced_accuracy',
                      cv=3,
                      n_jobs = -1)
forest_cv.fit(X_train_final[most_important_features], y_train)
print(f"balanced_accuracy_score : {balanced_accuracy_score(list(y_train),list(forest_cv.predict(X_train_final[most_important_features])))}")
print(f"balanced_accuracy_score : {balanced_accuracy_score(list(y_test), list(forest_cv.predict(X_test_final[most_important_features])))}")

Выводы

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

Надеюсь, вам понравилось читать статью, и вы найдете ее полезной.