当前位置: 代码迷 >> 综合 >> [论文笔记] [2008] [ICML] Extracting and Composing Robust Features with Denoising Autoencoders
  详细解决方案

[论文笔记] [2008] [ICML] Extracting and Composing Robust Features with Denoising Autoencoders

热度:29   发布时间:2023-11-18 04:29:48.0

在06年以前,想要去训练一个多层的神经网络是比较困难的,主要的问题是超过两层的模型,当时没有好的策略或方法使模型优化的很好,得不到预期的效果。在06年,Hinton提出的stacked autoencoders 改变了当时的情况,那时候的研究者就开始关注各种自编码模型以及相应的堆叠模型。这篇的作者提出的DAE(Denoising Autoencoders)就是当时蛮有影响力的工作。

那个时候多层模型效果得到提升的一个关键的因素就是采用像自编码器这类无监督训练方式做逐层的预训练(layer-wise pre-training),然后后接分类器做 global fine-tuning。而作者提出的去噪自编码器通过给原始输入加入噪音作为编码器的输入,解码器重构出原始输入 的方式改进了原始自编码器。

Methods

自编码器作为深度学习模型进行表示学习的典型方法,它的思路非常简单,就是将输入映射到某个特征空间,再从这个特征空间映射回输入空间进行重构。从结构上看,它由编码器解码器组成,编码器用于从输入数据中提取特征,解码器用于基于提取的特征重构输入数据。在训练完成后,使用编码器进行特征提取。这种编码器-解码器的思想在许多深度学习模型中都有体现。

这种原始的自编码器能力是受限的,在满足 d′<dd' < dd<d,模型提取的特征效果比较好,如果放宽限制,允许d′≥dd' \geq ddd,除此之外如果不加任何限制,有可能学习不到任何有用的信息。其本质原因不是维度约束的变化,而是当我们赋予编码器和解码器过于强大的“能力”时,自编码器便倾向于直接将输入拷贝到输出,而不会从数据中提取到有用的特征。

那么考虑一个问题,好的特征应该满足哪些标准?上面也提到了中间表示(intermediate representation)的维度限制能确保比较好的性能,但这个限制又不能太强,那样会造成原信息的损失,那么第一个标准就是保证不损失原输入信息的限制表示

第二点就是表示的稀疏性(可以由稀疏自编码器实现)。这里值得注意的是,这里实现的稀疏性不是由像L1这类正则项产生的稀疏解,而是模型提取出的特征是稀疏的,但如果非线性变换用的是ReLU这类激活函数,相应的特征也是稀疏的。

这时,作者提出一个标准,即对输入的部分"破坏"具有鲁棒性(robustness to partial destruction of the input),换句话说就是对于扰动的输入能得到相同的 representation。他的这么一个想法来自于:我们人类能够识别受损或遮掩的图像(A hallmark of this is our human ability to recognize partially occluded or corrupted images)。这也就意味着一个好的 representation 应该捕捉稳定的结构,而这种结构就是依赖关系和原始输入分布的规律性特征,尽管输入部分"破坏"(未破坏其规律特征的情况下),依旧能提取出好的表示。

The Denoising Autoencoder

基于这个想法,那就考虑如何能使学到的特征尽可能的鲁棒。一个很自然的想法就是在训练时,"破坏"部分输入,这样自编码器就不是从原始输入中重构出原始输入,而是从被"破坏"的原始输入中重构出原始输入,而这中间学习到的特征就具备了捕捉稳定结构的能力。其具体的数学定义如下:

原始输入为 x∈Rdx \in \mathbb{R}^dxRd,设定一个"破坏"比例 vvv,即输入的xxx 向量中,将随机vdvdvd 个位置的数将被"破坏"为0,得到被"破坏"的输入 x~\tilde{x}x~,那么编码阶段有 y=fθ(x~)=s(Wx~+b)y = f_{\theta}(\tilde{x})=s(W\tilde{x} + b)y=fθ?(x~)=s(Wx~+b),其中 s(?)s(\cdot)s(?) 为sigmoid激活函数;解码阶段有 z=gθ′(y)=s(W′y+b′)z = g_{\theta'}(y) = s(W'y + b')z=gθ?(y)=s(Wy+b),那么损失函数为:
L=1N∑i=1∥x(i)?gθ′(fθ(x~(i)))∥L = \frac{1}{N} \sum_{i=1}{\|x^{(i)} - g_{\theta'}(f_{\theta}(\tilde{x}^{(i)}))\|} L=N1?i=1?x(i)?gθ?(fθ?(x~(i)))
其过程可见下图。这种mask的思想就类似于dropout,也都起到正则的作用,但他们还是有区别的:1) 去噪自编码器mask掉的是部分输入,而dropout mask掉的是隐藏层单元;2) dropout在 pre-training时候是不参与的,只在 fine-tuning 的时候参与,而去噪自编码器是在逐层预训练阶段参与。

在这里插入图片描述

Layer-wise Initialization and Fine Tuning

这里的stacked autoencoder(SAE)就是常规的方式,可见下图。单个自编码器实现了 x→y→xx \rightarrow y \rightarrow xxyx 的变换,而在实现 SAE 时,只需要编码部分,即 x~→h1\tilde{x} \rightarrow h_1x~h1?,再训练下一个自编码器 h1→h2→h1h_1 \rightarrow h_2 \rightarrow h_1h1?h2?h1?,得到 h1→h2h_1 \rightarrow h_2h1?h2?的变换,最终得到 SAE。这种每一层都训练一个自编码器,并取编码部分的过程就叫做 layer-wise unsuperwised pre-training(逐层无监督预训练)。在得到SAE后,即完成了初始化,在整个模型后面后接一个分类器,对整体模型进行fine-tuning。

为什么逐层预训练使得深层网络的训练成为可能? 这个问题一个比较直观的解释是采用逐层预训练,使得每一层的参数初始值较之随机参数初始化,已经处于一个局部的最小值,这也就使得后面的监督学习的模型能较快的收敛。

在这里插入图片描述
对于单个自编码器的代码实现[3]如下:

class dA(nn.Module):def __init__(self, in_features, out_features):super(dA, self).__init__()self.encoder = nn.Sequential(nn.Linear(in_features, out_features),nn.ReLU())self.decoder = nn.Sequential(nn.Linear(out_features, in_features),nn.ReLU())self.decoder[0].weight.data = self.encoder[0].weight.data.transpose(0, 1)def forward(self, x):h = self.encoder(x)return self.decoder(h)

encoder 和 decoder 的实现比较简单,其中有个将编码器的权值的转置赋值给解码器的权值是去实现论文中提到的"tied weight",是对编码器做了一个限制。

而SAE实现如下:

class SdA(nn.Module):def __init__(self, config):super(SdA, self).__init__()# ---------------- A -----------------layers = []in_features = config.input_featuresfor out_features in config.hidden_features:layer = dA(in_features, out_features)in_features = out_featureslayers.append(layer)layers.append(nn.Linear(in_features, config.classes))self.layers = nn.Sequential(*layers)# ---------------- A -----------------# ---------------- B -----------------if config.is_train:self.mse_criterion = nn.MSELoss()self.ce_criterion = nn.CrossEntropyLoss()# ---------------- B1 -----------------self.da_optimizers = []for layer in self.layers[:-1]:optimizer = optim.SGD(layer.parameters(), lr=config.lr,momentum=config.momentum, weight_decay=config.weight_decay)self.da_optimizers.append(optimizer)# ---------------- B1 -----------------# ---------------- B2 -----------------sda_params = []for layer in self.layers[:-1]:sda_params.extend(layer.encoder.parameters())sda_params.extend(self.layers[-1].parameters())self.sda_optimizer = optim.SGD(sda_params, lr=config.lr,momentum=config.momentum, weight_decay=config.weight_decay)# ---------------- B2 -----------------# ---------------- B -----------------def forward(self, x):h = xfor layer in self.layers[:-1]:h = layer.encoder(h)return self.layers[-1](h)

其中A部分定义SAE中的自编码器,采用循环的方式,上一个自编码器输出的特征数就是当前自编码器输入的特征数,并在最后一层定义了一个分类器。

B1部分定义SAE中各个自编码器的优化器。这里需要注意的是,自编码器训练和整体模型训练的损失函数是不一样的,自编码器训练时的损失函数是MSE,而整个模型训练时交差熵。在B2部分定义整体模型的权值,即自编码器中encoder部分的参数。

在前向传播时,即只有自编码器encoder部分对输入做变换,输出到下一个自编码器encoder部分做变换,最后经过分类器输出。

总结

目前对生成模型那方面没接触,不理解从生成模型角度看 autoencoder。另外,autoencoder也有工作拓展到图表示学习上,还没去研究过。论文中提到的逐层预训练的方式在这些年都没见到了,因为现在一些初始化的策略,以及dropout方法的提出,ReLU等激活函数,使得训练一个深层的神经网络已经不是问题,但预训练的故事还将继续。

参考文献

[1] 为什么稀疏自编码器很少见到多层的, https://www.zhihu.com/question/41490383/answer/103006793
[2] [自编码器:理论+代码]:自编码器、栈式自编码器、欠完备自编码器、稀疏自编码器、去噪自编码器、卷积自编码器, https://blog.csdn.net/quiet_girl/article/details/84401029
[3] A PyTorch Implementation of “Extracting and Composing Robust Features with Denoising Autoencoders”, https://github.com/shadow2496/KAIST_2019_Deep-Learning_HW3

  相关解决方案