1.自编码器的介绍
自编码器的思想很简单,就是将一张图像通过Encoder变成一个code,然后再通过Decoder将这个生成出来的code重构成一张图像,然后希望重构出来的图像与原图像越接近好。
1)传统自编码器
通过神经网络来实现传统的自编码器
传统自编码器的目的是使输出与输入尽量相同,这完全可以通过学习两个恒等函数来完成,但是这样的变换没有任何意义,因为我们真正关心的是隐层表达,而不是实际输出.因此,针对自编码器的很多改进方法都是对隐层表达增加一定的约束,迫使隐层表达与输入不同.如果此时模型还可以重建输入信号,那么说明隐层表达足以表示输入信号,这个隐层表达就是通过模型自动学习出来的有效特征.
2)降噪自编码器
一个好的表达应该能够捕获输入信号的稳定结构,具有一定的鲁棒性,同时对重建信号是有用的.
降噪自编码器的提出是受到一个现象的启发,那就是对于部分被遮挡或损坏的图像,人类仍然可以进行准确的识别.因此,降噪自编码器的主要研究目标是:隐层表达对被局部损坏的输入信号的鲁棒性.也就是说,如果一个模型具有足够的鲁棒性,那么被局部损坏的输入在隐层上的表达应该与没有被破坏的干净输入几乎相同,而利用这个隐层表达就完全可以重建干净的输入信号.
因此,降噪自编码器通过对干净输入信号人为加入一些噪声,使干净信号受到局部损坏,产生与它对应的一个损坏信号,然后将这个损坏信号送入传统自编码器,使其尽量重建一个与干净输入相同的输出.
3)卷积自编码器
卷积神经网络所取得的各种优异表现,直接推动了卷积自编码器的产生.严格上来说,卷积自编码器 属于传统自编码器的一个特例,它使用卷积层和池化层替代了原来的全连接层.传统自编码器一般使用的是全连接层,对于一维信号并没有什么影响,而对于二维图像或视频信号,全连接层会损失空间信息,通过采用卷积操作,卷积自编码器能很好的保留二维信号的空间信息.
卷积自编码器与传统自编码器非常类似,其主要差别在于卷积自编码器采用卷积方式对输入信号进行线性变换,并且其权重是共享的,这点与卷积神经网络一样.因此,重建过程就是基于隐藏编码的基本图像块的线性组合.
对于其中的Unpooling与Deconvolution可能会有些理解问题。
- Unpooling
我们知道,卷积中的池化层就是将原本一个局部区域中去平均或者是去最大来实现尺寸的下降,通常池化后的尺度大小是原来的二分之一。或者,池化也可以理解为下采样。而Unpooling可以理解为上采样。
其中的一种实现的方式是,其保留上一步池化的记忆,也就是记住了选择的是最大值的那个区域,然后当尺度扩张时,保留那个最大的值,然后在其他的地方补0,从而实现尺寸的变大。
另外的一种方法是,不需要补0,而是单一扩张区域中直接复制一模一样的值。
Unpooling后的效果:
2. Deconvolution
其实Deconvolution就是convolution,其中convolution是通过卷积而降维,而Deconvolution可以通过padding来实现扩维。如下图,convolution是左边的操作,Deconvolution其实就是右边的操作。
2.自编码器的应用
基于自编码器降维得到的隐藏变量code,其实可以有一些应用的。
1)文字检索(Text Retrieval)
我们可以将一篇文章,当成是一个二维空间中一个数据分布。一篇文章代表了这个数据分布中的一个点,那么我们可以将一篇文章通过自编码器提取到一个二维的code。
然后根据这歌二维的code在数据空间中的分布来检索其与其他文章的相识性,从而实现分类或其他操作。
2)相识图像检索(Similar Image Search)
其实我们还可以通过自编码寻找数据分布的能力,来进行图像检索。具体的操作是,当我们希望寻找一些相识图像的时候,如果我们使用的pixel之间的相识性,也就是如果我们根绝两张图像之间的像素点的分布来做损失,那么得到的结果往往不会太好。
但是,这时候我们可以通过将需要寻找的图像通过编码器变成一个code,然后通过这个code来进行检索。也就是说,如果两张图像的code,之间的差异是比较小的,我们可以相信这有可能找到我们需要的图像,而最后的结果往往比上诉的找像素之间的分布的结果要好。最起码自编码器中的code找到知道我们要找的是一个人像。
3)模型的预训练(Pre-training)
我们知道在神经网络的训练中,通常初始化这一步是非常中重要的,所以,其实我们可以使用自编码器来实现预训练的操作。
当我们进行半监督学习时候,我们通常有非常多的unlabel的data,然后只有少部分的带label的data,那么这时候,我们就可以使用这些大量的unlabel的data来对模型进行预训练。
我们假设模型结构是3层隐藏层的全连接神经网络结构,那么对于第一层隐藏层来说,我们输入一个图像,然后通过自编码器,经过这隐层层得到一个1000维的code,而这个code其实就是第二层的隐含层。当然此时需要避免当code比图像的维度还要高的时候,网络可以原封不动的直接保留整个图像的维度信息,需要注意这点。然后通过Decoder将code重构出原来的模型,此时我们可以固定住第一层隐藏层的参数,因为这已经训练好了。
同样的,我们对第二层隐藏层就行训练,将第二层应汉成变成一个code,而这个code的维度就是第三层隐藏层的维度,然后再重构出来,然后再固定第二层隐藏层的参数。
类似的,对于其他的隐藏层操作类似。当保留好全部网络层数的参数时,这个网络就已经预训练好了。现在只需要使用那些少量的带label的data来对网络进行back propagation,稍微调整一下参数即可。
但是,其实对于现在的网络来说,没有初始化一般也是可以训练出来的,但这是得益于训练技术的提高,在曾经没有初始化与有初始化的差距还是比较大的。
3.自编码器的实现
框架使用Pytorch
数据集使用MNIST
参考代码
model.py
import torch import torchvision import torch.nn as nn # 定义自编码器结构 class Auto_Encoder(nn.Module): def __init__(self): super(Auto_Encoder, self).__init__() # 定义编码器结构 self.Encoder = nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 64), nn.ReLU(), nn.Linear(64, 20), nn.ReLU() ) # 定义解码器结构 self.Decoder = nn.Sequential( nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 256), nn.ReLU(), nn.Linear(256, 784), nn.Sigmoid() ) def forward(self, input): code = input.view(input.size(0), -1) code = self.Encoder(code) output = self.Decoder(code) output = output.view(input.size(0), 1, 28, 28) return output
train.py
import torch import torchvision from torch import nn, optim from torchvision import datasets, transforms from torchvision.utils import save_image from torch.utils.data import DataLoader from model import Auto_Encoder import os # 定义超参数 learning_rate = 0.0003 batch_size = 64 epochsize = 30 root = 'E:/学习/机器学习/数据集/MNIST' sample_dir = "image" if not os.path.exists(sample_dir): os.makedirs(sample_dir) # 图像相关处理操作 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5]) ]) # 训练集下载 mnist_train = datasets.MNIST(root=root, train=True, transform=transform, download=False) mnist_train = DataLoader(dataset=mnist_train, batch_size=batch_size, shuffle=True) # 测试集下载 mnist_test = datasets.MNIST(root=root, train=False, transform=transform, download=False) mnist_test = DataLoader(dataset=mnist_test, batch_size=batch_size, shuffle=True) # image,_ = iter(mnist_test).next() # print("image.shape:",image.shape) # torch.Size([64, 1, 28, 28]) AE = Auto_Encoder() AE.load_state_dict(torch.load('AE.ckpt')) criteon = nn.MSELoss() optimizer = optim.Adam(AE.parameters(), lr=learning_rate) print("start train...") for epoch in range(epochsize): # 训练网络 for batchidx, (realimage, _) in enumerate(mnist_train): # 生成假图像 fakeimage = AE(realimage) # 计算损失 loss = criteon(fakeimage, realimage) # 更新参数 optimizer.zero_grad() loss.backward() optimizer.step() if batchidx%300 == 0: print("epoch:{}/{}, batchidx:{}/{}, loss:{}".format(epoch, epochsize, batchidx, len(mnist_train), loss)) # 生成图像 realimage,_ = iter(mnist_test).next() fakeimage = AE(realimage) # 真假图像何必成一张 image = torch.cat([realimage, fakeimage], dim=0) # 保存图像 save_image(image, os.path.join(sample_dir, 'image-{}.png'.format(epoch + 1)), nrow=8, normalize=True) torch.save(AE.state_dict(), 'AE.ckpt')
结果展示
Epoch1生成的图像
Epoch10生成的图像
Epoch30生成的图像
结果可以看出慢慢训练得变好,但是还是有些模糊,使用VAE的效果可能会好一点。