G1学习打卡

发布于:2025-04-09 ⋅ 阅读:(25) ⋅ 点赞:(0)
import argparse
import os
import numpy as np
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch.nn as nn
import torch

## 创建文件夹
os.makedirs(r"C:\Users\11054\Desktop\kLearning\G1/images/", exist_ok=True)         # 记录训练过程的图片效果
os.makedirs(r"C:\Users\11054\Desktop\kLearning\G1/save/", exist_ok=True)           # 训练完成时模型保存的位置
os.makedirs(r"C:\Users\11054\Desktop\kLearning\G1/datasets/mnist", exist_ok=True)  # 下载数据集存放的位置

## 超参数配置
n_epochs  = 50
batch_size= 64
lr        = 0.0002
b1        = 0.5
b2        = 0.999
n_cpu     = 2
latent_dim= 100
img_size  = 28
channels  = 1
sample_interval=500

# 图像的尺寸:(1, 28, 28),  和图像的像素面积:(784)
img_shape = (channels, img_size, img_size)
img_area = np.prod(img_shape)

# 设置cuda:(cuda:0)
cuda = True if torch.cuda.is_available() else False
print(cuda)
C:\Users\11054\.conda\envs\py311\Lib\site-packages\torch\utils\_pytree.py:185: FutureWarning: optree is installed but the version is too old to support PyTorch Dynamo in C++ pytree. C++ pytree support is disabled. Please consider upgrading optree using `python3 -m pip install --upgrade 'optree>=0.13.0'`.
  warnings.warn(


False
# mnist数据集下载
mnist = datasets.MNIST(
    root=r'C:\Users\11054\Desktop\kLearning\G1/datasets/', train=True, download=True, transform=transforms.Compose(
            [transforms.Resize(img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]),
)
100%|██████████████████████████████████████████████████████████████████████████████| 9.91M/9.91M [01:23<00:00, 119kB/s]
100%|██████████████████████████████████████████████████████████████████████████████| 28.9k/28.9k [00:00<00:00, 136kB/s]
100%|██████████████████████████████████████████████████████████████████████████████| 1.65M/1.65M [00:03<00:00, 459kB/s]
100%|█████████████████████████████████████████████████████████████████████████████| 4.54k/4.54k [00:00<00:00, 2.75MB/s]
# 配置数据到加载器
dataloader = DataLoader(
    mnist,
    batch_size=batch_size,
    shuffle=True,
)

三、定义模型

  1. 定义鉴别器
    这段代码定义了一个名为Discriminator的类,它继承自nn.Module。这个类是一个判别器模型,用于判断输入图像是否为真实图像。下面是对代码中每一行的详细解释:
  • class Discriminator(nn.Module)::定义一个名为Discriminator的类,它继承自nn.Module。nn.Module是PyTorch中的一个基类,用于构建神经网络模型。
  • def init(self)::定义类的构造函数,用于初始化模型的参数和层。
  • super(Discriminator,self).init():调用父类nn.Module的构造函数,以确保正确地初始化模型。
  • self.model = nn.Sequential(:创建一个nn.Sequential对象,它是一个容器,用于按顺序堆叠多个神经网络层。
  • nn.Linear(img_area,512),:添加一个线性层,输入大小为img_area(图像区域的像素数),输出大小为512。这个层用于将输入图像展平并映射到一个新的特征空间。
  • nn.LeakyReLU(0.2,inplace=True),:添加一个Leaky ReLU激活函数,其负斜率为0.2。inplace=True表示在原始数据上进行操作,以节省内存。
  • nn.Linear(512,256),:添加一个线性层,输入大小为512,输出大小为256。这个层用于进一步将特征映射到更小的特征空间。
  • nn.LeakyReLU(0.2,inplace=True),:再次添加一个Leaky ReLU激活函数,与之前的层相同。
  • nn.Linear(256,1),:添加一个线性层,输入大小为256,输出大小为1。这个层用于将特征映射到一个标量值,用于表示输入图像的真实性。
  • nn.Sigmoid(),:添加一个Sigmoid激活函数,将输出值限制在0到1之间。这可以解释为输入图像为真实图像的概率。
  • def forward(self, img)::定义模型的前向传播函数,用于计算输入图像的输出。
  • img_flat = img.view(img.size(0),-1):将输入图像img展平为一个一维向量。img.size(0)表示批量大小,-1表示自动计算剩余维度的大小。
  • validity = self.model(img_flat):将展平后的图像传递给之前定义的nn.Sequential模型,得到一个表示图像真实性的标量值。
  • return validity:返回计算得到的图像真实性值。
# 定义判别器
# 将图片28x28展开成784,然后通过多层感知器,中间经过斜率设置为0.2的LeakyReLU激活函数,
# 最后接sigmoid激活函数得到一个0到1之间的概率进行二分类
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(img_area, 512),         # 输入特征数为784,输出为512
            nn.LeakyReLU(0.2, inplace=True),  # 进行非线性映射
            nn.Linear(512, 256),              # 输入特征数为512,输出为256
            nn.LeakyReLU(0.2, inplace=True),  # 进行非线性映射
            nn.Linear(256, 1),                # 输入特征数为256,输出为1
            nn.Sigmoid(),                     # sigmoid是一个激活函数,二分类问题中可将实数映射到[0, 1],作为概率值, 多分类用softmax函数
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1) # 鉴别器输入是一个被view展开的(784)的一维图像:(64, 784)
        validity = self.model(img_flat)      # 通过鉴别器网络
        return validity
# 定义生成器
# 输入一个100维的0~1之间的高斯分布,然后通过第一层线性变换将其映射到256维,
# 然后通过LeakyReLU激活函数,接着进行一个线性变换,再经过一个LeakyReLU激活函数,
# 然后经过线性变换将其变成784维,最后经过Tanh激活函数是希望生成的假的图片数据分布, 能够在-1~1之间。
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        ## 模型中间块儿
        def block(in_feat, out_feat, normalize=True):        # block(in, out )
            layers = [nn.Linear(in_feat, out_feat)]          # 线性变换将输入映射到out维
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8)) # 正则化
            layers.append(nn.LeakyReLU(0.2, inplace=True))   # 非线性激活函数
            return layers
        ## prod():返回给定轴上的数组元素的乘积:1*28*28=784
        self.model = nn.Sequential(
            *block(latent_dim, 128, normalize=False), # 线性变化将输入映射 100 to 128, 正则化, LeakyReLU
            *block(128, 256),                         # 线性变化将输入映射 128 to 256, 正则化, LeakyReLU
            *block(256, 512),                         # 线性变化将输入映射 256 to 512, 正则化, LeakyReLU
            *block(512, 1024),                        # 线性变化将输入映射 512 to 1024, 正则化, LeakyReLU
            nn.Linear(1024, img_area),                # 线性变化将输入映射 1024 to 784
            nn.Tanh()                                 # 将(784)的数据每一个都映射到[-1, 1]之间
        )
    ## view():相当于numpy中的reshape,重新定义矩阵的形状:这里是reshape(64, 1, 28, 28)
    def forward(self, z):                           # 输入的是(64, 100)的噪声数据
        imgs = self.model(z)                        # 噪声数据通过生成器模型
        imgs = imgs.view(imgs.size(0), *img_shape)  # reshape成(64, 1, 28, 28)
        return imgs
## 创建生成器,判别器对象
generator = Generator()
discriminator = Discriminator()

## 首先需要定义loss的度量方式  (二分类的交叉熵)
criterion = torch.nn.BCELoss()

## 其次定义 优化函数,优化函数的学习率为0.0003
## betas:用于计算梯度以及梯度平方的运行平均值的系数
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(b1, b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))

## 如果有显卡,都在cuda模式中运行
if torch.cuda.is_available():
    generator     = generator.cuda()
    discriminator = discriminator.cuda()
    criterion     = criterion.cuda()
## 进行多个epoch的训练
for epoch in range(n_epochs):                   # epoch:50
    for i, (imgs, _) in enumerate(dataloader):  # imgs:(64, 1, 28, 28)     _:label(64)

        ## =============================训练判别器==================
        ## view(): 相当于numpy中的reshape,重新定义矩阵的形状, 相当于reshape(128,784)  原来是(128, 1, 28, 28)
        imgs = imgs.view(imgs.size(0), -1)    # 将图片展开为28*28=784  imgs:(64, 784)
        real_img = Variable(imgs)    # 将tensor变成Variable放入计算图中,tensor变成variable之后才能进行反向传播求梯度
        real_label = Variable(torch.ones(imgs.size(0), 1))     ## 定义真实的图片label为1
        fake_label = Variable(torch.zeros(imgs.size(0), 1)) ## 定义假的图片的label为0

        ## ---------------------
        ##  Train Discriminator
        ## 分为两部分:1、真的图像判别为真;2、假的图像判别为假
        ## ---------------------
        ## 计算真实图片的损失
        real_out = discriminator(real_img)            # 将真实图片放入判别器中
        loss_real_D = criterion(real_out, real_label) # 得到真实图片的loss
        real_scores = real_out                        # 得到真实图片的判别值,输出的值越接近1越好
        ## 计算假的图片的损失
        ## detach(): 从当前计算图中分离下来避免梯度传到G,因为G不用更新
        z = Variable(torch.randn(imgs.size(0), latent_dim))     ## 随机生成一些噪声, 大小为(128, 100)
        fake_img    = generator(z).detach()                                    ## 随机噪声放入生成网络中,生成一张假的图片。
        fake_out    = discriminator(fake_img)                                  ## 判别器判断假的图片
        loss_fake_D = criterion(fake_out, fake_label)                       ## 得到假的图片的loss
        fake_scores = fake_out                                              ## 得到假图片的判别值,对于判别器来说,假图片的损失越接近0越好
        ## 损失函数和优化
        loss_D = loss_real_D + loss_fake_D  # 损失包括判真损失和判假损失
        optimizer_D.zero_grad()             # 在反向传播之前,先将梯度归0
        loss_D.backward()                   # 将误差反向传播
        optimizer_D.step()                  # 更新参数

        ## -----------------
        ##  Train Generator
        ## 原理:目的是希望生成的假的图片被判别器判断为真的图片,
        ## 在此过程中,将判别器固定,将假的图片传入判别器的结果与真实的label对应,
        ## 反向传播更新的参数是生成网络里面的参数,
        ## 这样可以通过更新生成网络里面的参数,来训练网络,使得生成的图片让判别器以为是真的, 这样就达到了对抗的目的
        ## -----------------
        z = Variable(torch.randn(imgs.size(0), latent_dim))     ## 得到随机噪声
        fake_img = generator(z)                                             ## 随机噪声输入到生成器中,得到一副假的图片
        output = discriminator(fake_img)                                    ## 经过判别器得到的结果
        ## 损失函数和优化
        loss_G = criterion(output, real_label)                              ## 得到的假的图片与真实的图片的label的loss
        optimizer_G.zero_grad()                                             ## 梯度归0
        loss_G.backward()                                                   ## 进行反向传播
        optimizer_G.step()                                                  ## step()一般用在反向传播后面,用于更新生成网络的参数

        ## 打印训练过程中的日志
        ## item():取出单元素张量的元素值并返回该值,保持原元素类型不变
        if (i + 1) % 300 == 0:
            print(
                "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f] [D real: %f] [D fake: %f]"
                % (epoch, n_epochs, i, len(dataloader), loss_D.item(), loss_G.item(), real_scores.data.mean(), fake_scores.data.mean())
            )
        ## 保存训练过程中的图像
        batches_done = epoch * len(dataloader) + i
        if batches_done % sample_interval == 0:
            save_image(fake_img.data[:25], r"C:\Users\11054\Desktop\kLearning\G1/images/%d.png" % batches_done, nrow=5, normalize=True)
[Epoch 0/50] [Batch 299/938] [D loss: 1.097366] [G loss: 0.851174] [D real: 0.596493] [D fake: 0.427791]
[Epoch 0/50] [Batch 599/938] [D loss: 0.982202] [G loss: 1.788269] [D real: 0.712241] [D fake: 0.456241]
[Epoch 0/50] [Batch 899/938] [D loss: 1.020093] [G loss: 1.027379] [D real: 0.535230] [D fake: 0.255605]
## 保存模型
torch.save(generator.state_dict(), r'C:\Users\11054\Desktop\kLearning\G1/save/generator.pth')
torch.save(discriminator.state_dict(), r'C:\Users\11054\Desktop\kLearning\G1/save/discriminator.pth')

个人总结

  1. GAN的核心思想
    GAN由**生成器(Generator)和判别器(Discriminator)**组成:

生成器:输入随机噪声(如100维高斯分布),输出伪造图像(如28x28的MNIST手写数字)。

判别器:输入真实图像或生成图像,输出一个概率值(0~1),判断图像是否为真。

对抗过程:生成器试图欺骗判别器,判别器则努力识破生成器的伪造,两者在对抗中共同提升。

  1. 关键实现细节
    (1) 模型架构
    生成器:

使用全连接层逐步提升维度(100 → 128 → 256 → 512 → 1024 → 784)。

激活函数:LeakyReLU(负斜率0.2)引入非线性,Tanh将输出限制在[-1, 1]。

批归一化(BatchNorm)加速训练(除第一层外)。

判别器:

输入图像展平为784维,通过全连接层降维(784 → 512 → 256 → 1)。

激活函数:LeakyReLU,输出层用Sigmoid进行二分类。

(2) 损失函数与优化
损失函数:二元交叉熵(BCELoss)。

判别器损失:loss_D = loss_real_D + loss_fake_D(真图判真 + 假图判假)。

生成器损失:loss_G = criterion(fake_out, real_label)(让假图被判为真)。

优化器:Adam(学习率0.0002,动量参数betas=(0.5, 0.999))。

(3) 训练技巧
判别器先更新:固定生成器,优先训练判别器(避免生成器过早“获胜”)。

梯度分离:生成器训练时用detach()切断判别器梯度回传。

结果可视化:定期保存生成图像(save_image),观察生成质量。

  1. 遇到的问题与解决
    生成图像模糊:

原因:生成器过于简单或训练不足。

改进:增加网络深度(如扩展至1024维),延长训练轮数(n_epochs=50)。

模式崩溃(Mode Collapse):

现象:生成器只输出少数几种图像。

缓解:使用更复杂的损失(如Wasserstein GAN)或调整学习率。

硬件限制:

无GPU:训练速度较慢,改用小批量(batch_size=64)和轻量模型。

  1. 学习收获
    理论到实践:从GAN的数学原理(最小化JS散度)到代码实现,理解了对抗训练的动态平衡。

调试经验:通过调整超参数(如学习率、LeakyReLU斜率)观察模型表现。

扩展思考:GAN的变体(如DCGAN、CycleGAN)在图像生成、风格迁移中的应用。


网站公告

今日签到

点亮在社区的每一天
去签到