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

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

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

Набор данных и определение проблемы:

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

У нас есть несколько функций, таких как:

  • Числовые значения: калории, углеводы, белки, жиры и т. д.
  • Категория: сложность, время приготовления и т. д.
  • Текст:описание и recipe_name

Как использовать вложения для извлечения информации из описания и названия рецепта?

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

Во-первых: нам нужно импортировать модель и токенизатор:

Есть разные модели, которые мы можем попробовать, и вы можете проверить их здесь: https://huggingface.co/models?pipeline_tag=feature-extraction.

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

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

from transformers import AutoModel, AutoTokenizer
model_ckpt = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
model = AutoModel.from_pretrained(model_ckpt)

Во-вторых: мы извлекаем скрытое состояние, связанное с токеном CLS, которое представляет всю последовательность текста, и вместо того, чтобы работать с массивом 768 для каждого токена в строке, нам просто нужно иметь дело с одним (токеном). 768 размер варьируется от модели к модели).

Токен CLS в случае DistilBert является первым, поэтому мы можем использовать следующий код для доступа к скрытому состоянию:

df_train_clean['recipe_name'] = df_train_clean['recipe_name'].apply(lambda x: model(**tokenizer(x, return_tensors="pt")).last_hidden_state[:,0,:].detach().numpy()[0])
df_train_clean['description'] = df_train_clean['description'].apply(lambda x: model(**tokenizer(x, return_tensors="pt")).last_hidden_state[:,0,:].detach().numpy()[0])

Нанесение вложений

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

Возьмем в качестве примера recipe_name.

Начнем с создания фрейма данных из вложений:

recipe_name_df = helper.get_embeddings_df(df_train_clean, 'recipe_name')

Затем мы применяем PCA к этому новому набору данных:

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
recipe_name_2_components = pd.DataFrame(pca.fit_transform(recipe_name_df), columns = ['X', 'Y'])

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

from sklearn.preprocessing import KBinsDiscretizer
bin = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='uniform')
df_train_clean['bin_sales'] = bin.fit_transform(df_train_clean['sales'].values.reshape(-1, 1))
sns.countplot(df_train_clean.bin_sales)
plt.title("Number of samples in each bin level")
plt.show()

Теперь мы можем создать точечную диаграмму с двумя компонентами и другим цветом на основе корзины.

recipe_name_2_components['bin_sales'] = df_train_clean['bin_sales']
sns.scatterplot(data=recipe_name_2_components, x='X', y='Y', hue='bin_sales', palette="tab10")
plt.show()

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

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

Модель

# get a 768 column data frame for recipe_name and description
recipe_name_df = helper.get_embeddings_df(df_train_clean, 'recipe_name')
description_df = helper.get_embeddings_df(df_train_clean, 'description')
recipe_name_df_test = helper.get_embeddings_df(df_test_clean, 'recipe_name')
description_df_test = helper.get_embeddings_df(df_test_clean, 'description')
# identify the number of components needed based on variance explained
recipe_name_components, recipe_name_components_test = helper.apply_PCA(recipe_name_df, recipe_name_df_test, 'recipe_name', variance_explained=0.8)
description_components, description_components_test = helper.apply_PCA(description_df, description_df_test, 'description', variance_explained=0.8)
# merge with data frame
df_train_clean = pd.merge(df_train_clean, recipe_name_components, left_index=True, right_index=True, how='left')
df_train_clean = pd.merge(df_train_clean, description_components, left_index=True, right_index=True, how='left')
df_test_clean = pd.merge(df_test_clean, recipe_name_components_test, left_index=True, right_index=True, how='left')
df_test_clean = pd.merge(df_test_clean, description_components_test, left_index=True, right_index=True, how='left')

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

Вы можете проверить весь код здесь: https://github.com/rjguedes8/feature_embedding/blob/main/notebooks/feature_embedding.ipynb