Плюс советы по работе с категориальными, числовыми и смешанными данными.

Что такое выбор признаков?

Выбор функций в машинном обучении — это выбор наиболее важных функций или столбцов в наборе данных.

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

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

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

  1. Категориальные данные с использованием критерия хи-квадрат
  2. Коэффициент корреляции Пирсона для числовых данных
  3. Анализ главных компонентов для числовых данных
  4. Важность функции с случайными лесами как для категориальных, так и для числовых данных

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

Данные и импорт

Для нашей сегодняшней демонстрации мы будем использовать набор данных Bank Marketing UCI, который можно найти на Kaggle. Этот набор данных содержит информацию о клиентах банка в маркетинговой кампании, а также целевую переменную, которую можно использовать в модели классификации. Этот набор данных находится в общественном достоянии под CC0: Public Domain и может использоваться для любых целей.

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

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

import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
from sklearn.preprocessing import LabelEncoder, OrdinalEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import MinMaxScaler
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder
from sklearn.compose import make_column_selector as selector
from sklearn.pipeline import Pipeline

import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("bank.csv", delimiter=";")

Выбор функций для категориальных значений

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

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

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

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

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

# get categorical data
cat_data = df.select_dtypes(include=['object'])

Затем мы можем изолировать функции и целевые значения. Целевая переменная y, — это последний столбец во фрейме данных, поэтому мы можем использовать технику нарезки Python, чтобы разделить их на X и y.

X = cat_data.iloc[:, :-1].values
y = cat_data.iloc[:,-1].values

Далее у нас есть две функции. Эти функции будут использовать OrdinalEncoder для данных X и LabelEncoder для данных y. Как следует из названия, OrdinalEncoder преобразует категориальные значения в числовое представление в соответствии с определенным порядком. По умолчанию кодировщик автоматически выбирает этот порядок. Однако вы можете предоставить список значений для заказа. LabelEncoder преобразует подобные значения в числовые представления. Согласно документации Scikit-Learn, вы должны использовать только LabelEncoder в целевой переменной.

def prepare_inputs(X_train, X_test):
    oe = OrdinalEncoder()
    oe.fit(X_train)
    X_train_enc = oe.transform(X_train)
    X_test_enc = oe.transform(X_test)
    return X_train_enc, X_test_enc

def prepare_targets(y_train, y_test):
    le = LabelEncoder()
    le.fit(y_train)
    y_train_enc = le.transform(y_train)
    y_test_enc = le.transform(y_test)
    return y_train_enc, y_test_enc

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

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# prepare input data
X_train_enc, X_test_enc = prepare_inputs(X_train, X_test)
# prepare output data
y_train_enc, y_test_enc = prepare_targets(y_train, y_test)

Теперь функция, которая поможет нам выбрать лучшие функции, используя тест хи-квадрат внутри метода SelectKBest. Мы можем начать с установки аргумента k='all', который сначала запустит тест для всех функций, а позже мы сможем применить его к определенному количеству функций.

def select_features(X_train, y_train, X_test, k_value='all'):
    fs = SelectKBest(score_func=chi2, k=k_value)
    fs.fit(X_train, y_train)
    X_train_fs = fs.transform(X_train)
    X_test_fs = fs.transform(X_test)
    return X_train_fs, X_test_fs, fs

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

# feature selection
X_train_fs, X_test_fs, fs = select_features(X_train_enc, y_train_enc, X_test_enc)
# what are scores for the features
for i in range(len(fs.scores_)):
    print('Feature %d: %f' % (i, fs.scores_[i]))
Feature 0: 11.679248
Feature 1: 0.248626
Feature 2: 3.339391
Feature 3: 0.039239
Feature 4: 11.788867
Feature 5: 12.889637
Feature 6: 64.864792
Feature 7: 4.102635
Feature 8: 10.921719

Тем не менее, эти показатели проще визуально проверить с помощью графика.

# what are scores for the features
names = []
values = []
for i in range(len(fs.scores_)):
    names.append(cat_data.columns[i])
    values.append(fs.scores_[i])
chi_list = zip(names, values)

# plot the scores
plt.figure(figsize=(10,4))
sns.barplot(x=names, y=values)
plt.xticks(rotation = 90)
plt.show()

Контакт имеет здесь самый высокий балл, а брачный, по умолчанию и месяц — самый низкий. В целом, похоже, есть около 5 функций, которые стоит рассмотреть. Мы воспользуемся методом SelectKBest для выбора 5 наиболее важных объектов.

X_train_fs, X_test_fs, fs = select_features(X_train_enc, y_train_enc, X_test_enc, 5)

И мы можем вывести основные функции, значения которых соответствуют их индексу выше.

fs.get_feature_names_out()
[OUT:]
array(['x0', 'x4', 'x5', 'x6', 'x8'], dtype=object)

И, наконец, мы можем распечатать форму данных X_train_fs и X_test_fs и увидеть, что второе измерение для выбранных объектов равно 5.

print(X_train_fs.shape)
print(X_test_fs.shape)
(3029, 5)
(1492, 5)

Выбор функций для числовых значений

Имея дело с чистыми числовыми данными, я предпочитаю использовать два метода. Первый — это Коэффициент корреляции Пирсона, а второй — Анализ главных компонентов или PCA.

Коэффициент корреляции Пирсона

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

corr = df.corr()

f, ax = plt.subplots(figsize=(12, 8))

sns.heatmap(corr, cmap="Blues", annot=True, square=False, ax=ax)
plt.title('Pearson Correlation of Features')
plt.yticks(rotation=45);

В этом примере pdays и previous имеют наибольшую корреляцию 0.58, а все остальное не зависит друг от друга. Корреляция 0.58 не очень сильная. Поэтому я предпочту оставить оба в модели.

Анализ главных компонентов

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

На следующем изображении показано применение PCA к фотографии. График представляет собой сумму кумулятивной объясненной дисперсии для каждого основного компонента. В этом примере мы можем объяснить 95% дисперсии изображения только с помощью 54 из более чем 3000 основных компонентов изображения.

Для получения дополнительной информации о том, как работает и работает PCA, ознакомьтесь со следующими статьями: 2 красивых способа визуализации PCA и Магия анализа главных компонентов с помощью сжатия изображений.

Важность функций из случайных лесов

Наконец, я предпочитаю использовать метод случайного леса и его способность вычислять важность признаков. Scikit-Learn вычислит список функций и их относительный вклад в общую производительность как результат обученной модели. Этот метод также работает с другими деревьями в пакетах, такими как Extra Trees и Gradient Boosting, для классификации и регрессии.

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

print(df.columns)
Index(['age', 'job', 'marital', 'education', 'default', 'balance', 'housing', 'loan', 'contact', 'day', 'month', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'y'],
      dtype='object')

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

Продолжительность: продолжительность последнего контакта в секундах (числовое). Этот атрибут сильно влияет на цель вывода (например, если продолжительность = 0, то y = «нет»). Тем не менее, продолжительность не известна до выполнения вызова. Кроме того, после окончания вызова y, очевидно, известен. Таким образом, эти входные данные следует включать только для целей сравнительного анализа и от них следует отказаться, если вы хотите получить реалистичную прогностическую модель.

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

# Remove columns from the list that are not relevant. 
targets = ['age', 'job', 'marital', 'education', 'default', 'balance', 'housing', 'loan', 'contact', 'day', 'month', 'campaign', 'pdays', 'previous', 'poutcome']

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

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

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

column_trans = ColumnTransformer(transformers=
       [('num', MinMaxScaler(), selector(dtype_exclude="object")),
       ('cat', OrdinalEncoder(), selector(dtype_include="object"))],
        remainder='drop')

Затем мы создаем экземпляр нашего классификатора с несколькими предпочтительными настройками, такими как class_weight='balanced', что помогает обрабатывать несбалансированные данные. Мы также установим random_state=42, чтобы гарантировать, что мы получим те же результаты.

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

# Create a random forest classifier for feature importance
clf = RandomForestClassifier(random_state=42, n_jobs=6, class_weight='balanced')

pipeline = Pipeline([('prep',column_trans),
                     ('clf', clf)])

Далее мы разделим наши данные на обучающие и тестовые наборы и подгоним нашу модель к обучающим данным.

# Split the data into 30% test and 70% training
X_train, X_test, y_train, y_test = train_test_split(df[targets], df['y'], test_size=0.3, random_state=0)

pipeline.fit(X_train, y_train)

Мы можем вызвать метод feature_importances_ для классификатора, чтобы увидеть результат. Обратите внимание, как вы ссылаетесь на классификатор в конвейере, называя его имя clf, аналогично словарю в Python.

pipeline['clf'].feature_importances_
array([0.12097191, 0.1551929 , 0.10382712, 0.04618367, 0.04876248,
       0.02484967, 0.11530121, 0.15703306, 0.10358275, 0.04916597,
       0.05092775, 0.02420151])

Далее давайте отсортируем их по наибольшей и совокупной важности. Столбец кумулятивной важности полезен для визуализации того, как общая сумма составляет 1. Существует также цикл, позволяющий отсечь любую функцию, которая вносит менее 0.5 вклада в общую важность. Это значение отсечки является произвольным. Вы можете найти общую совокупную важность 0.8 или 0.9. Поэкспериментируйте с тем, как ваша модель работает с меньшим или большим количеством функций.

feat_list = []

total_importance = 0
# Print the name and gini importance of each feature
for feature in zip(targets, pipeline['clf'].feature_importances_):
    feat_list.append(feature)
    total_importance += feature[1]

included_feats = []
# Print the name and gini importance of each feature
for feature in zip(targets, pipeline['clf'].feature_importances_):
    if feature[1] > .05:
        included_feats.append(feature[0])

print('\n',"Cumulative Importance =", total_importance)

# create DataFrame using data
df_imp = pd.DataFrame(feat_list, columns =['FEATURE', 'IMPORTANCE']).sort_values(by='IMPORTANCE', ascending=False)
df_imp['CUMSUM'] = df_imp['IMPORTANCE'].cumsum()
df_imp
      FEATURE  IMPORTANCE    CUMSUM
1         job    0.166889  0.166889
0         age    0.151696  0.318585
2     marital    0.134290  0.452875
13   previous    0.092159  0.545034
6     housing    0.078803  0.623837
3   education    0.072885  0.696722
4     default    0.056480  0.753202
12      pdays    0.048966  0.802168
8     contact    0.043289  0.845457
7        loan    0.037978  0.883436
14   poutcome    0.034298  0.917733
10      month    0.028382  0.946116
5     balance    0.028184  0.974300
11   campaign    0.021657  0.995957
9         day    0.004043  1.000000

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

print('Most Important Features:')
print(included_feats)
print('Number of Included Features =', len(included_feats))
Most Important Features:
['age', 'job', 'marital', 'education', 'default', 'housing', 'previous']
Number of Included Features = 7

Спасибо за чтение! Вы можете найти весь код этой статьи на GitHub

Заключение

Выбор функций является важной частью процесса построения модели, и он не только помогает повысить производительность, но и упрощает вашу модель и ее интерпретацию. Мы начали с рассмотрения использования теста хи-квадрат на независимость для выбора категориальных признаков. Затем мы рассмотрели матрицу корреляции Пирсона, чтобы визуально идентифицировать сильно коррелированные числовые признаки, а также коснулись анализа основных компонентов (АПК) как инструмента для автоматического уменьшения размерности наборов числовых данных. Наконец, мы рассмотрели feature_importances_ из Random Forest как способ выбора как категориальных, так и числовых значений. Мы надеемся, что это поможет вам начать свой путь к выбору функций!

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