为何Keras与SKLearn计算的图像分类模型准确率存在差异?
问题背景
训练了一个图像二分类线性模型,使用model.evaluate在测试集上得到73.33%的准确率,但手动用sklearn计算混淆矩阵得到的准确率仅为47.33%,两者差异极大。
模型编译参数:
model.compile( optimizer=keras.optimizers.SGD(learning_rate=0.1), loss=keras.losses.CategoricalCrossentropy(), metrics=['accuracy'] )
测试集加载代码:
dataset = keras.utils.image_dataset_from_directory( '/content/test-dataset-2', image_size=(512, 384), labels='inferred', label_mode='categorical', ).map(preprocess)
手动计算准确率的代码:
from tensorflow.keras.utils import to_categorical from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, classification_report import numpy as np def get_labels(arr): return np.argmax(arr, axis=1) # 冗余操作 predictions = to_categorical(np.argmax(model.predict(dataset), axis=1), 2) y = np.concatenate([y for x, y in dataset], axis=0) cm = confusion_matrix(get_labels(y), get_labels(predictions)) cm_display = ConfusionMatrixDisplay(cm).plot()
错误原因排查
1. 类别索引错位(最可能)
keras.utils.image_dataset_from_directory会按文件夹名称的字典序自动分配类别索引(例如文件夹class_a和class_b会分别对应索引0和1)。如果训练集与测试集的文件夹名称/顺序不一致,会直接导致模型预测的类别索引与测试集的标签索引完全错位:
- 例:训练集文件夹为
['cat', 'dog'](cat=0、dog=1),测试集文件夹为['dog', 'cat'](dog=0、cat=1) - 此时模型预测的0对应cat,但测试集标签的0对应dog,混淆矩阵会把所有正确预测判定为错误,准确率直接反转。
2. 冗余操作增加出错风险
代码中to_categorical(np.argmax(model.predict(dataset), axis=1), 2)属于完全冗余的操作:
model.predict(dataset)已输出二分类概率分布(shape为(样本数, 2))np.argmax(..., axis=1)已得到类别索引(0或1),可直接传入confusion_matrix,无需转独热编码后再转回索引。这一步虽不直接导致错误,但会增加代码复杂度,隐藏潜在问题。
3. 预处理函数不一致
如果preprocess函数在训练和测试阶段行为不同(例如训练时用了随机翻转、特定归一化逻辑,测试时未执行或参数不符),会导致模型预测结果失真,若预处理差异引发标签与预测的对应错位,也会出现准确率不一致的情况。
4. 数据集批次处理差异
model.evaluate与model.predict处理tf.data.Dataset的默认逻辑一致,但如果测试集加载时设置了shuffle=True(用户代码中未设置,默认False),会导致标签与预测结果顺序不匹配,不过该可能性较低。
验证与修正方案
步骤1:对齐训练集与测试集的类别索引
打印训练集和测试集的类别名称,确认顺序是否一致:
# 打印训练集类别 train_dataset = keras.utils.image_dataset_from_directory("训练集路径") print("训练集类别:", train_dataset.class_names) # 打印测试集类别 print("测试集类别:", dataset.class_names)
若顺序不一致,加载测试集时强制指定类别顺序:
dataset = keras.utils.image_dataset_from_directory( '/content/test-dataset-2', image_size=(512, 384), labels='inferred', label_mode='categorical', class_names=train_dataset.class_names # 与训练集类别顺序对齐 ).map(preprocess)
步骤2:简化手动计算代码
去掉冗余的独热编码转换,直接使用类别索引计算:
import numpy as np from sklearn.metrics import confusion_matrix, classification_report # 获取模型预测的类别索引 y_pred = np.argmax(model.predict(dataset), axis=1) # 获取测试集真实类别索引 y_true = np.concatenate([np.argmax(y, axis=1) for x, y in dataset], axis=0) # 计算混淆矩阵和分类报告 cm = confusion_matrix(y_true, y_pred) print(classification_report(y_true, y_pred)) ConfusionMatrixDisplay(cm).plot()
步骤3:确保预处理函数一致
测试集的preprocess函数必须与训练集完全相同,例如训练时使用了特定的归一化逻辑,测试时不能省略或修改参数。
步骤4:检查模型输出合理性
损失值高达56明显异常,二分类任务的CategoricalCrossentropy损失正常范围通常在0~10之间,建议检查:
- 模型输出层是否使用了
softmax激活(配合CategoricalCrossentropy需要输出概率分布) - 训练时的标签格式是否与测试集一致(均为categorical格式)
结论
最可能的原因是训练集与测试集的类别索引顺序不匹配,导致手动计算准确率时标签与预测结果错位。修正类别索引对齐后,手动计算的准确率应与model.evaluate的结果一致。
内容的提问来源于stack exchange,提问作者Allan Juan

