Попробуйте: они работают лучше, чем горячие кодировки.

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

Если вам не терпится ввести код, смело переходите к разделу Реализация. Еще есть сопутствующая тетрадь с разложенным конвейером полностью.

Почему вложения сущностей?

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

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

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

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

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

Хватит разговоров; перейдем к реализации.

Выполнение

Я буду использовать PyTorch, fastai и sklearn. Конвейер состоит из трех этапов:

1. Обучите нейронную сеть с помощью вложений.

# import modules, read data, and define options
from fastai.tabular.all import *
df = pd.read_csv('/train.csv', low_memory=False)
cont,cat = cont_cat_split(df_nn, max_card=9000, dep_var='target')
procs = [Categorify, Normalize]
splits = RandomSplitter()(df)
device =torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# feed the data into the Learner and train
to_nn = TabularPandas(nn, procs, cat, cont,
                      splits=splits, y_names='target')
dls = to_nn.dataloaders(1024, device = device)
learn = tabular_learner(dls, layers=[500,250], n_out=1)
learn.fit_one_cycle(12, 3e-3)

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

Этот шаг можно выполнить в чистом PyTorch или TensorFlow; просто убедитесь, что вы изменили соответствующие части последующего кода, если захотите.

2. Замените каждое категориальное значение его вектором вложения.

def embed_features(learner, xs):
  """
  learner: fastai Learner used to train the neural net
  xs: DataFrame containing input variables. Categorical values are defined by their rank. 
 ::return:: copy of `xs` with embeddings replacing each categorical variable
  """
  xs = xs.copy()
  for i,col in enumerate(learn.dls.cat_names):
    
    # get matrix containing each row's embedding vector
    emb = learn.model.embeds[i]
    emb_data = emb(tensor(xs[col], dtype=torch.int64))
    emb_names = [f'{col}_{j}' for j in range(emb_data.shape[1])]
    
    # join the embedded category and drop the old feature column
    feat_df = pd.DataFrame(data=emb_data, index=xs.index,               
                           columns=emb_names)
    xs = xs.drop(col, axis=1)
    xs = xs.join(feat_df)
  return xs

Эта функция расширяет каждый категориальный столбец (вектор размера n_rows) в матрицу встраивания формы (n_rows, embedding_dim). Теперь мы используем его для встраивания категориальных столбцов в наши данные.

emb_xs = embed_features(learn, to.train.xs)
emb_valid_xs = embed_features(learn, to.valid.xs)

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

3. Обучите алгоритмы машинного обучения на встроенных данных.

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

Вот пример конвейера для обучения случайного леса:

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
 
rf = RandomForestClassifier(n_estimators=40, max_samples=100_000,      
                          max_features=.5, min_samples_leaf=5)
rf = rf.fit(emb_xs, y)
roc_auc_score(rf.predict(emb_valid_xs), to.valid.y)

Если вы хотите использовать другой алгоритм, просто замените RandomForestClassifier на GradientBoostingClassifier или любой другой алгоритм, и вы увидите повышение производительности по сравнению с методом однократного кодирования.

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

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

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

Мы можем решить эту проблему: выбрать наиболее важные функции и тренироваться только на них.

Ансамбли деревьев Sklearn могут автоматически вычислять важность признаков с помощью атрибута feature_importances_ модели, который возвращает список процентов, соответствующих каждому столбцу признаков. Чтобы вычислить эти проценты, sklearn проходит через каждое разбиение на каждом дереве, суммирует информационный прирост каждого разбиения, в котором используется функция, и использует полученный накопленный информационный прирост в качестве прокси для вклада.

m = RandomForestClassifier().fit(emb_xs, y)
fi = pd.DataFrame({'cols':df.columns, 'imp':m.feature_importances_})
emb_xs_filt = emb_xs.loc[fi['imp'] > .002]

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

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

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

Вывод

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

использованная литература

  1. Го, Ченг и др. Сущностные вложения категориальных переменных. ArXiv: 1604.06737
  2. Райт MN, König IR. 2019. Разделение по категориальным предикторам в случайных лесах. PeerJ. https://doi.org/10.7717/peerj.6339.
  3. Ховард, Джереми. Глубокое обучение для программистов с помощью Fastai и PyTorch: приложения искусственного интеллекта без докторской степени. Https://www.amazon.com/Deep-Learning-Coders-fastai-PyTorch/dp/1492045527

И спасибо fastai forum и fastai book [3], которые предоставили ресурсы для изучения материала.