Ответ
Одним из проектов, которым я горжусь, была разработка системы предсказания оттока (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)) Ответ 18+ 🔞
А, вспомнил один проект, до сих пор сам от себя охуеваю, как мы его вытянули. Делали систему, чтобы предсказывать, какие клиенты телека от нас нахуй сбегут в следующем месяце. Чтобы служба удержания могла им сладкий пряник подсунуть, пока они не решили всё, ёпта, нафиг.
Суть задачи: Угадать, кому уже в ротберунчик наш сервис, и кто готов накрыться медным тазом.
Что наворотили:
- Сначала сидел, копался в данных, как крот ебаный. Нагенерил овердохуища новых признаков — больше полусотни. Не просто «звонил/не звонил», а всякую хитрожопую статистику: как часто клиент трафик хавает, стабильно ли платит или вечно в долгах как в шелках, сколько раз уже в поддержку звонил материться. В общем, полный психологический портрет нарисовал.
- Главная засада была в том, что уходить-то хотели единицы, всего 3%, а остальные 97% — сидели тихо. Дисбаланс классов, ёперный театр! Если тупо всех предсказывать как «останутся», то accuracy будет 97%, а модель — пизда рулю, бесполезная. Поэтому я с этим дисбалансом воевал, как лев. В LightGBM веса классов подкрутил, чтобы на «уйдут» больше внимания обращал, ещё SMOTE применил, чтобы синтетических «уходящих» наделать. И смотрел не на accuracy, а на Precision-Recall AUC и F1-score — вот это были наши ключевые метрики.
- Потом запилил автоматический пайплайн на Apache Airflow, чтобы всё само бегало. От загрузки сырых данных из озера, до предсказания и выгрузки списка «рисковых» в CRM. Красота.
Что вышло: Модель на LightGBM выдала ROC-AUC = 0.92 и F1-score = 0.78. Это было охуенно для такой нестабильной задачи. Раньше маркетологи неделю списки в экселе мучили, а теперь за два часа всё готово. Провели 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))