
文章目录
1. 课前导读
1.1 本节课学习目标
- 理解混淆矩阵的四个基本元素:TP、TN、FP、FN。
- 掌握分类指标:准确率、精确率、召回率、F1分数的计算公式及适用场景。
- 理解ROC曲线和AUC值的含义,能使用TensorFlow计算AUC。
- 掌握回归指标:MSE、MAE、RMSE、R²的公式与优缺点。
- 熟练使用
tf.keras.metrics中的内置指标,并能够在model.compile和自定义训练循环中应用。 - 学会处理多分类问题中的指标聚合(宏平均、微平均、加权平均)。
- 能够根据业务需求选择或自定义评估指标。
1.2 知识重难点
| 类别 | 内容 |
|---|---|
| 重点 | 混淆矩阵、精确率/召回率的权衡;F1分数的调和平均本质;MSE与MAE对异常值的敏感性差异;R²的解释 |
| 难点 | 多分类问题的宏平均与微平均计算;ROC曲线绘制原理;回归指标中自由度的调整(R²) |
| 易混淆点 | 准确率(Accuracy)与精确率(Precision)的区别;召回率与敏感度的关系;MSE与RMSE的单位差异;R²可能为负值的含义 |
1.3 学习前置条件
- 已完成第17课,掌握模型训练流程。
- 熟悉二分类和多分类的基本概念。
- 了解线性回归的基础知识。
1.4 学完可掌握能力
- 针对不同任务选择正确评估指标,避免被误导性指标欺骗。
- 在模型训练过程中实时监控多个指标,并基于指标调整超参数。
- 实现自定义评估指标(如F1的自定义回调)。
- 解读论文或工业报告中的模型性能数据。
1.5 行业应用场景
- 医疗诊断:更关注召回率(不漏诊)或精确率(不误诊)。
- 金融风控:关注精确率(减少误判损失)和AUC。
- 图像分类:多分类任务常用准确率、Top-5准确率。
- 目标检测:mAP(mean Average Precision)基于精确率/召回率。
- 推荐系统:使用召回率、NDCG等排序指标。
2. 核心理论精讲
2.1 分类评估指标的基础:混淆矩阵
对于二分类问题,预测结果与真实标签构成2×2混淆矩阵:
| 真实 \ 预测 | 正类(Positive) | 负类(Negative) |
|---|---|---|
| 正类(Positive) | TP(True Positive) | FN(False Negative) |
| 负类(Negative) | FP(False Positive) | TN(True Negative) |
- TP:正确预测为正。
- TN:正确预测为负。
- FP:错误预测为正(误报)。
- FN:错误预测为负(漏报)。
2.2 常用分类指标
准确率(Accuracy):
[
\text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}
]
- 优点:直观。
- 缺点:类别不平衡时失效(例如90%负类,全预测负类准确率90%但无意义)。
精确率(Precision):
[
\text{Precision} = \frac{TP}{TP + FP}
]
- 含义:预测为正类的样本中有多少真正是正类。关注“不误报”。
召回率(Recall):
[
\text{Recall} = \frac{TP}{TP + FN}
]
- 含义:真实正类样本中有多少被正确检出。关注“不漏报”。
F1分数:
[
F_1 = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}
]
- 调和平均,综合平衡精确率和召回率。
特异性(Specificity):
[
\text{Specificity} = \frac{TN}{TN + FP}
]
- 含义:真实负类被正确预测的比例。
ROC曲线与AUC:
- ROC曲线以假阳性率(FPR = FP/(FP+TN))为横轴,真阳性率(TPR = Recall)为纵轴。
- AUC(Area Under Curve)值范围[0.5,1],越接近1分类器性能越好。0.5为随机猜测。
2.3 多分类指标扩展
- 宏平均(Macro-average):对每个类别单独计算指标(如精确率),然后算术平均。平等对待所有类别,不关心类别样本数。
- 微平均(Micro-average):将所有类别的TP、FP、FN汇总后再计算指标。对样本数多的类别更敏感。
- 加权平均(Weighted-average):按每个类别的样本数加权平均。
在TensorFlow中,tf.keras.metrics.Precision等指标默认对每个类别单独计算,然后通过average参数控制聚合方式。
2.4 回归评估指标
设真实值 ( y_i ),预测值 ( \hat{y}_i ),样本数 ( n )。
均方误差(MSE):
[
\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
]
- 对大误差敏感,易于优化(可导),但单位是原始单位的平方。
均方根误差(RMSE):
[
\text{RMSE} = \sqrt{\text{MSE}}
]
- 单位与原始数据一致,更易解释。
平均绝对误差(MAE):
[
\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|
]
- 对异常值鲁棒,但零点不可导。
R²(决定系数):
[
R^2 = 1 - \frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)2}{\sum_{i=1}{n} (y_i - \bar{y})^2}
]
- 表示模型解释的变异比例,取值范围(-∞, 1]。1表示完美拟合,0表示与简单使用均值预测相同,负值表示模型比均值还差。
2.5 指标选择指南
| 任务场景 | 推荐指标 | 原因 |
|---|---|---|
| 平衡分类 | Accuracy | 简单直观 |
| 不平衡分类 | Precision, Recall, F1, AUC | 准确率会虚高 |
| 癌症筛查 | Recall(高灵敏度) | 不漏掉病人 |
| 垃圾邮件过滤 | Precision | 避免误删重要邮件 |
| 多分类 | Macro F1或Weighted F1 | 平衡类别影响 |
| 回归(正常分布) | MSE/RMSE | 可导,优化友好 |
| 回归(有异常值) | MAE | 鲁棒 |
| 回归(解释性) | R² | 相对性能度量 |
3. 环境搭建与工具配置
沿用第17课环境。需额外安装scikit-learn用于对比验证(可选)。
conda activate tf213
pip install scikit-learn
导入:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, roc_curve, auc, classification_report
from tensorflow.keras import layers, models, datasets
4. 代码实战教学
4.1 使用 TensorFlow 内置指标
# 二分类示例
y_true = tf.constant([1, 0, 1, 1, 0, 1, 0, 0])
y_pred = tf.constant([0.9, 0.2, 0.8, 0.7, 0.4, 0.6, 0.3, 0.1])
# 定义指标
accuracy = tf.keras.metrics.BinaryAccuracy()
precision = tf.keras.metrics.Precision()
recall = tf.keras.metrics.Recall()
auc_metric = tf.keras.metrics.AUC()
# 更新指标(需要阈值化预测值?指标内部会自动将概率与0.5比较,除了AUC)
accuracy.update_state(y_true, y_pred)
precision.update_state(y_true, y_pred)
recall.update_state(y_true, y_pred)
auc_metric.update_state(y_true, y_pred)
print(f"Accuracy: {accuracy.result().numpy():.4f}")
print(f"Precision: {precision.result().numpy():.4f}")
print(f"Recall: {recall.result().numpy():.4f}")
print(f"AUC: {auc_metric.result().numpy():.4f}")
4.2 手动实现混淆矩阵与指标
def binary_metrics(y_true, y_pred, threshold=0.5):
y_pred_class = (y_pred > threshold).astype(np.int32)
TP = np.sum((y_true == 1) & (y_pred_class == 1))
TN = np.sum((y_true == 0) & (y_pred_class == 0))
FP = np.sum((y_true == 0) & (y_pred_class == 1))
FN = np.sum((y_true == 1) & (y_pred_class == 0))
accuracy = (TP+TN)/(TP+TN+FP+FN)
precision = TP/(TP+FP) if (TP+FP)>0 else 0
recall = TP/(TP+FN) if (TP+FN)>0 else 0
f1 = 2*precision*recall/(precision+recall) if (precision+recall)>0 else 0
return {'acc': accuracy, 'prec': precision, 'rec': recall, 'f1': f1}
# 模拟数据
np.random.seed(42)
y_true_np = np.random.randint(0,2,100)
y_pred_proba = np.random.rand(100)
metrics_manual = binary_metrics(y_true_np, y_pred_proba, 0.5)
print("Manual metrics:", metrics_manual)
4.3 多分类指标
# 多分类(3类)
y_true_mc = tf.constant([0, 1, 2, 0, 1, 2])
y_pred_mc = tf.constant([[0.8, 0.1, 0.1],
[0.2, 0.7, 0.1],
[0.1, 0.2, 0.7],
[0.6, 0.2, 0.2],
[0.3, 0.4, 0.3],
[0.1, 0.3, 0.6]])
# 分类准确率
acc = tf.keras.metrics.CategoricalAccuracy()
acc.update_state(y_true_mc, y_pred_mc) # y_true_mc 需为one-hot?此处是整数,使用SparseCategoricalAccuracy
print(f"CategoricalAccuracy: {acc.result().numpy():.4f}")
# 稀疏版本
sparse_acc = tf.keras.metrics.SparseCategoricalAccuracy()
sparse_acc.update_state(y_true_mc, y_pred_mc)
print(f"SparseCategoricalAccuracy: {sparse_acc.result().numpy():.4f}")
# 每个类别的精确率
for i in range(3):
prec = tf.keras.metrics.Precision(class_id=i)
prec.update_state(y_true_mc, y_pred_mc) # 内部会处理多分类
print(f"Precision class {i}: {prec.result().numpy():.4f}")
4.4 回归指标
y_true_reg = tf.constant([1.0, 2.0, 3.0, 4.0])
y_pred_reg = tf.constant([1.2, 1.9, 3.1, 3.8])
mse = tf.keras.metrics.MeanSquaredError()
mse.update_state(y_true_reg, y_pred_reg)
print(f"MSE: {mse.result().numpy():.4f}")
mae = tf.keras.metrics.MeanAbsoluteError()
mae.update_state(y_true_reg, y_pred_reg)
print(f"MAE: {mae.result().numpy():.4f}")
rmse = tf.sqrt(mse.result()) # 手动计算RMSE
print(f"RMSE: {rmse.numpy():.4f}")
# R² 手动实现
def r_squared(y_true, y_pred):
ss_res = tf.reduce_sum(tf.square(y_true - y_pred))
ss_tot = tf.reduce_sum(tf.square(y_true - tf.reduce_mean(y_true)))
return 1 - ss_res/(ss_tot + 1e-8)
print(f"R²: {r_squared(y_true_reg, y_pred_reg).numpy():.4f}")
4.5 在 model.fit 中使用指标
# 构建简单模型
model = tf.keras.Sequential([
layers.Dense(10, activation='relu', input_shape=(20,)),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', tf.keras.metrics.Precision(), tf.keras.metrics.Recall(), tf.keras.metrics.AUC()])
# 模拟数据
x_train = np.random.randn(1000, 20).astype(np.float32)
y_train = np.random.randint(0, 2, size=(1000, 1)).astype(np.float32)
history = model.fit(x_train, y_train, epochs=5, batch_size=64, validation_split=0.2, verbose=1)
# 输出会包含 accuracy, precision, recall, auc 等
4.6 自定义指标(F1分数)
TensorFlow没有内置F1,因为F1需要在每个epoch结束时基于所有预测计算,而不能简单平均batch。可以通过自定义回调实现。
class F1ScoreCallback(tf.keras.callbacks.Callback):
def __init__(self, validation_data):
super().__init__()
self.validation_data = validation_data
self.f1_scores = []
def on_epoch_end(self, epoch, logs=None):
x_val, y_val = self.validation_data
y_pred = self.model.predict(x_val, verbose=0)
y_pred_class = (y_pred > 0.5).astype(np.int32).flatten()
y_val = y_val.flatten()
tp = np.sum((y_val == 1) & (y_pred_class == 1))
fp = np.sum((y_val == 0) & (y_pred_class == 1))
fn = np.sum((y_val == 1) & (y_pred_class == 0))
precision = tp / (tp + fp + 1e-7)
recall = tp / (tp + fn + 1e-7)
f1 = 2 * precision * recall / (precision + recall + 1e-7)
self.f1_scores.append(f1)
print(f" - val_f1: {f1:.4f}")
# 使用
x_val = np.random.randn(200, 20)
y_val = np.random.randint(0,2,200)
model.fit(x_train, y_train, epochs=10, callbacks=[F1ScoreCallback((x_val, y_val))], verbose=0)
5. 案例实操演练
案例:二分类信用卡欺诈检测(不平衡数据)——使用精确率、召回率、F1、AUC评估模型。
5.1 生成不平衡数据集
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=10000, n_features=20, weights=[0.95, 0.05], random_state=42)
X = X.astype(np.float32)
y = y.astype(np.float32).reshape(-1,1)
# 划分训练测试集
split = int(0.8*len(X))
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
5.2 构建模型并训练
model = tf.keras.Sequential([
layers.Dense(64, activation='relu', input_shape=(20,)),
layers.Dropout(0.3),
layers.Dense(32, activation='relu'),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=[tf.keras.metrics.Precision(), tf.keras.metrics.Recall(), tf.keras.metrics.AUC()])
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=30, batch_size=128, verbose=0)
5.3 评估与可视化
# 获取测试集预测概率
y_pred_proba = model.predict(X_test).flatten()
y_pred_class = (y_pred_proba > 0.5).astype(np.int32)
# 混淆矩阵
cm = confusion_matrix(y_test, y_pred_class)
print("Confusion Matrix:\n", cm)
# 精确率、召回率、F1
from sklearn.metrics import precision_score, recall_score, f1_score
prec = precision_score(y_test, y_pred_class)
rec = recall_score(y_test, y_pred_class)
f1 = f1_score(y_test, y_pred_class)
print(f"Precision: {prec:.4f}, Recall: {rec:.4f}, F1: {f1:.4f}")
# ROC曲线
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
auc_val = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, label=f'AUC = {auc_val:.4f}')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.show()
5.4 调整阈值以优化F1
# 寻找最佳阈值
best_thresh = 0.5
best_f1 = 0
for thresh in np.arange(0.1, 0.9, 0.05):
y_pred_thresh = (y_pred_proba > thresh).astype(np.int32)
f1_thresh = f1_score(y_test, y_pred_thresh)
if f1_thresh > best_f1:
best_f1 = f1_thresh
best_thresh = thresh
print(f"Best threshold: {best_thresh:.2f}, F1: {best_f1:.4f}")
6. 常见坑点与排错总结
6.1 分类指标坑点
-
坑1:在不平衡数据集上只看准确率,误以为模型性能很好。
- 解决:同时关注精确率、召回率、F1、AUC。
-
坑2:混淆了精确率(Precision)和准确率(Accuracy)。
- 记忆:精确率“预测为正的里面有多少对的”,准确率“所有预测有多少对的”。
-
坑3:多分类时直接使用
CategoricalAccuracy但标签为整数,导致维度错误。- 解决:使用
SparseCategoricalAccuracy或将标签转为one-hot。
- 解决:使用
-
坑4:AUC值在严重不平衡数据上可能仍然较高,但模型实际无用(例如正样本极少时,AUC可能因排序能力而高)。需结合其他指标。
6.2 回归指标坑点
-
坑5:MSE量纲平方,难以解释;RMSE与原始单位一致,但对异常值敏感。
- 建议:同时报告MAE和RMSE。
-
坑6:R²为负值,但很多初学者认为R²应在0~1之间。负值表示模型不如直接预测均值。
-
坑7:回归问题中使用分类指标(如准确率),毫无意义。
6.3 指标在Keras中的使用误区
-
坑8:在
model.compile的metrics参数中传入字符串'precision',但TensorFlow 2.x中标准名称为'Precision'(注意大小写),或使用tf.keras.metrics.Precision()。- 解决:使用类实例或小写名称(部分支持)但推荐显式类。
-
坑9:自定义指标时,忘记实现
update_state、result和reset_states方法,导致状态无法重置。 -
坑10:在
model.fit中使用validation_data时,验证集的指标计算是在每个epoch结束时对全部验证集计算,而非batch平均,因此正确。但若使用validation_steps,则只计算部分批次。
6.4 多分类宏平均/微平均实现
# 手动计算宏平均F1
from sklearn.metrics import f1_score
y_true_multi = [0,1,2,0,1,2]
y_pred_multi = [0,1,1,0,2,2]
macro_f1 = f1_score(y_true_multi, y_pred_multi, average='macro')
weighted_f1 = f1_score(y_true_multi, y_pred_multi, average='weighted')
print(f"Macro F1: {macro_f1:.4f}, Weighted F1: {weighted_f1:.4f}")
7. 知识点总结 + 课后作业
7.1 核心知识点梳理
- 混淆矩阵基础:TP, TN, FP, FN。
- 分类指标:准确率、精确率、召回率、F1、ROC-AUC。
- 多分类指标:宏平均、微平均、加权平均。
- 回归指标:MSE、RMSE、MAE、R²。
- TensorFlow实现:
tf.keras.metrics模块,update_state/result。 - 不平衡数据:关注召回率、F1、AUC,慎用准确率。
7.2 基础作业
- 手动实现精确率、召回率、F1函数(接受NumPy数组),与
sklearn.metrics结果对比。 - 使用
tf.keras.metrics在自定义训练循环中计算每个epoch的AUC,并打印。 - 解释为什么在回归任务中,MAE对于异常值比MSE更鲁棒。
7.3 进阶实操作业
任务:实现一个可应用于多分类的F1指标(继承tf.keras.metrics.Metric)
要求:
- 支持
average='macro'和average='weighted'参数。 - 每个batch累积混淆矩阵,在
result时计算F1。 - 在CIFAR-10数据集上训练一个CNN,并在训练过程中使用该自定义指标监控验证集F1。
- 与
sklearn的计算结果对比验证。
7.4 思考拓展题
-
在目标检测任务中,mAP(mean Average Precision)是如何计算的?它与分类任务中的AP有何联系?
-
假设你有一个分类器,其AUC值为0.95,但精确率为0.3,召回率为0.9。这种组合可能吗?请说明原因并画出可能的ROC曲线形状。
-
对于回归任务,R²可以为负,这意味着什么?在实际应用中,如果R²为负,你会如何调整模型或特征?
下一课预告:过拟合与欠拟合解决方案——我们将探讨深度学习中的两大核心问题:过拟合和欠拟合,并学习正则化(L1/L2)、Dropout、数据增强、早停等应对策略,通过实验对比不同方法的有效性。
🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
第一部分:基础入门(1-10 课)
第二部分:神经网络核心(11-25 课)
第三部分:进阶网络与框架高阶(26-40 课)
第四部分:企业实战与项目落地(41-50 课)
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

369

被折叠的 条评论
为什么被折叠?



