找回密码
 会员注册
查看: 34|回复: 0

自编码器(autoencoder)

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64101
发表于 2024-9-13 09:25:04 | 显示全部楼层 |阅读模式
1.自编码器的由来最初的自编码器是用来降维的,后来也逐渐用于去噪、生成任务。2.自编码器的基本结构自编码器(autoencoder)内部有一个隐藏层h,可以产生编码(code)表示输入。该网络可以看作由两部分组成:一个由函数h=f(x)h=f(x)h=f(x)表示的编码器和一个生成重构的解码器r=g(h)r=g(h)r=g(h),整体结构如下图所示:自编码器通过内部表示或编码h将输入x映射到输出(称为重构)r。自编码器具有两个组件:编码器f(将x映射到h)和解码器g(将h映射到r)。映射关系可以分别表示为pencoder(h∣x)p_{encoder}(h|x)pencoder​(h∣x)、pdecoder(r∣h)p_{decoder}(r|h)pdecoder​(r∣h)。3.自编码器的一些基本概念3.1欠完备与过完备①欠完备自编码器:h维度=x维度。过完备则与欠完备相反。“过完备”意味着编码器的表示空间非常大,能够容纳甚至超过输入数据中的所有信息。举一个简单的例子:假设你有一堆猫和狗的图片。一个过完备自编码器可能会记住每张图片的所有细节,包括背景、毛色、姿态等等。这使得它在训练数据上表现非常好,但在遇到新的猫狗图片时,可能无法很好地识别出它们的共同特征。在过完备的情况下,可能会出现编码器无法学习到有效信息的情况。这是因为:当编码维数与输入维数相等或更大时,自编码器的表示空间非常大,足以容纳输入数据中的所有信息。在这种情况下,甚至简单的线性编码器和解码器也可以直接将输入复制到输出,而不需要提取任何有用的特征。这意味着自编码器没有被迫去学习数据的分布特征,因为它可以简单地记住所有的数据。这种记忆机制使得模型在训练数据上表现很好,但在面对新数据时可能表现很差,因为它没有学到数据的内在模式或结构。3.2自编码器的目的与损失函数自编码器的目的是得到编码后的有效表示h,而这种目的是通过尝试将网络输入复制到输出来实现的。所以自编码器最简单的损失函数如下:loss=L(x,g(f(x)))loss=L(x,g(f(x)))loss=L(x,g(f(x))),其中L就表示一个函数,如L2范数。3.3自编码器的正则化3.2中提到自编码器是通过输入输出的loss来达到有效表示h的目的,而当过完备时,编码器又可能无法学习到有效的h。所以有了自编码器的正则化,使得过完备时网络仍能得到有效的h。举个例子:比如在稀疏自编码器中,损失函数修改为L(x,g(f(x)))+Ω(h)L(x,g(f(x)))+Ω(h)L(x,g(f(x)))+Ω(h),针对h稀疏特性而增加了Ω(h)Ω(h)Ω(h)部分。虽然引入稀疏性惩罚项Ω(ℎ)会导致稀疏自编码器的复制动作变差,即重构误差可能增加,但这种修改能够迫使模型学习到更加有用的特征,提高模型在后续任务中的性能和泛化能力。这种权衡在实际应用中通常是有益的。4.多种自编码器自编码器可以分为以下几种:下面介绍几种常见的自编码器。4.1传统自编码器(AE)AE的网络结构如下:包含三层——输入层、隐藏层、输出层,每一层都是由若干个神经元组成的。上面的网络可以表示为①编码:输入层+隐藏层h=f(x)=f(W1X+b1)h=f(x)=f(W_1X+b_1)h=f(x)=f(W1​X+b1​)。W1W_1W1​表示神经元的权重,b1b_1b1​表示神经元的偏置。②解码:隐藏层+输出层Xd=g(x)=g(W2h+b2)X^d=g(x)=g(W_2h+b_2)Xd=g(x)=g(W2​h+b2​)。③损失函数用θ\thetaθ表示网络的权重和偏置参数,则损失函数为:JAE(θ)=J(X,Xd)=−∑i=1n(xilog⁡(xid)+(1−xi)log⁡(1−xid))J_{AE}(\theta)=J(X,X^{d})=-\sum_{i=1}^{n}(x_{i}\log(x_{i}^{d})+(1-x_{i})\log(1-x_{i}^{d}))JAE​(θ)=J(X,Xd)=−∑i=1n​(xi​log(xid​)+(1−xi​)log(1−xid​))采用梯度下降法即可进行训练。此外,为了控制权重降低的程度,防止自编码器的过拟合,将在上述损失函数中加入正则化项(也称重量衰减项),变为正则化自编码器。JReAE(θ)=J(X,Xd)+λ∥W∥22J_{\mathrm{ReAE}}(\theta)=J(X,X^{d})+\lambda\parallelW\parallel_{2}^{2}JReAE​(θ)=J(X,Xd)+λ∥W∥22​通过约束网络权重从而间接使得隐藏层神经元稀疏,提高了整个自编码器模型的泛化性。4.2去噪自编码器(DAE)DAE的网络结构如下,AE的目的是求h,但它没有使用h的真实值来训练,所以是无监督的。而DAE的目的是使得网络能够进行去噪,目的是求X,但它用到了X真实值做loss,所以他是监督学习。DAE的动机是主动给X加噪,使得网络带有去噪的能力。但是在每次网络训练之前,人为地在干净的输入信号中加入噪声,增加了模型的处理时间。而且,如果加入过多的噪声,会导致输入样本的严重失真,从而降低算法的性能。4.3稀疏自编码器(SAE)稀疏自编码器利用了X的先验信息,这个先验信息就是X的稀疏度。它的网络结构和AE没有什么区别,但是损失函数变了,添加了一项KL散度,是编码后h的稀疏度和真实稀疏度之间的散度。其中β\betaβ是控制稀疏惩罚的系数,为0~1.JSAE(θ)=J(X,Xd)+β∑j=1tKL(ρ∥ρ^j)J_{SAE}(\theta)=J(X,X^d)+\beta\sum_{j=1}^tKL(\rho\parallel\hat{\rho}_j)JSAE​(θ)=J(X,Xd)+β∑j=1t​KL(ρ∥ρ^​j​)首先定义每个隐藏单元jjj的平均激活值ρ^j:\hat{\rho}_j:ρ^​j​:ρ^j=1n∑i=1nhj(xi)\hat{\rho}_j=\frac{1}{n}\sum_{i=1}^nh_j{(x_i)}ρ^​j​=n1​i=1∑n​hj​(xi​)其中,nnn是训练样本数量,hj(xi)h_j{(x_i)}hj​(xi​)是第iii个样本对于隐藏单元jjj的激活值。然后定义目标稀疏度ρ\rhoρ,这是希望隐藏单元的平均激活值。例如,如果ρ\rhoρ较小(如0.05),则希望大多数隐藏单元在任何给定时间都不活跃。最后,将稀疏惩罚项加入到损失函数中,使用KL散度来衡量目标稀疏度和实际稀疏度之间的差异:KL(ρ∣∣ρ^j)=ρlog⁡ρρ^j+(1−ρ)log⁡1−ρ1−ρ^j\mathrm{KL}(\rho||\hat{\rho}_j)=\rho\log\frac{\rho}{\hat{\rho}_j}+(1-\rho)\log\frac{1-\rho}{1-\hat{\rho}_j}KL(ρ∣∣ρ^​j​)=ρlogρ^​j​ρ​+(1−ρ)log1−ρ^​j​1−ρ​稀疏惩罚项的总和是所有隐藏单元的KL散度之和:∑j=1tKL(ρ∣∣ρj^)\sum_{j=1}^{t}\mathrm{KL}(\rho||\hat{\rho_j})j=1∑t​KL(ρ∣∣ρj​^​)KL散度是描述两个分布之间差异的指标,KL散度越小,分布越接近,具体公式如下:KL(ρ∥ρ^j)=ρlog⁡ρρ^j+(1−ρ)log⁡1−ρ1−ρ^jKL(\rho\parallel\hat{\rho}_j)=\rho\log\frac{\rho}{\hat{\rho}_j}+(1-\rho)\log\frac{1-\rho}{1-\hat{\rho}_j}KL(ρ∥ρ^​j​)=ρlogρ^​j​ρ​+(1−ρ)log1−ρ^​j​1−ρ​4.4收缩自编码器(CAE)这里的收缩指的是在学习过程中对隐藏层表示进行收缩,使得隐藏层表示对输入数据的小变化不敏感,从而增强模型的鲁棒性和特征提取能力。使得隐藏层表示对输入数据的小变化不敏感?也就是说X变化,h不变或变化很小,如果这种关系用导数关系来表示,h对X的一阶导应该越小越好,所以有如下损失函数,同样通过系数λ\lambdaλ来控制收缩。JCAE(θ)=J(X,Xd)+λ∥Jf(x)∥F2J_{CAE}(\theta)=J(X,X^d)+\lambda\parallelJ_f(x)\parallel_F^2JCAE​(θ)=J(X,Xd)+λ∥Jf​(x)∥F2​其中,∥Jf(x)∥F2=∑j=1t∑i=1n(∂hj(x)∂xi)2\parallelJ_f(x)\parallel_F^2=\sum_{j=1}^t\sum_{i=1}^n(\frac{\partialh_j(x)}{\partialx_i})^2∥Jf​(x)∥F2​=∑j=1t​∑i=1n​(∂xi​∂hj​(x)​)2,Jf(x)J_f(x)Jf​(x)是雅可比矩阵(JacobianMatrix),雅可比矩阵是向量值函数对其输入向量的偏导数组成的矩阵。Tips:①这里可以看到,如果CAE的编码是线性的,那么CAE就和正则化自编码器没有区别了,因为线性的一阶导就是权重参数。②CAE和DAE都对带噪数据有鲁棒性,CAE是对提取的特征有鲁棒性;DAE是对输出的去噪数据有鲁棒性。4.5卷积自编码器(CoAE)卷积自编码器使用卷积层和池化层处理二维或三维数据,保留空间结构并减少参数数量,而普通自编码器使用全连接层处理一维向量数据,参数数量较多且不保留数据的空间结构。二维空间结构信息对图像来说十分重要,用AE时,需要把图像变为一维向量输入网络,这就破坏了图像的二维空间信息。从上图可以看出,网络的输入输出都是二维的图像,其损失函数如下:JCoAE(θ)=J(X,Xd)+λ∥W∥22J_{CoAE}(\theta)=J(X,X^d)+\lambda\parallelW\parallel_2^2JCoAE​(θ)=J(X,Xd)+λ∥W∥22​4.6变分自编码器(VAE)关于变分自编码器,知乎上的这篇文章写的很好。首先说明变分自编码器的总体意义:VAE是在自编码器基础上结合了变分贝叶斯推断的方法,旨在学习数据的隐含结构,并能够生成新的、类似于训练数据的样本。可以说VAE的主要目的是生成新的数据。VAE通过显式地建模潜在变量的概率分布,使得潜在空间结构更加明确和可解释,最终能生成新样本。这在生成对抗网络(GAN)出现之前是一个重要的进展。下面介绍VAE的主要做法:VAE的整体网络结构简图如下:针对一个输入xix_ixi​(比如一个样本就是一张图像),网络结构如下(下图中的μi′\mu_{i}^{\prime}μi′​就是输出的XdX^dXd):具体公式推导参考上面的那篇博客,下面是对博客推导思想的简单总结:为了更好理解,还是直接博客的代码吧:#-*-coding:utf-8-*-"""CreatedonJanuary28,2021@author:SiqiMiao"""importosfromtqdmimporttqdmos.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"importtorchimporttorch.nnasnnfromtorchvisionimporttransformsfromtorchvision.utilsimportsave_imagefromtorchvision.datasetsimportMNISTclassVAE(nn.Module):def__init__(self,in_features,latent_size,y_size=0):super(VAE,self).__init__()self.latent_size=latent_sizeself.encoder_forward=nn.Sequential(nn.Linear(in_features+y_size,in_features),nn.LeakyReLU(),nn.Linear(in_features,in_features),nn.LeakyReLU(),nn.Linear(in_features,self.latent_size*2)#latent_size表示潜变量的个数,每一个变量有均值和方差两个数值)self.decoder_forward=nn.Sequential(nn.Linear(self.latent_size+y_size,in_features),#解码器输入的时候只需要输入编码器输出的潜在变量的均值nn.LeakyReLU(),nn.Linear(in_features,in_features),nn.LeakyReLU(),nn.Linear(in_features,in_features),nn.Sigmoid())defencoder(self,X)ut=self.encoder_forward(X)mu=out[:,:self.latent_size]#前latent_size个数据是均值log_var=out[:,self.latent_size:]#后latent_size个数据是log(方差)returnmu,log_vardefdecoder(self,z):mu_prime=self.decoder_forward(z)returnmu_primedefreparameterization(self,mu,log_var):#ReparameterizationTrickepsilon=torch.randn_like(log_var)#产生和log_var维度一样的高斯分布数据z=mu+epsilon*torch.sqrt(log_var.exp())#log_var.exp()=var,sqrt(var)就是sigemareturnzdefloss(self,X,mu_prime,mu,log_var):#mu_prime编码器的输出;mu潜变量的均值,也就是潜变量的值了;log_var潜变量的log(方差)#reconstruction_loss=F.mse_loss(mu_prime,X,reduction='mean')iswrong!#print(mu_prime.shape)#[1024,784]#print(mu.shape)#[1024,64]#print(log_var.shape)#[1024,64]#torch.square是一个用于计算张量中每个元素的平方的函数。这个函数返回一个新的张量,其中包含原始张量中每个元素的平方值reconstruction_loss=torch.mean(torch.square(X-mu_prime).sum(dim=1))#sum(dim=1)表示对列求和,torch.mean就相当于是对1024个样本求均值了,也就是公式里的1/nlatent_loss=torch.mean(0.5*(log_var.exp()+torch.square(mu)-log_var).sum(dim=1))#sum(dim=1)表示对潜变量求和,torch.mean相当于是对1024个样本求均值returnreconstruction_loss+latent_lossdefforward(self,X,*args,**kwargs):mu,log_var=self.encoder(X)z=self.reparameterization(mu,log_var)mu_prime=self.decoder(z)returnmu_prime,mu,log_varclassCVAE(VAE):#继承VAE类,所以可以使用VAE的编码和解码器def__init__(self,in_features,latent_size,y_size):super(CVAE,self).__init__(in_features,latent_size,y_size)defforward(self,X,y=None,*args,**kwargs):y=y.to(next(self.parameters()).device)#print(y.shape)#[1024]X_given_Y=torch.cat((X,y.unsqueeze(1)),dim=1)#print(X_given_Y.shape)#[1024,785]mu,log_var=self.encoder(X_given_Y)z=self.reparameterization(mu,log_var)z_given_Y=torch.cat((z,y.unsqueeze(1)),dim=1)mu_prime_given_Y=self.decoder(z_given_Y)returnmu_prime_given_Y,mu,log_vardeftrain(model,optimizer,data_loader,device,name='VAE'):model.train()total_loss=0pbar=tqdm(data_loader)forX,yinpbar:#print(X.shape)#[1024,1,28,28]#print(y.shape)#[1024]batch_size=X.shape[0]X=X.view(batch_size,-1).to(device)#print(X.shape)#[1024,784]model.zero_grad()#将模型中所有参数的梯度缓存清零。在进行反向传播计算梯度之前,必须先将之前计算的梯度清零。这是因为在PyTorch中,梯度是累积的。ifname=='VAE':mu_prime,mu,log_var=model(X)else:mu_prime,mu,log_var=model(X,y)loss=model.loss(X.view(batch_size,-1),mu_prime,mu,log_var)loss.backward()optimizer.step()total_loss+=loss.item()pbar.set_description('Loss:{loss:.4f}'.format(loss=loss.item()))returntotal_loss/len(data_loader)@torch.no_grad()defsave_res(vae,cvae,data,latent_size,device):num_classes=len(data.classes)#rawsamplesfromdatasetout=[]#用于存储每个类别的图像foriinrange(num_classes):#提取类别为i的图像img=data.data[torch.where(data.targets==i)[0][:num_classes]]out.append(img)out=torch.stack(out).transpose(0,1).reshape(-1,1,28,28)#将图像堆叠在一起,并转置维度以便于保存save_image(out.float(),'./img/raw_samples.png',nrow=num_classes,normalize=True)#100张图像#samplesgeneratedbyvanillaVAEz=torch.randn(num_classes**2,latent_size).to(device)#print(z.shape)#[100,64]out=vae.decoder(z)save_image(out.view(-1,1,28,28),'./img/vae_samples.png',nrow=num_classes)#samplegeneratedbyCVAEz=torch.randn(num_classes**2,latent_size).to(device)y=torch.arange(num_classes).repeat(num_classes).to(device)z_given_Y=torch.cat((z,y.unsqueeze(1)),dim=1)out=cvae.decoder(z_given_Y)save_image(out.view(-1,1,28,28),'./img/cvae_samples.png',nrow=num_classes)defmain():device='cuda'iftorch.cuda.is_available()else'cpu'device=torch.device(device)batch_size=256*4epochs=50latent_size=64in_features=28*28lr=0.001data=MNIST('./dataset/',download=True,transform=transforms.ToTensor())data_loader=torch.utils.data.DataLoader(data,batch_size=batch_size,shuffle=True)#trainVAEvae=VAE(in_features,latent_size).to(device)optimizer=torch.optim.AdamW(vae.parameters(),lr=lr)print('StartTrainingVAE...')forepochinrange(1,1+epochs):loss=train(vae,optimizer,data_loader,device,name='VAE')print("Epochs:{epoch},AvgLoss:{loss:.4f}".format(epoch=epoch,loss=loss))print('TrainingforVAEhasbeendone.')#trainVCAEcvae=CVAE(in_features,latent_size,y_size=1).to(device)optimizer=torch.optim.AdamW(cvae.parameters(),lr=lr)print('StartTrainingCVAE...')forepochinrange(1,1+epochs):loss=train(cvae,optimizer,data_loader,device,name='CVAE')print("Epochs:{epoch},AvgLoss:{loss:.4f}".format(epoch=epoch,loss=loss))print('TrainingforCVAEhasbeendone.')save_res(vae,cvae,data,latent_size,device)if__name__=='__main__':main()123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189VAE的变种:条件变分自编码器(CVAE)传统的VAE可以近似地生成输入数据,但不能定向地生成特定类型的数据。为解决这一问题,将数据x和x的部分标签(y)输入到CVAE的编码器部分。这样就会生成指定类别的数据。CVAE的结构与VAE相似,因此CVAE的计算方法和优化方法与VAE一致。由于在输入中存在一些标签Y,CVAE成为一种半监督学习形式。参考资料:[1]《DeepLearning》[2]LiP,PeiY,LiJ.Acomprehensivesurveyondesignandapplicationofautoencoderindeeplearning[J].AppliedSoftComputing,2023,138:110176.[3]https://zhuanlan.zhihu.com/p/348498294
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:23 , Processed in 1.492032 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表