基于视网膜图像的EfficientNet多标签分类模型sklearn指标异常问题求助
咱们先拆解你遇到的两个核心问题:指标数值完全相同和混淆矩阵异常(模型输出单一类别),逐个来解决:
一、指标数值完全相同的原因与修复
你在计算sklearn指标时用了average='micro',但对每个标签单独做二分类评估的场景来说,这个参数用错了:
问题根源
average='micro'是多分类任务的全局平均方式,它会把所有类别的TP、FP、FN合并计算。但你现在是针对每个标签单独判断"是/否",属于二分类场景,应该用average='binary'(这也是sklearn二分类的默认值)。
当模型所有预测结果都是同一类别时(比如第一个类全预测为0),micro平均下的准确率、精确率、召回率、F1值会巧合地相等——因为此时全局正确数就是TN(或TP),而其他指标在遇到除以零的情况时被sklearn做了特殊处理,最终和准确率趋同。
修复代码
把每个指标的average='micro'去掉(默认就是binary),或者显式指定average='binary':
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, precision_score, recall_score # 针对第一个标签(DR)计算指标 y_true = val_generator.labels[:,0] y_pred = [round(x) for x in val_pred[:,0]] print(f'Accuracy = {accuracy_score(y_true, y_pred)}') # 使用二分类默认的average方式 print(f"F1 = {f1_score(y_true, y_pred)}") print(f"Precision = {precision_score(y_true, y_pred)}") print(f"Recall = {recall_score(y_true, y_pred)}") print('Confusion matrix =') print(confusion_matrix(y_true, y_pred))
这样就能得到每个指标的正确数值,不会再全部相等。
二、混淆矩阵异常(模型输出单一类别)的原因与修复
你的混淆矩阵显示模型要么全输出0(前两个类),要么全输出1(第三个类),说明模型根本没学到有效特征,核心原因可能有这几点:
1. 预训练模型未加载权重
你初始化EfficientNetB7时用了weights=None,这意味着模型是随机初始化的,没有用ImageNet预训练权重。医学图像数据量通常不大,随机初始化的大模型很难快速收敛,甚至根本学不到东西。
修复:加载预训练权重
把weights=None改成weights='imagenet':
conv_base = EfficientNetB7(weights='imagenet', include_top=False, input_shape=input_shape)
2. 训练参数设置不合理
- steps_per_epoch=10:这个值太小了,如果你的训练数据集较大,每轮只训练10个batch,模型得不到足够的训练信号。建议设置为
len(train_generator),让模型每轮遍历全部训练数据。 - 学习率衰减设置:
ExponentialDecay的decay_steps=100000,结合小的steps_per_epoch,会导致学习率几乎不会下降,加上只有100轮,模型可能还没收敛。
修复训练参数:
history = en_model.fit( train_generator, steps_per_epoch=len(train_generator), # 遍历全部训练数据 epochs=100, validation_data=val_generator, validation_freq=1, verbose=1, callbacks=[tensorboard_callbacks], use_multiprocessing=True, workers=4 )
3. 数据生成器正确性验证
需要确认几个关键点:
val_generator.labels是否是正确的多标签格式?比如每个样本的标签是[0,1,0]这样的数组,代表该样本属于青光眼类。val_pred是否是模型对验证集的正确输出?可以打印几个样本的val_pred值,看看是否都趋近于0或1(如果是,说明模型输出极端,没有区分度)。- 数据是否存在严重类别不平衡?比如第一个类的正样本太少,导致模型倾向于预测负类。可以用
val_generator.labels[:,0].sum()查看正样本数量是否合理。
4. 模型微调的可能性
当模型用预训练权重训练一段时间后,可以尝试解冻卷积基的部分层进行微调,让模型更好适配你的视网膜图像数据:
# 训练几轮后解冻卷积基的顶部几层 conv_base.trainable = True # 冻结前N层,只训练顶部的层 for layer in conv_base.layers[:-20]: layer.trainable = False # 重新编译模型,使用更小的学习率 en_model.compile( loss='binary_crossentropy', optimizer=optimizers.Adam(learning_rate=1e-6), # 微调时学习率要更小 metrics=['accuracy'] ) # 继续训练 history_fine = en_model.fit( train_generator, steps_per_epoch=len(train_generator), epochs=150, # 多训练几轮 initial_epoch=history.epoch[-1], validation_data=val_generator, verbose=1 )
三、Keras内置指标的替代方案
如果你怀疑是sklearn指标库的问题,可以直接用Keras的内置指标在训练时监控,或者在验证后计算:
1. 训练时添加更多指标
from tensorflow.keras import metrics en_model.compile( loss='binary_crossentropy', optimizer=optimizers.Adam(learning_rate=lr_schedule), metrics=['accuracy', metrics.Precision(name='precision'), metrics.Recall(name='recall'), metrics.F1Score(name='f1', average='macro', num_classes=3)] )
2. 验证后计算单标签指标
# 计算每个标签的精确率和召回率 precision = metrics.Precision() recall = metrics.Recall() # 针对第一个标签 precision.update_state(val_generator.labels[:,0], val_pred[:,0]) recall.update_state(val_generator.labels[:,0], val_pred[:,0]) print(f"Precision for DR: {precision.result().numpy()}") print(f"Recall for DR: {recall.result().numpy()}")
内容的提问来源于stack exchange,提问作者Yogesh Riyat

