You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

二分类模型仅预测类别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能正确获取类别索引,所以指标计算正常,模型表现也没问题。

修正方案

修改自定义评估指标,适配二分类的标签格式:

  1. 对于y_true:直接使用原始值,不需要K.argmax,因为二分类标签是一维的0/1值。
  2. 对于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 04:50:30