Какими выполненными задачами или проектами ты особенно гордишься?

«Какими выполненными задачами или проектами ты особенно гордишься?» — вопрос из категории Софт-скиллы, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый пример ответа, который можно адаптировать под свой опыт.

Ответ

Одним из проектов, которым я горжусь, была разработка системы предсказания оттока (churn prediction) для крупного телеком-оператора.

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

Что было сделано:

  • Провел глубокий EDA и создал более 50 новых признаков, описывающих поведение клиента: паттерны использования услуг (средняя длительность звонков, трафик данных), финансовые метрики (стабильность платежей, отношение оплаченной суммы к счету), историю обращений в поддержку.
  • Основной проблемой была сильная дисбаланс классов (отток — всего ~3% в месячной выборке). Для борьбы с этим я использовал комбинацию техник: подбор весов классов в LightGBM, применение SMOTE для синтеза примеров минорного класса на уровне признаков и фокусировку на метрике Precision-Recall AUC и F1-score, а не на accuracy.
  • Построил автоматизированный пайплайн от сырых данных до прогноза с использованием Apache Airflow для оркестрации. Пайплайн включал этапы извлечения данных из Data Lake, предобработки, применения модели и загрузки прогнозов в CRM-систему.

Результат: Модель на LightGBM достигла ROC-AUC = 0.92 и F1-score = 0.78 на тестовой выборке. Внедрение системы позволило сократить время генерации ежемесячного списка «рисковых» клиентов с нескольких дней до 2 часов. По итогам A/B-теста кампания удержания, основанная на прогнозах модели, показала увеличение эффективности на 15% по сравнению с предыдущей рандомной стратегией.

# Ключевой фрагмент: обработка дисбаланса в LightGBM
import lightgbm as lgb
from sklearn.metrics import classification_report

# Расчет весов классов для фокуса на минорный класс (отток)
scale_pos_weight = len(y_train[y_train == 0]) / len(y_train[y_train == 1])  # ~ 32

params = {
    'objective': 'binary',
    'metric': ['auc', 'binary_logloss'],
    'scale_pos_weight': scale_pos_weight,  # Критичный параметр
    'boosting_type': 'gbdt',
    'learning_rate': 0.05,
    'num_leaves': 40,
    'feature_fraction': 0.8
}

# Обучение с валидацией
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_val, label=y_val, reference=train_data)

model = lgb.train(params,
                  train_data,
                  valid_sets=[valid_data],
                  num_boost_round=1000,
                  callbacks=[lgb.early_stopping(50)])

# Оценка на тесте
y_pred_proba = model.predict(X_test, num_iteration=model.best_iteration)
y_pred = (y_pred_proba > 0.5).astype(int)  # Порог можно оптимизировать под F1
print(classification_report(y_test, y_pred))