深度学习系列27:VAE生成模型

总结:
AE(自编码器):编码->n维向量->直接解码
VAE(变分自编码器):编码->n维向量->每个维度使用正态分布拟合->每个维度采样形成新的n维向量->解码
CVAE(条件VAE):每个维度用拟合后的分布均值要和目标图片一致
VQVAE(向量量化自编码器):编码->n维向量->每个维度使用cookbook找到最近向量->解码
注意VQVAE没有采样的过程。

1. AE

AE(Autoencoder),自动编码器。自编码器的初衷是为了数据降维,假设原始特征x维度过高,那么我们希望通过编码器E将其编码成低维特征向量z=E(x),编码的原则是尽可能保留原始信息,因此我们再训练一个解码器D,希望能通过z重构原始信息,即x≈D(E(x)),其优化目标一般是
在这里插入图片描述
我们常用的encoder-decoder即为最简单的一种AE。训练过程中加上一些扰动,就可以变成去噪自编码器(DAE):
在这里插入图片描述

或者用遮盖(MIM,mask image modeling)的方法来加扰动:
在这里插入图片描述

2. VAE

在这里插入图片描述
损失为重构误差+KL散度。
对于每一个样本,需要用神经网络拟合均值 u u u和方差 δ 2 \delta^2 δ2,然后用标准正态分布采样得到Z,然后再恢复成X。其中方差项是核心,是用来进行对抗生成的关键。重构部分误差项会让 u u u尽量接近真实值,而KL散度会保证生成器拥有标准的随机性。
我们来看下keras版本代码:

from keras.layers import Input, Dense, Lambda
from keras.models import Model
from keras import backend as K
# 输入
x = Input(shape=(original_dim,))
h = Dense(intermediate_dim, activation='relu')(x)

# 全连接层计算p(Z|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))
    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)

# xent_loss是重构loss,kl_loss是KL loss
xent_loss = K.sum(K.binary_crossentropy(x, x_decoded_mean), axis=-1)
kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
vae_loss = K.mean(xent_loss + kl_loss)
vae.add_loss(vae_loss)
vae.compile(optimizer='rmsprop')
vae.summary()

# 开始训练
vae.fit(x_train,
        shuffle=True,
        epochs=epochs,
        batch_size=batch_size,
        validation_data=(x_test, None))

3. CVAE

CVAE是有标签下的条件VAE,最简单的方法,就是通过控制均值来控制生成图像的类别,只需要修改KL散度,从
在这里插入图片描述

改成
在这里插入图片描述

4. 直观解释

参见:https://spaces.ac.cn/archives/7725
首先是AE的解释,样本点经过降维后还要保证能够充分表达原信息,因此必须是在降维空间中分布非常规整无冗余的,下图中显然第四种是最好的。
在这里插入图片描述
变分自编码器通过加入后验分布p(z|x),可以让编码空间更加“规整”。
在这里插入图片描述
现在每个样本x都对应一个“椭圆”,而确定一个“椭圆”需要两个信息:椭圆中心、椭圆轴长,它们各自构成一个向量,并且这个向量依赖于样本x,我们将其记为μ(x),σ(x)。既然整个椭圆都对应着样本x,我们要求椭圆内任意一点都可以重构x,所以训练目标为:
在这里插入图片描述

在这里插入图片描述
这样一来,VAE能达到两个效果:1、从标准高斯分布(单位圆)随机采样一个向量,就可以由解码器得到真实样本,即实现了生成模型;2、由于编码空间的紧凑形以及训练时对编码向量所加入的噪声,使得编码向量的各个分量能做到一定程度的解耦,并赋予编码向量一定的线性运算性质。

5. VQ-VAE

可以参考这篇:https://zhuanlan.zhihu.com/p/91434658
VQ-VAE的基础是逐点回归生成图像模型。由于图像像素点非常多,因此需要先降维,然后再用PixelCNN建模。降维过程如下:
在这里插入图片描述
具体来说, n ∗ n ∗ 3 n*n*3 nn3的图片被encode成 m ∗ m ∗ d m*m*d mmd的矩阵,然后每个d维向量进行最近邻搜素,得到一个m*m的整数矩阵。一般来说,现在的m×m比原来的n×n×3要小得多,比如用CelebA数据做实验的时候,原来128×128×3的图可以编码为32×32的编码而基本不失真。
优化目标函数为:
在这里插入图片描述
前向计算的时候用的是 z q z_q zq,计算梯度时用的是 z z z
在这里插入图片描述
在这里插入图片描述

下面是使用keras的示例代码:

# 编码器
x_in = Input(shape=(img_dim, img_dim, 3))
......# 这里加入了一堆Conv2D, resnet层
e_model = Model(x_in, x)

# 解码器
z_in = Input(shape=K.int_shape(x)[1:])
......# 这里加入了一堆Conv2D, resnet层
g_model = Model(z_in, z)

# 硬编码模型(寻找最近邻)
z_in = Input(shape=K.int_shape(x)[1:])
z = z_in
vq_layer = VectorQuantizer(num_codes)
codes, code_vecs = vq_layer(z)
q_model = Model(z_in, [codes, code_vecs])

# 训练流程
z = e_model(x)
_, e = q_model(z) # 注意这里仅使用code_vec,没有用code。
ze = Lambda(lambda x: x[0] + K.stop_gradient(x[1] - x[0]))([z, e])
x = g_model(ze)

train_model = Model(x_in, [x, _])
mse_x = K.mean((x_in - x)**2)
mse_e = K.mean((K.stop_gradient(z) - e)**2) # 这里的e就是上文中的q_z.
mse_z = K.mean((K.stop_gradient(e) - z)**2)
loss = mse_x + mse_e + 0.25 * mse_z
train_model.add_loss(loss)
train_model.compile(optimizer=Adam(1e-3))

这里生成模型e_model使用了8个卷积层,其中前面2个缩小尺寸,后面6个加残差。尺寸由(None,128,128,3)变为(None, 32, 32, 128)
离散化的硬编码模型q_model是一个VectorQuantizer,需要特别看一下。计算inputs和embeddings之间的距离矩阵distance,尺寸为[2, 32, 32, 64],然后用argmin得到下标,然后再用gather函数得到实际的向量。
解码模型g_model使用了3个卷积层(都有残差)+2个反卷积层(用于扩大尺寸)。尺寸由(None, 32, 32, 128)变为(None, 128, 128, 3)
总的模型是e_model->q_model(要加stop_gradient,即反向时消失)->g_model

class VectorQuantizer(Layer):
    """量化层
    """
    def __init__(self, num_codes, **kwargs):
        super(VectorQuantizer, self).__init__(**kwargs)
        self.num_codes = num_codes
        
    def build(self, input_shape): # 定义权重
        super(VectorQuantizer, self).build(input_shape)
        dim = input_shape[-1]
        self.embeddings = self.add_weight(
            name='embeddings',
            shape=(self.num_codes, dim),
            initializer='uniform'
        ) #使用add_weight函数创建权重矩阵
        
    def call(self, inputs): # 定义计算图
        """inputs.shape=[None, m, m, dim]
        """
        l2_inputs = K.sum(inputs**2, -1, keepdims=True)
        l2_embeddings = K.sum(self.embeddings**2, -1)
        for _ in range(K.ndim(inputs) - 1):
            l2_embeddings = K.expand_dims(l2_embeddings, 0)
        embeddings = K.transpose(self.embeddings)
        dot = K.dot(inputs, embeddings)
        distance = l2_inputs + l2_embeddings - 2 * dot
        codes = K.cast(K.argmin(distance, -1), 'int32')
        code_vecs = K.gather(self.embeddings, codes)
        return [codes, code_vecs]
    def compute_output_shape(self, input_shape):
        return [input_shape[:-1], input_shape]
  • 15
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习生成模型VAE(Variational Autoencoder)是一种基于神经网络生成模型VAE在产生新数据的时候是基于已有数据来做的,通过学习数据的潜在空间表示,然后从该空间中采样生成新的数据样本。VAE模型结合了自编码器和变分推断的思想,通过最大化样本的下界来优化模型参数,使得生成的样本能够更好地拟合原始数据分布。 与传统的自编码器相比,VAE在编码器部分引入了一个均值向量和方差向量,这样可以使得编码后的潜在表示服从一个高斯分布。这种设计使得VAE不仅能够学习到数据的低维表示,还能够通过在潜在空间中进行采样来生成新的样本。VAE模型的损失函数由重构误差项和正则化项组成,通过最小化该损失函数可以使得生成的样本能够尽可能地接近原始数据分布。 尽管VAE生成新数据方面的效果相对于其他模型可能有些模糊,但它在学习数据分布和生成新数据方面仍然具有一定的优势。通过使用变分推断和重参数化技巧,VAE能够生成具有多样性的样本,并且能够在潜在空间中进行插值和操作,从而得到更多样化的结果。 总结来说,VAE是一种深度学习生成模型,通过学习数据的潜在空间表示,可以生成新的样本。它结合了自编码器和变分推断的思想,并通过最大化样本的下界来优化模型参数。尽管生成的样本可能有些模糊,但VAE在学习数据分布和生成多样化样本方面具有一定的优势。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值