深入浅出搞懂VAE

2013年提出的VAE,跨越11年,获得首届ICLR‘24时间检验奖,是深度学习的重要技术之一。

原文:https://arxiv.org/pdf/1312.6114 


这篇论文把深度学习和可扩展的概率推理整合在一起,从而产生了变分自编码器(VAE),这项工作其持久的价值在于优雅,加深了我们对于深度学习和概率建模之间相互作用的理解,引发了许多后续有趣的概率模型和编码方法的开发。

下面按照三个部分进行,由易到难:核心思想,技术难点和实现(如何将概率转换成网络?),理论支持(变分下界推导,损失函数),复盘

0. Variational Auto-Encoder翻译

翻译成变分自编码器,为什么???

“变分”这个词指的是使用变分推断(Variational Inference)的技术。变分推断是一种用于近似复杂概率分布的技术。在变分自编码器中,它用于近似潜在变量的后验分布,因为这些分布往往是难以直接计算的。

VAE中的“variational”翻译为“变分”是为了反映其核心技术——变分推断,这是一种用于处理和近似难以解析求解的概率分布的数学方法。

1. 核心思想

自编码器AE一般用于降维或者特征学习,一般由编码器和解码器两个部分组成。AE的缺点在于,能对输入图进行重建,却不能生成新的图。根本原因是,AE学习的隐变量是确定的、离散的,没有很好的解释性。(给一张图像x,得到确定的隐变量z,解码出重建的\hat{x}

变分自编码VAE 核心思想将隐变量看成一个概率分布,具有不确定的、连续的特点,而且可以生成不同于输入图像的样本。希望能定义一个从隐变量生成样本的模型。

2.技术难点(直接积分方式

训练生成模型一般通过对数似然函数极大化来求解模型参数\theta,即:\underset{\theta }{max^{}}\sum_{i=1}^{N}log[p_{\theta }(x^{(i)})],其中训练集包括N个i.i.d的训练样本。

图像生成过程可以表示为:先从隐变量分布p_{\theta }(z)采样得到z,然后再根据条件分布p_{\theta }(x\mid z)中采样生成样本,即:

p_{\theta }(x)=\int _{z}p_{\theta }(x\mid z)p_{\theta }(z)dz

如何将概率转换成网络???

  • p_{\theta }(z),隐变量z先验分布,可以设计为简单的高斯分布
  • p_{\theta }(x\mid z),条件分布,可以用一个神经网络学习(解码器)

但,积分的过程\int _{z}\left ( \cdot \right )存在困难,理论上需要对所有的z精确遍历,行不通!!!

VAE解决该技术难点的想法:虽然不能求解准确的对数似然函数log(p_{\theta }(x)),但可以设法得到对数似然函数的下界,然后最大化其下界(Evidence Lower Bound),相当于近似地令对数似然函数最大化。

3. 技术实现(考虑后验概率分布解出P(X)

 遍历z不行,那么考虑隐变量z的后验分布,能否算出p_{\theta }(x)

p_{\theta }(z\mid x)=\frac{p_{\theta }(x\mid z)p_{\vartheta }(z)}{p_{\vartheta }(x)} (Eq1),可以推导出p_{\theta }(x)但也难以求解!!!

引入一个新的概率分布q_{\phi }(z\mid x) 来逼近后验分布p_{\theta }(z\mid x).

  • 实线表示生成模型p_{\theta }(z)p_{\vartheta }(x\mid z),解码器
  • 虚线表示用q_{\phi }(z\mid x)变分近似难以解析的后验分布p_{\theta }(z\mid x)

如此一来,大致模型结构就定了。

下面解决如何优化的问题,近似对数似然函数

log(p_{\theta }(x^{(i)}))=\mathbb{E}_{z\sim q_{\phi }(z\mid x) }log(p_{\theta }(x^{(i)}))

                        =\mathbb{E}_{z}\left [ log\frac{p_{\theta }(x^{(i)}\mid z)p_{\theta }(z)}{p_{\theta }(z\mid x)} \right ] Eq(1)

                        =\mathbb{E}_{z}\left [ log \left ( \frac{p_{\theta }(x^{(i)}\mid z)p_{\theta }(z)}{p_{\theta }(z\mid x)} \frac{q_{\phi (z\mid x^{(i)})}}{q_{\phi (z\mid x^{(i)})}} \right )\right ]

                        =\mathbb{E}_{z}\left [ log(p_{\theta }(x^{(i)}\mid z)) \right ]-\mathbb{E}_{z}[log(\frac {q_{\phi }(z\mid x^{(i)})}{p_{\theta }(z)})]+\mathbb{E}_{z}\left [ log\left ( \frac{q_{\phi }(z\mid x^{(i)})}{p_{\theta }(z\mid x^{(i)})} \right )\right ]

                        =\mathbb{E}_{z}\left [ log(p_{\theta }(x^{(i)}\mid z)) \right ]-D_{KL}(q_{\phi }(z\mid x^{(i)})\parallel p_{\theta }(z) )+D_{KL}(q_{\phi }(z\mid x^{(i)})\parallel p_{\theta }(z\mid x^{(i)}))

                        \geqslant\mathbb{E}_{z}\left [ log(p_{\theta }(x^{(i)}\mid z)) \right ]-D_{KL}(q_{\phi }(z\mid x^{(i)})\parallel p_{\theta }(z) ) 

                        变分下界Evidence Lower Bound (ELBO)

                        

最大化变分下界,以近似最大化log似然函数。

将变分下界做为损失函数,即可实现VAE核心难点。

公式第二项

  • 本质上,对隐变量分布进行了一个“规划化”
  • D_{KL}(q_{\phi }(z\mid x^{(i)})\parallel p_{\theta }(z) ),计算z的后验分布和隐变量先验分布的KL散度。
    • 假设1:隐变量先验分布p_{\theta }(z)为D维高斯分布N(0,I),由于不包含任何未知参数,重写为p(z)
    • 假设2:隐变量后验分布的近似分布q_{\phi }(z\mid x^{(i)})为各分量独立的高斯分布N(\mu ,\Sigma ;x^{(i)}),即:每个样本对应一个D维高斯分布

实现:使用两个神经网络编码器(\phi)分别求解均值\mu ^{(i)}方差的对数log(\Sigma ^{(i)})(因为方差对数值域为实数,更便于计算)。

  1. \mu (x^{(i)})=enc_{1,\phi }(x^{(i)})
  2. log\sigma ^{2}(x^{(i)})=enc_{2,\phi }(x^{(i)})

第一个编码器输出均值为D维向量\left [ \mu _{1}, \mu _{2},..., \mu _{D} \right ],方差为D维向量\left [ log\sigma_{1}^{2}, log\sigma_{2}^{2},...,log\sigma_{D}^{2}\right ]

如此以来,就可以计算KL散度。本质上,对隐变量分布进行了一个“规划化”,VAE训练编码器希望KL散度达到最小,令后验近似分布趋近于高斯分布。即,每个样本都像高斯分布靠拢。

公式第一项

  • 本质:希望样本重构误差最小
  • 我们使用经验近似, \mathbb{E}_{z}\left [ log(p_{\theta }(x^{(i)}\mid z)) \right ]\approx log(p_{\theta }(x^{(i)}\mid z))。不需要采样很多z来计算log(),只需要从中采样一次,实际效果证明约等于是成立的
  • 下面假设p(x\mid z)的分布
    • 如果是伯努利分布,解码器\rho (z)=dec_{\theta }(z),输出为\left [ \rho _{1},\rho _{2},...,\rho _{Q} \right ],把编码器最后一层激活函数设置为sigmoid,使用二分类交叉熵作为解码器的损失函数
    • 如果是高斯分布,解码器\mu(z)=dec_{\theta }(z),输出为\left [ \mu _{1},\mu _{2},...,\mu _{Q} \right ],把编码器最后一层设置为值域为全体实值得激活函数,使用MSE为损失函数
4. 复盘(overview理解)

过程:样本x输入编码器得到隐变量后验的近似分布的各项参数D维的均值和log方差。再从(学习的)分布中采样z进入解码器,最后计算损失函数(重构损失+隐变量规范化)。

如何解决从分布中采样不可导的问题???

需要把\mu (x^{(i)})\sigma (x^{(i)})与解码器建立联系,使其可以反向传播。令:z=\mu +\varepsilon \times \sigma,直接在标准分布中得到\varepsilon

VAE论文提出了一个重参数技巧,它把从分布中采样的过程改写成从标准高斯分布采样并进行线性变换。这样梯度就可以直接反向传播了。

 5.其他参考资料

1. 首个ICLR时间检验奖出炉!3万被引论文奠定图像生成范式,DALL-E 3/SD背后都靠它

2.《生成对抗网络GAN,原理与实践》言有三

VAE核心代码:

# VAE 模型
class VAE(nn.Module):
    def __init__(self, encoder_layer_size, latent_size, decoder_layer_sizes, conditional=False, num_labels=0):
        super().__init__()
        if conditional:
            assert num_labels>0
        assert type(encoder_layer_sizes)==list
        assert type(latent_size)==int
        assert type(decoder_layer_sizes)==list
        self.latent_size = latent_size
        self.encoder = Encoder(encoder_layer_sizes, latent_size, conditional, num_labels)
        self.decoder = Decoder(decoder_layer_sizes, latent_size, conditional, num_labels)

    def forward(self, x, c=None):
        if x.dim() > 2:
            x = x.view(-1, 28*28)
        means, log_var = self.encoder(x, c)
        z = self.reparameterize(means, log_var)
        recon_x = self.decoder(z, c)
        return recon_x, means, log_var, z

    def reparameterize(self, mu, log_var):
        std = torch.exp(0.5 * log_var)
        eps = torch.randn_like(std)
        return mu + eps*std

    def inference(self, z, c=None):
        recon_x = self.decoder(z, c)
        return recon_x
        
# 编码器

class Encoder(nn.Module):
    def __init__(self, layer_sizes, latent_size, conditional, num_labels):
        super().__init()
        self.conditional = conditional
        if self.conditional:
            layer_sizes[0] += num_labels

        self.MLP = nn.Sequential()

        for i, (in_size, out_size) in enumerate(zip(layer_sizes[:-1], layer_sizes[1:])):
            self.MLP.add_module(name="L{:d}".format(i), module=nn.Linear(in_size, out_size))
            self.MLP.add_module(name="A{:d}".format(i), module=nn.ReLU())

        self.linear_means = nn.Linear(layer_sizes[-1], latent_size)
        self.linear_log_var = nn.Linear(layer_sizes[-1], latent_size)

    def forward(self, x, c=None):
        if self.conditional:
            c = idx2onehot(c, n=10)
            x = torch.cat((x,c), dim=-1)
        x = self.MLP(x)
        means = self.linear_means(x)
        log_vars = self.linear_log_var(x)
        
        return means, log_vars
# 解码器

class Decoder(nn.Module):
    def __init__(self, layer_sizes, latent_size, conditional, num_labels):
        super().__init__()
        self.MLP = nn.Sequential()
        self.conditional = conditional
        if self.conditional:
            input_size = latent_size + num_labels
        else:
            input_size = latent_size

        for i, (in_size, out_size) in enumerate(zip([input_size]+layer_sizes[:-1], layer_sizes)):
            self.MLP.add_module(name="L{:d}".format(i),module=nn.Linear(in_size, out_size))
            if i+1<len(layer_sizes):
                self.MLP.add_module(name="A{:d}".format(i),module=nn.ReLU())
            else:
                self.MLP.add_module(name="Sigmoid", module=nn.Sigmoid())

    def forward(self, z, c):
        if self.conditional:
            c = idx2onehot(c, n=10)
            z = torch.cat((z,c), dim=-1)
        x = self.MLP(z)
        
        return x

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值