Gan+FashionMNIS+代码详解

目录

一、引言

二、数据集

三、环境配置

四、准备代码

1、包的导入

2、数据集准备

五、Gan的主要结构

1、生成器

2、判别器

3、优化器和损失函数

4、随机正态分布

六、绘图函数

七、Gan的训练

1、训练判别器

2、训练生成器

3、计算平均loss并画图

4、保存模型

八、生成结果

九、总结

十、后续


一、引言

        生成对抗网络(Generative Adversarial Network),相信想学习人工智能的大家对gan耳熟能详,gan是一种机器学习模型,由两个主要部分组成:生成器(Generator)和判别器(Discriminator)。生成器试图生成看起来像真实样本的数据,而判别器则试图区分生成器生成的数据和真实数据。它们通过对抗训练的方式相互竞争和提升,最终使得生成器能够生成足够逼真的数据,以至于判别器无法轻易区分生成的数据和真实的数据

        对gan相关知识的讲解的博客非常多,笔者这里就不赘述,直接上代码讲解。

二、数据集

        数据集仍然采用上一个博客中介绍的Fashion-MNIST,这里也不在介绍,具体可以看上一篇文章。

链接:CNN+FashionMNIST+分类+代码详解-CSDN博客

数据集获取链接:GitHub - zalandoresearch/fashion-mnist: A MNIST-like fashion product database. Benchmark

三、环境配置

 首先是笔者使用的IDE为PyCharm2022.1.3专业版。

 主要的虚拟环境的配置为:

  • python                       3.9
  • torch                          2.0.1+cu117
  • torchaudio                 2.0.2+cu117
  • torchvision                0.15.2+cu117
  • numpy                      1.26.4
  • pillow                        10.4.0

        虚拟环境最重要的是兼容性,电脑可以使用cuda的最好安装编译时与 CUDA  兼容的PyTorch,可以加快训练和推理的速度。

四、准备代码

1、包的导入

import os

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np

import matplotlib
matplotlib.use('TkAgg')  # 或者 'Qt5Agg'
import matplotlib.pyplot as plt

import torchvision
from torchvision import transforms

2、数据集准备

# 定义复杂的预处理过程
transform = transforms.Compose([
    transforms.ToTensor(),  # 转换为张量
    transforms.Normalize((0.5,), (0.5,))  # 归一化到[-1, 1]
])

train_ds = torchvision.datasets.FashionMNIST('data',
                                      train=True,
                                      transform=transform,
                                      download=True)  # 定义Fashion_MNIST数据集

dataloader = torch.utils.data.DataLoader(train_ds, batch_size=64, shuffle=True)  # 加载数据集,打乱,batch_size设置为64

1)预处理:

  • transforms.Compose: 这是一个用于组合多个数据预处理操作的函数。

  • transforms.ToTensor(): 这个操作将PIL图像或numpy.ndarray转换为PyTorch的张量(tensor)。这是深度学习框架中常用的数据表示方式。

  • transforms.Normalize((0.5,), (0.5,)): 这个操作将图像张量进行归一化处理。对于每个通道,它将张量的数值从[0, 1]范围归一化到[-1, 1]范围。具体地,它执行以下转换: input[channel] = (input[channel] - mean[channel]) / std[channel],其中mean是0.5,std是0.5。

2)加载数据集

train_ds: 使用torchvision.datasets.FashionMNIST创建了一个FashionMNIST的训练数据集实例。传入的参数包括:

  • 'data': 指定数据集存储的位置。
  • train=True: 表示加载训练集数据。
  • transform=transform: 将之前定义的transform应用到数据集中的每个样本上,即在加载数据时进行预处理。

dataloader: 使用torch.utils.data.DataLoader创建了一个数据加载器,用于实现数据的批处理和打乱顺序。具体参数包括:

  • train_ds: 上面定义的FashionMNIST训练数据集。
  • batch_size=64: 指定每个批次加载的样本数为64。
  • shuffle=True: 表示在每个epoch开始时,对数据进行随机打乱,以增加训练过程的随机性,有助于模型的泛化能力。

五、Gan的主要结构

1、生成器

#定义生成器
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            nn.Linear(100, 256),
            nn.LeakyReLU(),
            nn.Linear(256, 512),
            nn.LeakyReLU(),
            nn.Linear(512, 28 * 28),
            nn.Tanh()  # 对于-1, 1之间的数据分布,Tanh效果最好。输出的取值范围是-1,1之间
        )

    def forward(self, x):  # 前向传播,x 表示长度为100 的noise输入
        img = self.main(x)  # 将x输入到main模型中 得到img
        img = img.view(-1, 28, 28)  # 通过view函数reshape成(-1,28,28,1)
        return img

        输入是长度为100的正态分布随机数

  • nn.Linear: 这些层定义了全连接神经网络的线性变换。在这里,Generator包含三个线性层:

    • 第一个 nn.Linear(100, 256) 接受一个长度为100的噪声向量作为输入,并输出一个大小为256的中间表示。
    • 第二个 nn.Linear(256, 512) 将256维的中间表示映射到一个更大的512维空间。
    • 第三个 nn.Linear(512, 28*28) 最后将512维的向量映射到一个长度为784的向量,对应于28x28像素的图像展开为784个像素值。
  • nn.LeakyReLU: 在每个线性层后面添加了LeakyReLU:激活函数,用于引入非线性特性,LeakyReLU是对ReLU的改进,通过引入一个小的负斜率来避免神经元死亡问题。。

  • nn.Tanh: 最后一层使用Tanh激活函数,将输出限制在[-1, 1]的范围内。这是因为MNIST数据集中的像素值通常在[0, 1]之间,因此将生成器的输出限制在[-1, 1]有助于匹配输入数据的范围。

2、判别器

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()  # 继承父类的属性
        self.main = nn.Sequential(
            nn.Linear(28 * 28, 512),  # 输入一张图片28,8,然后展平成28*28,再卷积到256
            nn.BatchNorm1d(256),  # 批标准化层
            nn.LeakyReLU(),
            nn.Linear(512, 256),
            nn.BatchNorm1d(512),  # 批标准化层
            nn.LeakyReLU(),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, x):  # x输入的是28,28的图片
        x = x.view(-1, 28 * 28)  # 展平
        x = self.main(x)
        return x

        输入为生成器生成的28*28的图片,输出为判别器对其打的分数,即输出为二分类的概率值,输出使用sigmoid激活 0-1。

1)初始化方法 (__init__):

  • self.main: 使用nn.Sequential定义了判别器的主要神经网络结构,包含了几个线性层和激活函数层。

2)神经网络结构 (self.main):

  • nn.Linear(28 * 28, 512): 输入层到第一个隐藏层,将28x28大小的图像展平成784维向量,然后映射到512维的隐藏表示。
  • nn.LeakyReLU(0.2): LeakyReLU激活函数,斜率为0.2。LeakyReLU相比ReLU能够在输入为负时引入小的梯度,避免神经元死亡问题。
  • nn.Linear(512, 256): 第二个隐藏层,将512维的隐藏表示映射到256维。
  • nn.LeakyReLU(0.2): LeakyReLU激活函数。
  • nn.Linear(256, 1): 输出层,将256维的向量映射到一个标量,用于表示真假概率。
  • nn.Sigmoid(): Sigmoid激活函数,将输出限制在[0, 1]之间,表示概率值,用于对二分类结果进行概率化输出。

3)前向传播方法 (forward):

  • 接收输入 x,大小为(batch_size, 1, 28, 28),其中 batch_size 是批处理大小。
  • x.view(-1, 28 * 28): 将输入的图像张量展平成一维向量,大小为(batch_size, 784)。
  • self.main(x): 将展平后的向量输入到判别器的主模型中,得到输出结果,大小为(batch_size, 1)。

3、优化器和损失函数

#初始化模型、优化器及损失计算函数
device = 'cuda' if torch.cuda.is_available() else 'cpu'#默认使用cuda,否则cpu
#%%
gen = Generator().to(device)#初始化Generator模型
dis = Discriminator().to(device)#初始化Discriminator模型
#%%
d_optim = torch.optim.Adam(dis.parameters(), lr=0.0001)#定义优化器,学习率
g_optim = torch.optim.Adam(gen.parameters(), lr=0.0001)
# 定义损失函数

loss_fn = torch.nn.BCELoss()#二分类判别模型

1)设备选择

  • torch.cuda.is_available(): 检查当前系统是否支持CUDA。
  • 如果支持CUDA,则将device设为'cuda',表示使用GPU加速;否则设为'cpu',使用CPU。

2)初始化生成器和判别器模型

  • Generator()Discriminator() 分别是之前定义的生成器和判别器模型。
  • .to(device): 将模型移动到指定的设备,这里根据前面检测到的CUDA是否可用,选择将模型放在GPU上或者CPU上进行运算。

3)定义优化器

  • torch.optim.Adam: 使用Adam优化算法
  • dis.parameters()gen.parameters(): 分别传入判别器和生成器模型的参数,用于优化器更新模型参数。
  • lr=0.0001: 学习率设置为0.0001,控制每次参数更新的步长大小。

4)定义损失函数

  • torch.nn.BCELoss(): 二分类交叉熵损失函数,用于计算生成器和判别器在二分类任务中的损失。
  • 在GAN中,判别器的目标是最小化真实样本和生成样本之间的差别,生成器的目标是最大化判别器无法区分真实样本和生成样本的能力。BCELoss是常用的损失函数之一,适合处理二分类问题。

4、随机正态分布

#定义输入随机正态分布
test_input = torch.randn(16, 100, device=device)#生成长度为100的一个批次16张的随机噪声输入

六、绘图函数

#绘图函数
def gen_img_plot(model, test_input,epoch, save_path):#每次都给一个同样的test_input正态分布随机数
    prediction = np.squeeze(model(test_input).detach().cpu().numpy())#detach用来截断梯度,放到cpu上,转换为numpy,squeeze用于去掉维度为一的值,鲁棒性更高===>28*28的数组
    fig = plt.figure(figsize=(4, 4))#绘制16张图片
    for i in range(16):#循环
        plt.subplot(4, 4, i+1)#四行四列的第一张
        plt.imshow((prediction[i] + 1)/2)#转换成0,1之间的数值(预测的结果恢复到0,1之间
        plt.axis('off')#关闭
    # plt.show()
    plt.savefig(save_path.format(epoch))
    plt.close(fig)

1)形参

def gen_img_plot(model, test_input,epoch, save_path)
  • model: 输入生成器模型对象,用于生成图像。
  • test_input: 输入生成器的测试输入,长度为100的正态分布随机数。
  • epoch: 当前的epoch数,用于保存生成的图像时命名文件。
  • save_path: 保存图像的路径格式,包含占位符 {} 用于插入epoch数。

2)模型预测

prediction = np.squeeze(model(test_input).detach().cpu().numpy())
  • model(test_input): 调用生成器模型,输入test_input,生成图像的预测结果。
  • .detach().cpu().numpy(): 将生成的张量从计算图中分离,移动到CPU上,并转换为NumPy数组。
  • np.squeeze(): 去除数组中维度为1的维度,使得数组的维度更加符合预期(从 (1, 28, 28) 变为 (28, 28))。

3)绘图过程

for i in range(16):#循环
    plt.subplot(4, 4, i+1)#四行四列的第一张
    plt.imshow((prediction[i] + 1)/2)#转换成0,1之间的数值(预测的结果恢复到0,1之间
    plt.axis('off')#关闭
# plt.show()
  • fig = plt.figure(figsize=(4, 4)): 创建一个大小为 4x4 英寸的图像。
  • for i in range(16):: 循环绘制16张图像。
    • plt.subplot(4, 4, i+1): 在4行4列的布局中,绘制第i+1张图像。
    • plt.imshow((prediction[i] + 1) / 2): 显示预测的图像,prediction[i] 是生成器预测的第i张图像,+1 并除以2是将生成的图像数据从[-1, 1]范围恢复到[0, 1]范围。
    • plt.axis('off'): 关闭坐标轴显示。

4)保存图像

plt.savefig(save_path.format(epoch))
plt.close(fig)
  • plt.savefig(save_path.format(epoch)): 将当前绘制的图像保存到指定路径,并根据当前epoch数命名文件。
  • plt.close(fig): 关闭当前图像,释放资源。

七、Gan的训练

1、训练判别器

# 训练循环
for epoch in range(20):  # 训练20轮
    d_epoch_loss = 0
    g_epoch_loss = 0  # 初始化损失函数为0
    count = len(dataloader)  # 返回批次数,len(dataset)返回样本数
    for step, (img, _) in enumerate(dataloader):  # _表示标签,这里生成模型用不到,enumerate用于对dataloader迭代
        img = img.to(device)  # 将照片上传到设备上
        size = img.size(0)  # 获批次大小根据这个大小来输入我们随机噪声的输入大小
        random_noise = torch.randn(size, 100, device=device)  # 生成噪声随机数,大小个数是size

        # 训练判别器
        d_optim.zero_grad()  # 将梯度归0

        real_output = dis(img)  # 判别器输入真实的图片,real_output对真实图片的预测结果 真实图片为1,假图片为0
        d_real_loss = loss_fn(real_output,
                              torch.ones_like(real_output))  # 得到判别器在真实图像上的损失  ones_like:全1数组
        d_real_loss.backward()  # 反向传播,计算梯度

        gen_img = gen(random_noise)
        # 判别器输入生成的图片,fake_output对生成图片的预测
        fake_output = dis(
            gen_img.detach())  # 这里阶段梯度是因为,这里通过对判别器输入生成图片去计算损失是用来优化判别器的。对生成器的参数暂时不做优化。所以梯度不用再传递到生成器模型当中了,我们希望fake_output被判定为0
        d_fake_loss = loss_fn(fake_output,
                              torch.zeros_like(fake_output))  # 得到判别器在生成图像上的损失,zeros_like:全0数组
        d_fake_loss.backward()  # 同样计算梯度


        d_loss = d_real_loss + d_fake_loss  # 判别器的总损失(两部分)
        d_optim.step()  # 进行优化

2、训练生成器

        # 训练生成器
        g_optim.zero_grad()  # 梯度归零
        fake_output = dis(gen_img)  # 将生成图片放到判别器当中--不要梯度截断
        g_loss = loss_fn(fake_output,  # 我们这里就希望fake_output被判定为1用来优化生成器
                         torch.ones_like(fake_output))  # 生成器的损失
        g_loss.backward()  # 计算梯度
        g_optim.step()  # 权重优化

        with torch.no_grad():  # 两个模型的损失函数做累加(不需要计算梯度)---每个批次累加==一个epoch
            d_epoch_loss += d_loss
            g_epoch_loss += g_loss

3、计算平均loss并画图

    with torch.no_grad():  # 得到平均loss
        d_epoch_loss /= count
        g_epoch_loss /= count
        D_loss.append(d_epoch_loss.item())
        G_loss.append(g_epoch_loss.item())  # 这样列表当中会保存每个epoch的平均loss
        print('Epoch:', epoch)  # 打印当前epoch
        # 保存每个epoch的图片
        save_path = './show/the generated images_2/epoch_{}.png'
        os.makedirs(save_path, exist_ok=True)  # 自动创建文件夹,如果不存在的话
        gen_img_plot(gen, test_input, epoch, save_path)
        # gen_img_plot(gen, test_input)  # 绘图

4、保存模型

os.makedirs("./model_2", exist_ok=True)  # 自动创建文件夹,如果不存在的话
torch.save(gen.state_dict(), './model_2/generator.pth')
torch.save(dis.state_dict(), './model_2/discriminator.pth')
print("Models saved.")

八、生成结果

九、总结

        从上面的图中可以看出,从刚开始的全是随机的的噪声形成的图片,最后经过10轮训练之后衣物的形状和轮廓已经比较清晰了,二十轮训练之后,Gan生成的图片已经非常清晰了,衬衫,裤子,靴子已经非常明显,说明我们的Gan网络是搭建成功了。

        在训练gan的时候有几个注意的点,首先是预处理阶段不可以加上随机旋转或者是翻转图片的函数,然后是损失函数可以选择更加合理的Wasserstein GAN(WGAN)损失函数,但是对生成器得有一定的更改。最后还可以对生成器和鉴别器的网络进行优化,有可能可以实现更加优秀的效果。

十、后续

后续可能文章的计划安排:

        Mamba、loss function等等;

        语音领域相关基础知识;

        还可能会分享一些电路方面的内容。

敬请期待!

  • 39
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值