深度学习《变分自编码器》

一个模型总是从简单到复杂,由粗糙到精致不断演化。
本文学习自:李宏毅机器学习视频(https://www.bilibili.com/video/av10590361/?p=29)
很多截图也都是来自于视频内容。

一:AE的特点
之前我们学习了自动编码器,姑且称之为标准的编码器,给一个输入数据X,经过encoder转换为一个低维度向量,最后再经过decoder还原后输出图像。

在这里插入图片描述

过程好比是下图所示,也就是每一个数据都在code的维度上是一个单点,用这个单点去还原图像,训练还是很快的,我们经过试验也能看出来训练挺迅速,但是反过来想想,如果我们随机给一个code维度的随机值,他会生成什么?还会是我们想要的正常图像么?答案是不一定的,标准的AE在数据降维和压缩方面表现很好,但是对于生成领域而言,表现极差,随意给定某个随机值的code,不一定能得到正常的数据,也就是标准的AE训练后,decoder不能直接拿来用于生成数据。
在这里插入图片描述

二:VAE的引入

AE的code层直接就是由encoder经过全连接生成的一个coder维度的向量V,直接将V送给译码器decoder,结构如下:

在这里插入图片描述

VAE,也叫做变分自动译码器,用于生成模型,模型如下所示:

在这里插入图片描述

VAE比AE的区别在于code层的形成,在code层生成了两个向量m和σ,分别作为均值和标准差,额外人为增加一些满足高斯分布的随机噪音e进去,经过如下计算:

c = exp⁡(σ) * e + m

经过计算后,就可以得到一个跟m同样维度的信息的值c,这个值c就是送到下一层decoder的原始code,简单来看的话,VAE和AE的区别就是对code层做了这么个运算上的变化,传统的AE就是简单输出一个m直接送到了decoder,而VAE是输出了相同的m和σ,经过上述式子计算后再送入Decoder。

这么做有什么好处呢?
正如第一章节所说的,标准的AE,只适合于数据降维和压缩,不适合于数据生成场景,也就是Decoder是没有什么用处的。还是用那个月亮的图片直接说明吧。

在VAE的模型下,数据在code空间内映射成一个范围,这样随机取值的话,就可以用decoder直接生成大概率类似的数据。

在这里插入图片描述

如图所示,每一个数据经过encoder都是在code的维度空间内,不再映射到该空间的一个单点,而是映射到一个范围,这个范围具体描述是一个数据分布,也就是说encoder学习的是在数据空间中的一个数据分布,在这个分布内,有多大概率是跟原数据是类似的。正是因为引入了误差,对于相同的input经过encoder后虽然得到的m和σ是相同的,但是noise是不同的,所以会有一些较小的误差.

黄色框中的是优化函数:

在这里插入图片描述

这里还用到了高斯混合模型的知识,在code维度下,每一个维度都是一个高斯分布,假设code是N维的,那么就有N个高斯分布,模型训练就是就是为了训练出这N个高斯分布模型出来,我就不展开讲了

三:实例运行
还是拿MNIST距离吧

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as lossFunc
from torch.autograd import Variable
import torchvision.datasets as dst
import matplotlib.pyplot as plt
import torchvision.transforms as tfs
import torchvision.transforms as transforms
import matplotlib.gridspec as gridspec
import numpy as np
from torchvision.utils import save_image

#======================================================定义图像展示相关的
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


# 展示图片时候的处理函数
def deprocess_img(x):
    return (x + 1.0) / 2.0



# Step 1:====================================记载数据
EPOCH = 100
BATCH_SIZE = 64
SAMPLE_BATCH_SIZE = BATCH_SIZE
LATENT_CODE_NUM = 32
log_interval = 100

preprocess_img = transforms.Compose([transforms.ToTensor()])
data_train = dst.MNIST('./data', train=True, transform=preprocess_img, download=False)
train_loader = torch.utils.data.DataLoader(dataset=data_train, batch_size=BATCH_SIZE, shuffle=True)


# Step 2:====================================定义模型
class VAEModel(nn.Module):
    def __init__(self):
        super(VAEModel, self).__init__()

        self.encoder = nn.Sequential(
            nn.Conv2d(1, 64, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(64),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2, inplace=True),
        )

        # encoder输出一
        self.fc11 = nn.Linear(128 * 7 * 7, LATENT_CODE_NUM)
        # encoder输出二
        self.fc12 = nn.Linear(128 * 7 * 7, LATENT_CODE_NUM)

        # noise_code 后面的全连接层
        self.fc2 = nn.Linear(LATENT_CODE_NUM, 128 * 7 * 7)

        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
            nn.ReLU(inplace=True),

            nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1),
            nn.Sigmoid()
        )

    def addNoise(self, mu, logvar):
        noise = Variable(torch.randn(mu.size(0), mu.size(1))) # 随机高斯分布的噪声
        z = mu + noise * torch.exp(logvar / 2) # 合成新的输出
        return z

    def forward(self, x):
        out1, out2 = self.encoder(x), self.encoder(x)  # out1, out2: [batch_size, 128, 7, 7]

        mu = self.fc11(out1.view(out1.size(0), -1))  # mu: [batch_s, LATENT_CODE_NUM]
        logvar = self.fc12(out2.view(out2.size(0), -1))  # logvar: [batch_s, LATENT_CODE_NUM]
        noise_code = self.addNoise(mu, logvar)  # noise_code: [batch_s, LATENT_CODE_NUM]

        out3 = self.fc2(noise_code).view(noise_code.size(0), 128, 7, 7)  # out3: [batch_size, 128, 7, 7]

        return self.decoder(out3), mu, logvar


vae = VAEModel()

# Step 3:====================================定义优化器和损失函数
def loss_func(reconstruct_x, real_data, mu, logvar):
    BCE = lossFunc.binary_cross_entropy(reconstruct_x, real_data, size_average=False)
    KLD = -0.5 * torch.sum((1 + logvar) - mu.pow(2) - logvar.exp())
    return BCE + KLD

optimizer = optim.Adam(vae.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)


# Step 4:====================================开始训练
def train(EPOCH):
    vae.train()
    total_loss = 0
    for i, real_data in enumerate(train_loader, 0):
        imgs = real_data[0]
        print(imgs.shape)

        optimizer.zero_grad()
        reconstruct_x, mu, logvar = vae.forward(imgs)
        loss = loss_func(reconstruct_x, imgs, mu, logvar)
        loss.backward()
        optimizer.step()
        # total_loss += loss.data[0]

        if i % log_interval == 0:
            # 随机生成噪声去通过decoder生成数字
            sample = Variable(torch.randn(SAMPLE_BATCH_SIZE, LATENT_CODE_NUM))
            sample = vae.decoder(vae.fc2(sample).view(SAMPLE_BATCH_SIZE, 128, 7, 7)).cpu()

            # 显示图片
            # imgs_numpy = deprocess_img(sample.data.cpu().numpy())
            # show_images(imgs_numpy[0:16])
            # plt.show()

            # 保存图片
            save_image(sample.data.view(SAMPLE_BATCH_SIZE, 1, 28, 28), 'D:/software/Anaconda3/doc/3D_Img/vae/test_%d_%d.png' % (EPOCH, i))

    print('====> Epoch: {} Average loss: {:.4f}'.format(
        epoch, total_loss / len(train_loader.dataset)))


for epoch in range(1, EPOCH):
    train(epoch)

在每次一个小的训练阶段,都拿一小批随机的高斯分布随机数直接送入到decoder去产生图片。效果如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实例二,运行我鸣人的照片

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
from torch.autograd import Variable
import torch.nn.functional as lossFunc


image_size = 64  # 图片大小
batch_size = 16   # 批量大小,我就只用16个鸣人的图片做个测试的哈,都是从百度上取截图取得的。
EPOCHS = 10000

SAMPLE_BATCH_SIZE =batch_size
HIDDEN_SIZE = 128


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.ToTensor()]))
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, 64, 4, 2, 1),
            nn.BatchNorm2d(64),
            nn.Tanh(),
            nn.Conv2d(64, 8, 3, 1, 1),   # 输出: [batch_size, 8, 8, 8]
            nn.BatchNorm2d(8),
            nn.Tanh()
        )

        # 隐藏层是 HIDDEN_SIZE 个节点组成的。
        self.en_fc_mean = nn.Linear(8 * 8 * 8, HIDDEN_SIZE)   # 输出: [batch_size, HIDDEN_SIZE]
        self.en_fc_sigm = nn.Linear(8 * 8 * 8, HIDDEN_SIZE)   # 输出: [batch_size, HIDDEN_SIZE]

        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, 32, 4, 2, 1),
            nn.BatchNorm2d(32),
            nn.Tanh(),
            nn.ConvTranspose2d(32, 3, 4, 2, 1),   # 输出: [batch_size, 3, 64, 64]
            nn.Sigmoid()
        )

    def addNoise(self, mu, logvar):
        noise = Variable(torch.randn(mu.size(0), mu.size(1))) # 随机高斯分布的噪声
        z = mu + noise * torch.exp(logvar / 2) # 合成新的输出
        return z

    def forward(self, x): # x : [batch_size, 1, 28, 28]
        out1, out2 = self.en_conv(x), self.en_conv(x)  # en: [batch_size, 8, 8, 8]
        out1 = out1.view(out1.size(0), -1)  # mu: [batch_s, 512]
        out2 = out2.view(out2.size(0), -1)  # mu: [batch_s, 512]

        mu = self.en_fc_mean(out1)      # logvar: [batch_s, HIDDEN_SIZE=128]
        logvar = self.en_fc_sigm(out2)  # logvar: [batch_s, HIDDEN_SIZE=128]
        noise_code = self.addNoise(mu, logvar)  # noise_code: [batch_s, HIDDEN_SIZE=128]


        de = self.de_fc(noise_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 decoded, mu, logvar

net = AutoEncoderNet()

# step 3: ======================================定义优化器和损失函数
# 使用Adam梯度下降
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
# 由于问题不是分类,需要最终得到的编码器生成的图像和原始图像很像,因此需要使用回归用的误差函数,均方差函数。
def loss_func(reconstruct_x, real_data, mu, logvar):
    BCE = lossFunc.binary_cross_entropy(reconstruct_x, real_data, size_average=False)
    KLD = -0.5 * torch.sum((1 + logvar) - mu.pow(2) - logvar.exp())
    return BCE + KLD

# 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)

        # 前向传播
        decoded, mu, logvar = net(img_data)

        # 计算损失值,反向传播更新参数。
        loss = loss_func(decoded, img_data, mu, logvar)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if (iter_count % 100 == 0):
            print('Iter: {}, Loss: {:.4}'.format(iter_count, loss.item(), ))

            sample = Variable(torch.randn(SAMPLE_BATCH_SIZE, HIDDEN_SIZE))
            sample = net.de_conv(net.de_fc(sample).view(SAMPLE_BATCH_SIZE, 8, 8, 8)).cpu()

            save_image(sample.data.view(SAMPLE_BATCH_SIZE, 3, 64, 64), 'D:/software/Anaconda3/doc/3D_Img/VAE2/test_%d.png' % (iter_count))
            print()
        iter_count = iter_count + 1

效果嘛,暂时不知道为何会这么模糊,各位要是知道请给我留言说明一下,我只有16张鸣人的照片。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_29367075/article/details/109126817