第18课:TensorFlow|模型评估指标详解【准确率、召回率、F1、MSE、MAE计算与实现】

在这里插入图片描述


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鲁棒
回归(解释性)相对性能度量

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.compilemetrics参数中传入字符串'precision',但TensorFlow 2.x中标准名称为'Precision'(注意大小写),或使用tf.keras.metrics.Precision()

    • 解决:使用类实例或小写名称(部分支持)但推荐显式类。
  • 坑9:自定义指标时,忘记实现update_stateresultreset_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 基础作业

  1. 手动实现精确率、召回率、F1函数(接受NumPy数组),与sklearn.metrics结果对比。
  2. 使用tf.keras.metrics在自定义训练循环中计算每个epoch的AUC,并打印。
  3. 解释为什么在回归任务中,MAE对于异常值比MSE更鲁棒。

7.3 进阶实操作业

任务:实现一个可应用于多分类的F1指标(继承tf.keras.metrics.Metric

要求:

  • 支持average='macro'average='weighted'参数。
  • 每个batch累积混淆矩阵,在result时计算F1。
  • 在CIFAR-10数据集上训练一个CNN,并在训练过程中使用该自定义指标监控验证集F1。
  • sklearn的计算结果对比验证。

7.4 思考拓展题

  1. 在目标检测任务中,mAP(mean Average Precision)是如何计算的?它与分类任务中的AP有何联系?

  2. 假设你有一个分类器,其AUC值为0.95,但精确率为0.3,召回率为0.9。这种组合可能吗?请说明原因并画出可能的ROC曲线形状。

  3. 对于回归任务,R²可以为负,这意味着什么?在实际应用中,如果R²为负,你会如何调整模型或特征?


下一课预告:过拟合与欠拟合解决方案——我们将探讨深度学习中的两大核心问题:过拟合和欠拟合,并学习正则化(L1/L2)、Dropout、数据增强、早停等应对策略,通过实验对比不同方法的有效性。


🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航

去订阅

第一部分:基础入门(1-10 课)
第二部分:神经网络核心(11-25 课)
第三部分:进阶网络与框架高阶(26-40 课)
第四部分:企业实战与项目落地(41-50 课)

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值