自定义含可训练参数的损失函数遇报错,求排查方案
问题排查与解决方案
一、报错根源分析
你遇到的ValueError: expected no data, but got: [array...]是因为模型定义和训练数据传入的逻辑冲突:
- 你使用了
model.add_loss()注入自定义损失,此时Keras会认为模型损失已在内部计算完成,不需要外部传入目标标签(y参数); - 但你训练时还是传入了
y_true作为目标数据,导致Keras报错——它预期y为None,却收到了真实标签数组。
二、自定义损失层的问题修正
你的损失层存在几个小问题,我帮你调整后实现了正确的逻辑:
- 初始化代码冗余:
__init__里的gamma_init重复定义,且旧版Keras的initializations.get()已不再推荐使用; compute_output_shape返回值错误:输入是两个张量,输出应为每个样本的损失维度;- 参数命名混淆:把可训练参数命名为
gamma而非gamma_init,避免歧义。
修正后的损失层代码:
from keras.layers import Layer import keras.backend as K class BCLoss(Layer): def __init__(self, lamada=0.00005, **kwargs): self.lamada = lamada super(BCLoss, self).__init__(**kwargs) def build(self, input_shape): # 定义可训练参数gamma,初始化方式为uniform self.gamma = self.add_weight( name='gamma', shape=(1,), initializer='uniform', trainable=True ) super(BCLoss, self).build(input_shape) # 必须调用父类build方法完成层构建 def call(self, inputs, **kwargs): y_true, y_pred = inputs # 计算每个样本的二元交叉熵,按最后一维取平均 bce_loss = K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1) # 添加L1正则项:λ乘以gamma的绝对值 reg_loss = self.lamada * K.abs(self.gamma) # 总损失 = 交叉熵损失 + 正则项(正则项会自动广播到batch中每个样本) total_loss = bce_loss + reg_loss return total_loss def compute_output_shape(self, input_shape): # 返回每个样本的损失维度:(batch_size,) return (input_shape[0][0],)
三、模型构建与训练代码调整
1. 模型构建修正
需要明确:当使用add_loss时,y_true需要作为模型的输入层传入,同时要手动添加准确率指标(因为默认的metrics=['accuracy']在这种模式下无法直接生效):
def build_model(self): img_input = layers.Input(shape=self.input_shape, name='img_input') nb_channels = self.growth_rate # 保留你原有的卷积、DenseBlock等网络结构代码 layer_1 = layers.Convolution2D(2 * self.growth_rate, (1, 1), strides=(2, 2), kernel_regularizer=keras.regularizers.l2(self.weight_decay))(img_input) average_1 = layers.AveragePooling2D((2, 2), strides=(4, 4))(layer_1) x = layers.BatchNormalization()(layer_1) x = layers.Activation('relu')(x) x = layers.MaxPooling2D()(x) skip_layer = [average_1] for block in range(self.dense_blocks - 1): x, nb_channels = self.dense_block(x, self.dense_layers[block], nb_channels, self.growth_rate, self.dropout_rate, self.bottleneck, self.weight_decay) x_left,x_right = self.transition_layer(x, nb_channels, self.dropout_rate, self.compression, self.weight_decay) x = x_left nb_channels = int(nb_channels * self.compression) skip_layer.append(x_right) x, nb_channels = self.dense_block(x, self.dense_layers[-1], nb_channels, self.growth_rate, self.dropout_rate, self.weight_decay, skip_layer=skip_layer) x = layers.BatchNormalization()(x) x = layers.Activation('relu')(x) x = layers.GlobalAveragePooling2D()(x) x = layers.Dropout(0.2)(x) y_pred = layers.Dense(self.nb_classes, activation='sigmoid')(x) # 添加y_true输入层,对应二分类的(1,)维度 y_true = layers.Input(shape=(self.nb_classes,), name='y_true') # 实例化自定义损失层 loss_layer = BCLoss()([y_true, y_pred]) # 定义模型:输入为图像和真实标签,输出为预测结果 model = keras.Model(inputs=[img_input, y_true], outputs=y_pred) # 注入自定义损失 model.add_loss(loss_layer) # 手动添加准确率指标,用于训练监控 model.add_metric(K.mean(K.equal(y_true, K.round(y_pred))), name='accuracy') return model
2. 模型编译与训练修正
编译时loss设为None,训练时传入x为图像和标签的列表,y设为None:
from keras.optimizers import Adam # 编译模型:无需指定loss,因为已通过add_loss注入 model.compile(optimizer=Adam(lr=0.001)) # 训练模型:x是[训练图像, 训练标签],y为None model.fit( x=[train_imgs, train_labels], y=None, validation_data=([val_imgs, val_labels], None), epochs=50, batch_size=32, # 其他参数如早停等保留你的原有设置 )
四、验证损失函数逻辑正确性
修正后的代码完全符合你定义的损失公式:Loss function = binary_crossentropy(y_true, y_pred) + λ|w|
binary_crossentropy部分:通过K.binary_crossentropy计算,按样本取平均;λ|w|部分:λ对应lamada参数,w对应可训练参数gamma,通过K.abs(self.gamma)实现L1正则。
内容的提问来源于stack exchange,提问作者jianhong cheng
相关产品推荐
相关产品推荐

