第G2周:人脸图像生成(DCGAN)

知识点简介

DCGAN,即深度卷积生成对抗网络(Deep Convolutional Generative Adversarial Networks),是一种利用深度神经网络来生成与真实数据分布高度相似的新数据的算法。

  1. 基本结构:DCGAN由两个深度卷积神经网络组成,一个生成器(Generator,记为G)和一个判别器(Discriminator,记为D)。生成器的作用是产生逼真的假数据(如图像),而判别器的任务是区分输入的数据是真实的还是由生成器产生的假数据。
  2. 训练过程:在训练过程中,生成器接收随机噪声作为输入,并输出一组假数据。判别器则同时接收来自真实数据集的真实数据和生成器产生的假数据。它需要对输入数据进行判断,区分它们是真实数据还是由生成器创建的假数据。此时,生成器和判别器的目标相反:生成器试图产生越来越逼真的假数据以“欺骗”判别器,而判别器则努力提高其识别真假数据的能力。
  3. 博弈优化:随着训练的进行,判别器变得越来越擅长于辨别真假数据,这迫使生成器也必须不断改进其生成数据的质量。这个过程可以类比为一个双方不断进步的博弈过程。理想情况下,当判别器再也无法一致地识别出真假数据时,即认为达到了纳什均衡,此时生成器产生的数据与真实数据分布非常接近。
  4. 深度卷积网络:DCGAN特别使用了深度卷积神经网络来实现生成器和判别器,代替了传统GAN中的全连接层。这使得模型能够学习到更复杂的模式,并且更适合处理图像等高维数据。
  5. 架构创新:为了提高GAN的稳定性并使其能够产生更高分辨率的图像,DCGAN对CNN架构进行了一系列的修改。例如,采用了全卷积网络,使用步幅卷积替代池化层,让网络自己学习下采样的方式。
  6. 技巧应用:为了避免模型崩溃和不收敛的问题,DCGAN还引入了一些训练技巧,比如批量归一化(Batch Normalization)、适当的初始化策略和Leaky ReLU激活函数等。

DCGAN模型具有以下特点:

  1. 架构特点
  • DCGAN使用了全卷积网络结构,这意味着在生成器和判别器中都采用了卷积层而不是传统的全连接层。这种结构有助于模型学习空间上的特征,特别是在处理图像数据时。
  • 生成器中采用转置卷积(transposed convolutional layer)进行上采样,而判别器中则使用带有步幅(stride)的卷积来代替池化层,这样做可以让网络自己学习下采样的方式。
  • 除了生成器的输出层和判别器的输入层之外,网络的其他层都使用了批量归一化(Batch Normalization)。这可以稳定学习过程,帮助解决因初始化不当导致的训练问题。
  1. 激活函数的选择
  • 生成器网络中使用ReLU作为激活函数,在输出层使用tanh,这有助于生成介于-1和1之间的像素值,使得生成的图像更加逼真。
  • 判别器网络中使用LeakyReLU作为激活函数,这可以在一定程度上缓解梯度消失的问题,提高模型的学习能力。
  1. 性能提升
  • DCGAN极大地提升了原始GAN的训练稳定性以及生成结果的质量。它将GAN与CNN相结合,奠定了后续几乎所有GAN变体的基本网络架构。
  1. 训练细节
  • 尽管DCGAN在架构上做了很多改进,但它并没有从根本上解决GAN训练不稳定的问题。在训练过程中,仍然需要小心地平衡生成器和判别器的训练频率,通常采取交替训练的策略。

关于转置卷积层的详细解释:

  • 上采样方法:转置卷积是一种可学习的上采样方法,它与传统的插值方法不同,不依赖于固定的算法,而是通过训练来学习如何恢复图像的细节。
  • 与正卷积的关系:转置卷积可以看作是正卷积的逆过程。在某些情况下,经过一系列的卷积层和池化层后,原始数据的空间维度会减小。转置卷积可以用来恢复这些减小的维度,尽管它并不直接尝试恢复到原始的输入值。
  • 工作原理:转置卷积通过维护一个一对一的映射关系来实现上采样,这意味着每个输出像素都对应于输入特征图中的一个像素。在这个过程中,卷积核会根据指定的步幅和填充进行“翻转”,从而得到更大的输出尺寸。
  • 应用范围:转置卷积层在对抗生成网络(如DCGAN)中的应用非常广泛,尤其是在生成器网络中的上采样部分。它帮助生成器逐步构建出高分辨率的图像。

一、前期准备

1.导入第三方库

代码知识点

  1. import torch, random, random, os:导入了PyTorch库(用于深度学习)、random库(用于生成随机数)和os库(用于操作系统相关的操作)。
  2. import torch.nn as nn:导入了PyTorch的神经网络模块,并将其重命名为nn。
  3. import torch.nn.parallel:导入了PyTorch的并行计算模块。
  4. import torch.optim as optim:导入了PyTorch的优化器模块,并将其重命名为optim。
  5. import torch.utils.data:导入了PyTorch的数据加载模块。
  6. import torchvision.datasets as dset:导入了torchvision库的数据集模块,并将其重命名为dset。
  7. import torchvision.transforms as transforms:导入了torchvision库的数据预处理模块,并将其重命名为transforms。
  8. import torchvision.utils as vutils:导入了torchvision库的实用工具模块,并将其重命名为vutils。
  9. import numpy as np:导入了NumPy库,并将其重命名为np。
  10. import matplotlib.pyplot as plt:导入了Matplotlib库的绘图模块,并将其重命名为plt。
  11. import matplotlib.animation as animation:导入了Matplotlib库的动画模块,并将其重命名为animation。
  12. from IPython.display import HTML:从IPython库中导入HTML模块,用于在Jupyter Notebook中显示HTML内容。

------- 接下来的几行代码是设置随机种子,以确保实验的可重复性:

  1. manualSeed = 999:设置一个固定的随机种子值。
  2. print("Random Seed: ", manualSeed):打印出设置的随机种子值。
  3. random.seed(manualSeed):设置Python内置的random库的随机种子。
  4. torch.manual_seed(manualSeed):设置PyTorch的随机种子。
  5. torch.use_deterministic_algorithms(True):启用PyTorch的确定性算法,确保实验的可重复性。
import torch, random, random, os
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML

manualSeed = 999
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
torch.use_deterministic_algorithms(True)

2.设置超参数

代码知识点

  1. dataroot: 数据存储路径,用于指定训练数据的存放位置。
  2. batch_size: 批处理大小,表示每次训练迭代时使用的样本数量。
  3. image_size: 图像尺寸,表示输入图像的大小。
  4. nz: 噪声向量的维度,用于生成器生成假图像时的随机噪声输入。
  5. ngf: 生成器的特征图数量,表示生成器网络中卷积层输出的特征图数量。
  6. ndf: 判别器的特征图数量,表示判别器网络中卷积层输出的特征图数量。
  7. num_epochs: 训练迭代次数,表示整个训练过程需要运行的轮数。
  8. lr: 学习率,控制模型参数更新的速度。
  9. betal: Beta系数,用于平衡生成器和判别器的损失函数。

这些参数对GAN性能的影响是什么呢,该如何去修改这些参数呢

  1. 数据路径(dataroot):确保提供正确且完整的数据路径,以便模型能够访问到训练数据。

  2. 批处理大小(batch_size):根据可用的计算资源和内存限制,选择合适的批处理大小。较小的批处理大小可以减少内存占用,但可能会增加训练时间;较大的批处理大小可以提高训练速度,但可能会增加内存消耗。

  3. 图像尺寸(image_size):根据数据集的特点和模型架构的要求,选择合适的图像尺寸。较小的图像尺寸可以减少计算量和内存占用,但可能会降低生成图像的质量。

  4. 噪声向量维度(nz):噪声向量的维度决定了生成器生成假图像时的随机性。较大的噪声向量维度可以提供更多的随机性,但可能会增加计算量和内存占用。

  5. 生成器特征图数量(ngf)和判别器特征图数量(ndf):这些参数控制了生成器和判别器网络中卷积层输出的特征图数量。增加特征图数量可以提高模型的表达能力,但可能会增加计算量和内存占用。

  6. 训练迭代次数(num_epochs):增加训练迭代次数可以提高模型的训练效果,但可能会导致过拟合。需要根据数据集的大小和模型的复杂度来选择合适的迭代次数。

  7. 学习率(lr):学习率控制模型参数更新的速度。较小的学习率可以使模型收敛得更稳定,但可能需要更多的迭代次数。较大的学习率可以加快收敛速度,但可能会导致不稳定的训练过程。

  8. Beta系数(betal):Beta系数用于平衡生成器和判别器的损失函数。调整Beta系数可以影响生成器和判别器之间的竞争关系,从而影响生成图像的质量。

dataroot = "G:/GAN-Data_2"
batch_size = 128
image_size = 64
nz = 100
ngf = 64
ndf = 64
num_epochs = 50
lr = 0.0002
betal = 0.5

3.导入数据

代码知识点

  1. datasets = dset.ImageFolder(root=dataroot,:创建一个ImageFolder对象,用于从指定的根目录(dataroot)中加载图像数据。
  2. transform=transforms.Compose([:定义一个变换序列,用于对图像进行预处理。
  3. transforms.Resize(image_size),:将图像调整为指定的大小(image_size)。
  4. transforms.CenterCrop(image_size),:从图像中心裁剪出指定大小(image_size)的区域。
  5. transforms.ToTensor(),:将图像转换为张量(tensor)。
  6. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),:对图像进行归一化处理,将像素值范围从[0, 1]映射到[-1, 1]。
  7. ])):结束变换序列的定义。
  8. dataloader = torch.utils.data.DataLoader(datasets,:创建一个DataLoader对象,用于批量加载和处理图像数据。
  9. batch_size=batch_size,:设置每个批次的图像数量(batch_size)。
  10. shuffle=True,:在每个epoch开始时打乱数据顺序。
  11. num_workers=5:设置用于数据加载的工作进程数量。
  12. ):结束DataLoader对象的创建。
  13. device = torch.device("cuda:0" if (torch.cuda.is_available()) else "cpu"):检查是否有可用的GPU设备,如果有则使用GPU,否则使用CPU。
  14. print("使用的设备是: ",device):打印当前使用的设备信息。
  15. real_batch = next(iter(dataloader)):从DataLoader中获取一个批次的图像数据。
  16. plt.figure(figsize=(8, 8)):创建一个8x8英寸的图形窗口。
  17. plt.axis("off"):关闭坐标轴。
  18. plt.title("Training Images"):设置图形的标题为"Training Images"。
  19. plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:24],:将一个批次的图像数据转换为网格形式,并将其转换为NumPy数组,然后进行转置操作。
  20. padding=2,:设置网格中的图像之间的间距为2个像素。
  21. normalize=True).cpu(),(1, 2, 0))):将图像数据从GPU转移到CPU,并进行归一化处理,最后将其转置回原始的形状。

这段代码的主要目的是加载和显示图像数据集。下面是代码的工作流程:

  1. 首先,通过dset.ImageFolder(root=dataroot, transform=transforms.Compose([...]))创建一个ImageFolder对象,用于从指定的根目录(dataroot)中加载图像数据,并对图像进行预处理。预处理包括调整图像大小、中心裁剪、转换为张量以及归一化处理。

  2. 然后,通过torch.utils.data.DataLoader(datasets, batch_size=batch_size, shuffle=True, num_workers=5)创建一个DataLoader对象,用于批量加载和处理图像数据。设置每个批次的图像数量为batch_size,并在每个epoch开始时打乱数据顺序。同时,设置用于数据加载的工作进程数量为5。

  3. 接下来,通过torch.device("cuda:0" if (torch.cuda.is_available()) else "cpu")检查是否有可用的GPU设备,如果有则使用GPU,否则使用CPU,并将结果存储在变量device中。

  4. 打印当前使用的设备信息。

  5. 通过next(iter(dataloader))从DataLoader中获取一个批次的图像数据,并将其存储在变量real_batch中。

  6. 创建一个8x8英寸的图形窗口,关闭坐标轴,并设置图形的标题为"Training Images"。

  7. 最后,通过plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:24], padding=2, normalize=True).cpu(),(1, 2, 0)))将一个批次的图像数据转换为网格形式,并将其转换为NumPy数组,然后进行转置操作。将图像数据从GPU转移到CPU,并进行归一化处理,最后将其转置回原始的形状。最终,通过plt.imshow()函数将处理后的图像数据显示在图形窗口中。

datasets = dset.ImageFolder(root=dataroot,
                           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)),  
                           ]))

dataloader = torch.utils.data.DataLoader(datasets,
                                        batch_size=batch_size,
                                        shuffle=True,
                                        num_workers=5
                                        )

device = torch.device("cuda:0" if (torch.cuda.is_available()) else "cpu")
print("使用的设备是: ",device)

real_batch = next(iter(dataloader))
plt.figure(figsize=(8, 8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:24],
                                        padding=2,
                                        normalize=True).cpu(),(1, 2, 0)))

代码输出
在这里插入图片描述

三、定义模型

1.初始化参数

代码知识点
这段代码是一个权重初始化函数,用于初始化神经网络中的卷积层和批量归一化层的权重。具体解释如下:

  1. def weights_init(m): 定义一个名为weights_init的函数,输入参数为m,表示网络中的一个模块。
  2. classname = m.__class__.__name__ 获取模块m的类名,并将其赋值给变量classname
  3. if classname.find('Conv') != -1: 如果classname中包含字符串'Conv',说明该模块是卷积层。如果找到了子字符串 ‘Conv’(即返回值不为 -1),则执行冒号后面的代码块。
  4. nn.init.normal_(m.weight.data, 0.0, 0.02) 对卷积层的权重进行正态分布初始化,均值为0,标准差为0.02。
  5. elif classname.find('BatchNorm') != -1: 如果classname中包含字符串'BatchNorm',说明该模块是批量归一化层。
  6. nn.init.normal_(m.weight.data, 1.0, 0.02) 对批量归一化层的权重进行正态分布初始化,均值为1,标准差为0.02。
  7. nn.init.constant_(m.bias.data, 0.0) 将批量归一化层的偏置项初始化为常数0。
def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0.0)

2.定义生成器

代码知识点

  1. class Generator(nn.Module)::定义一个名为Generator的类,继承自nn.Module。
  2. def __init__(self)::初始化函数,用于设置生成器模型的结构。
  3. super(Generator, self).__init__():调用父类的初始化函数。
  4. self.main = nn.Sequential(:创建一个顺序容器,用于存储生成器模型的各个层。
  5. nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0,bias=False),:添加一个转置卷积层,输入通道数为nz,输出通道数为ngf *
    8,卷积核大小为4x4,步长为1,填充为0,不使用偏置。
  6. nn.BatchNorm2d(ngf * 8),:添加一个批量归一化层,输入通道数为ngf * 8。
  7. nn.ReLU(True),:添加一个ReLU激活函数,inplace参数设置为True,表示在原处进行操作。
  8. nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1,bias=False),:添加一个转置卷积层,输入通道数为ngf * 8,输出通道数为ngf *
    4,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  9. nn.BatchNorm2d(ngf * 4),:添加一个批量归一化层,输入通道数为ngf * 4。
  10. nn.ReLU(True),:添加一个ReLU激活函数,inplace参数设置为True。
  11. nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1,bias=False),:添加一个转置卷积层,输入通道数为ngf * 4,输出通道数为ngf *
    2,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  12. nn.BatchNorm2d(ngf * 2),:添加一个批量归一化层,输入通道数为ngf * 2。
  13. nn.ReLU(True),:添加一个ReLU激活函数,inplace参数设置为True。
  14. nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1,bias=False),:添加一个转置卷积层,输入通道数为ngf *
    2,输出通道数为ngf,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  15. nn.BatchNorm2d(ngf),:添加一个批量归一化层,输入通道数为ngf。
  16. nn.ReLU(True),:添加一个ReLU激活函数,inplace参数设置为True。
  17. nn.ConvTranspose2d(ngf, 3, 4, 2, 1,bias=False),:添加一个转置卷积层,输入通道数为ngf,输出通道数为3(彩色图像的三个通道),卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  18. nn.Tanh():添加一个双曲正切激活函数,将输出值映射到[-1, 1]区间。
  19. ):结束顺序容器的定义。
  20. def forward(self, input)::定义前向传播函数,接收一个输入张量。
  21. return self.main(input):将输入张量传递给顺序容器,并返回输出结果。
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
        nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0,bias=False),
        nn.BatchNorm2d(ngf * 8),
        nn.ReLU(True),
                          
        nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1,bias=False),
        nn.BatchNorm2d(ngf * 4),
        nn.ReLU(True),                
        
        nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1,bias=False),
        nn.BatchNorm2d(ngf * 2),
        nn.ReLU(True),                
    
        nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1,bias=False),
        nn.BatchNorm2d(ngf),
        nn.ReLU(True),                
        
        nn.ConvTranspose2d(ngf, 3, 4, 2, 1,bias=False),
        nn.Tanh()   
            
        )
    def forward(self, input):
        return self.main(input)

代码知识点

  1. netG = Generator().to(device): 这行代码创建了一个名为netG的生成器对象,并将其移动到指定的设备(如GPU)上进行计算。Generator()是一个自定义的生成器类或函数,用于生成模型的网络结构。

  2. netG.apply(weights_init): 这行代码对生成器netG中的权重进行初始化。weights_init是一个自定义的权重初始化函数或方法,用于设置网络层的初始权重值。

  3. print(netG): 这行代码打印出生成器netG的网络结构信息,以便查看其具体的层和参数配置。

netG = Generator().to(device)
netG.apply(weights_init)
print(netG)

输出
在这里插入图片描述

3.定义鉴别器

代码知识点

  1. class Discriminator(nn.Module)::定义一个名为Discriminator的类,继承自nn.Module
  2. def __init__(self)::初始化函数,用于设置模型的参数和结构。
  3. super(Discriminator, self).__init__():调用父类的初始化函数。
  4. self.main = nn.Sequential(:创建一个nn.Sequential对象,用于定义模型的主要结构。
  5. nn.Conv2d(3, ndf, 4, 2, 1, bias=False),:添加一个卷积层,输入通道数为3,输出通道数为ndf,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  6. nn.LeakyReLU(0.2, inplace=True),:添加一个LeakyReLU激活函数,负斜率为0.2,原地操作。
  7. nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),:添加一个卷积层,输入通道数为ndf,输出通道数为ndf * 2,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  8. nn.BatchNorm2d(ndf * 2),:添加一个批量归一化层,输入通道数为ndf * 2
  9. nn.LeakyReLU(0.2, inplace=True),:添加一个LeakyReLU激活函数,负斜率为0.2,原地操作。
  10. nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),:添加一个卷积层,输入通道数为ndf * 2,输出通道数为ndf * 4,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  11. nn.BatchNorm2d(ndf * 4),:添加一个批量归一化层,输入通道数为ndf * 4
  12. nn.LeakyReLU(0.2, inplace=True),:添加一个LeakyReLU激活函数,负斜率为0.2,原地操作。
  13. nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),:添加一个卷积层,输入通道数为ndf * 4,输出通道数为ndf * 8,卷积核大小为4x4,步长为2,填充为1,不使用偏置。
  14. nn.BatchNorm2d(ndf * 8),:添加一个批量归一化层,输入通道数为ndf * 8
  15. nn.LeakyReLU(0.2, inplace=True),:添加一个LeakyReLU激活函数,负斜率为0.2,原地操作。
  16. nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),:添加一个卷积层,输入通道数为ndf * 8,输出通道数为1,卷积核大小为4x4,步长为1,填充为0,不使用偏置。
  17. nn.Sigmoid():添加一个Sigmoid激活函数,将输出值映射到[0, 1]区间。
  18. ):结束nn.Sequential的定义。
  19. def forward(self, input)::定义前向传播函数,接收输入数据。
  20. return self.main(input):将输入数据传递给模型的主要结构,并返回输出结果。
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        
        self.main = nn.Sequential(
        
            nn.Conv2d(3, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )
    def forward(self, input):
        return self.main(input)
netD = Discriminator().to(device)
netD.apply(weights_init)
print(netD)

输出
在这里插入图片描述

四、训练模型

1.定义训练参数

代码知识点

  1. criterion = nn.BCELoss():定义二进制交叉熵损失函数(Binary Cross Entropy Loss),用于衡量生成器和判别器的损失。
  2. fixed_noise = torch.randn(64, nz, 1, 1, device=device):创建一个随机噪声张量,形状为(64, nz, 1, 1),并将其放置在指定的设备上(例如GPU)。
  3. real_label = 1.:定义真实标签的值为1,用于训练判别器。
  4. fake_label = 0.:定义假标签的值为0,用于训练判别器。
  5. optimizerD = optim.Adam(netD.parameters(), lr =lr, betas=(betal, 0.999)):定义判别器的优化器,使用Adam算法,学习率为lr,beta参数为(betal, 0.999) betal的值越接近1,过去的梯度对当前更新的影响越小,这有助于模型更快地适应新的趋势。
  6. optimizerG = optim.Adam(netG.parameters(), lr =lr, betas=(betal, 0.999)):定义生成器的优化器,使用Adam算法,学习率为lr,beta参数为(betal, 0.999)。
criterion = nn.BCELoss()

fixed_noise = torch.randn(64, nz, 1, 1, device=device)

real_label = 1.
fake_label = 0.

optimizerD = optim.Adam(netD.parameters(), lr =lr, betas=(betal, 0.999))
optimizerG = optim.Adam(netG.parameters(), lr =lr, betas=(betal, 0.999))

2.训练模型

代码知识点

  1. img_list = []:创建一个空列表,用于存储生成的图像。
  2. G_losses = []:创建一个空列表,用于存储生成器的损失值。
  3. D_losses = []:创建一个空列表,用于存储判别器的损失值。
  4. iters = 0:初始化迭代次数计数器。
  5. print("Starting Training Loop..."):打印开始训练循环的信息。
  6. for epoch in range(num_epochs)::开始训练循环,每个循环代表一个训练周期。
  7. for i, data in enumerate(dataloader, 0)::遍历数据加载器中的每个批次数据。
  8. netD.zero_grad():将判别器的梯度清零。
  9. real_cpu = data[0].to(device):将真实图像数据转移到设备上(例如GPU)。
  10. b_size = real_cpu.size(0):获取当前批次的大小。
  11. label = torch.full((b_size,), real_label, dtype=torch.float, device=device):创建一个与批次大小相同的标签张量,填充为真实标签值。
  12. output = netD(real_cpu).view(-1):将真实图像输入判别器,并将输出展平为一维张量。
  13. errD_real = criterion(output, label):计算判别器在真实图像上的损失值。
  14. errD_real.backward():计算判别器在真实图像上的梯度。
  15. D_x = output.mean().item():计算判别器在真实图像上的输出均值。
  16. noise = torch.randn(b_size, nz, 1, 1, device=device):生成随机噪声张量。
  17. fake = netG(noise):将随机噪声输入生成器,生成假图像。
  18. label.fill_(fake_label):将标签张量填充为假标签值。
  19. output = netD(fake.detach()).view(-1):将生成的假图像输入判别器,并将输出展平为一维张量。
  20. errD_fake = criterion(output, label):计算判别器在假图像上的损失值。
  21. errD_fake.backward():计算判别器在假图像上的梯度。
  22. D_G_z1 = output.mean().item():计算判别器在假图像上的输出均值。
  23. errD = errD_real + errD_fake:计算判别器的总损失值。
  24. optimizerD.step():更新判别器的参数。
  25. netG.zero_grad():将生成器的梯度清零。
  26. label.fill_(real_label):将标签张量填充为真实标签值。
  27. output = netD(fake).view(-1):将生成的假图像输入判别器,并将输出展平为一维张量。
  28. errG = criterion(output, label):计算生成器的损失值。
  29. errG.backward():计算生成器的梯度。
  30. D_G_z2 = output.mean().item():计算判别器在生成的假图像上的输出均值。
  31. optimizerG.step():更新生成器的参数。
  32. if i % 400 == 0::每400次迭代打印一次训练信息。
  33. print('[%d/%d][%d/%d]\tLoss_D:%.4f\tLoss_G:%.4f\tD(x):%.4ftD(G(z)):%.4f/%.4f' % (epoch, num_epochs, i, len(dataloader), errD.item(), errG.item(), D_x, D_G_z1, D_G_z2)):打印当前训练周期、迭代次数、判别器和生成器的损失值以及判别器在真实图像和假图像上的输出均值。
  34. G_losses.append(errG.item()):将生成器的损失值添加到列表中。
  35. D_losses.append(errD.item()):将判别器的损失值添加到列表中。
  36. if (iters % 500 == 0) or ((epoch == num_epochs -1) and (i == len(dataloader)-1))::每500次迭代或最后一次迭代时保存生成的图像。
  37. with torch.no_grad()::禁用梯度计算,以便在推理模式下运行。
  38. fake = netG(fixed_noise).detach().cpu():使用固定的随机噪声生成假图像,并将其从GPU转移到CPU。
  39. img_list.append(vutils.make_grid(fake, padding=2, normalize=True)):将生成的图像添加到列表中,并进行网格化显示。
img_list = []
G_losses = []
D_losses = []
iters = 0

print("Starting Training Loop...")

for epoch in range(num_epochs):
    for i, data in enumerate(dataloader, 0):
        netD.zero_grad()
        real_cpu = data[0].to(device)
        b_size = real_cpu.size(0)
        label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
        output = netD(real_cpu).view(-1)
        errD_real = criterion(output, label)
        errD_real.backward()
        D_x = output.mean().item()
        
        noise = torch.randn(b_size, nz, 1, 1, device=device)
        fake = netG(noise)
        label.fill_(fake_label)
        output = netD(fake.detach()).view(-1)
        errD_fake = criterion(output, label)
        errD_fake.backward()
        D_G_z1 = output.mean().item()
        errD = errD_real + errD_fake
        optimizerD.step()
        
        
        netG.zero_grad()
        label.fill_(real_label)
        output = netD(fake).view(-1)
        errG = criterion(output, label)
        errG.backward()
        D_G_z2 = output.mean().item()
        optimizerG.step()
        
        if i % 400 == 0:
            print('[%d/%d][%d/%d]\tLoss_D:%.4f\tLoss_G:%.4f\tD(x):%.4ftD(G(z)):%.4f/%.4f'
                   % (epoch, num_epochs, i, len(dataloader),
                      errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
            
        G_losses.append(errG.item())
        D_losses.append(errD.item())
        
        if (iters % 500 == 0) or ((epoch == num_epochs -1) and (i == len(dataloader)-1)):
            with torch.no_grad():
                fake = netG(fixed_noise).detach().cpu()
            img_list.append(vutils.make_grid(fake, padding=2, normalize=True))
            
        iters += 1

输出
Starting Training Loop…
[0/50][0/36] Loss_D:1.7512 Loss_G:5.2982 D(x):0.5573tD(G(z)):0.5731/0.0074
[1/50][0/36] Loss_D:0.1342 Loss_G:28.9289 D(x):0.9278tD(G(z)):0.0000/0.0000
[2/50][0/36] Loss_D:0.6605 Loss_G:14.8656 D(x):0.7325tD(G(z)):0.0000/0.0000
[3/50][0/36] Loss_D:0.4214 Loss_G:6.6441 D(x):0.8965tD(G(z)):0.1566/0.0033
[4/50][0/36] Loss_D:0.3467 Loss_G:5.1888 D(x):0.9558tD(G(z)):0.2277/0.0091
[5/50][0/36] Loss_D:1.0012 Loss_G:4.2516 D(x):0.5013tD(G(z)):0.0074/0.0249
[6/50][0/36] Loss_D:0.9800 Loss_G:3.2626 D(x):0.5386tD(G(z)):0.0961/0.0641
[7/50][0/36] Loss_D:0.5802 Loss_G:4.7380 D(x):0.9461tD(G(z)):0.3547/0.0162
[8/50][0/36] Loss_D:0.5353 Loss_G:4.5286 D(x):0.9072tD(G(z)):0.3150/0.0179
[9/50][0/36] Loss_D:0.7859 Loss_G:6.1445 D(x):0.9532tD(G(z)):0.4525/0.0052
[10/50][0/36] Loss_D:1.0251 Loss_G:7.7611 D(x):0.8947tD(G(z)):0.5173/0.0020
[11/50][0/36] Loss_D:0.6613 Loss_G:6.4536 D(x):0.8239tD(G(z)):0.2801/0.0046
[12/50][0/36] Loss_D:0.6788 Loss_G:5.7669 D(x):0.8874tD(G(z)):0.3635/0.0056
[13/50][0/36] Loss_D:0.3853 Loss_G:4.4132 D(x):0.8608tD(G(z)):0.1810/0.0196
[14/50][0/36] Loss_D:0.4935 Loss_G:4.9815 D(x):0.8226tD(G(z)):0.1956/0.0122
[15/50][0/36] Loss_D:0.6487 Loss_G:6.8847 D(x):0.8666tD(G(z)):0.3226/0.0029
[16/50][0/36] Loss_D:0.7899 Loss_G:5.6435 D(x):0.8101tD(G(z)):0.3172/0.0102
[17/50][0/36] Loss_D:0.9606 Loss_G:8.9583 D(x):0.9130tD(G(z)):0.5161/0.0005
[18/50][0/36] Loss_D:0.5718 Loss_G:4.6443 D(x):0.6876tD(G(z)):0.0241/0.0398
[19/50][0/36] Loss_D:1.6950 Loss_G:5.1246 D(x):0.3253tD(G(z)):0.0005/0.0284
[20/50][0/36] Loss_D:0.8161 Loss_G:3.2833 D(x):0.5513tD(G(z)):0.0136/0.0604
[21/50][0/36] Loss_D:0.3978 Loss_G:4.9781 D(x):0.9113tD(G(z)):0.2162/0.0164
[22/50][0/36] Loss_D:0.2571 Loss_G:5.6374 D(x):0.8514tD(G(z)):0.0548/0.0075
[23/50][0/36] Loss_D:0.3984 Loss_G:3.6587 D(x):0.8116tD(G(z)):0.1314/0.0434
[24/50][0/36] Loss_D:0.7403 Loss_G:7.3338 D(x):0.9381tD(G(z)):0.4129/0.0015
[25/50][0/36] Loss_D:0.3967 Loss_G:4.0376 D(x):0.7694tD(G(z)):0.0779/0.0288
[26/50][0/36] Loss_D:0.4141 Loss_G:5.8788 D(x):0.7959tD(G(z)):0.0949/0.0078
[27/50][0/36] Loss_D:0.5122 Loss_G:2.7977 D(x):0.7391tD(G(z)):0.0862/0.1072
[28/50][0/36] Loss_D:0.4085 Loss_G:5.0800 D(x):0.9577tD(G(z)):0.2720/0.0131
[29/50][0/36] Loss_D:0.4535 Loss_G:3.5107 D(x):0.8008tD(G(z)):0.1410/0.0514
[30/50][0/36] Loss_D:0.5364 Loss_G:4.6663 D(x):0.6982tD(G(z)):0.0377/0.0276
[31/50][0/36] Loss_D:0.5703 Loss_G:5.2116 D(x):0.8833tD(G(z)):0.2863/0.0108
[32/50][0/36] Loss_D:0.2624 Loss_G:5.0783 D(x):0.8353tD(G(z)):0.0292/0.0145
[33/50][0/36] Loss_D:0.3959 Loss_G:4.8949 D(x):0.9465tD(G(z)):0.2461/0.0152
[34/50][0/36] Loss_D:0.3826 Loss_G:4.1611 D(x):0.8160tD(G(z)):0.1173/0.0328
[35/50][0/36] Loss_D:1.3097 Loss_G:4.1756 D(x):0.3965tD(G(z)):0.0051/0.0421
[36/50][0/36] Loss_D:0.4109 Loss_G:3.5671 D(x):0.7336tD(G(z)):0.0235/0.0426
[37/50][0/36] Loss_D:0.5402 Loss_G:5.2489 D(x):0.8510tD(G(z)):0.2621/0.0091
[38/50][0/36] Loss_D:0.5255 Loss_G:2.8001 D(x):0.7298tD(G(z)):0.1000/0.0974
[39/50][0/36] Loss_D:0.4328 Loss_G:3.6189 D(x):0.7766tD(G(z)):0.0773/0.0638
[40/50][0/36] Loss_D:0.4934 Loss_G:4.9533 D(x):0.8823tD(G(z)):0.2455/0.0137
[41/50][0/36] Loss_D:0.7040 Loss_G:2.3156 D(x):0.6258tD(G(z)):0.0720/0.1509
[42/50][0/36] Loss_D:0.4011 Loss_G:3.8578 D(x):0.8031tD(G(z)):0.1103/0.0434
[43/50][0/36] Loss_D:0.7086 Loss_G:2.9687 D(x):0.6672tD(G(z)):0.1397/0.0868
[44/50][0/36] Loss_D:0.5523 Loss_G:6.0340 D(x):0.8927tD(G(z)):0.3117/0.0054
[45/50][0/36] Loss_D:0.4263 Loss_G:4.6967 D(x):0.8723tD(G(z)):0.2154/0.0159
[46/50][0/36] Loss_D:0.3622 Loss_G:4.2612 D(x):0.9144tD(G(z)):0.2098/0.0273
[47/50][0/36] Loss_D:0.3794 Loss_G:5.2087 D(x):0.9115tD(G(z)):0.2058/0.0110
[48/50][0/36] Loss_D:0.8007 Loss_G:2.1731 D(x):0.5737tD(G(z)):0.0358/0.2054
[49/50][0/36] Loss_D:0.4732 Loss_G:6.3719 D(x):0.9446tD(G(z)):0.3083/0.0028

3.可视化

plt.figure(figsize=(10, 5))
plt.title("生成器和鉴别器在训练过程中的损失")
plt.plot(G_losses, label="G")
plt.plot(D_losses, label="D")
plt.xlabel("iterations")
plt.ylabel("loss")
plt.legend()
plt.show()

输出
在这里插入图片描述

代码知识点

  1. fig = plt.figure(figsize=(8, 8)):创建一个大小为8x8的图形对象。
  2. plt.axis("off"):关闭坐标轴显示。
  3. ims = [[plt.imshow(np.transpose(i, (1, 2, 0)), animated=True)] for i in img_list]:将img_list中的每个图像进行转置操作,然后使用plt.imshow()函数将其显示在图形对象上,并将结果存储在ims列表中。
  4. ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True):创建一个动画对象ani,将图形对象fig、图像列表ims、帧间隔时间interval(单位为毫秒)、重复延迟时间repeat_delay(单位为毫秒)和是否使用双缓冲技术blit作为参数传入。
  5. HTML(ani.to_jshtml()):将动画对象ani转换为JavaScript HTML格式,并返回一个HTML对象。

animation.ArtistAnimation类的作用是基于一组Artist对象来创建动画
animation.ArtistAnimation类是matplotlib库中的一个动画类,它主要用于制作动画。这个类的核心功能是将一系列的Artist对象(如图像、曲线等)按顺序展示,从而形成动画效果。每一帧的Artist对象在展示后会被擦除,以便下一帧的对象展示。具体来说,animation.ArtistAnimation类的构造方法接收以下参数:

  • fig:表示动画所在的画布,通常使用plt.figure()生成。
  • artists:一个列表,每个元素代表一帧的Artist对象集合。
  • interval:帧间隔时间,单位为毫秒。
  • repeat_delay:动画重复播放之间的延迟时间,单位为毫秒。
  • repeat:是否重复播放动画。
  • blit:是否使用双缓冲技术优化绘图性能。

总的来说,animation.ArtistAnimation类提供了一种灵活的方式来创建动画,尤其是当需要展示一系列静态图像或者matplotlib绘图对象时。通过调整参数,可以控制动画的播放速度、是否循环播放以及绘图性能等。

fig = plt.figure(figsize=(8, 8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i, (1, 2, 0)), animated=True)] for i in img_list]
ani = animation.ArtistAnimation(fig, ims, interval=1000, repeat_delay=1000, blit=True)
HTML(ani.to_jshtml())

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

代码知识点

  1. real_batch = next(iter(dataloader)):从数据加载器(dataloader)中获取一批真实图像数据,并将其存储在变量real_batch中。

  2. plt.figure(figsize=(15,15)):创建一个大小为15x15的图形窗口。

  3. plt.subplot(1, 2, 1):创建一个1行2列的子图网格,并将当前子图设置为第1个子图。

  4. plt.axis("off"):关闭当前子图的坐标轴。

  5. plt.title("real images"):为当前子图设置标题为"real images"。

  6. plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(), (1, 2, 0))):这一行代码的作用是显示一个由64个真实图像组成的网格。下面是对每个参数的解释:

  1. real_batch[0].to(device): 这部分代码将真实图像数据从CPU移动到指定的设备(如GPU)上进行计算。
  2. [:64]: 这部分代码选择了前64个真实图像。
  3. padding=5: 这个参数表示在生成网格时,图像之间添加的填充大小为5个像素。
  4. normalize=True: 这个参数表示对图像进行归一化处理,将像素值缩放到0到1之间。
  5. vutils.make_grid(): 这个函数用于将多个图像组合成一个网格。
  6. np.transpose(): 这个函数用于调整图像的维度顺序,将通道维度(C)放在最后。
  7. plt.imshow(): 这个函数用于在matplotlib中显示图像。
  1. plt.subplot(1, 2, 2):将当前子图设置为第2个子图。

  2. plt.axis("off"):关闭当前子图的坐标轴。

  3. plt.title("fake images"):为当前子图设置标题为"fake images"。

  4. plt.imshow(np.transpose(img_list[-1], (1, 2, 0))):将img_list中的最后一个生成图像进行维度顺序调整,并在当前子图中显示。

  5. plt.show():显示整个图形窗口。

real_batch = next(iter(dataloader))

plt.figure(figsize=(15,15))
plt.subplot(1, 2, 1)
plt.axis("off")
plt.title("real images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(), (1, 2, 0)))

plt.subplot(1, 2, 2)
plt.axis("off")
plt.title("fake images")
plt.imshow(np.transpose(img_list[-1], (1, 2, 0)))
plt.show()

输出
在这里插入图片描述

  • 22
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值