Pytorch 第十四回:神经网络编码器——变分自动编解码器

Pytorch 第十四回:神经网络编码器------变分自动编解码器

本次开启深度学习第十四回,基于Pytorch的神经网络编码器。本回分享VAE变分自动编码器。在本回中,通过minist数据集来分享如何建立一个变分自动编码器。接下来给大家分享具体思路。

本次学习,借助的平台是PyCharm 2024.1.3,python版本3.11 numpy版本是1.26.4,pytorch版本2.0.0

文章目录


前言

讲述模型前,先讲述两个概念,统一下思路:

1 变分自动编码

变分自动编码(Variational Auto-Encoder,VAE) 是一种通过"概率压缩+结构约束"实现数据生成与特征提取的深度学习模型。简而言之,编码器不直接输出一个隐藏变量,而是输出一个均值和一个方差,这两个变量刻画了隐藏变量的高斯分布。这样,可以从这个分布中随机采样隐藏变量,再用解码器生成新图片。其结构图如下所示:

注:

VAE多用在数据生成、插值、半监督学习等场景中。VAE是自动编码器在生成能力上的‌概率化扩展‌,通过引入‌潜在空间分布约束‌和‌变分推断优化目标‌,克服了传统自动编码器无法生成新样本的缺陷。

2 证据下界

证据下界(Evidence Lower Bound, ELBO)‌ 是VAE模型优化的核心,其本质是‌对数据真实分布的下界近似‌。ELBO可拆解为两部分,分别对应VAE的两个核心目标:一个是重构项,即解码器从隐藏变量 z 生成的数据 x 与原始输入 x 的匹配程度(本代码中为均方误差MES);另一个是KL散度项(正则化项),约束近似后验分布 q(z∣x) 与先验分布 p(z)(通常为标准正态分布)的相似性,其作用为防止过拟合,保证潜在空间连续性(使 z 的分布平滑,便于生成新样本)。

注:为何需要证据下界?

1)VAE的目标是建模输入数据分布 ,但由于数据复杂(如图像、文本),直接建模非常困难。因此引入隐藏变量 z,借助隐藏变量生成数据的方式得到数据分布 ,即隐变量建模。

2)但这个过程中积分难以直接计算,导致优化目标不可行。因此采用一个近似分布 (编码器)逼近真实后验分布 ,进而推导出一个可优化的下界(ELBO)(换句话说,就是用近似的代替真实的进行积分计算),即变分推断。

闲言少叙,直接展示逻辑,先上引用:

c 复制代码
import os
import torch
import torch.nn.functional as F
from torch import nn
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision import transforms as tfs
from torchvision.utils import save_image
import time

一、数据准备

首先准备MNIST数据集,并进行数据的预处理。关于数据集、数据加载器的介绍,可以查看第五回内容。

c 复制代码
data_treating = tfs.Compose([
    tfs.ToTensor(),
    tfs.Normalize([0.5], [0.5])
])
train_set = MNIST('./data', transform=data_treating)
train_data = DataLoader(train_set, batch_size=64, shuffle=True)

二、使用步骤

1、定义模型

模型由五部分组成,分别是初始化函数、编码函数、解码函数、参数重构函数、前向传播函数。各函数的连接对应上面所展示的VAE结构图。即,编码器输出均值和方差,再经过重构函数进行参数重构,最后通过解码器传出新的数据。在初始化函数中,c21用来获得编码器的均值参数,c22用获得编码器的对数方差参数。其代码如下:

c 复制代码
class vae_net(nn.Module):
    def __init__(self):
        super(vae_net, self).__init__()
        self.c1 = nn.Linear(28 * 28, 400)
        self.c21 = nn.Linear(400, 20)
        self.c22 = nn.Linear(400, 20)
        self.c3 = nn.Linear(20, 400)
        self.c4 = nn.Linear(400, 28*28)

    def encode(self, x):
        code1 = F.relu(self.c1(x))
        return self.c21(code1), self.c22(code1)

    # 重参数化
    def reparameterization(self, b, a):
        value1 = torch.exp(a.mul(0.5))
        value2 = torch.FloatTensor(value1.size()).normal_()
        if torch.cuda.is_available():
            value2 = value2.cuda()
        return value2.mul(value1).add_(b)

    def decode(self, x):
        code2 = F.relu(self.c3(x))
        return F.tanh(self.c4(code2))

    def forward(self, x):
        b, a = self.encode(x)
        value = self.reparameterization(b, a)
        return self.decode(value), b, a

2、定义损失函数和优化函数

本次定义的损失函数比较特殊,不能和之前分享的内容一样,直接采用均方误差损失函数计算的损失值。这里,需要计算KL散度项,通过KL散度约束获得模型的损失函数。具体代码如下所示:

c 复制代码
reconstruction_function = nn.MSELoss(reduction='sum')
optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)

def loss_f(recons_x, x, b, a):
    MSE = reconstruction_function(recons_x, x)
    KLD_element = b.pow(2).add_(a.exp()).mul_(-1).add_(1).add_(a)
    KLD = torch.sum(KLD_element).mul_(-0.5)
    return MSE + KLD

3、定义图片生成函数

模型训练后,需要将图片进行保存。由于训练时修改了数据的格式,因此这里需要还原数据的图片格式,代码如下。

c 复制代码
def change_image(x):
    x = 0.5 * (x + 1.)
    x = x.clamp(0, 1)
    x = x.view(x.shape[0], 1, 28, 28)
    return x

三、模型训练

1、实例化模型

这里实例化了一个变分自动编码的模型,同时为了加快模型训练,引入GPU进行训练(如何引入GPU进行训练,可以查看第六回

c 复制代码
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("device=", device)
net = vae_net().to(device)

2、迭代训练模型

进行100次迭代训练,并将生成的图片保存。

c 复制代码
time1 = time.time()
for e in range(100):
    for image, _ in train_data:
        image = image.view(image.shape[0], -1)
        image = image.to(device)
        optimizer.zero_grad()
        recons_image, b, a = net(image)
        loss = loss_f(recons_image, image, b, a) / image.shape[0]
        loss.backward()
        optimizer.step()
        time_consume = time.time() - time1
    if (e + 1) % 10 == 0:
        print('epoch:	{},	Loss:	{:.4f},consume time:{:.3f}s'.format(e + 1, loss, time_consume))
        new_image = change_image(recons_image.cpu().data)
        if not os.path.exists('./vae_image'):
            os.mkdir('./vae_image')
        save_image(new_image, './vae_image/image_{}.png'.format(e + 1))

3、生成图片展示

训练10次的图片

训练100次的图片

相对来说,经过100次迭代训练,生成的数字轮廓还是比较清晰的。

总结

1)数据准备:准备MNIST集;

2)模型准备:定义变分自动编码模型、损失函数和优化器;

3)数据训练:实例化模型并训练,生成新的图片数据

相关推荐
小学生搞程序9 分钟前
学习Python的优势体现在哪些方面?
开发语言·python·学习
_玖-幽10 分钟前
Python 数据分析01 环境搭建教程
大数据·python·jupyter
databook15 分钟前
『Plotly实战指南』--面积图绘制与应用
python·数据分析·数据可视化
carpell25 分钟前
【双指针法】:这么常用的你怎么能不知道
python·链表·字符串·数组·双指针法
pound12730 分钟前
第五章.python函数
windows·python·microsoft
仙人掌_lz32 分钟前
企业年报问答RAG挑战赛冠军方案:从零到SotA,一战封神
python·gpt·ai·llm·rag·问答·年报
Code_流苏35 分钟前
《Python星球日记》第27天:Seaborn 可视化
python·数据可视化·python入门·seaborn·统计图表
过期动态1 小时前
【动手学深度学习】LeNet:卷积神经网络的开山之作
人工智能·python·深度学习·神经网络·机器学习·分类·cnn
晴天毕设工作室1 小时前
计算机毕业设计指南
java·开发语言·python·计算机网络·课程设计
明月看潮生1 小时前
青少年编程与数学 02-016 Python数据结构与算法 16课题、贪心算法
python·算法·青少年编程·贪心算法·编程与数学