跳转至

自编码器1

简介

自编码器最早以一种神经网络出现在2中. 它的主要任务是将输入数据编码为一个低维或者紧凑的表示, 然后再用这部分表示去重构输入数据. 它通常分为两个部分, 编码器和解码器. 编码器将原始数据映射到一个潜在表示(latent representation), 解码器则根据该潜在表示重构出与原始输入尽可能相似的数据.

问题概述

自编码器解决的问题是, 学习函数\(A: \mathbb{R}^n\rightarrow \mathbb{R}^p\)(编码器), 和函数\(B: \mathbb{R}^p\rightarrow \mathbb{R}^n\)(解码器), 满足:

\[ \arg \min_{A,B} \mathbb{E}[\Delta(\mathbf{x}, B \circ A(\mathbf{x}))] \]

其中, \(\Delta\)表示的是重构损失函数, 衡量的是输入和输出之间的距离/差距, 通常使用L2范数(即向量元素平方和的平方根)来衡量这种差异. 通常使用 \(\mathbf{x}\)是编码器的输入. \(\mathbf{x}\)表示的是某个输入样本(例如一张图片)的输入向量\(\mathbf{x}=\{x_1, x_2, ...\}\), \(x_1, x_2, ...\)这些是像素值. 中间的小圆点\(\circ\)表示的是函数复合, \(B\circ A(\mathbf{x})\)就是先对输入\(\mathbf{x}\)施加函数\(A\), 然后再将结果输入到函数\(B\)中, 如图所示.

一张图片对应多少个隐向量

注意, 在AE中, 每张图片只对应一个隐向量.

线性自编码器

最流行的自编码器形式中, \(A\)\(B\)是神经网络. 当自编码器中的操作(即编码器和解码器的变换)都是线性的时候, 即没有任何非线性激活函数的时候, 这样的自编码器被称为线性自编码器, 在这种情况下, 它等价于主成分分析(PCA), 因此, 可以认为自编码器是PCA的一种更通用的形式, PCA会找到一个数据分布的低维超平面, 而自编码器会找到一个低维非线性流形.

自编码器训练方式

两种常见的自编码器训练方式为: 端到端训练, 即直接将整个网络架构连接起来, 同时训练所有层的参数; 分层逐步训练, 也就是将多个自编码器依次"堆叠"起来, 这种做法能够让训练从浅层网络开始, 先学到较低级的特征, 然后再把该编码器的编码层拿来作为下一层自编码器的输入, 逐层加深整个网络. 堆叠方式可以应用在不同类型的自编码器上, 如卷积自编码器, 降噪自编码器(denoising ae).


本章由下列内容组成, 在第二小节中, 我们将会讲解自编码器的不同正则化技术, 确保学习到的压缩表示是有意义的. 在第三小节中, 我们会介绍变分自动编码器(VAE), 它被认为是目前最流行的自动编码器形式.

正则化自编码器

需要解决的问题

在实际训练中, 如果没有任何约束或者正则化, 网络往往可能学到恒等映射\(A\)\(B\), 即直接把输入复制到输出. 为了应对这个问题, 最常见的方法是在网络结构中添加一种瓶颈结构(bottleneck), 即让中间隐藏层的维度比输入的维度更小, 从而迫使网络只能用更少的参数表示输入信息, 这种方法不仅能直接压缩数据, 还能起到提取数据主要特征的作用, 为后续的任务(例如数据可视化, 特征提取等)提供简化的表示.

瓶颈结构

不过, 即使我们把隐藏层的维度设置得非常小(哪怕只有一个神经元), 如果编码器和解码器的capacity(网络深度, 宽度, 参数量等)足够大, 依然可能出现过拟合, 网络可能学会为每个训练样本都分配一个唯一的索引, 本质上仍旧是把输入完整保留下来, 从而记住了训练集的细节, 而没有学到任何通用或者抽象化的表示.

为什么瓶颈结构仍有可能过拟合

假设我们由一个很小的数据集, 只有10条样本, 我们设计了一个自编码器, 编码器的输出层只有一个神经元, 然而, 如果编码器和解码器的结构足够复杂, 参数量足够大, 编码器可以把第一条样本映射到某个独特的数值\(a_1\), 第二条样本映射到另一个数值\(a_2\), ..., 第十条样本映射到\(a_{10}\), 这些数值之间两两之间足够区分, 这种, 解码器只要学会一个几乎是"查表"的映射, 当输入\(a_1\)的时候就输出第一条样本的重构结果, 当输入\(a_2\)的时候就输出第二条样本的重构结果, 以此类推, 这本质上相当于给每个样本分配了一个索引. 这说明网络只是死记硬背了所有样本, 并没有学习到任何对新样本也适用的通用表示.

新的策略

如果隐藏层的维度和输入层相同甚至更高, 网络学习到恒等映射的可能性就越大. 这个时候, 如果我们依然想获得有用的表示, 就需要在不"缩小"隐藏层的情况下(即不构建瓶颈结构)进行其他形式的正则化策略使得自编码器能够学到输入的 不同的表示.

在选择自编码器正则化方式的时候, 需要在以下偏差-方差上面做一个trade-off. 在一方面, 我们希望自编码器的结构能够很好的重构输入, 即减小重构偏差; 在另一个方面, 我们希望低维的表示能够很好的泛化, 即减小方差, 模型产生的效果在不同的数据集之间没有很大的波动.

稀疏自编码器

其中一种进行正则化的方法是使用稀疏自编码器. 这种自编码器会迫使隐藏层的激活值稀疏化. 可以在瓶颈结构的基础上稀疏化, 也可以不使用瓶颈结构, 只对激活值稀疏化. 稀疏化的方法进一步分为两种.

L1正则化

其中一种实现稀疏化的方式是在隐藏层的 激活值 上施加L1正则化. 普通的L1正则化通常是作用在网络的权重上, 以限制模型的复杂度. 这里, 我们把L1正则项放在隐藏单元的激活值上, 这比仅仅对权重施加L1更能直接地控制输出稀疏性. 现在, 自编码器损失函数可以改写为:

\[ \arg\min_{A,B} \; \mathbb{E}\bigl[\Delta(\mathbf{x},\, B \circ A(\mathbf{x}))\bigr] \;+\; \lambda \sum_{i} \lvert a_i \rvert \]

其中, \(a_i\)是第\(i\)层隐藏层的所有激活值.

激活值施加L1正则化和Dropout的区别

在思想上, 激活值上使用L1正则化听起来和Dropout有一些区别, 但是两者并不是等价的. Dropout是在训练的时候 随机将部分神经元的输出置零, 其他神经元的输出保持不变 , 而对激活值的L1正则化是让网络 对所有激活值都施加同样力度的惩罚, 这会使得不必要的激活值趋近于0.

KL散度

另一种实现稀疏化的方法是KL散度. 这种方法不会去调试L1正则化损失函数里面的那个\(\lambda\), 从而改变神经元的激活概率, 而是尝试让网络自动调节神经元的激活概率, 注意, 这两种方法不同神经元之间的激活概率都是一致的, 即同等力度.

将神经元的激活视作一个伯努利随机变量, 并且为它设定一个理想的激活概率\(p\). 在每个批次中, 我们先根据实际数据计算每个神经元的激活概率, 得到一个实际观测到的分布, 然后把这两个分布(理想的伯努利分布Bernoulli(p)分布和实际观测到的分布)之间的KL散度加到损失函数里面, 用来惩罚偏离理想激活概率的情况. 注意, 对于所有神经元来说, 其理想的激活概率都是\(p\).

根据实际数据计算每个神经元的激活概率: 假设每个batch有\(m\)个样本, 对于神经元\(j\), 其实际的经验激活概率为\(\hat{p}_j=\frac{1}{m}\sum_{i=1}^m a_{ij}\), 其中, \(a_{ij}\)表示第\(i\)个样本/张图片在神经元\(j\)的激活值, 这样, 我们就会得到实际观测的一个分布, \(\hat{p}_1, \hat{p}_2, \hat{p}_3, ...\). 然后我们会比较这个分布和\(p, p, p, ...\)这个分布的差距, 计算出KL散度. 现在, 自编码器优化的目标可以改写为:

\[ \arg \min_{A,B} \mathbb{E}[\Delta(\mathbf{x}, B \circ A(\mathbf{x})) + \sum_j KL(p||\hat{p}_j)] \]

去噪自编码器

去噪自编码器是另一种实现正则化的方法. 其核心思路是在输入层人为地添加噪声, 然后让网络学习从噪声数据中恢复出干净地输入. 这样做有两个好处: 1. 作为一种正则化手段, 给输入加噪声可以避免网络过拟合, 从而使模型具有更好的泛化能力; 2. 将其视为一种鲁棒的自编码器, 可以帮助模型纠正输入中的错误, 在面对带有缺失或者噪声的数据的时候依然能够输出更为准确的重构结果. 其过程如下图所示.

在具体的实现中, 常见的加噪方式包括添加高斯白噪声或者利用Dropout模拟输入缺失, 或者使用token掩码/片段掩码? 然后模型学习如何从这些带噪数据中重构原始输入.

给定一个原始输入\(\mathbf{x}\), 在训练去噪自编码器的时候, 我们会从一个分布\(C(\tilde{\mathbf{x}}|\mathbf{x})\)中采样得到带有噪声或者缺失的样本\(\tilde{\mathbf{x}}\), 作为网络的输入, 从而让模型学习如何在有干扰的情况下仍能重构出接近原始\(\mathbf{x}\)的输出. 两种常见\(C(\tilde{\mathbf{x}}|\mathbf{x})\)分布的形式为:

  1. $ C_sigma(tilde{mathbf{x}} | mathbf{x}) = mathcal{N}(mathbf{x}, sigma^2 mathcal{I}) $ 这是基于添加高斯噪声的做法, 表示我们对每个特征/像素维度都加上来自均值为\(\mathbf{x}\), 协方差为\(\sigma^2\mathcal{I}\)的高斯噪声. 这里的\(\sigma\)控制的是噪声的强度, \(\sigma\)越大, 加到\(\mathbf{x}\)上的扰动就越大.

  2. $ C_p(tilde{mathbf{x}} | mathbf{x}) = beta odot mathbf{x}, quad beta sim Ber(p) $ 这是基于擦除或者Dropout的做法, 其中\(\beta\)个每个元素独立地服从伯努利分布\(Ber(p)\), \(\beta_i\in \{0, 1\}\), \(\odot\)表示逐元素(Hadamard)乘积, 当\(\beta_i=1\)地时候, 第\(i\)个特征/像素保留原值; 当\(\beta_i=0\)的时候, 第\(i\)个特征/像素被置为\(0\), 这就相当于以概率\(1-p\)将某些元素"擦除"掉, 只保留其余元素.

在文献3中, 进一步论证了对输入进行Dropout的做法和模拟编码领域"擦除信道"问题的关系.

收缩自编码器

收缩自编码器是实现正则化的又一种方法. 它和去噪自编码器不同, 去噪编码器是主动加入噪声, 收缩自编码器是使隐藏层的输出对输入的微小扰动不太敏感, 这种扰动对于重构过程是不重要的. 它通过Jacobian矩阵施加惩罚.

隐藏层\(h\)的某个神经元\(j\)的输出\(h_j\)对某个像素\(x_i\)的偏导数为Jacobian矩阵元素\(J_{ji}=\Delta_{x_i}h_j(x_i)\). 这个值越大, 说明该隐藏层神经元的输出对该像素变化越敏感.

如果我们在损失函数中添加一个L2范数施加惩罚, 即\(||J_A(\mathbf{x})||_2^2\). 迫使隐藏层输出对输入微小的变化收缩(contract), 变得不易受到干扰, 其损失函数可以表示为:

\[\arg\min_{A,B} \mathbb{E}[\Delta(\mathbf{x}, B \circ A(\mathbf{x}))] + \lambda\|J_A(\mathbf{x})\|_2^2\]

这种做法实际上会导致一个权衡, 如果Jacobian矩阵的L2范数的重要性过大, 即\(\lambda\)较大, 那么所有对输入的表示可能会变得非常相似, 导致重构过程变得困难; 如果Jacobian矩阵的L2范数的重要性过小, 即\(\lambda\)较小, 那么隐藏层会对输入变化高度敏感, 有助于重构, 但是也更加容易拟合.

所以, 最佳的情况是, 对不影响重构的细微扰动, 用惩罚项把它缩小或者忽略, 使得表示更加鲁棒, 平滑; 对于那些确实影响重构的差异, 则会在重构误差的推动下保留下来.


  1. Bank, D., Koenigstein, N., & Giryes, R. (2021). Autoencoders (No. arXiv:2003.05991). arXiv. https://doi.org/10.48550/arXiv.2003.05991 

  2. Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning internal representations by error propagation. 收入 Parallel distributed processing: Explorations in the microstructure of cognition, vol. 1: Foundations (页 318–362). MIT Press. 

  3. Bank, D., & Giryes, R. (2020). An ETF view of dropout regularization (No. arXiv:1810.06049). arXiv. https://doi.org/10.48550/arXiv.1810.06049 

评论