自编码器是深度学习的研究热点之一,在很多领域都有应用。其应用主要有两个方面,第一个是对数据降维,或者降维后对数据进行可视化;第二是对数据进行去噪,尤其是图像数据去噪。
最初的自编码器是一个三层网络,即输入层、中间隐藏层和输出层,其中输入层和输出层的神经元个数相同,且中间隐藏层的神经元个数会较少,从而达到降维的目的。
深度自编码器是将自编码器堆积起来,可以包含多个隐藏层。由于其可以有更多的中间隐藏层,所以对数据的表示和编码能力更强,而且在实际应用中也更加常用。
稀疏自编码器,是在原有自编码器的基础上,对隐层单元施加稀疏性约束,这样会得到对输入数据更加紧凑的表示,在网络中仅有小部分神经元会被激活。常用的稀疏约束是使用l1范数约束,目的是让不重要的神经元的权重为0.
卷积自编码器是使用卷积层搭建获得的自编码网络。当输入数据为图像时,由于卷积操作可以从图像数据中获取更丰富的信息,所以使用卷积层作为自编码器隐藏层,通常可以对图像数据进行更好的表示。在实际的应用中,用于处理图像的自动编码器的隐藏层几乎都是基于卷积的自动编码器。在卷积自编码器的编码器部分,通常可以通过池化层负责对数据进行下采样,卷积层负责对数据进行表示,而解码器则通常使用可以对特征映射进行上采样的操作来完成。
下面将介绍类似于全连接神经网络的自编码模型,即网络中编码层和解码层都使用线性层包含不同数量的神经元来表示。针对手写字体数据集,利用自编码模型对数据降维和重构。
针对自编码器主要介绍三个相关的应用。
(1)使用自编码器对手写字体图像进行重构
(2)可视化测试样本通过网络得到的特征编码,将其在三维空间中进行数据可视化,观察数据的分布规律。
(3)使用自编码网络降维后的数据特征和SVM分类器结合,将手写字体数据建立分类器,并将分类结果和使用PCA降维后建立的SVM分类器进行对比。
在进行分析之前,先导入需要的库和模块。
from symbol import parameters
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import hiddenlayer as hl
from sklearn.manifold import TSNE
from sklearn.svm import SVC
from sklearn.decomposition import PCA
from sklearn.metrics import classification_report,accuracy_score
import torch
from torch import nn
import torch.utils.data as Data
import torch.optim as optim
from torchvision import transforms
from torchvision.datasets import MNIST
from torchvision.utils import make_grid
在上述库和模块中,Axes3D用于三维数据的可视化,SVC用于建立支持向量机分类器,PCA则是对数据进行主要成分分析以获取数据的主成分。
通过torchvision库中的MNIST()函数导入训练和测试所需要的数据集--手写字体数据集,并对数据进行预处理,程序如下:
train_data =MNIST(
root="./data/MNIST", #数据的路径
train=True, #只使用训练数据集
transform=transforms.ToTensor(),
download=False
)
#将图像数据转换为向量数据
train_data_x=train_data.data.type(torch.FloatTensor)/255.0
train_data_x=train_data_x.reshape(train_data_x.shape[0],-1)
train_data_y=train_data.targets
#定义一个加载器
train_loader=Data.DataLoader(
dataset=train_data_x, #使用的数据集
batch_size=64, #批处理样本大小
shuffle=True, #每次迭代前打乱数据
num_workers=2, #使用两个进程
)
#对测试数据集进行导入
test_data=MNIST(
root="./data/MNIST", #数据的路径
train=False, #只是用测试数据集
transform=transforms.ToTensor(),
download=False
)
#为测试数据添加一个通道维度,获取测试数据的X和Y
test_data_x=test_data.data.type(torch.FloatTensor)/255.0
test_data_x=test_data_x.reshape(test_data_x.shape[0],-1)
test_data_y=test_data.targets
# print("训练数据集",train_data_x.shape)
# print("测试数据集",test_data_x.shape)
上述程序导入训练数据集后,将训练数据集中的图像数据和标签数据分别保存为train_data_x和train_data_y变量,并且针对训练数据集中的图像将像素值处理在0-1之间,并且将每个图像处理为长784的向量,最后通过Data.DataLoader()函数将训练数据train_data_x处理为数据加载器,此处并没有包含对应的类别标签,这是因为上述自编码网络训练时不需要图像的类别标签数据,在数据加载器中每个batch包含64个样本。针对测试集将其图像和经过预处理后的图像分别保存为test_data_x和test_data_y变量。最后输出训练数据和测试数据的形状,可得到下面的结果:
可视化训练数据集中一个batch的图像内容,以观察手写体图像的情况,程序如下:
for step,b_x in enumerate(train_loader):
if step >0:
break
im = make_grid(b_x.reshape((-1,1,28,28)))
im=im.data.numpy().transpose((1,2,0))
plt.figure()
plt.imshow(im)
plt.axis("off")
# plt.show()
上面的程序在获取一个batch数据后,通过make_grid()转换数据,方便对图像数据进行可视化
。该函数是pytorch库中的函数,可以直接将数据结构[batch,channel,height,width]形式的batch图像转换为图像矩阵,便于将多张图像进行可视化。 运行结果如下:
然后我们来搭建自编码网络,为了搭建自编码网络,需要构件一个EnDecoder()类,程序如下:
class EnDecoder(nn.Module):
def __init__(self):
super(EnDecoder,self).__init__()
self.Encoder=nn.Sequential(
nn.Linear(784,512),
nn.Tanh(),
nn.Linear(512,256),
nn.Tanh(),
nn.Linear(256,128),
nn.Tanh(),
nn.Linear(128,3),
nn.Tanh(),
)
self.Decoder=nn.Sequential(
nn.Linear(3,128),
nn.Tanh(),
nn.Linear(128,256),
nn.Tanh(),
nn.Linear(256,512),
nn.Tanh(),
nn.Linear(512,784),
nn.Sigmoid(),
)
def forward(self,x):
encoder=self.Encoder(x)
decoder=self.Decoder(encoder)
return encoder,decoder
在上面的程序中,搭建自编码网络时,将网络分为编码器部分Encoder和解码器部分Decoder.编码器部分将数据的维度从784维逐步减少到三维,每个隐藏层使用的激活函数为Tanh激活函数。解码器部分将特征编码从三维逐步增加到784维,除输出层使用Sigmoid激活函数外,其它隐藏层使用Tanh激活函数。在网络的前向传播函数forward()中,输出编码后的结果encoder和解码后的结果decoder。
下面定义EnDecoder类的自编码器网络edmodel,程序如下所示:
edmodel = EnDecoder()
print(edmodel)
输出结果为:
然后我们对自编码网络进行训练,使用训练数据对网络中的参数进行训练时,使用torch.optim.Adam()优化器对网络中的参数进行优化,并使用nn.MSELoss()函数定义损失函数,即使用均方根误差损失(因为自编码网络需要重构出原始的手写体数据,所以看做回归问题,即与原始图像的误差越小越好,使用均方根误差作为损失函数较合适,也可以使用绝对值误差作为损失函数)为了观察网络的训练过程,通过HiddenLayer库将网络在训练数据过程中的损失函数的大小进行动态可视化。网络的训练及可视化程序如下所示:
optimizer=torch.optim.Adam(edmodel.parameters(),lr=0.003)
loss_func=nn.MSELoss()
historyl=hl.History()
canvasl=hl.Canvas()
train_num=0
val_num=0
for epoch in range(10):
train_loss_epoch=0
for step,b_x in enumerate(train_loader):
_,output=edmodel(b_x)
loss=loss_func(output,b_x)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss_epoch+=loss.item()*b_x.size(0)
train_num=train_num+b_x.size(0)
train_loss=train_loss_epoch/train_num
historyl.log(epoch,train_loss=train_loss)
with canvasl:
canvasl.draw_plot(historyl["train_loss"])
在上面的程序中,计算每个batch的损失函数时,使用loss=loss_func(output,b_x),其中b_x表示每次网络的输入数据,output表示经过自编码网络的输出内容,即自编码网络的损失是网络重构的图像与输入图像之间的差异,计算两者之间的均方根误差损失。网络在训练过程中损失函数的大小变化如下所示,损失函数先迅速减小,然后再一个很小的值上趋于稳定。网络训练结束后,最终的均方根损失约为0.00342.
然后对自编码网络进行数据重构,为了展示自编码网络的效果,可视化一部分测试集经过编码前后的图像,此处使用测试集的前100张图像,程序如下:
# edmodel.eval()
# _,test_decoder=edmodel(test_data_x[0:100,:])
# plt.figure(figsize=(6,6))
# for ii in range(test_decoder.shape[0]):
# plt.subplot(10,10,ii+1)
# im=test_data_x[ii,:]
# im=im.data.numpy().reshape(28,28)
# plt.imshow(im,cmap=plt.cm.gray)
# plt.axis("off")
# plt.show()
# plt.figure(figsize=(6,6))
# for ii in range(test_decoder.shape[0]):
# plt.subplot(10,10,ii+1)
# im=test_decoder[ii,:]
# im=im.data.numpy().reshape(28,28)
# plt.imshow(im,cmap=plt.cm.gray)
# plt.axis("off")
# plt.show()
在上面的图像中首先使用edmodel.eval()将模型设置为验证模式,通过“_,test_decoder=edmodel(test_data_x[0:100,:])”获取测试集前100张图像在经过网络后的解码器输出结果test_decoder。在可视化分别先可视化原始的图像,再可视化经过自编码网络后的图像。得到如下图像:
原始图像:
经过自编码网络后的图像:
对比两张图像,自编码网络很多地重构了原始图像的结构,但不住的是自编码网络得到的图像有些模糊,而且针对原始图像中的某些细节并不能很好地重构,如某些手写体不规范的4会重构成9等。这是因为在网络中,自编码器部分最后一层只有三个神经元,将784维数据压缩到3维,会损失大量信息,故重构的效果会有一些模糊和错误。这里降到三维主要为了方便数据可视化,在实际情况中,可能使用较多的神经元,保留更丰富的信息。