Собесов

DataLearn ML-101: Кодирование категориальных фичей

ML / Data ScienceFeature engineeringСредняяMiddle

Условие

В датасете есть категориальные фичи разной природы:

  • city — 5000 уникальных значений;
  • device_type — 4 значения (mobile/tablet/desktop/tv);
  • user_segment — 10 значений с порядком (S → M → L → XL);
  • product_id — 1М уникальных;
  • referrer_domain — 50К уникальных, длинный хвост.

Выберите способ кодирования для каждой фичи под (а) логистическую регрессию; (б) градиентный бустинг (CatBoost/LightGBM). Объясните выбор и риски.

Решение

Способы

Способ Размерность Логрег Бустинг Утечка target
One-Hot Encoding взрыв ок при ≤ 50 ок при ≤ 50-100 нет
Ordinal (label encoding) 1 плохо нормально (но не оптимально) нет
Target / Mean encoding 1 хорошо хорошо да — нужна защита
Frequency / Count encoding 1 средне хорошо нет
Hashing trick задаётся средне хорошо для high-card нет
Embeddings (NN/DL) задаётся n/a n/a (но FFM/факторизация) нет

По фичам

device_type (4 уник.): One-Hot для логрег. Для бустинга — нативная категориальная (CatBoost / LightGBM).

user_segment (10, порядок): Ordinal (S=1, M=2, L=3, XL=4). И для логрег, и для бустинга. Если поведение монотонно — отлично.

city (5000):

  • Логрег: Target encoding с регуляризацией (sklearn-contrib TargetEncoder или category_encoders). Альтернатива — частотное.
  • Бустинг: нативная категориальная или target encoding со smoothing.

product_id (1М):

  • Логрег: Hashing trick (например, 2¹⁵ корзин) или embeddings, если переходим в FFM/DL.
  • Бустинг: чаще не подаём raw product_id — заменяем агрегатами (CTR_30d, avg_price, n_views).

referrer_domain (50K, длинный хвост):

  • Группировать редкие в OTHER: df[col].value_counts() < 100 → 'OTHER'.
  • Дальше target encoding или frequency.

Target encoding с защитой от утечки

from category_encoders import TargetEncoder
 
# CV-based encoding (out-of-fold)
from sklearn.model_selection import KFold
import numpy as np, pandas as pd
 
def oof_target_encode(X, y, col, n_splits=5, smoothing=10):
    kf = KFold(n_splits=n_splits, shuffle=True, random_state=0)
    out = np.full(len(X), np.nan)
    for tr, va in kf.split(X):
        prior = y.iloc[tr].mean()
        stats = y.iloc[tr].groupby(X.iloc[tr][col]).agg(['mean', 'count'])
        smooth = (stats['mean'] * stats['count'] + prior * smoothing) / (stats['count'] + smoothing)
        out[va] = X.iloc[va][col].map(smooth).fillna(prior).values
    return out

smoothing затягивает редкие категории к prior — защищает от переобучения на маленьких группах.

Что делать с unseen в test

# при .transform на test
encoded = X_test[col].map(encoding_dict).fillna(global_prior)

CatBoost: ordered target encoding

CatBoost делает target encoding онлайн — для каждой строки использует только предыдущие строки. Это уже out-of-fold-like — отсюда устойчивость и не нужно вручную энкодить.

Подводные камни

  1. One-Hot на 5000 значений в логрег → разреженная матрица 5000 колонок, переобучение и медленный fit. Использовать sparse + L1.
  2. Mean encoding без CV → leakage; в проде Gini падает на 5-10 п.п.
  3. Ordinal без порядка (city → произвольные числа) для логрег = бред: модель решит, что «10 > 1». Только для деревьев и осторожно.
  4. CatBoost категориальная фича = строка, а LightGBM — category dtype или явный список колонок.
  5. Хеш-коллизии на hashing trick: 2¹⁵ для 1М значений = 30+ значений на bucket. Часто всё равно работает для логрег с регуляризацией.
  6. product_id подавать как число в логрег / линейку = leakage в худшем виде (модель «думает», что 12345 > 100).
  7. Test-only категории: после fit encoder может не знать значение — обязательно fill prior.

Эталонный ответ

  • device_type (4): One-Hot для логрег, native cat для бустинга.
  • user_segment (10, порядок): Ordinal для обоих.
  • city (5000): Target encoding с CV+smoothing для логрег; native cat или target enc для бустинга.
  • product_id (1М): Hashing trick для логрег; агрегаты (CTR, avg_price) для бустинга.
  • referrer_domain (50К, хвост): группировать редкие → OTHER, потом target encoding.

Ключи: target encoding делать out-of-fold (или через CatBoost ordered), unseen в test → fill prior, ordinal — только при настоящем порядке.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти