【深度学习08】变分自编码器(VAE)

本文介绍了编码器-解码器结构在机器学习中的应用,包括自编码器的原理、特点和局限,以及变分自编码器如何通过引入概率分布和正则化避免过拟合。重点讨论了自编码器的深度非线性降维能力以及变分自编码器的训练策略,包括重参数化技巧在训练过程中的作用。
摘要由CSDN通过智能技术生成

大致内容: 

1.编码器-解码器(E-D)

1.1降维

机器学习中有类似主成分分析(PCA)这样的降维方法:减少描述数据的特征数量的过程。而编码器-解码器结构也可以实现降维思想。如图所示:

  • 编码器(encoder)用于压缩数据:把一张图片压缩成一个向量的数据。
  • 解码器(decoder)用于解压缩:把一个的向量解压缩成一张图片。
  • 中间这个向量所在的编码空间也称之为隐空间(latent space),相比于真实空间,隐空间的维度大大降低了。

这种压缩表示的原理是:通过捕捉原始空间中的重要特征。同时减少噪声和冗余信息。而且,在隐空间中,相似的样本也是相近的,这确保了在隐空间中进行各种计算操作的正确性,同时在隐空间中计算也会更加便捷。

2.自编码器(AE)

2.1原理

  • 自编码器作为一种神经网络结构,将输入数据映射到隐藏层,解码器将隐藏层映射到输出层。
  • 输入层和输出层有相同的大小,隐藏层的大小通常小于输入层和输出层的大小。
  • 在训练的过程中,自编码器的目标就是最小化输入数据和解码数据之间的重构误差(对应图中的loss),以此来学习参数。e和d分别对应编码器encoder和解码器decoder的映射函数。
  • 编解码器结构,为数据创造了这样一个瓶颈:确保了只有重要的信息特征能够通过并且重建。

2.2特点

  1. 我们知道PCA方法实际上是对基坐标轴进行线性变换。而如果自编码器中的编解码函数也是线性的。那么就跟PCA是类似的了,唯一不同的就是神经网络不对坐标轴的正交性有所限制(如右图中ae 的两个维度彼此并不正交)。
  2. 自编码器的深度降维能力其实来自于编解码函数的深度非线性。可以这么说:当编解码器有足够的自由度(足够大)时,甚至可以将任何初始数据的维度减少到1(N个数据对应实轴上N个整数相关的解码器,再进行逆变换的过程中实现无损解压缩)。
  3. 但是我们仍然要记住降维的目的:尽量将数据主要的结构信息保留在简化的表示中,这需要我们仔细控制和调整潜空间的大小和自编码器的“深度。

2.3局限

  • 从这个例子可以看出,对于中间的原始数据来说,分类成左边的图虽然看似更简单,但是丢失了很多信息。不如分类成右边二维的情形,效果更好。
  • 自编码器训练,以最小损失为目标,而不管潜空间如何组织。容易导致严重的过拟合问题,很难直接用于内容生成任务。

3.变分自编码器(VAE)

3.1直观

  • 从直观的角度理解,输入编码不再是点,而是概率分布。

  • 因为输入数据在隐空间里对应的特征编码是一个分布,所以可以从分布当中进行采样,然后再生成内容。 这个就是变分自编码器的思想。

3.2数学

从数学的角度理解,如果把照片中人微笑的程度用一个参数 \theta 来衡量。一开始自编码器的做法是,\theta是一个未知的确定性的常数,可以通过观测数据 X 来估计 \theta 的具体取值多少:g(X)\to \theta
而VAE的做法是把 \theta 看做是一个随机变量,它有自己的分布,目标转为已知观测数据X,求后验分布p(\theta|X),或者说求后验分布的最大后验估计MAP(Maximum A Posteriori Estimation)

不难发现,这其实是经典统计到贝叶斯统计的转变:求MLE最大似然改求最大后验估计MAP

输入数据 x 可以看作是由隐变量 z 生成的。但是我们只能观测到 x 观测不到 z,想通过 x 来反推 z,换句话说想估计后验概率 p(z|x),我们就可以用到贝叶斯公式:

p(z|x)=\frac{p(x|z)p(z)}{p(x)}

其中,先验p(z) 是正则化项,当它是拉普拉斯分布时对应L1正则(z容易得到稀疏解),当它是高斯分布时对应L2正则。而在VAE当中,通常假定是L2正则。从这里我们也就能够看出VAE用来避免过拟合的套路就是引入正则化。

不过,这里有一个后验分布的计算困难,因为下面个积分没有解析解:

p(x)=\int p(x|z)p(z)dz

这里就不得不说一句,贝叶斯能够成长到今天,真的离不开采样(MCMC)和变分(VB)这两种思路。而变分推断在解决后验分布计算的问题上有它的足够优势。VAE中的Variational采用的就是变分推断。

变分解决计算 p(z|x) 的途径是通过设置一个参数化分布族 q_x(z)

这里假设q_x(z)服从高斯分布N(g,h),\ g\in G,h\in H,并在该族中寻找目标分布的最佳近似。

\begin{aligned} (g^{*},h^{*})& =\underset{(g,h)\in G\times H}{\arg\min}KL(q_{x}(z),p(z|x)) \\ &=\underset{(g,h)\in G\times H}{\arg\min}\left(\mathbb{E}_{z\sim q_{x}}(\log q_{x}(z))-\mathbb{E}_{z\sim q_{x}}\left(\log\frac{p(x|z)p(z)}{p(x)}\right)\right) \\ &\begin{array}{rcl}=\underset{(g,h)\in G\times H}{\arg\min}(\mathbb{E}_{z\sim q_z}(\log q_x(z))-\mathbb{E}_{z\sim q_x}(\log p(z))-\mathbb{E}_{z\sim q_x}(\log p(x|z))+\mathbb{E}_{z\sim q_x}(\log p(x)))\end{array} \\ &=\underset{(g,h)\in G\times H}{\operatorname*{\arg\max}}(\mathbb{E}_{z\sim q_x}(\log p(x|z))-KL(q_x(z),p(z))) \\ &=\underset{(g,h)\in G\times H}{\operatorname*{\arg\max}}\left(\mathbb{E}_{z\sim q_{x}}\left(-\frac{||x-f(z)||^{2}}{2c}\right)-KL(q_{x}(z),p(z))\right) \end{aligned}

这里把 p(x|z) 也假设为高斯分布(总之为了能够log一下就变简单,令成一个指数族是不错的选择), f(z) 是解码器函数。如此一来,不仅巧妙地将重构损失函数融合进了变分的迭代优化过程中。还可以通过参数 c 起到一个权重调整的作用。KL(q_{x}(z),p(z))的作用是作为正则项,把隐空间的结构规则限定在高斯,使之不至于太复杂而过拟合。

如此一来,损失函数:尽量无损重构+正则项

(f^*,g^*,h^*)=\underset{(f,g,h)\in F\times G\times H}{\arg\max}(\mathbb{E}_{z\sim q_x}(-\frac{||x-f(z)||^2}{2c}-KL(q_x(z),p(z))))

整体的训练过程如图所示:

 3.3可视化

  1. 只有重建损失的情况对应AE,不同的类别之间留有空隙,如果数据点在这些空隙出现,那么AE生成的内容将会毫无意义。
  2. 如果只有KL正则项作为损失函数,则最终会用相同的高斯分布来描述所有的先验数据。
  3. 当同时优化这两项时,对应VAE的情形,模型会鼓励隐变量的分布接近高斯先验,但必要时偏离以描述输入特征。

4.神经网络实现

  • 编码器部分,g和h可以共享部分的结构和权重,而不是两个独立的网络。协方差矩阵被简化为标准高斯分布的对角阵(可以用一个一维向量表示)
  • 从编码器获得的分布当中采样得到z\zeta是从标准正态分布中采样得到的,而采样操作不可导,VAE模型提出了一个重参数化技巧,使得梯度下降成为可能:z=\sigma_x \bigodot \zeta^{'} +\mu_x其中,\bigodot表示逐个元素相乘。\zeta^{'}是一个可导的随机向量,元素也是从标准正态分布中采样得到。重参数化技巧把采样操作变成一种可导操作,使得我们可以对均值和标准差向量进行反向传播,进而实现对整个模型端到端的训练。


 

以下是一个简单的对抗自编码器的Python示例代码,用于实现降维: ```python import numpy as np import tensorflow as tf from tensorflow.keras import layers # 定义对抗自编码器的生成器和判别器 def make_generator_model(): model = tf.keras.Sequential() model.add(layers.Dense(256, activation='relu', input_shape=(100,))) model.add(layers.Dense(784, activation='sigmoid')) model.add(layers.Reshape((28, 28, 1))) return model def make_discriminator_model(): model = tf.keras.Sequential() model.add(layers.Flatten()) model.add(layers.Dense(256, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) return model # 定义损失函数和优化器 cross_entropy = tf.keras.losses.BinaryCrossentropy() def discriminator_loss(real_output, fake_output): real_loss = cross_entropy(tf.ones_like(real_output), real_output) fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output) total_loss = real_loss + fake_loss return total_loss def generator_loss(fake_output): return cross_entropy(tf.ones_like(fake_output), fake_output) generator_optimizer = tf.keras.optimizers.Adam(1e-4) discriminator_optimizer = tf.keras.optimizers.Adam(1e-4) # 定义对抗自编码器模型 class AdversarialAutoencoder(tf.keras.Model): def __init__(self, latent_dim): super(AdversarialAutoencoder, self).__init__() self.latent_dim = latent_dim self.generator = make_generator_model() self.discriminator = make_discriminator_model() def call(self, inputs): x = self.generator(inputs) return x def train_step(self, data): real_images = data # 训练判别器 with tf.GradientTape() as disc_tape: # 从噪声中生成假图像 noise = tf.random.normal([real_images.shape[0], self.latent_dim]) fake_images = self.generator(noise) # 计算判别器损失 real_output = self.discriminator(real_images) fake_output = self.discriminator(fake_images) disc_loss = discriminator_loss(real_output, fake_output) # 计算判别器梯度并更新 gradients_of_discriminator = disc_tape.gradient(disc_loss, self.discriminator.trainable_variables) discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, self.discriminator.trainable_variables)) # 训练生成器 with tf.GradientTape() as gen_tape: # 从噪声中生成假图像 noise = tf.random.normal([real_images.shape[0], self.latent_dim]) fake_images = self.generator(noise) # 计算生成器损失 fake_output = self.discriminator(fake_images) gen_loss = generator_loss(fake_output) # 计算生成器梯度并更新 gradients_of_generator = gen_tape.gradient(gen_loss, self.generator.trainable_variables) generator_optimizer.apply_gradients(zip(gradients_of_generator, self.generator.trainable_variables)) return {'discriminator_loss': disc_loss, 'generator_loss': gen_loss} # 加载MNIST数据集 (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() # 标准化像素值 x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. # 将图像展平 x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:]))) x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:]))) # 定义对抗自编码器模型和训练参数 aae = AdversarialAutoencoder(latent_dim=100) aae.compile(optimizer=tf.keras.optimizers.Adam(1e-4)) # 训练对抗自编码器 aae.fit(x_train, x_train, epochs=20, batch_size=128, validation_data=(x_test, x_test)) ``` 这个代码示例使用了MNIST数据集,将图像展平后进行降维。对抗自编码器使用了一个生成器和一个判别器,通过对抗训练来实现降维。每个训练步骤中,先训练判别器,然后训练生成器,以此来实现对抗训练。最终的目标是让生成器可以生成与输入数据相似的噪声图像,这些噪声图像可以被判别器正确地分类为真实数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值