手把手教你用Python+sklearn计算classification_report(附多分类不平衡数据集实战)
实战指南:用Python+sklearn深度解析classification_report与不平衡数据评估
当你在Kaggle竞赛或实际业务中遇到一个信用卡欺诈检测项目时,可能会发现99%的交易都是正常的,只有1%是欺诈交易。如果简单地预测所有交易都正常,准确率高达99%——但这显然是个失败的模型。这就是为什么我们需要classification_report这个强大的工具来揭示模型真实性能。
1. 环境准备与数据加载
首先确保你的Python环境已安装以下库:
pip install scikit-learn pandas numpy matplotlib我们将使用sklearn内置的信用卡欺诈数据集进行演示。这个数据集高度不平衡,正负样本比例约为1:1000,非常适合演示不平衡分类问题。
from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split # 生成不平衡数据集 X, y = make_classification(n_samples=10000, n_classes=2, weights=[0.99, 0.01], random_state=42) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=42) print(f"训练集样本分布: {np.bincount(y_train)}") print(f"测试集样本分布: {np.bincount(y_test)}")注意:使用
stratify=y参数确保训练集和测试集保持相同的类别分布比例
2. 基础模型训练与准确率陷阱
让我们先训练一个简单的随机森林模型,看看仅依赖准确率会有什么问题:
from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score # 训练模型 model = RandomForestClassifier(random_state=42) model.fit(X_train, y_train) # 预测并计算准确率 y_pred = model.predict(X_test) print(f"模型准确率: {accuracy_score(y_test, y_pred):.4f}")你可能会看到类似这样的输出:
模型准确率: 0.9933这个数字看起来非常优秀,但实际上可能掩盖了严重的问题。让我们深入分析。
3. classification_report全面解析
现在让我们引入classification_report来获取更全面的评估:
from sklearn.metrics import classification_report print(classification_report(y_test, y_pred))典型输出如下:
precision recall f1-score support 0 0.99 1.00 1.00 2970 1 0.00 0.00 0.00 30 accuracy 0.99 3000 macro avg 0.50 0.50 0.50 3000 weighted avg 0.98 0.99 0.99 30003.1 关键指标解读
precision(精确率):预测为正类的样本中,实际为正类的比例
- 公式:
TP / (TP + FP)
- 公式:
recall(召回率):实际为正类的样本中,被正确预测的比例
- 公式:
TP / (TP + FN)
- 公式:
f1-score:precision和recall的调和平均数
- 公式:
2 * (precision * recall) / (precision + recall)
- 公式:
support:每个类别的真实样本数
macro avg:各类别指标的简单算术平均
weighted avg:按各类别样本数加权的平均
3.2 不平衡数据的指标选择
对于我们的欺诈检测案例:
- 召回率最重要:我们希望尽可能捕捉所有欺诈交易
- 精确率次重要:减少误报,避免打扰正常客户
- F1分数:平衡精确率和召回率的综合指标
4. 改进不平衡数据表现的实战技巧
4.1 类别权重调整
# 设置类别权重 model = RandomForestClassifier( class_weight={0:1, 1:10}, # 给少数类10倍权重 random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred))4.2 采样方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 随机过采样 | 实现简单 | 可能导致过拟合 | 小规模数据 |
| SMOTE | 生成新样本 | 可能产生噪声 | 中等规模数据 |
| 随机欠采样 | 减少计算量 | 丢失信息 | 大规模数据 |
| 组合采样 | 平衡优缺点 | 实现复杂 | 各类场景 |
from imblearn.over_sampling import SMOTE # 使用SMOTE过采样 smote = SMOTE(random_state=42) X_res, y_res = smote.fit_resample(X_train, y_train) model = RandomForestClassifier(random_state=42) model.fit(X_res, y_res) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred))4.3 阈值调整技巧
默认情况下,分类器使用0.5作为决策阈值。对于不平衡数据,我们可以寻找最佳阈值:
from sklearn.metrics import precision_recall_curve # 获取预测概率 y_proba = model.predict_proba(X_test)[:, 1] # 计算PR曲线 precision, recall, thresholds = precision_recall_curve(y_test, y_proba) # 寻找最佳阈值(F1最大) f1_scores = 2 * (precision * recall) / (precision + recall) best_idx = np.argmax(f1_scores) best_threshold = thresholds[best_idx] # 使用新阈值预测 y_pred = (y_proba >= best_threshold).astype(int) print(classification_report(y_test, y_pred))5. 多分类场景下的classification_report
当处理超过两个类别时,classification_report同样适用。让我们看一个新闻分类的例子:
from sklearn.datasets import fetch_20newsgroups from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB # 加载数据 categories = ['sci.space', 'rec.sport.baseball', 'talk.politics.mideast'] newsgroups = fetch_20newsgroups(subset='train', categories=categories) X, y = newsgroups.data, newsgroups.target # 文本向量化 vectorizer = TfidfVectorizer(max_features=1000) X_vec = vectorizer.fit_transform(X) # 训练模型 model = MultinomialNB() model.fit(X_vec, y) # 测试集评估 test = fetch_20newsgroups(subset='test', categories=categories) X_test_vec = vectorizer.transform(test.data) y_pred = model.predict(X_test_vec) print(classification_report(test.target, y_pred, target_names=categories))多分类报告中,每个类别都有自己的precision、recall和f1-score,同时提供了micro、macro和weighted三种平均方式:
- micro avg:全局统计TP/FP/FN计算
- macro avg:各类别指标的简单平均
- weighted avg:按各类别样本数加权的平均
6. 高级应用与可视化
6.1 自定义报告输出
import pandas as pd report = classification_report(y_test, y_pred, output_dict=True) df = pd.DataFrame(report).transpose() df.to_csv('classification_report.csv', float_format='%.3f')6.2 与混淆矩阵结合分析
from sklearn.metrics import ConfusionMatrixDisplay import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) # 绘制分类报告热力图 report = classification_report(y_test, y_pred, output_dict=True) del report['accuracy'], report['macro avg'], report['weighted avg'] df = pd.DataFrame(report).iloc[:-3, :3] sns.heatmap(df, annot=True, cmap='Blues', ax=ax1) ax1.set_title('Classification Report Heatmap') # 绘制混淆矩阵 ConfusionMatrixDisplay.from_predictions(y_test, y_pred, ax=ax2) ax2.set_title('Confusion Matrix') plt.tight_layout() plt.show()6.3 多模型对比报告
from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC models = { 'Random Forest': RandomForestClassifier(random_state=42), 'Logistic Regression': LogisticRegression(class_weight='balanced'), 'SVM': SVC(class_weight='balanced', probability=True) } results = {} for name, model in models.items(): model.fit(X_train, y_train) y_pred = model.predict(X_test) results[name] = classification_report(y_test, y_pred, output_dict=True) # 提取各模型的f1-score进行比较 f1_scores = { name: report['weighted avg']['f1-score'] for name, report in results.items() } pd.Series(f1_scores).plot(kind='bar') plt.title('Model Comparison by Weighted F1-score') plt.ylabel('F1-score') plt.show()在实际项目中,我发现对于极端不平衡数据(如1:10000),单纯依赖class_weight可能不够,通常需要结合过采样和阈值调整才能获得理想结果。另外,不同业务场景对precision和recall的侧重不同——风控场景通常更看重recall,而推荐系统可能更关注precision。
