← Назад к вопросам

Расскажи про самый неуспешный рабочий ML-проект

1.0 Junior🔥 151 комментариев
#Машинное обучение

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Неуспешный 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)

# Если даже это показали бы, пилот никогда не одобрили бы

Что мы выучили

Позитивный итог из неуспеха:

  1. EDA критична — всегда начинай с 2-3 часов исследования данных
  2. Выбор метрики — для imbalanced классификации: AUC-PR, F1, или business metric
  3. Data leakage — вопрос #1 перед обучением: "Есть ли информация из будущего?"
  4. Temporal splits — для временных рядов: никогда не миксуй старое и новое
  5. Business KPI — не бери accuracy, тестируй на реальной метрике (revenue, retention rate)
  6. Пилот перед production — хотя бы 1% трафика, A/B test

Финальная статистика неудачи

  • Время потрачено: 6 недель разработки
  • Бюджет: ~$80k (зарплаты, инфра)
  • Результат: модель не развёрнута
  • AUC в production-ready validation: 0.468 (хуже random)
  • Уроки: бесценны

Этот проект научил меня, что в ML 80% успеха — правильные questions и хороший baseline, а не fancy algorithms.