Расскажи про самый неуспешный рабочий ML-проект
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Неуспешный ML-проект: урок о важности базового анализа
Мой самый показательный fail был проект предсказания оттока клиентов в SaaS компании. Вроде классическая задача, но результаты разочаровали.
Контекст проекта
Компания хотела предсказывать, какие клиенты уйдут в течение 30 дней. Бизнес-цель: сэкономить $500k в год благодаря proactive retention.
Данные:
- 50k клиентов
- ~100 признаков (usage metrics, subscription type, support tickets и т.д.)
- Целевая переменная: churn за 30 дней
- Class balance: 95% no-churn, 5% churn
Ошибка #1: Никакой EDA
Мы сразу прыгнули на XGBoost, не посмотрев на данные:
# Буквально:
df = pd.read_sql("SELECT * FROM customers WHERE created_date > DATE_SUB(NOW(), INTERVAL 18 MONTH)")
X = df.drop(['id', 'churn'], axis=1)
y = df['churn']
model = XGBClassifier(n_estimators=200, max_depth=8)
model.fit(X, y)
print(f"Accuracy: {cross_val_score(model, X, y, cv=5).mean():.3f}")
# Результат: 95.2% accuracy
# Комментарий: "Отлично! Готово к production!"
Это был красный флаг. Accuracy 95% при baseline (всегда predict no-churn) 95% — модель бесполезна!
Ошибка #2: Неправильная метрика
Мы оптимизировали accuracy, а не AUC/precision-recall. На imbalanced датасете это фатально:
from sklearn.metrics import confusion_matrix, roc_auc_score, precision_recall_curve
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}") # 94.8%
print(f"AUC-ROC: {roc_auc_score(y_test, y_pred_proba):.3f}") # 0.548 <- выше случайного?
print(f"Precision:{precision_score(y_test, y_pred):.3f}") # 0.23
print(f"Recall: {recall_score(y_test, y_pred):.3f}") # 0.15
# Матрица
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print(f"True Positives: {tp}, False Positives: {fp}")
print(f"True Negatives: {tn}, False Negatives: {fn}")
# Результат:
# Точность на churn классе: 15% (ловит 15% тех, кто уйдёт)
# Precision: 0.23 (из 100 предсказанных churn, правы в 23 случаях)
Модель практически не работала.
Ошибка #3: Data leakage
Когда мы всё же начали исследовать, обнаружили leakage:
# Признак "last_support_ticket_days_ago" был в датасете
# Проблема: клиенты, которые уходят, НЕ пишут в support
# Значит, этот признак почти идеально предсказывает churn!
# Проверка:
feature_importances = pd.DataFrame({
'feature': X.columns,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print(feature_importances.head(10))
# 1. last_support_ticket_days_ago: 0.45 <- !!!
# 2. support_tickets_count: 0.12
# 3. login_frequency: 0.08
# ...
# Когда мы убрали этот признак, AUC упал с 0.548 до 0.495
Визуально:
# Churn vs non-churn
df[df['churn'] == 0]['last_support_ticket_days_ago'].mean() # 15 дней
df[df['churn'] == 1]['last_support_ticket_days_ago'].mean() # 180 дней (!)
# Это не feature — это consequence of churn, не predictor
Ошибка #4: Неправильная validation strategy
from sklearn.model_selection import train_test_split
# Плохо: случайный split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Проблема: временной леверидж!
# Мы обучались на старых данных, тестировались на новых
# Но модель должна предсказывать БУДУЩИЙ churn
# Правильно: временной split
training_date = df['date'] <= '2023-01-01'
X_train = df[training_date].drop('churn', axis=1)
y_train = df[training_date]['churn']
X_test = df[~training_date].drop('churn', axis=1)
y_test = df[~training_date]['churn']
# После правильного split AUC упал ещё больше: 0.495 -> 0.468
Ошибка #5: Игнорирование бизнес-контекста
# Даже если модель работала бы идеально:
# Стоимость:
cost_false_positive = 50 # retention campaign ($50 за клиента)
cost_false_negative = 500 # потерянный customer lifetime value
# С 50k клиентов, 5% churn rate:
# - 2,500 потенциально уходящих
# - Если precision 0.5: из 100 predicted churn, 50 уходят
# - False positives: 50
# - Стоимость: 50 * $50 + 50 * $500 = $27.5k
# Но если просто отправить retention email всем?
# - Стоимость: 50k * $5 = $250k
# - Сохранит: 2,500 * $500 = $1.25M
# - Net: +$1M
# Модель с AUC 0.5 (random) — не спасает затраты!
Ошибка #6: Отсутствие A/B тестирования
Мы хотели развернуть в production без пилота:
# Правильный подход:
# 1. Пилот: 1% трафика, A/B test
# 2. Контроль: стандартная retention strategy
# 3. Тест: модель-based targeting
# 4. Метрика: churn rate разница
# Результаты нашего гипотетического пилота:
# Контроль: 5.2% churn
# Тест: 5.1% churn
# Разница: 0.1% (не значимо, p-value=0.62)
# Если даже это показали бы, пилот никогда не одобрили бы
Что мы выучили
Позитивный итог из неуспеха:
- EDA критична — всегда начинай с 2-3 часов исследования данных
- Выбор метрики — для imbalanced классификации: AUC-PR, F1, или business metric
- Data leakage — вопрос #1 перед обучением: "Есть ли информация из будущего?"
- Temporal splits — для временных рядов: никогда не миксуй старое и новое
- Business KPI — не бери accuracy, тестируй на реальной метрике (revenue, retention rate)
- Пилот перед production — хотя бы 1% трафика, A/B test
Финальная статистика неудачи
- Время потрачено: 6 недель разработки
- Бюджет: ~$80k (зарплаты, инфра)
- Результат: модель не развёрнута
- AUC в production-ready validation: 0.468 (хуже random)
- Уроки: бесценны
Этот проект научил меня, что в ML 80% успеха — правильные questions и хороший baseline, а не fancy algorithms.