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

Как повысить precision и recall с 60% до 80%, если это требуется бизнесу?

2.0 Middle🔥 231 комментариев
#Машинное обучение#Метрики и оценка моделей#Опыт и проекты

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

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

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

Как повысить precision и recall с 60% до 80%?

Достижение 80% precision и recall — реальная задача, которую я решал много раз. Это требует систематического подхода, анализа и экспериментов.

1. Диагностика: где находится проблема?

import pandas as pd
import numpy as np
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support, roc_curve, auc
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

# Обучаем текущую модель
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)[:, 1]

# Текущие метрики
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()

precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * (precision * recall) / (precision + recall)

print(f"Current metrics:")
print(f"Precision: {precision:.2%}")
print(f"Recall: {recall:.2%}")
print(f"F1-score: {f1:.4f}")
print(f"\nConfusion Matrix:")
print(f"TN: {tn}, FP: {fp}, FN: {fn}, TP: {tp}")
print(f"\nErrors:")
print(f"False Positives: {fp} (Type I error)")
print(f"False Negatives: {fn} (Type II error)")

# Что важнее для бизнеса?
if fn > fp:
    print("\nПРОБЛЕМА: Много ложных отрицаний (пропускаем позитивные)")
    print("Решение: Повышаем recall")
else:
    print("\nПРОБЛЕМА: Много ложных положительных (ошибочно предсказываем позитив)")
    print("Решение: Повышаем precision")

2. Стратегия 1: Улучшение качества данных

# Шаг 1: Проверяем на выбросы и ошибки разметки
from scipy import stats

# Находим выбросы (IQR метод)
Q1 = X_train.quantile(0.25)
Q3 = X_train.quantile(0.75)
IQR = Q3 - Q1

outliers_mask = ((X_train < (Q1 - 1.5 * IQR)) | (X_train > (Q3 + 1.5 * IQR))).any(axis=1)
print(f"Found {outliers_mask.sum()} outlier rows")

# Опция 1: Удалить выбросы
X_clean = X_train[~outliers_mask]
y_clean = y_train[~outliers_mask]

# Опция 2: Обучить отдельную модель для "нормальных" примеров
model_clean = RandomForestClassifier(n_estimators=100, random_state=42)
model_clean.fit(X_clean, y_clean)

y_pred_clean = model_clean.predict(X_test)
precision_clean = (y_test[y_pred_clean == 1] == 1).sum() / (y_pred_clean == 1).sum()
recall_clean = (y_test[y_pred_clean == 1] == 1).sum() / (y_test == 1).sum()
print(f"\nAfter removing outliers:")
print(f"Precision: {precision_clean:.2%}")
print(f"Recall: {recall_clean:.2%}")

3. Стратегия 2: Feature Engineering

# Новые признаки могут радикально улучшить метрики

# Пример: если есть признаки user_age и purchase_amount
X_enhanced = X_train.copy()

# Полиномиальные признаки
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_enhanced[['feature1', 'feature2']])
X_enhanced['feature1_squared'] = X_poly[:, 2]
X_enhanced['feature2_squared'] = X_poly[:, 3]
X_enhanced['feature1_feature2'] = X_poly[:, 4]

# Взаимодействия
X_enhanced['interaction_age_amount'] = X_enhanced['age'] * X_enhanced['amount']
X_enhanced['ratio_amount_age'] = X_enhanced['amount'] / (X_enhanced['age'] + 1)

# Логарифмические преобразования (для положительных признаков)
X_enhanced['log_amount'] = np.log1p(X_enhanced['amount'])

# Бинаризация
X_enhanced['high_amount'] = (X_enhanced['amount'] > X_enhanced['amount'].median()).astype(int)

# Обучаем новую модель
model_enhanced = RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42)
model_enhanced.fit(X_enhanced, y_train)

# Оцениваем улучшение
from sklearn.metrics import precision_recall_fscore_support
X_test_enhanced = X_test.copy()
# (применяем те же преобразования к X_test)
y_pred_enhanced = model_enhanced.predict(X_test_enhanced)
prec, rec, f1, _ = precision_recall_fscore_support(y_test, y_pred_enhanced, average='binary')
print(f"After feature engineering:")
print(f"Precision: {prec:.2%}, Recall: {rec:.2%}, F1: {f1:.4f}")

4. Стратегия 3: Гиперпараметры и архитектура модели

from sklearn.model_selection import GridSearchCV
from xgboost import XGBClassifier

# Переходим на более мощный алгоритм
model_xgb = XGBClassifier(
    n_estimators=200,
    max_depth=6,
    learning_rate=0.05,
    subsample=0.8,
    colsample_bytree=0.8,
    min_child_weight=5,
    gamma=1,
    random_state=42
)

model_xgb.fit(X_train, y_train)
y_pred_xgb = model_xgb.predict(X_test)
prec_xgb, rec_xgb, f1_xgb, _ = precision_recall_fscore_support(y_test, y_pred_xgb, average='binary')
print(f"XGBoost:")
print(f"Precision: {prec_xgb:.2%}, Recall: {rec_xgb:.2%}, F1: {f1_xgb:.4f}")

# GridSearch для оптимизации
param_grid = {
    'max_depth': [5, 6, 7, 8],
    'learning_rate': [0.01, 0.05, 0.1],
    'min_child_weight': [3, 5, 7],
    'gamma': [0, 0.5, 1],
    'subsample': [0.7, 0.8, 0.9]
}

grid_search = GridSearchCV(
    XGBClassifier(n_estimators=100, random_state=42),
    param_grid,
    cv=5,
    scoring='f1',
    n_jobs=-1,
    verbose=1
)

grid_search.fit(X_train, y_train)
print(f"\nBest parameters: {grid_search.best_params_}")

model_best = grid_search.best_estimator_
y_pred_best = model_best.predict(X_test)
prec_best, rec_best, f1_best, _ = precision_recall_fscore_support(y_test, y_pred_best, average='binary')
print(f"After GridSearch:")
print(f"Precision: {prec_best:.2%}, Recall: {rec_best:.2%}, F1: {f1_best:.4f}")

5. Стратегия 4: Балансировка классов

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# Если дисбаланс классов — это проблема
print(f"Class distribution:")
print(f"Class 0: {(y_train == 0).sum()}")
print(f"Class 1: {(y_train == 1).sum()}")

# Способ 1: SMOTE (oversample minority class)
smote = SMOTE(random_state=42)
X_balanced, y_balanced = smote.fit_resample(X_train, y_train)

model_balanced = RandomForestClassifier(n_estimators=150, random_state=42)
model_balanced.fit(X_balanced, y_balanced)

y_pred_balanced = model_balanced.predict(X_test)
prec_bal, rec_bal, f1_bal, _ = precision_recall_fscore_support(y_test, y_pred_balanced, average='binary')
print(f"\nAfter SMOTE:")
print(f"Precision: {prec_bal:.2%}, Recall: {rec_bal:.2%}, F1: {f1_bal:.4f}")

# Способ 2: Взвешивание классов в модели
model_weighted = RandomForestClassifier(
    n_estimators=150,
    class_weight='balanced',  # автоматически взвешивает редкий класс
    random_state=42
)
model_weighted.fit(X_train, y_train)

y_pred_weighted = model_weighted.predict(X_test)
prec_w, rec_w, f1_w, _ = precision_recall_fscore_support(y_test, y_pred_weighted, average='binary')
print(f"\nWith class_weight='balanced':")
print(f"Precision: {prec_w:.2%}, Recall: {rec_w:.2%}, F1: {f1_w:.4f}")

6. Стратегия 5: Пороговая оптимизация

from sklearn.metrics import precision_recall_curve

# Стандартный порог = 0.5
# Но можно настроить в зависимости от требований бизнеса

fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
precisions, recalls, thresholds_pr = precision_recall_curve(y_test, y_pred_proba)

# Ищем порог, где precision >= 0.80 и recall >= 0.80
best_threshold = None
best_f1 = 0

for threshold in np.arange(0.1, 0.9, 0.01):
    y_pred_threshold = (y_pred_proba >= threshold).astype(int)
    
    if (y_pred_threshold == 1).sum() == 0:  # нет положительных предсказаний
        continue
    
    prec = (y_test[y_pred_threshold == 1] == 1).sum() / (y_pred_threshold == 1).sum()
    rec = (y_test[y_pred_threshold == 1] == 1).sum() / (y_test == 1).sum()
    
    if prec >= 0.80 and rec >= 0.80:
        f1 = 2 * (prec * rec) / (prec + rec)
        if f1 > best_f1:
            best_f1 = f1
            best_threshold = threshold

if best_threshold:
    print(f"\nFound optimal threshold: {best_threshold:.2f}")
    print(f"At this threshold: Precision={prec:.2%}, Recall={rec:.2%}, F1={best_f1:.4f}")
else:
    print("\nCannot reach 80% both precision and recall with current model")

7. Стратегия 6: Ensemble методы

from sklearn.ensemble import VotingClassifier, StackingClassifier
from sklearn.linear_model import LogisticRegression

# Комбинируем несколько моделей
rf = RandomForestClassifier(n_estimators=100, random_state=42)
xgb = XGBClassifier(n_estimators=100, random_state=42)
from sklearn.svm import SVC
svm = SVC(probability=True, kernel='rbf', random_state=42)

# Voting Classifier
voting_clf = VotingClassifier(
    estimators=[('rf', rf), ('xgb', xgb), ('svm', svm)],
    voting='soft'  # взвешивает вероятности
)

voting_clf.fit(X_train, y_train)
y_pred_voting = voting_clf.predict(X_test)
prec_v, rec_v, f1_v, _ = precision_recall_fscore_support(y_test, y_pred_voting, average='binary')
print(f"Voting Classifier:")
print(f"Precision: {prec_v:.2%}, Recall: {rec_v:.2%}, F1: {f1_v:.4f}")

# Stacking (мета-модель)
stacking_clf = StackingClassifier(
    estimators=[('rf', rf), ('xgb', xgb), ('svm', svm)],
    final_estimator=LogisticRegression()
)

stacking_clf.fit(X_train, y_train)
y_pred_stacking = stacking_clf.predict(X_test)
prec_s, rec_s, f1_s, _ = precision_recall_fscore_support(y_test, y_pred_stacking, average='binary')
print(f"\nStacking Classifier:")
print(f"Precision: {prec_s:.2%}, Recall: {rec_s:.2%}, F1: {f1_s:.4f}")

8. Полный workflow для повышения метрик

# Всё вместе:
print("=== Step-by-step improvement ===")

# 1. Baseline
baseline_model = RandomForestClassifier(n_estimators=100, random_state=42)
baseline_model.fit(X_train, y_train)
y_pred_baseline = baseline_model.predict(X_test)
prec_base, rec_base, f1_base, _ = precision_recall_fscore_support(y_test, y_pred_baseline, average='binary')
print(f"1. Baseline: Precision={prec_base:.2%}, Recall={rec_base:.2%}")

# 2. + Feature Engineering
# (код выше)
print(f"2. + Feature Engineering: Precision={prec_enhanced:.2%}, Recall={rec_enhanced:.2%}")

# 3. + Better model (XGBoost)
# (код выше)
print(f"3. + XGBoost: Precision={prec_xgb:.2%}, Recall={rec_xgb:.2%}")

# 4. + SMOTE
# (код выше)
print(f"4. + SMOTE: Precision={prec_bal:.2%}, Recall={rec_bal:.2%}")

# 5. + Threshold tuning
# (код выше)
print(f"5. + Threshold tuning: Precision={prec:.2%}, Recall={rec:.2%}")

# 6. + Ensemble
# (код выше)
print(f"6. + Ensemble: Precision={prec_v:.2%}, Recall={rec_v:.2%}")

print("\nGoal reached if final precision and recall >= 80%")

Ключевые выводы

  1. Диагностика первая — понимаем где проблема (FP или FN)
  2. Качество данных — удаляем выбросы, проверяем разметку
  3. Feature Engineering — часто даёт огромное улучшение
  4. Лучшие модели — XGBoost, CatBoost обычно лучше RandomForest
  5. SMOTE/взвешивание — критично при дисбалансе классов
  6. Threshold optimization — позволяет достичь нужного баланса
  7. Ensemble методы — часто даёт последний процент улучшения
  8. Итеративность — редко срабатывает с первой попытки, нужны эксперименты
Как повысить precision и recall с 60% до 80%, если это требуется бизнесу? | PrepBro