PyTorch 学习笔记(九):⾃动编码器(AutoEncoder )
⼀. ⽣成模型
⽣成模型(Generative Model)这⼀概念属于概率统计与机器学习,是指⼀系列⽤于随机⽣成可观测预测数据得模型。简⽽⾔之,就是“⽣成” 的样本和 “真实” 的样本尽可能地相似。⽣成模型的两个主要功能就是学习⼀个概率分布和⽣成数据,这是⾮常重要的,不仅可以⽤在⽆监督学习中,还可以⽤在监督学习中。
⽆监督学习的发展⼀直⽐较缓慢,⽣成模型希望能够让⽆监督学习取得⽐较⼤的进展。
⼆. ⾃动编码器——⾃监督学习
⾃动编码器(AutoEncoder)最开始作为⼀种数据的压缩⽅法,其特点有:
1. 跟数据相关程度很⾼,这意味着⾃动编码器只能压缩与训练数据相似的数据,因为使⽤神经⽹络提取的特征⼀般是⾼度相关于原始的训练集,使⽤⼈脸训练出的⾃动编码器在压缩⾃然界动物的图⽚时就会表现的很差,因为它只学习到了⼈脸的特征,⽽没有学习到⾃然界图⽚的特征。
2. 压缩后数据是有损的,这是因为降维的过程中不可避免地丢失信息,解压之后的输出和原始的输⼊相⽐是退化的。
到了2012年,⼈们发现在卷积神经⽹络中使⽤⾃动编码器做逐层预训练可以训练更深层的⽹络,但是⼈们很快发现,良好的初始化策略要⽐复杂的逐层预训练有效得多,2014年的Batch Normalization技术也使得更深的⽹络能够被有效训练,到了2015年年底,通过ResNet 基本可以训练任意深度的神经⽹络了。
现在⾃动编码器主要应⽤在两个⽅⾯:第⼀是数据去噪,第⼆是进⾏可视化降维。⾃动编码器还有⼀个功能,即⽣成数据。
那么⾃动编码器是如何对深层⽹络做分层训练的呢?我们先来看看⾃动编码器的⼀般结构:
从上图可以看到两个部分:第⼀个部分是编码器(Encoder),第⼆个部分是解码器(Decoder),编码器和解码器都可以是任意的模型,通常使⽤神经⽹络作为编码器和解码器。输⼊的数据经过神经⽹
络降维到⼀个编码(code),接着⼜通过另外⼀个神经⽹络去解码得到⼀个与输⼊原数据⼀模⼀样的⽣成数据,然后通过⽐较这两个数据,最⼩化它们之间的差异来训练这个⽹络中编码器和解码器的参数。看完了⽹络结构,我们再来看看⾃动编码器是如何对深层⽹络做分层训练,如下图所⽰,我们将input输⼊⼀个encoder编码器,就会得到⼀个code,这个code也就是输⼊的⼀个表⽰,那么我们怎么知道这个code表⽰的就是input呢?我们加⼀个decoder解码器,这时候decoder就会输出⼀个信息,如果输出的这个信息和⼀开始的输⼊信号input是很像的(理想情况下就是⼀样的),那很明显,我们就有理由相信这个code是靠谱的。所以,我们就通过调整encoder和decoder的参数,使得重构误差最⼩,这时候我们就得到了输⼊input信号的第⼀个表⽰了,也就是编码code了。因为是⽆标签数据,所以误差的来源就是直接重构后与原输⼊相⽐得到。我们的重构误差最⼩让我们相信这个code就是原输⼊信号的良好表达了,或者牵强点说,它和原信号是⼀模⼀样的。接着,我们将第⼀层输出的code当成第⼆层的输⼊信号,同样最⼩化重构误差,就会得到第⼆层的参数,并且得到第⼆层输出的code,也就是原输⼊信息的第⼆个表达了。其他层就同样的⽅法炮制就⾏了(训练这⼀层,前⾯层的参数都是固定的,并且他们的decoder已经没⽤了,都不需要了)。
P (X )model
需要注意的是,整个⽹络的训练不是⼀蹴⽽就的,⽽是逐层进⾏。如果按n→m→k
结构,实际上我们是先训练⽹络n→m→n,得到n→m的变换,然后再训练m→k→m,得到m→k的变换。最终堆叠成SAE,即为n→m→k 的结果,整个过程就像⼀层层往上盖房⼦,这便是⼤名⿍⿍的 layer-wise unsuperwised pre-training (逐层⾮监督预训练),正是导致
深度学习(神经⽹络)在2006年第3次兴起的核⼼技术。为了尽量学到有意义的表达,我们会给code加⼊⼀定的约束。从数据维度来看,常见以下两种情况:
如果input的维度⼤于code的维度,也就是说从的变换是⼀种降维的操作,⽹络试图以更⼩的维度来描述原始数据⽽尽量不损失数据信息。实际上,当两层之间的变换均为线性,且损失函数为平⽅差损失函数时,该⽹络等价于PCA;
如果input的维度⼩于等于code的维度。这⼜有什么⽤呢?其实不好说,但⽐如我们同时约束code的表达尽量稀疏(有⼤量维度为0,未被激活),此时的编码器便是⼤名⿍⿍的“稀疏⾃编码器”。可为什么稀疏的表达就是好的呢?这就说来话长了,有⼈试图从⼈脑机理对⽐,即⼈类神经系统在某刺激下,⼤部分神经元是被抑制的。个⼈觉得,从特征的⾓度来看直接些,稀疏的表达意味着系统在尝试去特征选择,出⼤量维度中真正重要的若⼲维度。
三. ⾃编码器的⼀般形式
构建⼀个⾃动编码器并当对其完成训练完之后,拿出这个解码器,随机传⼊⼀个编码(code),通过解码器能够⽣成⼀个和原始数据差不
多的数据,就是⽣成数据。
下⾯我们将⽤PyTorch简单地实现⼀个⾃动编码器实现“⽣成数据”:
import torch
from torch import nn, optim
from torch.autograd import Variable
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
from torchvision.utils import save_image
import os
import matplotlib.pyplot as plt
# 加载数据集
def get_data():
# 将像素点转换到[-1, 1]之间,使得输⼊变成⼀个⽐较对称的分布,训练容易收敛
data_tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
train_dataset = datasets.MNIST(root='./data', train=True, transform=data_tf, download=True)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, drop_last=True)
return train_loader
def to_img(x):
x = (x + 1.) * 0.5
x = x.clamp(0, 1)
input →code
x = x.clamp(0, 1)
x = x.view(x.size(0), 1, 28, 28)
return x
class autoencoder(nn.Module):
def __init__(self):
super(autoencoder, self).__init__()
nn.ReLU(True),
nn.Linear(128, 64),
nn.ReLU(True),
nn.Linear(64, 12),
nn.ReLU(True),
nn.Linear(12, 3))
self.decoder = nn.Sequential(nn.Linear(3, 12),
nn.ReLU(True),
nn.Linear(12, 64),
nn.ReLU(True),
nn.Linear(64, 128),
nn.ReLU(True),
nn.Linear(128, 28*28),
nn.Tanh())
def forward(self, x):
encode = der(x)
decode = self.decoder(encode)
return encode, decode
if __name__ == "__main__":
# 超参数设置
batch_size = 128
lr = 1e-2
weight_decay = 1e-5
epoches = 40
model = autoencoder()
# x = Variable(torch.randn(1, 28*28))
# encode, decode = model(x)
# print(encode.shape)
train_data = get_data()
criterion = nn.MSELoss()
optimizier = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay) if torch.cuda.is_available():
model.cuda()
for epoch in range(epoches):decoder
if epoch in [epoches * 0.25, epoches * 0.5]:
for param_group in optimizier.param_groups:
param_group['lr'] *= 0.1
for img, _ in train_data:
img = img.view(img.size(0), -1)
img = Variable(img.cuda())
# forward
_, output = model(img)
loss = criterion(output, img)
# backward
<_grad()
loss.backward()
optimizier.step()
print("epoch=", epoch, loss.data.float())
for param_group in optimizier.param_groups:
print(param_group['lr'])
if (epoch+1) % 5 == 0:
print("epoch: {}, loss is {}".format((epoch+1), loss.data))
pic = to_img(output.cpu().data)
if not ists('./simple_autoencoder'):
os.mkdir('./simple_autoencoder')
save_image(pic, './simple_autoencoder/image_{}.png'.format(epoch + 1)) # torch.save(model, './autoencoder.pth')
# model = torch.load('./autoencoder.pth')
# model = torch.load('./autoencoder.pth')
code = Variable(torch.FloatTensor([[1.19, -3.36, 2.06]]).cuda())
decode = model.decoder(code)
decode_img = to_img(decode).squeeze()
decode_img = decode_img.data.cpu().numpy() * 255
plt.imshow(decode_img.astype('uint8'), cmap='gray')
plt.show()
运⾏100个epoch之后的数据结果:
给训练后的autoencoder随机给⼀个code为[[1.19, -3.36, 2.06]](其实这⾥不严谨,我们并不知道给的这个随机向量是否包含有数字的信息,所以有可能你赋值的随机向量decoder之后的图⽚并不是⼀张数字图⽚),⽤decode解码得到图⽚:
可以看出来,解码得到的图⽚相当模糊,可能是因为我们code的维度太低了,导致encoder的过程中损失的信息过多,decoder得到的图⽚与原始图⽚相⽐就会⽐较模糊。
考虑到全连接⽹络的表征能⼒有限,下⾯将其换为卷积⽹络试试。
import torch
from torch import nn, optim
from torch.autograd import Variable
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
from torchvision.utils import save_image
import os
import matplotlib.pyplot as plt
# 加载数据集
def get_data():
# 将像素点转换到[-1, 1]之间,使得输⼊变成⼀个⽐较对称的分布,训练容易收敛
data_tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.5], [0.5])])
train_dataset = datasets.MNIST(root='./data', train=True, transform=data_tf, download=True) train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, drop_last=True) return train_loader
def to_img(x):
x = (x + 1.) * 0.5
x = x.clamp(0, 1)
x = x.view(x.size(0), 1, 28, 28)
return x
class autoencoder(nn.Module):
def __init__(self):
super(autoencoder, self).__init__()
nn.Conv2d(1, 16, 3, stride=3, padding=1), # (b, 16, 10, 10)
nn.ReLU(True),
nn.MaxPool2d(2, stride=2), # (b, 16, 5, 5)
nn.Conv2d(16, 8, 3, stride=2, padding=1), # (b, 8, 3, 3)
nn.ReLU(True),
nn.MaxPool2d(2, stride=1) # (b, 8, 2, 2)
)
self.decoder = nn.Sequential(
nn.ConvTranspose2d(8, 16, 3, stride=2), # (b, 16, 5, 5)
nn.ReLU(True),
nn.ConvTranspose2d(16, 8, 5, stride=3, padding=1), # (b, 8, 15, 15)
nn.ReLU(True),
nn.ConvTranspose2d(8, 1, 2, stride=2, padding=1), # (b, 1, 28, 28)
nn.Tanh()
)
def forward(self, x):
encode = der(x)
decode = self.decoder(encode)
return encode, decode
if __name__ == "__main__":
# 超参数设置
batch_size = 128
lr = 1e-2
weight_decay = 1e-5
epoches = 100
model = autoencoder()
# x = Variable(torch.randn(1, 28*28))
# encode, decode = model(x)
# print(encode.shape)
train_data = get_data()
criterion = nn.MSELoss()
optimizier = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay)
if torch.cuda.is_available():
model.cuda()
for epoch in range(epoches):
if epoch in [epoches * 0.25, epoches * 0.5]:
for param_group in optimizier.param_groups:
param_group['lr'] *= 0.1
for img, _ in train_data:
# img = img.view(img.size(0), -1)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论