- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
一、前置知识
CGAN(条件生成对抗网络)的原理是在原始GAN的基础上,为生成器和判别器提供 额外的条件信息。
CGAN通过将条件信息(如类别标签或其他辅助信息)加入生成器和判别器的输入中,使得生成器能够根据这些条件信息生成特定类型的数据,而判别器则负责区分真实数据和生成数据是否符合这些条件。这种方式让生成器在生成数据时有了明确的方向,从而提高了生成数据的质量与相关性。
CGAN的特点包括有监督学习、联合隐层表征、可控性、使用卷积结构等,其具体内容为:
- 有监督学习:CGAN通过额外信息的使用,将原本无监督的GAN转变为一种有监督的学习模式,这使得网络的训练更加目标明确,生成结果更加符合预期。
- 联合隐层表征:在生成模型中,噪声输入和条件信息共同构成了联合隐层表征,这有助于生成更多样化且具有特定属性的数据。
- 可控性:CGAN的一个关键特点是提高了生成过程的可控性,即可以通过调整条件信息来指导模型生成特定类型的数据。
- 使用卷积结构:CGAN可以采用卷积神经网络作为其内部结构,这在图像相关的任务中尤其有效,因为它能够捕捉到局部特征,并提高模型对细节的处理能力。
相比于传统的GAN,CGAN的主要异同点包括条件信息的输入、训练稳定性、损失函数、网络结构等,其具体内容为:
- 条件信息的输入:CGAN引入了条件变量,使得生成器和判别器都能接收到更多的信息来指导训练过程,这是传统GAN所不具备的。
- 训练稳定性:传统GAN在训练过程中容易产生模式崩溃(mode collapse)的问题,而CGAN由于有了额外的条件信息,可以提高训练的稳定性和生成数据的多样性。
- 损失函数:虽然CGAN的损失函数仍然保留了传统GAN的对抗损失函数的形式,但额外添加的条件信息使得损失计算更加复杂且有针对性。
- 网络结构:在实现上,CGAN可以采用更深更复杂的网络结构,如卷积神经网络,这有助于处理更为复杂的数据类型,比如高分辨率图像。
综上所述,CGAN的核心在于它通过引入条件信息来增强模型的生成能力和可控性,与传统GAN相比,它
提供了更明确的训练目标和更好的生成效果。
二、准备工作
代码知识点
torch
:PyTorch库,用于实现深度学习模型和张量计算。numpy
:NumPy库,用于进行数值计算和处理多维数组。torch.nn
:PyTorch的神经网络模块,包含各种神经网络层和损失函数。torch.optim
:PyTorch的优化器模块,包含各种优化算法,如SGD、Adam等。torchvision
:PyTorch的计算机视觉库,包含数据集、预处理、数据增强等工具。torch.autograd
:PyTorch的自动求导模块,用于自动计算梯度。torchvision.utils
:PyTorch计算机视觉工具包,包含一些实用函数,如保存图像、制 作网格等。torch.utils.tensorboard
:PyTorch的TensorBoard接口,用于可视化训练过程。torchsummary
:PyTorch的模型摘要工具,用于显示模型结构及参数数量。matplotlib.pyplot
:Matplotlib库的绘图模块,用于绘制图表和可视化数据。datetime
:Python的日期时间模块,用于处理日期和时间相关的操作。
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.autograd import Variable
from torchvision.utils import save_image
from torchvision.utils import make_grid
from torch.utils.tensorboard import SummaryWriter
from torchsummary import summary
import matplotlib.pyplot as plt
import datetime
设置随机种子,确保每次运行代码时生成的随机数序列是相同的
torch.manual_seed(1)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
batch_size = 128
1.导入数据
train_transform = transforms.Compose([transforms.Resize(128),transforms.ToTensor(),transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])])train_dataset = datasets.ImageFolder(root="H:/G3周数据集/rps/rps", transform=train_transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True,num_workers=6)
2.数据可视化
代码知识点
permute()函数的作用是对tensor进行重新排序或转置。
使用permute()进行维度调换主要是因为在深度学习中,不同的模型或操作可能要求输入数据的维度顺序不同。在PyTorch中,
permute()
是一个用于改变张量(tensor)形状的函数,它通过重新排列张量的维度来实现这一点。具体来说,permute()
函数接收一系列维度的索引作为参数,这些索引指定了新的维度顺序。以下是关于permute()
函数的详细信息:
- 维度调换:通过传递一组新的维度顺序,
permute()
可以改变张量的形状。我们可以使用x.permute(0, 2, 1, 3)来将其重新排列为(a, c, b, d)。这里的第一个参数0表示保持原始张量的第一个维度不变,2表示将原始的第三个维度移动到第二个位置,1表示将原始的第二个维度移动到第三个位置,最后一个参数3表示保持原始张量的最后一个维度不变- 灵活性:与
transpose()
函数相比,permute()
可以处理任意数量的维度,而transpose()
通常用于二维矩阵的转置。虽然连续使用transpose()
可以实现与permute()
相同的效果,但permute()
提供了一种更为直观和简洁的方式来调整多维数组的维度顺序。- 不变性:使用
permute()
不会改变张量中的数据,只会改变数据的组织方式。这意味着原始数据保持不变,只是按照新的顺序重新排列而已。综上所述,
permute()
函数是一个非常有用的工具,特别是在处理多维数据时,如图像、声音信号等,它可以帮助我们根据需要重新组织数据,以便于进行后续的处理和分析。
def show_images(images):fig, ax = plt.subplots(figsize=(20, 20))ax.set_xticks([]); ax.set_yticks([])ax.imshow(make_grid(images.detach(), nrow=22).permute(1, 2, 0))def show_batch(dl):for images, _ in dl:show_images(images)break
show_batch(train_loader)
输出
代码知识点
np.prod
函数的作用是计算数组中所有元素的乘积。
关于np.prod
函数的参数,以下是详细的解释:
a
: 输入的数组,可以是标量、一维向量、二维矩阵或者多维数组。axis
: 指定要沿其计算乘积的轴。如果为None
(默认值),则计算整个数组的乘积。dtype
: 指定结果的数据类型。如果未指定(默认为None
),则使用输入数组的数据类型。out
: 指定输出结果的数组。如果未指定(默认为None
),则创建一个新的数组来存储结果。keepdims
: 如果设置为True,则被缩减的轴将作为单例维度保留在结果中。如果为False,则被缩减的轴将完全删除。initial
: 指定乘积的初始值。默认值为1。where
: 可选参数,用于指定一个布尔数组,结果只有在该数组对应位置为True时才会被设置。
latent_dim
代表了潜空间(latent space)的维度,是深度学习中特别是生成模型的一个核心概念。首先,我们来了解为什么需要设置latent_dim。在生成对抗网络(GANs)和其他生成模型中,latent_dim定义了隐空间的大小,这个空间是编码器将输入数据映射到的地方。一个合适的latent_dim对于模型的表达能力至关重要,它决定了模型能够捕捉和表示数据的复杂性的程度。如果latent_dim设置得太低,模型可能会丢失数据的重要特征,导致生成的数据缺乏多样性;而如果设置得太高,则可能会导致模型学习效率低下,增加计算成本。
接下来,我们讨论如何设置latent_dim的数值。理论上,latent_dim的设置应该与数据的内在维数(intrinsic dimension)相匹配,这是流形学习中的一个概念,指的是数据分布的本质复杂性。在实际应用中,确定latent_dim的数值通常需要依赖实验和经验。研究人员会通过实验不同的维度值,观察模型性能的变化,以此来找到最佳的维度设置。例如,在处理简单模式的数据时,可能只需要较低的维度;而对于高度复杂的数据,如高分辨率图像,可能需要更高的维度来捕获细节。
image_shape = (3, 128, 128)
image_dim = int(np.prod(image_shape))
latent_dim = 100
代码知识点
embedding_dim是一个超参数,它定义了嵌入层中每个离散输入值映射到的连续向量的维度大小。
embedding_dim
的主要作用是控制嵌入向量的维度,这有助于模型捕捉输入数据中的语义关系和相似性。例如,在自然语言处理(NLP)任务中,单词可以被映射到一个多维空间,其中语义相近的单词在空间中的位置也相近。这样,模型就能够更好地理解和处理语言数据。设置embedding_dim的方法通常取决于具体任务的复杂性和数据集的特性。一方面,如果embedding_dim设置得太低,可能无法充分捕捉数据中的细微差别;另一方面,如果设置得过高,则可能会导致不必要的计算负担和过拟合的风险。因此,选择合适的embedding_dim需要权衡模型性能和计算效率。
在实际应用中,
通常会根据经验或实验来确定embedding_dim的最佳值。
例如,对于小型数据集或简单的任务,可能只需要较低的维度;而对于大型数据集或复杂的任务,可能需要更高的维度以获得更好的性能。此外,还可以通过交叉验证等方法来系统地评估不同维度值对模型性能的影响,从而找到最优的设置。
n_classes = 3
embedding_dim = 100
三、构建模型
代码知识点
这段代码是一个权重初始化函数,用于初始化神经网络中的卷积层和批量归一化层的权重。
def weights_init(m):
定义一个名为weights_init
的函数,输入参数为m
,表示网络中的一个模块。classname = m.__class__.__name__
获取模块m
的类名,并将其赋值给变量classname
。if classname.find('Conv') != -1:
如果classname
中包含字符串’Conv’,说明该模块是卷积层。torch.nn.init.normal_(m.weight, 0.0, 0.02)
使用正态分布初始化卷积层的权重,均值为0,标准差为0.02。elif classname.find('BatchNorm') != -1:
如果classname
中包含字符串’BatchNorm’,说明该模块是批量归一化层。torch.nn.init.normal_(m.weight, 1.0, 0.02)
使用正态分布初始化批量归一化层的权重,均值为1,标准差为0.02。torch.nn.init.zeros_(m.bias)
将批量归一化层的偏置项初始化为全零。
def weights_init(m):classname = m.__class__.__name__if classname.find('Conv') != -1:torch.nn.init.normal_(m.weight, 0.0, 0.02)elif classname.find('BatchNorm') != -1:torch.nn.init.normal_(m.weight, 1.0, 0.02)torch.nn.init.zeros_(m.bias)
1.构建生成器
class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()self.label_conditioned_generator = nn.Sequential(nn.Embedding(n_classes, embedding_dim), nn.Linear(embedding_dim, 16) )self.latent = nn.Sequential(nn.Linear(latent_dim, 4*4*512), nn.LeakyReLU(0.2, inplace=True) )self.model = nn.Sequential( nn.ConvTranspose2d(513, 64*8, 4, 2, 1, bias=False),nn.BatchNorm2d(64*8, momentum=0.1, eps=0.8), nn.ReLU(True), nn.ConvTranspose2d(64*8, 64*4, 4, 2, 1, bias=False),nn.BatchNorm2d(64*4, momentum=0.1, eps=0.8),nn.ReLU(True), nn.ConvTranspose2d(64*4, 64*2, 4, 2, 1, bias=False),nn.BatchNorm2d(64*2, momentum=0.1, eps=0.8),nn.ReLU(True), nn.ConvTranspose2d(64*2, 64*1, 4, 2, 1, bias=False),nn.BatchNorm2d(64*1, momentum=0.1, eps=0.8),nn.ReLU(True), nn.ConvTranspose2d(64*1, 3, 4, 2, 1, bias=False),nn.Tanh() )def forward(self, inputs):noise_vector, label = inputs label_output = self.label_conditioned_generator(label) label_output = label_output.view(-1, 1, 4, 4) latent_output = self.latent(noise_vector) latent_output = latent_output.view(-1, 512, 4, 4) concat = torch.cat((latent_output, label_output), dim=1)image = self.model(concat)return image
generator = Generator().to(device)
generator.apply(weights_init)
print(generator)
输出
代码知识点
torchinfo是一个用于PyTorch模型的库,它提供了一种简单的方式来获取模型的各种信息,如参数数量、每层的输出形状等。summary函数是torchinfo库中的一个函数,它可以生成一个模型的摘要,包括模型的每一层的信息。
from torchinfo import summary
summary(generator)
输出
代码知识点
这段代码的含义是将两个张量(tensor)a和b分别转换为设备上的数据类型,并将它们移动到指定的设备上。
a = torch.ones(100)
:创建一个形状为(100,)的全1张量a。b = torch.ones(1)
:创建一个形状为(1,)的全1张量b。b = b.long()
:将张量b的数据类型转换为长整型(long)。a = a.to(device)
:将张量a移动到指定的设备上。这里的device
是一个变量,表示要移动到的设备,例如GPU或CPU。b = b.to(device)
:将张量b也移动到指定的设备上。
a = torch.ones(100)
b = torch.ones(1)
b = b.long()
a = a.to(device)
b = b.to(device)
2.构建鉴别器
import torch
import torch.nn as nnclass Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()self.label_condition_disc = nn.Sequential(nn.Embedding(n_classes, embedding_dim), nn.Linear(embedding_dim, 3*128*128) )self.model = nn.Sequential(nn.Conv2d(6, 64, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(64, 64*2, 4, 3, 2, bias=False), nn.BatchNorm2d(64*2, momentum=0.1, eps=0.8), nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(64*2, 64*4, 4, 3, 2, bias=False), nn.BatchNorm2d(64*4, momentum=0.1, eps=0.8),nn.LeakyReLU(0.2, inplace=True),nn.Conv2d(64*4, 64*8, 4, 3, 2, bias=False), nn.BatchNorm2d(64*8, momentum=0.1, eps=0.8),nn.LeakyReLU(0.2, inplace=True),nn.Flatten(), nn.Dropout(0.4), nn.Linear(4608, 1), nn.Sigmoid() )def forward(self, inputs):img, label = inputslabel_output = self.label_condition_disc(label)label_output = label_output.view(-1, 3, 128, 128)concat = torch.cat((img, label_output), dim=1)output = self.model(concat)return output
discriminator = Discriminator().to(device)
discriminator.apply(weights_init)
print(discriminator)
输出
summary(discriminator)
输出
a = torch.ones(2,3,128,128)
b = torch.ones(2,1)
b = b.long()
a = a.to(device)
b = b.to(device)
c = discriminator((a,b))
c.size()
输出
torch.Size([2, 1])
三、训练模型
1.定义损失函数
代码知识点
nn.BCELoss()
函数用于计算二分类问题中的二元交叉熵损失。
nn.BCELoss()
是PyTorch中提供的一个损失函数,主要用于二元分类问题。它计算的是真实标签与模型预测概率之间的二元交叉熵损失(Binary Cross
Entropy
Loss)。这个损失函数衡量的是模型输出概率与实际标签的一致性,其目的是在训练过程中最小化这个损失值,从而提高模型的预测准确性。在二元分类问题中,模型的输出通常是一个介于0和1之间的概率值,表示某个样本属于正类的概率。
nn.BCELoss()
函数在计算损失时,会将模型预测的概率与真实标签结合起来,通过优化这个过程使得模型能够更好地预测样本的类别。在使用nn.BCELoss()
时,通常需要在模型的输出层之前添加一个Sigmoid激活函数,以将模型的输出转换为概率值。具体来说,二元交叉熵损失函数的计算公式为:
- BCELoss = - (y * log§ + (1 - y) * log(1 - p))
其中,(y) 是真实标签(通常取值为0或1),§
是模型预测样本为正类的概率。这个公式衡量了预测概率与真实标签之间的差异,当预测准确时损失值较低,预测不准确时损失值较高,通过梯度下降等优化算法可以逐步降低这个损失值,从而提升模型的性能。总的来说,
nn.BCELoss()
是深度学习中常用的损失函数之一,特别适用于二元分类问题,帮助模型学习到更好的预测结果。
这段代码是用于生成对抗网络(GAN)的损失函数计算。下面是对每行代码的解释:
adversarial_loss函数在生成对抗网络(GAN)中用于衡量生成器生成的样本与真实样本之间的差异。
adversarial_loss = nn.BCELoss()
:定义了一个二进制交叉熵损失函数(Binary Cross Entropy Loss),用于衡量生成器和判别器在生成假样本时与真实标签之间的差异。
def generator_loss(fake_output, label):
:定义了一个名为generator_loss
的函数,该函数接受两个参数:fake_output
表示生成器生成的假样本的输出,label
表示真实的标签。
gen_loss = adversarial_loss(fake_output, label)
:调用前面定义的二进制交叉熵损失函数,将生成器生成的假样本输出和真实标签作为输入,计算生成器的损失值,并将结果赋值给变量gen_loss
。
return gen_loss
:返回生成器的损失值。
def discriminator_loss(output, label):
:定义了一个名为discriminator_loss
的函数,该函数接受两个参数:output
表示判别器对样本的输出,label
表示真实的标签。
disc_loss = adversarial_loss(output, label)
:调用前面定义的二进制交叉熵损失函数,将判别器对样本的输出和真实标签作为输入,计算判别器的损失值,并将结果赋值给变量disc_loss
。
return disc_loss
:返回判别器的损失值。
adversarial_loss = nn.BCELoss() def generator_loss(fake_output, label):gen_loss = adversarial_loss(fake_output, label)return gen_lossdef discriminator_loss(output, label):disc_loss = adversarial_loss(output, label)return disc_loss
2.定义优化器
代码知识点
在Adam优化器中,betas是一个包含两个数值的元组(beta1, beta2),分别代表
一阶矩估计(梯度的指数加权平均)和二阶矩估计(梯度平方的指数加权平均)的衰减率。以下是关于Adam优化器中betas参数的一些详细解释:
- 一阶矩估计(beta1):它反映了历史梯度信息的衰减率。默认情况下,beta1通常设置为0.9,这意味着最近的梯度信息将更加重要,而较旧的梯度信息则会逐渐衰减。
- 二阶矩估计(beta2):它代表了历史梯度平方信息的衰减率。默认情况下,beta2通常设置为0.999,这有助于更精确地调整学习率。
综上所述,通过适当地调整这些参数,可以更好地控制模型训练过程中的学习动态,进而提高模型的性能。
learning_rate = 0.0002G_optimizer = optim.Adam(generator.parameters(), lr = learning_rate, betas=(0.5, 0.999))
D_optimizer = optim.Adam(discriminator.parameters(), lr = learning_rate, betas=(0.5, 0.999))
3.训练模型
代码知识点
这段代码是一个训练生成对抗网络(GAN)的循环。下面是对每一行代码的解释:
num_epochs = 100
:设置训练的总轮数为100。D_loss_plot, G_loss_plot = [], []
:初始化两个列表,用于记录判别器和生成器的损失值。for epoch in range(1, num_epochs + 1):
:开始一个循环,循环次数为总轮数。D_loss_list, G_loss_list = [], []
:在每轮开始时,初始化两个列表,用于记录当前轮次中判别器和生成器的损失值。for index, (real_images, labels) in enumerate(train_loader):
:遍历训练数据加载器中的每个批次。D_optimizer.zero_grad()
:将判别器的梯度清零。real_images = real_images.to(device)
:将真实图像转移到设备上(例如GPU)。labels = labels.to(device)
:将标签转移到设备上。labels = labels.unsqueeze(1).long()
:将标签的形状从(batch_size,)变为(batch_size,
1),并将其转换为长整型。real_target = Variable(torch.ones(real_images.size(0), 1).to(device))
:创建一个与真实图像大小相同的全1张量,表示真实的目标。fake_target = Variable(torch.zeros(real_images.size(0), 1).to(device))
:创建一个与真实图像大小相同的全0张量,表示假的目标。D_real_loss = discriminator_loss(discriminator((real_images, labels)), real_target)
:计算判别器对真实图像的损失。noise_vector = torch.randn(real_images.size(0), latent_dim, device=device)
:生成随机噪声向量。noise_vector = noise_vector.to(device)
:将噪声向量转移到设备上。generated_image = generator((noise_vector, labels))
:使用生成器生成图像。output = discriminator((generated_image.detach(), labels))
:将生成的图像输入判别器。D_fake_loss = discriminator_loss(output, fake_target)
:计算判别器对生成图像的损失。D_total_loss = (D_real_loss + D_fake_loss) / 2
:计算判别器的总体损失。D_loss_list.append(D_total_loss)
:将判别器的损失值添加到列表中。D_total_loss.backward()
:计算判别器的梯度。D_optimizer.step()
:更新判别器的参数。G_optimizer.zero_grad()
:将生成器的梯度清零。G_loss = generator_loss(discriminator((generated_image, labels)), real_target)
:计算生成器的损失。G_loss_list.append(G_loss)
:将生成器的损失值添加到列表中。G_loss.backward()
:计算生成器的梯度。G_optimizer.step()
:更新生成器的参数。print('Epoch: [%d/%d]: D_loss: %.3f, G_loss: %.3f' % ((epoch), num_epochs, torch.mean(torch.FloatTensor(D_loss_list)), torch.mean(torch.FloatTensor(G_loss_list))))
:打印当前轮次的判别器和生成器的平均损失值。D_loss_plot.append(torch.mean(torch.FloatTensor(D_loss_list)))
:将判别器的平均损失值添加到损失值列表中。G_loss_plot.append(torch.mean(torch.FloatTensor(G_loss_list)))
:将生成器的平均损失值添加到损失值列表中。if epoch%10 == 0:
:如果当前轮次是10的倍数,则执行以下操作。save_image(generated_image.data[:50], './images/sample_%d' % epoch + '.png', nrow=5, normalize=True)
:保存生成的前50个图像。torch.save(generator.state_dict(), './training_weights/generator_epoch_%d.pth' % (epoch))
:保存生成器的权重。torch.save(discriminator.state_dict(), './training_weights/discriminator_epoch_%d.pth' % (epoch))
:保存判别器的权重。
num_epochs = 100D_loss_plot, G_loss_plot = [], []for epoch in range(1, num_epochs + 1):D_loss_list, G_loss_list = [], []for index, (real_images, labels) in enumerate(train_loader):D_optimizer.zero_grad()real_images = real_images.to(device)labels = labels.to(device)labels = labels.unsqueeze(1).long()real_target = Variable(torch.ones(real_images.size(0), 1).to(device))fake_target = Variable(torch.zeros(real_images.size(0), 1).to(device))D_real_loss = discriminator_loss(discriminator((real_images, labels)), real_target)noise_vector = torch.randn(real_images.size(0), latent_dim, device=device)noise_vector = noise_vector.to(device)generated_image = generator((noise_vector, labels))output = discriminator((generated_image.detach(), labels))D_fake_loss = discriminator_loss(output, fake_target)D_total_loss = (D_real_loss + D_fake_loss) / 2D_loss_list.append(D_total_loss)D_total_loss.backward()D_optimizer.step()G_optimizer.zero_grad()G_loss = generator_loss(discriminator((generated_image, labels)), real_target)G_loss_list.append(G_loss)G_loss.backward()G_optimizer.step()print('Epoch: [%d/%d]: D_loss: %.3f, G_loss: %.3f' % ((epoch), num_epochs, torch.mean(torch.FloatTensor(D_loss_list)), torch.mean(torch.FloatTensor(G_loss_list))))D_loss_plot.append(torch.mean(torch.FloatTensor(D_loss_list)))G_loss_plot.append(torch.mean(torch.FloatTensor(G_loss_list)))if epoch%10 == 0:save_image(generated_image.data[:50], './images/sample_%d' % epoch + '.png', nrow=5, normalize=True)torch.save(generator.state_dict(), './training_weights/generator_epoch_%d.pth' % (epoch))torch.save(discriminator.state_dict(), './training_weights/discriminator_epoch_%d.pth' % (epoch))
梯度清零的作用是重置梯度信息,确保每个batch的梯度计算是独立的。
在训练神经网络时,梯度清零(通常通过调用优化器的
.zero_grad()
方法实现)是一个重要的步骤。由于PyTorch等框架中,梯度是累加的,即如果不清零,每次计算的梯度会叠加到之前的梯度上。这会导致不同batch的数据对模型参数更新的贡献混在一起,从而影响训练的准确性。通过梯度清零,我们确保了每个batch的梯度计算是独立于其他batches的,这样每个batch的梯度就能准确地反映出该batch数据对模型的影响。进行梯度清零的原因是为了避免梯度之间的混合和累积,保持模型更新的准确性。
在深度学习训练中,通常我们希望每个batch的梯度更新只基于该batch的数据。如果不进行梯度清零,前一个batch计算出的梯度会影响后一个batch的梯度计算,导致梯度不断累积,最终影响模型参数的更新。此外,梯度清零也与“梯度累加”策略相关,该策略允许我们在硬件资源有限的情况下,通过多次累加小batch的梯度来模拟大batch的训练效果,这样可以在一定程度上缓解显存限制问题。
将真实图像转移到设备上(例如GPU)的原因是为了加快计算速度和提高计算效率。
在深度学习训练中,尤其是在处理大规模数据集时,计算量通常非常大。CPU(中央处理单元)负责通用计算任务,而GPU(图形处理单元)则专为并行计算设计,能够同时处理大量数据。因此,当涉及到大量的矩阵运算、卷积运算等操作时,使用GPU可以显著加速这些计算过程。
具体来说,将真实图像转移到设备上的原因包括:
并行计算能力:GPU拥有成百上千个计算核心,能够同时执行多个计算任务。这意味着在处理图像或其他数据时,GPU可以在相同的时间内完成更多的计算工作。
提高内存带宽:GPU的内存带宽远高于CPU,这使得数据在GPU内存与计算核心之间的传输更加迅速,减少了数据传输的延迟。
专门的优化:深度学习框架(如PyTorch、TensorFlow等)通常对GPU进行专门的优化,以充分利用其计算能力。这些优化包括自动分配显存、优化张量操作等。
加快模型训练:通过将数据和模型参数存储在GPU上,可以加快模型的前向传播和反向传播过程,从而缩短训练时间。
支持更大规模的模型和数据集:GPU的显存通常比CPU内存大得多,这使得我们可以在有限的硬件资源下训练更大规模的模型和处理更大的数据集。
总之,将真实图像转移到设备上(尤其是GPU)是为了更好地利用GPU的高性能计算能力,加快深度学习模型的训练速度,提高整体的训练效率。这对于处理复杂的神经网络和大规模的数据集至关重要。
输出
四、模型分析
1.加载模型
输出
generator.load_state_dict()
是一个PyTorch函数,用于加载预训练模型的权重。参数解释:
torch.load('./training_weights/generator_epoch_100.pth')
:这部分代码是使用 PyTorch 的torch.load()
函数从指定的文件路径(‘./training_weights/generator_epoch_100.pth’)中加载预训练模型的权重。strict=False
:这是一个可选参数,默认值为 True。当设置为 False 时,如果预训练模型的架构与当前模型的架构不完全匹配,那么 PyTorch
将只加载匹配的部分,并忽略不匹配的部分。这在迁移学习中非常有用,因为我们可以加载一个预训练模型的一部分权重,而不需要完全匹配整个模型的架构。
generator.load_state_dict(torch.load('./training_weights/generator_epoch_100.pth'), strict=False)
generator.eval()
输出
代码知识点
这段代码的作用是生成一些随机的点,然后对这些点进行插值处理,最后使用一个生成器模型(generator)对插值后的点进行预测。
- 导入所需的库和函数:numpy、matplotlib、torch等。
- 定义一个函数
generate_latent_points
,用于生成随机的点。输入参数为潜在空间维度(latent_dim)、样本数量(n_samples)和类别数量(n_classes,默认为3)。函数内部首先生成一个随机数矩阵,然后将其重塑为指定的形状,最后返回这个矩阵。- 定义一个函数
interpolate_points
,用于在两个点之间进行插值。输入参数为两个点(p1和p2)以及插值步数(n_steps,默认为10)。函数内部首先计算插值比例,然后根据这些比例计算插值后的点,并将它们添加到一个列表中。最后将这个列表转换为numpy数组并返回。- 调用
generate_latent_points
函数生成两个随机点,并将它们存储在变量pts
中。- 调用
interpolate_points
函数对这两个点进行插值处理,并将结果存储在变量interpolated
中。然后将这个张量转换为PyTorch张量,并将其移动到指定的设备(device)上,并将其类型转换为float32。- 初始化一个名为
output
的变量,用于存储生成器的预测结果。- 对于每个类别(共3个),创建一个全为该类别标签的张量(长度为10),并将其移动到指定的设备上。然后将这个张量的维度扩展为(batch_size,
1),并将其类型转换为long。接着打印这个张量的大小。- 使用生成器模型对插值后的点进行预测,并将结果存储在变量
predictions
中。然后将这个张量的维度重新排列,并将其从GPU上移回到CPU上。如果output
为空,则将pred
赋值给output
;否则,将pred
与output
进行拼接。
from numpy import asarray
from numpy.random import randn
from numpy.random import randint
from numpy import linspace
from matplotlib import pyplot
from matplotlib import gridspecdef generate_latent_points(latent_dim, n_samples, n_classes=3):x_input = randn(latent_dim * n_samples)z_input = x_input.reshape(n_samples, latent_dim)return z_inputdef interpolate_points(p1, p2, n_steps=10):ratios = linspace(0, 1, num=n_steps)vectors = list()for ratio in ratios:v = (1.0 - ratio) * p1 + ratio * p2vectors.append(v)return asarray(vectors)pts = generate_latent_points(100, 2)
interpolated = interpolate_points(pts[0], pts[1])
interpolated = torch.tensor(interpolated).to(device).type(torch.float32)output = None
for label in range(3):labels = torch.ones(10) * labellabels = labels.to(device)labels = labels.unsqueeze(1).long()print(labels.size())predictions = generator((interpolated, labels))predictions = predictions.permute(0,2,3,1)pred = predictions.detach().cpu()if output is None:output = predelse:output = np.concatenate((output,pred))
输出
torch.Size([10, 1])
torch.Size([10, 1])
torch.Size([10, 1])
output.shape
输出
(30, 128, 128, 3)
代码知识点
这段代码的作用是在一个图形窗口中显示多个子图,每个子图显示一个预测结果。具体解释如下:
nrow = 3
和ncol = 10
分别表示子图的行数和列数。fig = plt.figure(figsize=(15,4))
创建一个图形窗口,设置窗口的大小为宽15英寸,高4英寸。gs = gridspec.GridSpec(nrow, ncol)
使用gridspec
模块创建一个网格布局,用于指定子图的位置。k = 0
初始化一个计数器变量k
,用于遍历输出数组output
。for i in range(nrow):
循环遍历行数。for j in range(ncol):
循环遍历列数。pred = (output[k, :, :, :] + 1 ) * 127.5
对输出数组output
的第k
个元素进行预处理,将其值范围从[-1, 1]映射到[0, 255]。pred = np.array(pred)
将预处理后的结果转换为NumPy数组。ax= plt.subplot(gs[i,j])
在网格布局中创建子图,并将其赋值给变量ax
。ax.imshow(pred.astype(np.uint8))
在子图中显示预处理后的图像,数据类型为无符号整数(np.uint8
)。ax.set_xticklabels([])
移除子图的x轴刻度标签。ax.set_yticklabels([])
移除子图的y轴刻度标签。ax.axis('off')
关闭子图的坐标轴。k += 1
更新计数器变量k
的值。plt.show()
显示图形窗口。
nrow = 3
ncol = 10fig = plt.figure(figsize=(15,4))
gs = gridspec.GridSpec(nrow, ncol) k = 0
for i in range(nrow):for j in range(ncol):pred = (output[k, :, :, :] + 1 ) * 127.5pred = np.array(pred) ax= plt.subplot(gs[i,j])ax.imshow(pred.astype(np.uint8))ax.set_xticklabels([])ax.set_yticklabels([])ax.axis('off')k += 1 plt.show()
输出