二分类模型仅预测类别0:是否与损失函数或sigmoid相关?
二分类模型仅预测类别0的异常原因分析
问题描述
在二分类任务中,模型仅对类别0进行预测,完全不预测类别1;但修改代码后,同一模型在多分类任务中运行正常。请问该异常是否与当前使用的binary_crossentropy损失函数或sigmoid激活函数有关?
训练输出
Epoch 5/5 19/19 [==============================] - 144s 8s/step - loss: 0.0232 - accuracy: 0.9884 - acc_1_0: 1.0000 - acc_1_1: 0.0000e+00 - prec_1_0: 1.0000 - recall_1_0: 1.0000 - prec_1_1: 0.0000e+00 - recall_1_1: 0.0000e+00 - val_loss: 0.0057 - val_accuracy: 1.0000 - val_acc_1_0: 1.0000 - val_acc_1_1: 0.0000e+00 - val_prec_1_0: 1.0000 - val_recall_1_0: 1.0000 - val_prec_1_1: 0.0000e+00 - val_recall_1_1: 0.0000e+00
代码实现
from keras.preprocessing.image import ImageDataGenerator train_datagen=ImageDataGenerator(rescale=1./255) test_datagen = ImageDataGenerator(rescale=1./255) data_dir1='Flower2' data_dir2='Flower' train_generator = train_datagen.flow_from_directory( data_dir1, target_size=(180, 180), batch_size=32, shuffle=True, class_mode='binary') val_generator = test_datagen.flow_from_directory( data_dir2, target_size=(180, 180), batch_size=32, shuffle=True, class_mode='binary') x_test, y_test=next(val_generator) interesting_class_id=0 def single_class_accuracy(interesting_class_id): def acc1(y_true, y_pred): class_id_true = K.argmax(y_true, axis=-1) class_id_preds = K.argmax(y_pred, axis=-1) accuracy_mask = K.cast(K.equal(class_id_preds, interesting_class_id), 'int32') class_acc_tensor = K.cast(K.equal(class_id_true, class_id_preds), 'int32') * accuracy_mask class_acc = K.cast(K.sum(class_acc_tensor), 'float32') / K.cast(K.maximum(K.sum(accuracy_mask), 1), 'float32') return class_acc acc1.__name__ = 'acc_1_{}'.format(interesting_class_id) return acc1 def single_class_precision(interesting_class_id): def prec(y_true, y_pred): class_id_true = K.argmax(y_true, axis=-1) class_id_pred = K.argmax(y_pred, axis=-1) precision_mask = K.cast(K.equal(class_id_pred, interesting_class_id), 'int32') class_prec_tensor = K.cast(K.equal(class_id_true, class_id_pred), 'int32') * precision_mask class_prec = K.cast(K.sum(class_prec_tensor), 'float32') / K.cast(K.maximum(K.sum(precision_mask), 1), 'float32') return class_prec prec.__name__ = 'prec_1_{}'.format(interesting_class_id) return prec def single_class_recall(interesting_class_id): def recall(y_true, y_pred): class_id_true = K.argmax(y_true, axis=-1) class_id_pred = K.argmax(y_pred, axis=-1) recall_mask = K.cast(K.equal(class_id_true, interesting_class_id), 'int32') class_recall_tensor = K.cast(K.equal(class_id_true, class_id_pred), 'int32') * recall_mask class_recall = K.cast(K.sum(class_recall_tensor), 'float32') / K.cast(K.maximum(K.sum(recall_mask), 1), 'float32') return class_recall recall.__name__ = 'recall_1_{}'.format(interesting_class_id) return recall model = Sequential() pretrained_model= tf.keras.applications.VGG16(include_top=False, input_shape=(180,180,3), pooling='avg',classes=2, weights='imagenet', classifier_activation= 'sigmoid' ) for layer in pretrained_model.layers: layer.trainable=False model.add(pretrained_model) model.add(Flatten()) model.add(Dense(512, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.summary() model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.01), metrics=['accuracy', single_class_accuracy(0), single_class_accuracy(1), single_class_precision(0), single_class_recall(0), single_class_precision(1), single_class_recall(1) ]) hist = model.fit(train_generator, validation_data=val_generator, epochs=5, batch_size=32)
问题根源与解决办法
这个异常不是binary_crossentropy或sigmoid激活函数的问题,核心问题出在自定义评估指标的实现上:
- 二分类任务中,
class_mode='binary'时,y_true是形状为(batch_size,)的一维向量(值为0或1),但自定义指标里用了K.argmax(y_true, axis=-1)——对一维张量做argmax会返回0(因为只有一个维度,最大值索引始终是0),导致真实标签被错误解析为全0,模型的预测逻辑完全混乱。 - 同时,输出层是
Dense(1, activation='sigmoid'),y_pred是形状为(batch_size,1)的张量,用K.argmax(y_pred, axis=-1)也会返回0(因为每个样本只有一个输出值,索引只能是0),所以所有预测都会被判定为类别0,这就是你看到的所有类别1的指标全为0的原因。 - 多分类任务中,
class_mode='categorical',y_true和y_pred都是二维独热编码张量,K.argmax能正确获取类别索引,所以指标计算正常,模型表现也没问题。
修正方案
修改自定义评估指标,适配二分类的标签格式:
- 对于
y_true:直接使用原始值,不需要K.argmax,因为二分类标签是一维的0/1值。 - 对于
y_pred:用sigmoid输出的阈值(通常是0.5)来判断类别,大于0.5为类别1,否则为类别0,而不是用K.argmax。
示例修正后的单类别准确率函数:
def single_class_accuracy(interesting_class_id): def acc1(y_true, y_pred): # 二分类下,y_true是一维0/1向量,直接使用 class_id_true = K.cast(y_true, 'int32') # sigmoid输出转类别:>0.5为1,否则为0 class_id_preds = K.cast(K.greater(y_pred, 0.5), 'int32') accuracy_mask = K.cast(K.equal(class_id_preds, interesting_class_id), 'int32') class_acc_tensor = K.cast(K.equal(class_id_true, class_id_preds), 'int32') * accuracy_mask class_acc = K.cast(K.sum(class_acc_tensor), 'float32') / K.cast(K.maximum(K.sum(accuracy_mask), 1), 'float32') return class_acc acc1.__name__ = 'acc_1_{}'.format(interesting_class_id) return acc1
同理,单类别精确率和召回率函数也需要做类似修改,把K.argmax替换为适配二分类的类别判断逻辑。
另外,还需要检查训练集是否存在严重的类别不平衡问题——如果类别1的样本极少,模型也可能倾向于只预测类别0,但从你的训练准确率来看,训练集的准确率接近100%,说明训练集里类别1的样本可能要么没有,要么模型轻松就能区分,但结合指标异常,核心问题还是自定义指标的错误导致的判断偏差。
内容的提问来源于stack exchange,提问作者Monika Roopak
相关产品推荐
相关产品推荐

