在CIFAR10上训练VAE时验证损失(val_loss)持续为0求助
问题原因与解决方案
验证损失全为0的核心原因是自定义VAE类未实现test_step方法,Keras在验证阶段默认无法计算你自定义的三个损失指标,因此输出全0。同时代码中还有几处小bug需要修复,具体解决步骤如下:
1. 实现test_step方法
在VAE类中添加验证阶段的损失计算逻辑,和训练逻辑类似但无需梯度更新:
def test_step(self, data): z_mean, z_log_var, z = self.encoder(data) reconstruction = self.decoder(z) # 计算三个损失,逻辑和train_step一致 reconstruction_loss = tf.reduce_mean( tf.reduce_sum( keras.losses.binary_crossentropy(data, reconstruction), axis=(1, 2) ) ) kl_loss = -0.5 * (1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var)) kl_loss = tf.reduce_mean(tf.reduce_sum(kl_loss, axis=1)) total_loss = reconstruction_loss + kl_loss # 更新验证阶段的指标追踪器 self.total_loss_tracker.update_state(total_loss) self.reconstruction_loss_tracker.update_state(reconstruction_loss) self.kl_loss_tracker.update_state(kl_loss) return { "loss": self.total_loss_tracker.result(), "reconstruction_loss": self.reconstruction_loss_tracker.result(), "kl_loss": self.kl_loss_tracker.result(), }
2. 修复VAE类call方法的错误
原代码中call方法直接引用全局的encoder,改为实例自身的self.encoder:
def call(self, inputs, training=None, mask=None): _, _, z = self.encoder(inputs) # 修复这里 outputs = self.decoder(z) # 同样改为self.decoder return outputs
3. 修复Decoder类的构造函数
原Decoder类的__init__方法缺失latent_dimension参数,补充后才能正常实例化:
class Decoder(keras.Model): def __init__(self, latent_dimension): # 补充参数 super(Decoder, self).__init__() self.latent_dim = latent_dimension # 后续代码不变...
修改后的完整可运行代码
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from sklearn.model_selection import train_test_split from tensorflow.keras.optimizers import Adam # 补充采样函数(原代码中缺失) def sample(z_mean, z_log_var): epsilon = tf.random.normal(shape=tf.shape(z_mean)) return z_mean + tf.exp(0.5 * z_log_var) * epsilon class VAE(keras.Model): def __init__(self, encoder, decoder, **kwargs): super().__init__(**kwargs) self.encoder = encoder self.decoder = decoder self.total_loss_tracker = keras.metrics.Mean(name="total_loss") self.reconstruction_loss_tracker = keras.metrics.Mean(name="reconstruction_loss") self.kl_loss_tracker = keras.metrics.Mean(name="kl_loss") @property def metrics(self): return [ self.total_loss_tracker, self.reconstruction_loss_tracker, self.kl_loss_tracker, ] def train_step(self, data): with tf.GradientTape() as tape: z_mean, z_log_var, z = self.encoder(data) reconstruction = self.decoder(z) reconstruction_loss = tf.reduce_mean( tf.reduce_sum( keras.losses.binary_crossentropy(data, reconstruction), axis=(1, 2) ) ) kl_loss = -0.5 * (1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var)) kl_loss = tf.reduce_mean(tf.reduce_sum(kl_loss, axis=1)) total_loss = reconstruction_loss + kl_loss grads = tape.gradient(total_loss, self.trainable_weights) self.optimizer.apply_gradients(zip(grads, self.trainable_weights)) self.total_loss_tracker.update_state(total_loss) self.reconstruction_loss_tracker.update_state(reconstruction_loss) self.kl_loss_tracker.update_state(kl_loss) return { "loss": self.total_loss_tracker.result(), "reconstruction_loss": self.reconstruction_loss_tracker.result(), "kl_loss": self.kl_loss_tracker.result(), } def test_step(self, data): z_mean, z_log_var, z = self.encoder(data) reconstruction = self.decoder(z) reconstruction_loss = tf.reduce_mean( tf.reduce_sum( keras.losses.binary_crossentropy(data, reconstruction), axis=(1, 2) ) ) kl_loss = -0.5 * (1 + z_log_var - tf.square(z_mean) - tf.exp(z_log_var)) kl_loss = tf.reduce_mean(tf.reduce_sum(kl_loss, axis=1)) total_loss = reconstruction_loss + kl_loss self.total_loss_tracker.update_state(total_loss) self.reconstruction_loss_tracker.update_state(reconstruction_loss) self.kl_loss_tracker.update_state(kl_loss) return { "loss": self.total_loss_tracker.result(), "reconstruction_loss": self.reconstruction_loss_tracker.result(), "kl_loss": self.kl_loss_tracker.result(), } def call(self, inputs, training=None, mask=None): _, _, z = self.encoder(inputs) outputs = self.decoder(z) return outputs class Encoder(keras.Model): def __init__(self, latent_dimension): super(Encoder, self).__init__() self.latent_dim = latent_dimension self.conv_block1 = keras.Sequential([ layers.Conv2D(filters=64, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.conv_block2 = keras.Sequential([ layers.Conv2D(filters=128, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.conv_block3 = keras.Sequential([ layers.Conv2D(filters=256, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.flatten = layers.Flatten() self.dense = layers.Dense(units=100, activation="relu") self.z_mean = layers.Dense(latent_dimension, name="z_mean") self.z_log_var = layers.Dense(latent_dimension, name="z_log_var") self.sampling = sample def call(self, inputs, training=None, mask=None): x = self.conv_block1(inputs) x = self.conv_block2(x) x = self.conv_block3(x) x = self.flatten(x) x = self.dense(x) z_mean = self.z_mean(x) z_log_var = self.z_log_var(x) z = self.sampling(z_mean, z_log_var) return z_mean, z_log_var, z class Decoder(keras.Model): def __init__(self, latent_dimension): super(Decoder, self).__init__() self.latent_dim = latent_dimension self.dense1 = keras.Sequential([ layers.Dense(units=100, activation="relu"), layers.BatchNormalization() ]) self.dense2 = keras.Sequential([ layers.Dense(units=1024, activation="relu"), layers.BatchNormalization() ]) self.dense3 = keras.Sequential([ layers.Dense(units=4096, activation="relu"), layers.BatchNormalization() ]) self.reshape = layers.Reshape((4, 4, 256)) self.deconv1 = keras.Sequential([ layers.Conv2DTranspose(filters=256, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.deconv2 = keras.Sequential([ layers.Conv2DTranspose(filters=128, kernel_size=3, activation="relu", strides=1, padding="same"), layers.BatchNormalization() ]) self.deconv3 = keras.Sequential([ layers.Conv2DTranspose(filters=128, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.deconv4 = keras.Sequential([ layers.Conv2DTranspose(filters=64, kernel_size=3, activation="relu", strides=1, padding="same"), layers.BatchNormalization() ]) self.deconv5 = keras.Sequential([ layers.Conv2DTranspose(filters=64, kernel_size=3, activation="relu", strides=2, padding="same"), layers.BatchNormalization() ]) self.deconv6 = layers.Conv2DTranspose(filters=3, kernel_size=3, activation="sigmoid", padding="same") def call(self, inputs, training=None, mask=None): x = self.dense1(inputs) x = self.dense2(x) x = self.dense3(x) x = self.reshape(x) x = self.deconv1(x) x = self.deconv2(x) x = self.deconv3(x) x = self.deconv4(x) x = self.deconv5(x) decoder_outputs = self.deconv6(x) return decoder_outputs latent_dimension = 100 encoder = Encoder(latent_dimension) decoder = Decoder(latent_dimension) # Load the CIFAR-10 dataset (x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() # Normalize the input data x_train = x_train.astype("float32") / 255.0 x_test = x_test.astype("float32") / 255.0 # Split the data into training, validation, and test sets validation_size = 0.2 # 20% of the training data will be used for validation x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=validation_size) vae = VAE(encoder, decoder) vae.compile(optimizer=Adam()) epochs = 2 batch_size = 128 history = vae.fit(x_train, epochs=epochs, batch_size=batch_size, validation_data=(x_val, y_val))
说明
- 补充了原代码中缺失的
sample采样函数,否则代码无法运行。 - 实现
test_step后,Keras在验证阶段会自动调用该方法计算并输出验证损失,不再显示为0。
内容的提问来源于stack exchange,提问作者tail
相关产品推荐
相关产品推荐

