查看原文
其他

【强基固本】深度学习入门与Pytorch4.1 深度学习中的几种自编码器的介绍与Pytorch代码实现

“强基固本,行稳致远”,科学研究离不开理论基础,人工智能学科更是需要数学、物理、神经科学等基础学科提供有力支撑,为了紧扣时代脉搏,我们推出“强基固本”专栏,讲解AI领域的基础知识,为你的科研学习提供助力,夯实理论基础,提升原始创新能力,敬请关注。

来源:知乎—aHiiLn

地址:https://zhuanlan.zhihu.com/p/418352070

实际使用中,我们的深度神经网络可能要接受样本数量极大且特征极多的数据(比如成千上万的图片),这时候将数据直接输入深度神经网络中学习,计算量太大,会降低学习的效率,并且随着特征的增多,我们的神经网络为了提取有效信息也必须变大以保证有足够的拟合能力去学习数据特征,会使得我们的神经网络越来越庞大,使我们的神经网络更难以训练。
既然如此,我们为何不先对数据进行降维,得到数据中的主要有效信息,而舍弃一些对结果影响甚微的信息呢?这样减小了数据的大小,减少了计算量也能使神经网络能更轻松地学习数据特征而不至于设计太庞大。基于这个目的,自编码器应运而生。

01

自编码器
自编码器是一种数据压缩技术,通过自监督学习来学习有效的数据编码,输入是原始数据,目的是为了得到压缩的数据,输出是通过解码器解码的数据。这个压缩的过程是有损的,会存在信息损失。
自编码器由一个编码器和一个解码器构成。它的工作流程如下图所示:
编码器的效果以及当前的压缩效果的好坏通过输入和输出的差距评估。如果输入和输出差距很小,说明编码含有原始数据的绝大部分信息,信息冗余很小,那么对于输入数据来说当前的自编码器压缩效果较好;如果差距较大,说明压缩效果不是很好。
将输入数据定义为X,编码器的编码函数为F,编码为h,解码器的解码函数为G,输出为r,则:

我们的目的是评估压缩效果,所以需要用损失函数来评估输入和输出数据的差距:

可以看到,这个损失函数很类似于我们之前的神经网络的损失函数,只是标签换成了输入数据,所以自编码器是一种自监督学习。
一般会用2范数来衡量r与X的差距:
下一节会先介绍一个简单的自编码器,用于重建MNIST手写数字图像。
下面的代码都是在jupyter运行,建议使用google浏览器。


02

不完备自编码器
在神经网络中,自编码器的结构有点类似于纺锤形,即像下面一样的结构:
可以看到,纺锤状结构中,编码的维数要少于输入数据的维数,这就是不完备自编码器。不完备自编码器获取数据的方式和PCA一样,都是通过降低数据位维数。
纺锤状结构的原因是我们希望自编码器学习到的是数据的编码而不只是简单地复制粘贴输入数据到输出,而是通过学习数据中的特征尽最大可能保留数据中的信息,重建输入数据。所以我们的神经网络的神经元会逐层减少而不是和数据的特征有一样数量的神经元,如果神经元过多会发生过拟合,神经网络可能会简单地复制粘贴数据。
导入库

# import packagesimport torchimport torch.nn as nnimport torch.utils.data as Dataimport torchvisionimport matplotlib.pyplot as pltfrom matplotlib import cmimport numpy as np%matplotlib inline
设置超参数
torch.manual_seed(1) # 重建# Hyper ParametersEPOCH = 5BATCH_SIZE = 128LR = 0.005 #学习率DOWNLOAD_MNIST = FalseN_TEST_IMG = 6
数据集准备。
train_data = torchvision.datasets.MNIST( root='../data/', train=True, # 训练集 transform= torchvision.transforms.Compose([torchvision.transforms.ToTensor(),]), # 将数据转换成tensor并归一化到[-1,1] download=DOWNLOAD_MNIST, # 如果没有,将下载)
使用pytorch的Data.DataLoader函数将下载的数据集转换成可迭代的数据加载器。由于设置了BATCH_SIZE=128,而MNIST手写数据集的单张图片的形状为1×28×28,所以打包后一个batch的形状为(128,1,28,28)。

train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
定义网络结构。
class AutoEncoder(nn.Module): def __init__(self): super(AutoEncoder, self).__init__() self.encoder = nn.Sequential( nn.Linear(28*28, 100), nn.ReLU(), ) self.decoder = nn.Sequential( nn.Linear(100, 28*28), nn.ReLU(), # compress to a range (0, 1) ) def forward(self, x): encoded = self.encoder(x) decoded = self.decoder(encoded)        return encoded, decoded
打印网络结构,设置损失函数和优化器:
autoencoder = AutoEncoder()print(autoencoder)optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)loss_func = nn.MSELoss()
训练:
for epoch in range(EPOCH): for step, (x, y) in enumerate(train_loader): b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28) b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28) b_label = y # batch label
encoded, decoded = autoencoder(b_x)
loss = loss_func(decoded, b_y) # mean square error optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients
if step % 500 == 0 and epoch in range(EPOCH): print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.item())
# plotting decoded image (second row) _, decoded_data = autoencoder(view_data)
# initialize figure f, a = plt.subplots(2, N_TEST_IMG, figsize=(5, 2))
for i in range(N_TEST_IMG): a[0][i].imshow(np.reshape(view_data.data.numpy()[i], (28, 28)), cmap='gray'); a[0][i].set_xticks(()); a[0][i].set_yticks(())
for i in range(N_TEST_IMG): a[1][i].clear() a[1][i].imshow(np.reshape(decoded_data.data.numpy()[i], (28, 28)), cmap='gray') a[1][i].set_xticks(()); a[1][i].set_yticks(()) plt.show(); plt.pause(0.05)


03

正则自编码器之稀疏自编码器
不完备自编码器的隐层节点数目小于输入的维数,如果我们想让隐层节点的数目较大时,我们就要考虑使用稀疏自编码器。稀疏自编码器通过对隐层的激活输出进行正则,同一时间只有部分隐层神经元是活跃的,这样整个自编码器就会是“稀疏”的。
稀疏自编码器可以通过损失函数对稀疏性进行额外惩罚。损失函数改变为:
Ω(X)为惩罚项。
加入L1惩罚项,定义网络结构:

class AutoEncoder(nn.Module): def __init__(self): super(AutoEncoder, self).__init__()
self.encoder = nn.Sequential( nn.Linear(28*28, 400), nn.ReLU(), ) self.decoder = nn.Sequential( nn.Linear(400, 28*28), nn.ReLU(), # compress to a range (0, 1) )
def forward(self, x): encoded = self.encoder(x) x = L1Penality.apply(x, 0.1) # 10% of the weights are supposed to be zero decoded = self.decoder(encoded)        return encoded, decoded    
定义损失函数以及优化器
autoencoder = AutoEncoder()print(autoencoder)init_weightsE = copy.deepcopy(autoencoder.encoder[0].weight.data)init_weightsD = copy.deepcopy(autoencoder.decoder[0].weight.data)optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)loss_func = nn.MSELoss()
训练:
for epoch in range(EPOCH): for step, (x, y) in enumerate(train_loader): b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28) b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28) b_label = y # batch label
encoded, decoded = autoencoder(b_x)
loss = loss_func(decoded, b_y) # mean square error optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients
if step % 500 == 0 and epoch in range(EPOCH): print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.item())
# plotting decoded image (second row) _, decoded_data = autoencoder(view_data)
# initialize figure f, a = plt.subplots(2, N_TEST_IMG, figsize=(5, 2))
for i in range(N_TEST_IMG): a[0][i].imshow(np.reshape(view_data.data.numpy()[i], (28, 28)), cmap='gray'); a[0][i].set_xticks(()); a[0][i].set_yticks(())
for i in range(N_TEST_IMG): a[1][i].clear() a[1][i].imshow(np.reshape(decoded_data.data.numpy()[i], (28, 28)), cmap='gray') a[1][i].set_xticks(()); a[1][i].set_yticks(()) plt.show(); plt.pause(0.05)


04

正则自编码器之降噪自编码器
如果我们想在不增加惩罚的情况下获得类似的结果呢?在这种情况下,我们可以使用降噪自编码器。我们可以改变解码器的重建过程来实现这一点。
考虑向输入数据添加噪声ε以使其代替 X. 那么损失函数变为:
网络结构,没加入L1惩罚,可以将稀疏自编码器和降噪自编码器组合使用的:
class AutoEncoder(nn.Module): def __init__(self): super(AutoEncoder, self).__init__()
self.encoder = nn.Sequential( nn.Linear(28*28, 400), nn.ReLU(), ) self.decoder = nn.Sequential( nn.Linear(400, 28*28), nn.ReLU(), # compress to a range (0, 1) )
def forward(self, x): encoded = self.encoder(x) decoded = self.decoder(encoded) return encoded, decoded
定义优化器等
noise_mean = 0.01noise_std = 0.02autoencoder = AutoEncoder()print(autoencoder)optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR)loss_func = nn.MSELoss()
for epoch in range(EPOCH): for step, (x, y) in enumerate(train_loader): b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28) b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28) b_label = y # batch label
noise = torch.autograd.Variable(torch.tensor(np.random.normal(noise_mean, noise_std, b_x.view(-1, 28*28).shape))) b_x_noise = torch.autograd.Variable(torch.clamp(b_x+noise, 0, 1))
encoded, decoded = autoencoder(b_x_noise.to(torch.float32))
loss = loss_func(decoded, b_y) # mean square error optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients
if step % 500 == 0 and epoch in range(EPOCH): print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.item())
# plotting decoded image (second row) _, decoded_data = autoencoder(view_data)
# initialize figure f, a = plt.subplots(2, N_TEST_IMG, figsize=(5, 2))
for i in range(N_TEST_IMG): a[0][i].imshow(np.reshape(view_data.data.numpy()[i], (28, 28)), cmap='gray'); a[0][i].set_xticks(()); a[0][i].set_yticks(())
for i in range(N_TEST_IMG): a[1][i].clear() a[1][i].imshow(np.reshape(decoded_data.data.numpy()[i], (28, 28)), cmap='gray') a[1][i].set_xticks(()); a[1][i].set_yticks(()) plt.show(); plt.pause(0.05)

Reference

https://jmlr.org/papers/volume11/vincent10a/vincent10a.pdf

https://www.deeplearningbook.org/contents/autoencoders.html

https://arxiv.org/pdf/1606.05908.pdf

https://www.deeplearningbook.org/

https://debuggercafe.com/implementing-deep-autoencoder-in-pytorch/

https://github.com/abhisheksamb

本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。


“强基固本”历史文章


更多强基固本专栏文章,

请点击文章底部“阅读原文”查看



分享、点赞、在看,给个三连击呗!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存