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

适配Keras变分自动编码器实现图像去噪的报错问题咨询

问题:带噪MNIST输入的VAE训练报错处理

我尝试修改Keras官方VAE示例,让模型以带噪MNIST图像作为输入,无噪MNIST原图作为输出,但运行训练代码时出现了如下错误:

ValueError: ('Error when checking model target: expected no data, but got:', array([[ 0., 0., 0., ..., 0., 0., 0.],
       [ 0., 0., 0., ..., 0., 0., 0.],
       [ 0., 0., 0., ..., 0., 0., 0.],
       ...,
       [ 0., 0., 0., ..., 0., 0., 0.],
       [ 0., 0., 0., ..., 0., 0., 0.],
       [ 0., 0., 0., ..., 0., 0., 0.]], dtype=float32))

当我把fit方法的目标参数改为None时,代码能正常运行,但不确定这是否符合我的需求,想知道报错的原因以及正确的处理方式。

我的核心代码(关键部分)如下:

# 自定义损失层
class CustomVariationalLayer(Layer):
    def __init__(self, **kwargs):
        self.is_placeholder = True
        super(CustomVariationalLayer, self).__init__(**kwargs)
    def vae_loss(self, x, x_decoded_mean):
        xent_loss = original_dim * metrics.binary_crossentropy(x, x_decoded_mean)
        kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
        return K.mean(xent_loss + kl_loss)
    def call(self, inputs):
        x = inputs[0]
        x_decoded_mean = inputs[1]
        loss = self.vae_loss(x, x_decoded_mean)
        self.add_loss(loss, inputs=inputs)
        return x

y = CustomVariationalLayer()([x, x_decoded_mean])
vae = Model(x, y)
vae.compile(optimizer='rmsprop', loss=None)

# 训练代码
vae.fit(x_train_noisy, x_train, shuffle=True, epochs=epochs, batch_size=batch_size, validation_data=(x_test_noisy,x_test))

原因分析与解决方案

你遇到的问题确实是由自定义损失层的定义方式导致的,而且这里还有一个容易被忽略的逻辑偏差:你的损失计算对象其实不符合你的需求。

1. 报错的直接原因

当你使用自定义层通过add_loss()方法添加损失时,Keras会认为这个模型的损失已经在内部完全定义好了,不需要外部传入目标数据(y参数)。所以当你在fit()里传入x_train作为目标时,Keras就会抛出“expected no data”的错误——它根本没期望你传目标值。

而你改成vae.fit(x_train_noisy, None...)能运行,是因为符合了模型“不需要外部目标”的设定,但此时模型的损失是计算带噪输入和重建图像的交叉熵,而不是你想要的无噪原图和重建图像的交叉熵,相当于模型在学习还原带噪图,而不是去噪还原原图,这完全偏离了你的需求。

2. 两种正确的解决方案

方案一:修改自定义损失层,让它接收无噪目标

我们需要调整自定义损失层的逻辑,让它能接收无噪原图作为输入,并用它来计算交叉熵损失:

# 修改后的自定义损失层
class CustomVariationalLayer(Layer):
    def __init__(self, **kwargs):
        self.is_placeholder = True
        super(CustomVariationalLayer, self).__init__(**kwargs)
    
    def vae_loss(self, x_input, target_clean, x_decoded_mean):
        # 用无噪原图target_clean和重建图计算交叉熵,实现去噪目标
        xent_loss = original_dim * metrics.binary_crossentropy(target_clean, x_decoded_mean)
        kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
        return K.mean(xent_loss + kl_loss)
    
    def call(self, inputs):
        x_input = inputs[0]
        target_clean = inputs[1]
        x_decoded_mean = inputs[2]
        loss = self.vae_loss(x_input, target_clean, x_decoded_mean)
        self.add_loss(loss, inputs=inputs)
        # 返回重建结果,方便后续查看
        return x_decoded_mean

# 新增无噪目标的输入层
target_input = Input(shape=(original_dim,))
# 让损失层接收三个输入:带噪输入、无噪目标、重建结果
y = CustomVariationalLayer()([x, target_input, x_decoded_mean])
# 模型输入为带噪图+无噪图,输出为重建结果
vae = Model([x, target_input], y)
vae.compile(optimizer='rmsprop', loss=None)

# 训练时传入两个输入:带噪训练集和无噪训练集
vae.fit([x_train_noisy, x_train], None, shuffle=True, epochs=epochs, batch_size=batch_size, 
        validation_data=([x_test_noisy, x_test], None))

方案二:放弃自定义损失层,改用常规损失函数定义(更推荐)

这种方式更符合Keras的常规用法,不需要自定义层,直接把KL散度和交叉熵合并成自定义损失函数:

# 编码器部分保持不变
x = Input(shape=(original_dim,))
h = Dense(intermediate_dim, activation='relu')(x)
z_mean = Dense(latent_dim)(h)
z_log_var = Dense(latent_dim)(h)

def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0., stddev=epsilon_std)
    return z_mean + K.exp(z_log_var / 2) * epsilon

z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])

# 解码器部分保持不变
decoder_h = Dense(intermediate_dim, activation='relu')
decoder_mean = Dense(original_dim, activation='sigmoid')
h_decoded = decoder_h(z)
x_decoded_mean = decoder_mean(h_decoded)

# 定义完整模型,输出为重建结果
vae = Model(x, x_decoded_mean)

# 自定义损失函数:交叉熵(无噪目标 vs 重建结果) + KL散度
def vae_loss(target_clean, x_decoded_mean):
    xent_loss = original_dim * metrics.binary_crossentropy(target_clean, x_decoded_mean)
    kl_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
    return K.mean(xent_loss + kl_loss)

# 编译模型时指定自定义损失函数
vae.compile(optimizer='rmsprop', loss=vae_loss)

# 正常传入带噪输入和无噪目标,完全符合你的需求
vae.fit(x_train_noisy, x_train, shuffle=True, epochs=epochs, batch_size=batch_size, 
        validation_data=(x_test_noisy, x_test))

3. 总结

  • 你之前改None能运行,但模型逻辑不符合去噪需求;
  • 报错的核心是自定义损失层通过add_loss绑定了损失,模型不期望外部传入目标;
  • 推荐使用方案二,更简洁直观,也完全满足“带噪输入→无噪输出”的VAE去噪需求。

内容的提问来源于stack exchange,提问作者Lafayette

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:25:47