← Назад к вопросам
Как повысить 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%")
Ключевые выводы
- Диагностика первая — понимаем где проблема (FP или FN)
- Качество данных — удаляем выбросы, проверяем разметку
- Feature Engineering — часто даёт огромное улучшение
- Лучшие модели — XGBoost, CatBoost обычно лучше RandomForest
- SMOTE/взвешивание — критично при дисбалансе классов
- Threshold optimization — позволяет достичь нужного баланса
- Ensemble методы — часто даёт последний процент улучшения
- Итеративность — редко срабатывает с первой попытки, нужны эксперименты