一:编码器(AE)介绍
它在形状上和普通的BP网络,或者是卷积网络很相似,都是由输入层,隐藏层,输出层组成的,只不过在我们的之前学习中,BP神经网络和卷积神经网络都用来做分类使用了,也就是在监督学习的场景下做分类。
AE是是无监督学习工作的,数据本身没有标签,AE的输出Y的标签刚好就是数据本身,也就是说,输入端是X,输出端是Y,且恰恰Y=X,先来看看网络结构。
输入端是数据本身X,最中间的隐藏层是code层,节点的数目维度要小于输出端的维度,在input层和code层之间,还可以自定义很多隐藏层,这一部分叫做编码器encoder,将高维度的X信息压缩和降维到了code层的维度,code层保留降维后的信息。
输出端是Y,我们希望能从code层原样输出数据X,也就是希望Y尽量逼近于数据X,Y和X的维度一致,都是大于code层的维度,在code层到输出层之间也可以自定义其他隐藏层,这部分是从低维度到高维度的转换,还原出数据X,所以这部分叫做解码器decoder。
感觉这个是不是特别像GAN,后面的decoder部分,可以从一个低维度的信息,直接转换(还原)成一个输入信息的X。
因此,我们训练完网络后,当我们随便输入一个测试样本数据X,那么自编码网络将对X先进行隐藏层的编码,得到X’,然后再从隐藏X’转换解码到输出层,完成解码,重构出X。隐藏层可以看成是原始数据X的另外一种特征表达。
自编码器的应用
- 数据降维 可视化
- 数据去噪
- 图像压缩
- 特征学习
常见的几种自编码器
- 稀疏自编码(SAE)
- 降噪自编码(DAE)
- 收缩自编码(CAE)
- 栈式自编码
如上说述,隐藏层也就是code层,通过encoder可以描述成是数据X的另外一种形式,它可以通过decoder还原出X,因此可以用于数据压缩,这里有点像之前学习的SVD。将数据降维后,可以跟KNN,Kmeans等聚类/类算法结合,降低模型的复杂度,也使得模型更加容易训练。
二:运行实例
例子一:还是拿MNIST实例集做测试。
from torchvision import transforms, datasets, models
import time
import torch
from torch import nn
from torch.autograd import Variable
from torch.utils.data import DataLoader, sampler
import numpy as np
import matplotlib.pyplot as plt
import torchvision.transforms as tfs
import matplotlib.gridspec as gridspec
#======================================================定义图像展示相关的
plt.rcParams['figure.figsize'] = (10.0, 8.0) # 设置画图的尺寸
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
def show_images(images): # 定义画图工具
images = np.reshape(images, [images.shape[0], -1])
print('show_images: ', images.shape)
sqrtn = int(np.ceil(np.sqrt(images.shape[0])))
sqrtimg = int(np.ceil(np.sqrt(images.shape[1])))
fig = plt.figure(figsize=(sqrtn, sqrtn))
gs = gridspec.GridSpec(sqrtn, sqrtn)
gs.update(wspace=0.05, hspace=0.05)
for i, img in enumerate(images):
ax = plt.subplot(gs[i])
plt.axis('off')
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_aspect('equal')
plt.imshow(img.reshape([sqrtimg, sqrtimg]))
return
# step 1: ===================================================加载数据
class ChunkSampler(sampler.Sampler): # 定义一个取样的函数
def __init__(self, num_samples, start=0):
self.num_samples = num_samples
self.start = start
def __iter__(self):
return iter(range(self.start, self.start + self.num_samples))
def __len__(self):
return self.num_samples
# 加载数据时候的处理函数
def preprocess_img(x):
x = tfs.ToTensor()(x)
return (x - 0.5) / 0.5
# 展示图片时候的处理函数
def deprocess_img(x):
return (x + 1.0) / 2.0
NUM_TRAIN = 60000
batch_size = 128
EPOCHS = 100
HIDDEN_SIZE = 30
train_dataset = datasets.MNIST('./data', train=True, transform=preprocess_img)
train_data = DataLoader(train_dataset, batch_size=batch_size, sampler=ChunkSampler(NUM_TRAIN, 0))
# step 2: ======================================定义自动编码器
class AutoEncoderNet(nn.Module):
def __init__(self):
super(AutoEncoderNet, self).__init__()
# 输入是[batch_size, 1, 28, 28]
self.en_conv = nn.Sequential(
nn.Conv2d(1, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.Conv2d(16, 32, 4, 2, 1),
nn.BatchNorm2d(32),
nn.Tanh(),
nn.Conv2d(32, 16, 3, 1, 1), # 输出: [batch_size, 16, 7, 7]
nn.BatchNorm2d(16),
nn.Tanh()
)
# 隐藏层是 HIDDEN_SIZE 个节点组成的。
self.en_fc = nn.Linear(16*7*7, HIDDEN_SIZE) # 输出: [batch_size, 30]
self.de_fc = nn.Linear(HIDDEN_SIZE, 16*7*7) # 输出: [batch_size, 16*7*7]
self.de_conv = nn.Sequential(
nn.ConvTranspose2d(16, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.ConvTranspose2d(16, 1, 4, 2, 1), # 输出: [batch_size, 1, 28, 28]
nn.Sigmoid()
)
def forward(self, x): # x : [batch_size, 1, 28, 28]
en = self.en_conv(x) # en: [batch_size, 16, 7, 7]
en = en.view(en.size(0), -1) # en: [batch_size, 784]
code = self.en_fc(en) # code: [batch_size, 30]
de = self.de_fc(code) # de: [batch_size, 784]
de = de.view(de.size(0), 16, 7, 7) # de: [batch_size, 16, 7, 7]
decoded = self.de_conv(de) # decoded: [batch_size, 1, 28, 28]
return code, decoded
net = AutoEncoderNet()
# step 3: ======================================定义优化器和损失函数
# 使用Adam梯度下降
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
# 由于问题不是分类,需要最终得到的编码器生成的图像和原始图像很像,因此需要使用回归用的误差函数,均方差函数。
loss_f = nn.MSELoss()
# step 4: ======================================开始训练
iter_count = 0
for epoch in range(EPOCHS):
net.train()
for step, (images, _) in enumerate(train_data, 1):
net.zero_grad()
print(images.shape)
# 前向传播
code, decoded = net(images)
print('code: ', code.shape)
print('decoded image: ', decoded.shape)
# 计算损失值,反向传播更新参数。
loss = loss_f(decoded, images)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (iter_count % 200 == 0):
print('Iter: {}, Loss: {:.4}'.format(iter_count, loss.item(), ))
imgs_numpy = deprocess_img(decoded.data.cpu().numpy())
show_images(imgs_numpy[0:16])
plt.show()
print()
iter_count = iter_count + 1
我们直接把decoder生成的图片展示了出来,下面挑选了几个图片,发现效果还是挺不错的。
例子二:
使用彩色图片的鸣人来做实验:
输入36464维度的图片,隐藏层是128维度的向量。
from torchvision import transforms, datasets
import torch
from torch import nn
from torch.utils.data import DataLoader
import os
from torchvision.utils import save_image
image_size = 64 # 图片大小
batch_size = 16 # 批量大小,我就只用16个鸣人的图片做个测试的哈,都是从百度上取截图取得的。
EPOCHS = 1000
HIDDEN_SIZE = 128
def deprocess_img(img):
out = 0.5 * (img + 1)
out = out.clamp(0, 1)
out = out.view(-1, 3, image_size, image_size)
return out
data_path = os.path.abspath("D:/software/Anaconda3/doc/3D_Naruto")
print (os.listdir(data_path))
# 请注意,在data_path下面再建立一个目录,存放所有图片,ImageFolder会在子目录下读取数据,否则下一步会报错。
dataset = datasets.ImageFolder(root=data_path, transform=transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]))
train_data = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
# step 2: ======================================定义自动编码器
class AutoEncoderNet(nn.Module):
def __init__(self):
super(AutoEncoderNet, self).__init__()
# 输入是[batch_size, 3, 64, 64]
self.en_conv = nn.Sequential(
nn.Conv2d(3, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.Conv2d(16, 32, 4, 2, 1),
nn.BatchNorm2d(32),
nn.Tanh(),
nn.Conv2d(32, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.Conv2d(16, 8, 3, 1, 1), # 输出: [batch_size, 8, 8, 8]
nn.BatchNorm2d(8),
nn.Tanh()
)
# 隐藏层是 HIDDEN_SIZE 个节点组成的。
self.en_fc = nn.Linear(8 * 8 * 8, HIDDEN_SIZE) # 输出: [batch_size, 30]
self.de_fc = nn.Linear(HIDDEN_SIZE, 8 * 8 * 8) # 输出: [batch_size, 8 * 8 * 8]
self.de_conv = nn.Sequential(
nn.ConvTranspose2d(8, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.ConvTranspose2d(16, 16, 4, 2, 1),
nn.BatchNorm2d(16),
nn.Tanh(),
nn.ConvTranspose2d(16, 3, 4, 2, 1), # 输出: [batch_size, 3, 28, 28]
nn.Sigmoid()
)
def forward(self, x): # x : [batch_size, 1, 28, 28]
en = self.en_conv(x) # en: [batch_size, 8, 8, 8]
en = en.view(en.size(0), -1) # en: [batch_size, 512]
code = self.en_fc(en) # code: [batch_size, 128]
de = self.de_fc(code) # de: [batch_size, 512]
de = de.view(de.size(0), 8, 8, 8) # de: [batch_size, 8, 8, 8]
decoded = self.de_conv(de) # decoded: [batch_size, 3, 64, 64]
return code, decoded
net = AutoEncoderNet()
# step 3: ======================================定义优化器和损失函数
# 使用Adam梯度下降
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
# 由于问题不是分类,需要最终得到的编码器生成的图像和原始图像很像,因此需要使用回归用的误差函数,均方差函数。
loss_f = nn.MSELoss()
# step 4: ======================================开始训练
# os.mkdir('D:/software/Anaconda3/doc/3D_Img/AE/')
iter_count = 0
for epoch in range(EPOCHS):
net.train()
for step, images in enumerate(train_data, 0):
net.zero_grad()
img_data = images[0]
print(img_data.shape)
# 前向传播
code, decoded = net(img_data)
print('code: ', code.shape)
print('decoded image: ', decoded.shape)
# 计算损失值,反向传播更新参数。
loss = loss_f(decoded, img_data)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (iter_count % 10 == 0):
print('Iter: {}, Loss: {:.4}'.format(iter_count, loss.item(), ))
real_images = deprocess_img(decoded.data)
save_image(real_images, 'D:/software/Anaconda3/doc/3D_Img/AE/test_%d.png' % (iter_count))
print()
iter_count = iter_count + 1
最后通过encoder和decoder后得到的图片如下: