Собесов

Стажировка ML — Банковский переполох: бинарная классификация по хешированным фичам

ML / Data ScienceБинарная классификацияСредняяMiddle

Условие

Бизнес-задача: предсказать, у кого из клиентов банка появится потребность взять отпуск ради эстетики (target ∈ {0, 1}). Дано:

  • train.csv: колонки customer, <hashed_factor1>, …, <hashed_factor20>, target.
  • test.csv: то же без target.

Формат вывода

answers.csv с колонками customer, target. Метрика — ROC-AUC ≥ 0.7.

Решение

Подход

Стандартный пайплайн бинарной классификации: разведка → препроцессинг → модель → оценка.

1. EDA (разведочный анализ)

import pandas as pd
import numpy as np
 
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
 
# Базовое
print(train.shape, test.shape)
print(train.dtypes)
print(train['target'].value_counts(normalize=True))   # дисбаланс?
print(train.isna().sum())                             # пропуски
print(train.describe())                               # масштабы фичей

Хешированные фичи hashed_factor1..20 — обычно категориальные (строки/числа). Понимаем:

  • сколько уникальных значений в каждой;
  • есть ли пересечение train/test по уровням;
  • зависимость target от каждой фичи через groupby('factor')['target'].mean().

2. Препроцессинг

features = [c for c in train.columns if c.startswith("hashed")]
 
# Если фичи категориальные с большим числом уровней — TargetEncoding.
from category_encoders import TargetEncoder
te = TargetEncoder(cols=features, smoothing=20)
X_train = te.fit_transform(train[features], train['target'])
X_test = te.transform(test[features])
 
# Альтернатива: для CatBoost/LightGBM — нативная поддержка категориальных.

3. Модель

Для табличных данных с категориальными фичами GBDT (CatBoost / LightGBM / XGBoost) почти всегда лучшее решение.

from catboost import CatBoostClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import roc_auc_score
 
X = train[features].astype(str)  # чтобы CatBoost понял как категориальные
y = train['target']
 
cat_features = list(range(X.shape[1]))
 
model = CatBoostClassifier(
    iterations=1000,
    learning_rate=0.05,
    depth=6,
    cat_features=cat_features,
    eval_metric='AUC',
    random_seed=42,
    verbose=100,
)
 
X_tr, X_va, y_tr, y_va = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
model.fit(X_tr, y_tr, eval_set=(X_va, y_va), early_stopping_rounds=50)
 
print("Val AUC:", roc_auc_score(y_va, model.predict_proba(X_va)[:, 1]))

4. Кросс-валидация

from sklearn.model_selection import StratifiedKFold
 
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
aucs = []
test_preds = np.zeros(len(test))
 
for fold, (tr_idx, va_idx) in enumerate(skf.split(X, y)):
    m = CatBoostClassifier(iterations=1000, learning_rate=0.05, depth=6,
                           cat_features=cat_features, eval_metric='AUC',
                           random_seed=42, verbose=False)
    m.fit(X.iloc[tr_idx], y.iloc[tr_idx],
          eval_set=(X.iloc[va_idx], y.iloc[va_idx]),
          early_stopping_rounds=50)
    val_pred = m.predict_proba(X.iloc[va_idx])[:, 1]
    aucs.append(roc_auc_score(y.iloc[va_idx], val_pred))
    test_preds += m.predict_proba(test[features].astype(str))[:, 1] / skf.n_splits
    print(f"Fold {fold} AUC: {aucs[-1]:.4f}")
 
print("Mean CV AUC:", np.mean(aucs))

5. Сабмит

submission = pd.DataFrame({
    "customer": test["customer"],
    "target": test_preds,
})
submission.to_csv("answers.csv", index=False)

6. Если AUC < 0.7

  • Feature engineering: пары фич (counts, ratios, target-encoded).
  • Stacking: 2-3 модели разного типа (CatBoost, LightGBM, LogReg на TargetEncoded), затем мета-модель.
  • Балансировка классов: если сильный дисбаланс, class_weights или undersampling.
  • Hyperparam tuning: Optuna / RandomSearch.

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

  1. Утечка таргета. При TargetEncoding обязательно делать out-of-fold энкодинг внутри CV, иначе утечка раздуёт AUC на валидации.
  2. predict vs predict_proba. Для AUC нужен скор/вероятность, а не класс.
  3. Категориальные через astype(str) для CatBoost. Иначе CatBoost будет пытаться использовать как числовые.
  4. Случайные сиды. Фиксируйте seed для воспроизводимости.
  5. Дисбаланс классов. Если 1% позитивов — AUC будет работать, но accuracy нет; используйте AUC и/или PR-AUC.
  6. Стратификация. StratifiedKFold обязательна при дисбалансе.
  7. Тестовые уровни не в train. TargetEncoding должен корректно обрабатывать unseen — обычно через default = global mean.
  8. Калибровка вероятностей. Для AUC некритично (порядок важен), но если бизнес требует «вероятность 70%», нужен CalibratedClassifierCV.

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

CatBoost / LightGBM с категориальными фичами + 5-fold StratifiedKFold + усреднение test-предсказаний по фолдам. Базовый pipeline даёт AUC 0.75-0.85 на табличке такого формата. При проблемах — TargetEncoding (out-of-fold), feature engineering и stacking.

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

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

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