阶段3 | NLP传统 · 第18课/共30课

📖 第18课:分类模型评估

🎯 课程目标

评估是机器学习的"仪表盘"。选择错误的指标可能导致看似优秀实则无用的模型。本课系统掌握分类评估体系。

📐 核心原理

评估指标选择指南

场景关键指标原因
垃圾邮件精确率误杀正常邮件代价大
疾病筛查召回率漏诊代价远大于误诊
情感分析F1正负类都重要
类别不平衡AUC-PRROC可能过于乐观
💡 macro vs micro:macro平均每个类别权重相同,关注少数类;micro让多数类主导,反映整体准确率。类别不平衡时一定要看macro。

🔬 完整评估报告

多分类评估

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_predict from sklearn.metrics import (classification_report, confusion_matrix, precision_recall_fscore_support, accuracy_score) import numpy as np texts = [ "这部电影非常好看剧情精彩", "太棒了强烈推荐", "令人感动的故事", "音乐优美画面精致", "经典之作百看不厌", "温馨治愈心情愉悦", "这部电影太差了浪费时间", "剧情无聊看不下去", "演技尴尬令人失望", "太烂了简直受不了", "无聊至极毫无亮点", "烂片无疑别去看", "还行吧一般般", "有点无聊但能看", "中规中矩没什么惊喜", ] labels = ['pos'] * 6 + ['neg'] * 6 + ['neu'] * 3 vectorizer = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b') X = vectorizer.fit_transform(texts) clf = LogisticRegression(max_iter=1000, random_state=42) y_pred = cross_val_predict(clf, X, labels, cv=3) print("=== 分类评估报告 ===") print(classification_report(labels, y_pred)) # 混淆矩阵 cm = confusion_matrix(labels, y_pred, labels=['pos', 'neg', 'neu']) print("混淆矩阵:") print(f"{'':8s} pred_pos pred_neg pred_neu") for i, true_label in enumerate(['pos', 'neg', 'neu']): print(f"{true_label:8s} {cm[i][0]:8d} {cm[i][1]:8d} {cm[i][2]:8d}") # 逐指标分析 p, r, f1, s = precision_recall_fscore_support(labels, y_pred, average=None, labels=['pos', 'neg', 'neu']) print("\n=== 逐类别指标 ===") for i, label in enumerate(['pos', 'neg', 'neu']): print(f" {label}: P={p[i]:.3f} R={r[i]:.3f} F1={f1[i]:.3f} support={s[i]}") # macro vs micro p_macro, r_macro, f1_macro, _ = precision_recall_fscore_support(labels, y_pred, average='macro') p_micro, r_micro, f1_micro, _ = precision_recall_fscore_support(labels, y_pred, average='micro') print(f"\nmacro: P={p_macro:.3f} R={r_macro:.3f} F1={f1_macro:.3f}") print(f"micro: P={p_micro:.3f} R={r_micro:.3f} F1={f1_micro:.3f}")
=== 分类评估报告 === precision recall f1-score support neg 0.40 1.00 0.57 6 neu 0.00 0.00 0.00 3 pos 0.00 0.00 0.00 6 accuracy 0.40 15 macro avg 0.13 0.33 0.19 15 weighted avg 0.16 0.40 0.23 15 混淆矩阵: pred_pos pred_neg pred_neu pos 0 6 0 neg 0 6 0 neu 0 3 0 === 逐类别指标 === pos: P=0.000 R=0.000 F1=0.000 support=6 neg: P=0.400 R=1.000 F1=0.571 support=6 neu: P=0.000 R=0.000 F1=0.000 support=3 macro: P=0.133 R=0.333 F1=0.190 micro: P=0.400 R=0.400 F1=0.400

✅ 验证通过:代码实机运行结果如上

🔧 ROC与PR曲线

ROC-AUC与精确率召回率权衡

import numpy as np from sklearn.metrics import roc_curve, auc from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # 二分类ROC texts = [ "这部电影好看剧情精彩", "太棒了推荐", "感动故事", "优美精致经典", "满意治愈愉悦", "好看得停不下来", "太差了浪费时间", "无聊看不下去", "尴尬失望", "烂片受不了", "廉价混乱", "后悔别去", ] labels = [1]*6 + [0]*6 # 1=pos, 0=neg vectorizer = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b') X = vectorizer.fit_transform(texts) X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.3, random_state=42) clf = LogisticRegression(max_iter=1000, random_state=42) clf.fit(X_train, y_train) y_prob = clf.predict_proba(X_test)[:, 1] fpr, tpr, thresholds = roc_curve(y_test, y_prob) roc_auc = auc(fpr, tpr) print("=== ROC曲线数据 ===") print(f"AUC = {roc_auc:.4f}") print(f"{'阈值':8s} {'FPR':8s} {'TPR':8s}") for t, f, tp in zip(thresholds, fpr, tpr): print(f"{t:8.4f} {f:8.4f} {tp:8.4f}") # 精确率-召回率权衡 print("\n=== 精确率-召回率权衡 ===") for threshold in [0.3, 0.5, 0.7]: y_pred_t = (y_prob >= threshold).astype(int) tp = sum((y_pred_t == 1) & (np.array(y_test) == 1)) fp = sum((y_pred_t == 1) & (np.array(y_test) == 0)) fn = sum((y_pred_t == 0) & (np.array(y_test) == 1)) prec = tp / (tp + fp) if (tp + fp) > 0 else 0 rec = tp / (tp + fn) if (tp + fn) > 0 else 0 print(f" 阈值={threshold:.1f}: P={prec:.3f} R={rec:.3f} F1={2*prec*rec/(prec+rec) if (prec+rec) > 0 else 0:.3f}")
=== ROC曲线数据 === AUC = 0.5000 阈值 FPR TPR inf 0.0000 0.0000 0.6290 1.0000 1.0000 === 精确率-召回率权衡 === 阈值=0.3: P=0.250 R=1.000 F1=0.400 阈值=0.5: P=0.250 R=1.000 F1=0.400 阈值=0.7: P=0.000 R=0.000 F1=0.000

✅ 验证通过:代码实机运行结果如上

📝 练习

动手练习

  1. 实现一个类别不平衡的数据集(正负比1:10),对比macro/micro F1和AUC
  2. 绘制ROC曲线和PR曲线,分析两者的差异
  3. 实现分层K折交叉验证,确保每折类别分布一致
📊

🏆 成就解锁:评估专家

掌握了分类评估的完整体系,能准确衡量模型性能并选择合适的指标。

🧪 扩展:交叉验证策略

K折交叉验证的变体

分类评估的常见陷阱

📚 参考文献

推荐阅读

在线资源

分类评估 - 关键概念速查

概念定义应用
Pipeline处理流水线,各步骤串行执行文本处理系统
Tokenization将文本切分为词/子词单元分词、子词分割
Embedding将离散符号映射到连续向量空间词向量、句向量
Feature Engineering从原始数据提取有效数值特征传统ML模型输入
Fine-tuning在预训练模型基础上微调BERT等模型适配

本课要点回顾

  1. 分类评估是NLP技术栈的重要组件
  2. 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
  3. 代码验证是学习的好方法——动手跑一遍胜过读十遍
  4. 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
  5. 实践出真知——用自己的数据跑一遍,发现课本上没写的问题

❓ 常见问题FAQ

Q: 这个方法的数据需求量是多少?
A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
Q: 如何处理中文的特殊情况?
A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
Q: 传统方法和深度学习如何选择?
A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
Q: 如何评估和调优?
A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
Q: 生产环境部署要注意什么?
A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。

🛠️ 实用工具箱

工具用途安装
jieba中文分词pip install jieba
gensim词向量训练pip install gensim
scikit-learn传统MLpip install scikit-learn
nltk英文NLP工具pip install nltk
spaCy工业NLP管线pip install spacy
hanlp中文NLP工具包pip install hanlp
fasttextFacebook文本分类pip install fasttext
textblob简单NLP接口pip install textblob