Ответ
Одним из проектов, которым я горжусь, была разработка системы предсказания оттока (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))