TensorFlow深度学习实战(24)——变分自编码器详解与实现
TensorFlow深度学习实战(24)——变分自编码器详解与实现
- 0. 前言
- 1. 变分自编码器
- 1.1 VAE 工作原理
- 1.2 VAE 构建策略
- 1.3 KL 散度
- 1.4 重参数化技巧
- 2. 数据集分析
- 3. 构建变分自编码器
- 小结
- 系列链接
0. 前言
变分自编码器 (Variational Autoencoder
, VAE
) 是一种生成模型,结合了自编码器和概率模型的思想,通过学习输入数据的潜分布,能够生成新的样本。与传统的自编码器不同,变分自编码器引入了概率建模的思想,并通过编码器和解码器之间的随机性来实现生成过程。编码器将输入数据映射到潜空间中的概率分布,假设潜变量是从多元正态分布中采样得到的,解码器则将从潜空间采样得到的潜在变量映射回原始数据空间,并生成新的样本。本节中,将介绍变分自编码器的基本概念,并使用 TensorFlow
实现变分自编码器生成新图像。
1. 变分自编码器
变分自编码器 (Variational Autoencoder
, VAE
) 融合了神经网络和贝叶斯推理,是无监督学习中最受欢迎的方法之一。在传统的自编码器的编码器和解码器网络基础上,变分自编码器增加了额外的随机层。随机层在编码器网络之后使用高斯分布对数据进行采样,而在解码器网络之后则使用伯努利分布进行采样,变分自编码器可以基于训练数据集的分布生成图像。
1.1 VAE 工作原理
VAE
使用深度学习构建概率模型,将输入数据映射到一个低维度的潜空间中,并通过解码器将潜空间中的分布转换回数据空间中,以生成与原始数据相似的数据。与传统的自编码器相比,VAE
更加稳定,生成样本的质量更高。
VAE
的核心思想是利用概率模型来描述高维的输入数据,将输入数据采样于一个低维度的潜变量分布中,并通过解码器生成与原始数据相似的输出。具体来说,VAE
同样是由编码器和解码器组成:
- 编码器将数据 xxx 映射到一个潜在空间 zzz 中,该空间定义在低维正态分布中,即 z∼N(0,I)z∼N(0,I)z∼N(0,I),编码器由两个部分组成:一是将数据映射到均值和方差,即 z∼N(μ,σ2)z∼N(μ,σ^2)z∼N(μ,σ2);二是通过重参数化技巧,将均值和方差的采样过程分离出来,并引入随机变量 ϵ∼N(0,I)ϵ∼N(0,I)ϵ∼N(0,I),使得 z=μ+ϵσz=μ+ϵσz=μ+ϵσ
- 解码器将潜在变量 zzz 映射回数据空间中,生成与原始数据 xxx 相似的数据 x′x′x′,为了使生成的数据 x′x′x′ 能够与原始数据 xxx 较高的相似度,
VAE
在损失函数中使用重构误差和正则化项,重构误差表示生成数据与原始数据之间的差异,正则化项用于约束潜在变量的分布,使其满足高斯正态分布,使得VAE
从潜空间中生成的样本质量更高
VAE
具有广泛的应用场景,如图像生成、语音、自然语言处理等领域,它能够通过有限的数据样本学习到输入数据背后的潜在规律,生成与原始数据类似的新数据,具有很强的潜数据的可解释性。
1.2 VAE 构建策略
在 VAE
中,基于预定义分布获得的随机向量生成逼真图像,而在传统自编码器中并未指定在网络中生成图像的数据分布。可以通过以下策略,实现 VAE
:
- 编码器的输出包括两个向量:
- 输入图像平均值
- 输入图像标准差
- 根据以上两个向量,通过在均值和标准差之和中引入随机变量 (ϵ∼N(0,I)ϵ∼N(0,I)ϵ∼N(0,I)) 获取随机向量 (z=μ+ϵσz=μ+ϵσz=μ+ϵσ)
- 将上一步得到的随机向量作为输入传递给解码器以重构图像
- 损失函数是均方误差和
KL
散度损失的组合:KL
散度损失衡量由均值向量 μ\muμ 和标准差向量 σ\sigmaσ 构建的分布与 N(0,I)N(0,I)N(0,I) 分布的偏差- 均方损失用于优化重建(解码)图像
通过训练网络,指定输入数据满足由均值向量 μ\muμ 和标准差向量 σ\sigmaσ 构建的 N(0,1)N(0,1)N(0,1) 分布,当我们生成均值为 0
且标准差为 1
的随机噪声时,解码器将能够生成逼真的图像。VAE
训练完成后,可以仅使用解码器网络生成新图像。
需要注意的是,如果只最小化 KL
散度,编码器将预测均值向量为 0
,标准差为 1
。因此,需要同时最小化 KL
散度损失和均方损失。在下一节中,让我们介绍 KL
散度,以便将其纳入模型的损失值计算中。
1.3 KL 散度
KL
散度(也称相对熵)可以用于衡量两个概率分布之间的差异:
KL(P∣∣Q)=∑x∈XP(x)ln(P(i)Q(i))KL(P||Q) = \sum_{x∈X} P(x) ln(\frac {P(i)}{Q(i)}) KL(P∣∣Q)=x∈X∑P(x)ln(Q(i)P(i))
其中,PPP 和 QQQ 为两个概率分布,KL
散度的值越小,两个分布的相似性就越高,当且仅当 PPP 和 QQQ 两个概率分布完全相同时,KL
散度等于 0
。在 VAE
中,我们希望瓶颈特征值遵循平均值为 0
和标准差为 1
的正态分布。因此,我们可以使用 KL
散度衡量变分自编码器中编码器输出的分布与标准高斯分布 N(0,1)N(0,1)N(0,1) 之间的差异。
可以通过以下公式计算 KL
散度损失:
∑i=1nσi2+μi2−log∗(σi)−1\sum_{i=1}^n\sigma_i^2+\mu_i^2-log*(\sigma_i)-1 i=1∑nσi2+μi2−log∗(σi)−1
在上式中,σσσ 和 μμμ 表示每个输入图像的均值和标准差值:
- 确保均值向量分布在
0
附近:- 最小化上式中的均方误差 (μi2\mu_i^2μi2) 可确保 μ\muμ 尽可能接近
0
- 最小化上式中的均方误差 (μi2\mu_i^2μi2) 可确保 μ\muμ 尽可能接近
- 确保标准差向量分布在
1
附近:- 上式中其余部分(除了 μi2\mu_i^2μi2 )用于确保标准差 (sigmasigmasigma) 分布在
1
附近
- 上式中其余部分(除了 μi2\mu_i^2μi2 )用于确保标准差 (sigmasigmasigma) 分布在
当均值 μμμ 为 0
且标准差 σ\sigmaσ 为 1
时,以上损失函数值达到最小,通过引入标准差的对数,确保 σ\sigmaσ 值不为负。通过最小化以上损失可以确保编码器输出遵循预定义分布。
1.4 重参数化技巧
下图左侧显示了 VAE
网络。编码器获取输入 xxx,并估计潜矢量 zzz 的多元高斯分布的均值 μμμ 和标准差 σσσ,解码器从潜矢量 zzz 采样,以将输入重构为 x′x'x′:
但是反向传播梯度不会通过随机采样块。虽然可以为神经网络提供随机输入,但梯度不可能穿过随机层。解决此问题的方法是将“采样”过程作为输入,如图右侧所示。 采样计算为:
Sample=μ+εσSample=\mu + εσSample=μ+εσ
如果 εεε 和 σσσ 以矢量形式表示,则 εσεσεσ 是逐元素乘法,使用上式,令采样好像直接来自于潜空间。 这种技术被称为重参数化技巧 (Reparameterization trick
)。
2. 数据集分析
接下来,使用 Tensorflow
构建 VAE
模型,并使用 Fashion-MNIST
数据集训练模型,该数据集包含了服装和配饰的图像,旨在提供一个比经典 MNIST
数据集更具挑战性的替代数据集,测试-训练数据集划分与 MNIST
完全相同,即 60,000
张训练图像和 10,000
张测试图像。每张图像的大小也是 28 × 28
,因此我们可以轻松地将运行在 MNIST
数据集上的代码替换为 Fashion-MNIST
数据集。
3. 构建变分自编码器
(1) 首先,导入所有必要的库:
import os
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
(2) 在构建 VAE
之前,简单探索 Fashion-MNIST
数据集,该数据集可以使用 TensorFlow Keras API
加载:
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
x_train, x_test = x_train.astype(np.float32)/255., x_test.astype(np.float32)/255.print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)
# (60000, 28, 28) (60000,)
# (10000, 28, 28) (10000,)
(3) 查看示例图像:
number = 10 # how many digits we will display
plt.figure(figsize=(20, 4))
for index in range(number):# display originalax = plt.subplot(2, number, index + 1)plt.imshow(x_train[index], cmap='gray')ax.get_xaxis().set_visible(False)ax.get_yaxis().set_visible(False)
plt.show()
(4) 定义超参数,包括学习率、隐藏层和潜空间的维度、批大小、训练 epoch
等:
image_size = x_train.shape[1]*x_train.shape[2]
hidden_dim = 512
latent_dim = 10
num_epochs = 80
batch_size = 100
learning_rate = 0.001
(5) 使用 TensorFlow Keras Model API
构建 VAE
模型,__init__()
函数定义将使用的网络层:
class VAE(tf.keras.Model):def __init__(self,dim,**kwargs):h_dim = dim[0]z_dim = dim[1]super(VAE, self).__init__(**kwargs)self.fc1 = tf.keras.layers.Dense(h_dim)self.fc2 = tf.keras.layers.Dense(z_dim)self.fc3 = tf.keras.layers.Dense(z_dim)self.fc4 = tf.keras.layers.Dense(h_dim)self.fc5 = tf.keras.layers.Dense(image_size)
定义生成编码器输出、解码器输出和重参数化的函数。变分自编码器从随机节点 zzz 进行采样,而该节点由真实后验的分布 q(z)q(z)q(z) 进行近似。为了获取参数,需要使用反向传播。然而,反向传播不能应用于随机节点。通过使用重参数化,我们可以引入一个新的参数 ϵ\epsilonϵ,使我们可以对 zzz 进行重参数化,从而使反向传播能够通过确定性随机节点:
def encode(self, x):h = tf.nn.relu(self.fc1(x))return self.fc2(h), self.fc3(h)def reparameterize(self, mu, log_var):std = tf.exp(log_var * 0.5)eps = tf.random.normal(std.shape)return mu + eps * stddef decode_logits(self, z):h = tf.nn.relu(self.fc4(z))return self.fc5(h)def decode(self, z):return tf.nn.sigmoid(self.decode_logits(z))
最后,定义 call()
函数,控制数据在 VAE
的不同层之间的流动:
def call(self, inputs, training=None, mask=None):mu, log_var = self.encode(inputs)z = self.reparameterize(mu, log_var)x_reconstructed_logits = self.decode_logits(z)return x_reconstructed_logits, mu, log_var
(6) 接下来,创建 VAE
模型并定义优化器,打印模型摘要信息:
model = VAE([hidden_dim, latent_dim])
model.build(input_shape=(4, image_size))
model.summary()
optimizer = tf.keras.optimizers.Adam(learning_rate)
(7) 训练模型。定义损失函数,损失函数是重建损失和 KL
散度损失之和:
dataset = tf.data.Dataset.from_tensor_slices(x_train)
dataset = dataset.shuffle(batch_size * 5).batch(batch_size)num_batches = x_train.shape[0] // batch_sizefor epoch in range(num_epochs):for step, x in enumerate(dataset):x = tf.reshape(x, [-1, image_size])with tf.GradientTape() as tape:# Forward passx_reconstruction_logits, mu, log_var = model(x)# Compute reconstruction loss and kl divergencereconstruction_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=x_reconstruction_logits)reconstruction_loss = tf.reduce_sum(reconstruction_loss) / batch_sizekl_div = - 0.5 * tf.reduce_sum(1. + log_var - tf.square(mu) - tf.exp(log_var), axis=-1)kl_div = tf.reduce_mean(kl_div)# Backprop and optimizeloss = tf.reduce_mean(reconstruction_loss) + kl_divgradients = tape.gradient(loss, model.trainable_variables)for g in gradients:tf.clip_by_norm(g, 15)optimizer.apply_gradients(zip(gradients, model.trainable_variables))if (step + 1) % 50 == 0:print("Epoch[{}/{}], Step [{}/{}], Reconst Loss: {:.4f}, KL Div: {:.4f}".format(epoch + 1, num_epochs, step + 1, num_batches, float(reconstruction_loss), float(kl_div)))
(8) 模型训练完成后,能够用于生成与原始 Fashion-MNIST
图像相似的图像,只需要使用解码器网络,并向其传递一个随机生成的 zzz 输入:
z = tf.random.normal((batch_size, latent_dim))
out = model.decode(z) # decode with sigmoid
out = tf.reshape(out, [-1, 28, 28]).numpy() * 255
out = out.astype(np.uint8)number = 10 # how many digits we will display
plt.figure(figsize=(20, 4))
for index in range(number):# display originalax = plt.subplot(2, number, index + 1)plt.imshow(out[index], cmap='gray')ax.get_xaxis().set_visible(False)ax.get_yaxis().set_visible(False)
plt.show()
经过 80
个 epoch
训练后结果如下,生成的图像类似于输入空间,与原始 Fashion-MNIST
图像相似。
小结
变分自编码器是一种结合了自编码器和概率建模的生成模型,通过编码器将输入数据映射到潜在空间中的概率分布,并通过解码器将从潜在空间采样得到的潜在变量映射回原始数据空间,实现了数据的生成和特征学习。
系列链接
TensorFlow深度学习实战(1)——神经网络与模型训练过程详解
TensorFlow深度学习实战(2)——使用TensorFlow构建神经网络
TensorFlow深度学习实战(3)——深度学习中常用激活函数详解
TensorFlow深度学习实战(4)——正则化技术详解
TensorFlow深度学习实战(5)——神经网络性能优化技术详解
TensorFlow深度学习实战(6)——回归分析详解
TensorFlow深度学习实战(7)——分类任务详解
TensorFlow深度学习实战(8)——卷积神经网络
TensorFlow深度学习实战(9)——构建VGG模型实现图像分类
TensorFlow深度学习实战(10)——迁移学习详解
TensorFlow深度学习实战(11)——风格迁移详解
TensorFlow深度学习实战(12)——词嵌入技术详解
TensorFlow深度学习实战(13)——神经嵌入详解
TensorFlow深度学习实战(14)——循环神经网络详解
TensorFlow深度学习实战(15)——编码器-解码器架构
TensorFlow深度学习实战(16)——注意力机制详解
TensorFlow深度学习实战(17)——主成分分析详解
TensorFlow深度学习实战(18)——K-means 聚类详解
TensorFlow深度学习实战(19)——受限玻尔兹曼机
TensorFlow深度学习实战(20)——自组织映射详解
TensorFlow深度学习实战(21)——Transformer架构详解与实现
TensorFlow深度学习实战(22)——从零开始实现Transformer机器翻译
TensorFlow深度学习实战(23)——自编码器详解与实现