本文为🔗365天深度学习训练营 中的学习记录博客**
原作者:K同学啊**
基础任务:
- 条件生成对抗网络(CGAN)的基本原理
- CGAN是如何实现条件控制的
- 学习本文CGAN代码,并跑通代码
进阶任务:
生成指定手势的图像(可查看下一章《第G4周:CGAN|生成手势图像 | 可控制生成》一文)
一、理论知识
条件生成对抗网络(CGAN)是在生成对抗网络(GAN)的基础上进行了一些改进。对于原始GAN的生成器而言,其生成的图像数据是随机不可预测的,因此我们无法控制网络的输出,在实际操作中的可控性不强。
针对上述原始GAN无法生成具有特定属性的图像数据的问题,Mehdi Mirza等人在2014年提出了条件生成对抗网络,通过给原始生成对抗网络中的生成器G和判别器D增加额外的条件,例如我们需要生成器G生成一张没有阴影的图像,此时判别器D就需要判断生成器所生成的图像是否是一张没有阴影的图像。条件生成对抗网络的本质是将额外添加的信息融入到生成器和判别器中,其中添加的信息可以是图像的类别、人脸表情和其他辅助信息等,旨在把无监督学习的GAN转化为有监督学习的CGAN,便于网络能够在我们的掌控下更好地进行训练。CGAN网络结构如图1所示。
图1:条件生成对抗网络结构
由图1的网络结构可知,条件信息y作为额外的输入被引入对抗网络中,与生成器中的噪声z合并作为隐含层表达;而在判别器D中,条件信息y则与原始数据x合并作为判别函数的输入。这种改进在以后的诸多方面研究中被证明是非常有效的,也为后续的相关工作提供了积极的指导作用。
我的环境:
语言环境:Python3.10.11
编译器:Jupyter Notebook
深度学习框架:Pytorch 2.2.2+cpu
数据集:手势数据集
二、准备工作
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
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
batch_size = 128
- 导入数据
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='./G3/GAN-3-data/data/rps/rps/', transform=train_transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=6)
- 数据可视化
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
关于ax.imshow(make_grid(images.detach(), nrow=22).permute(1, 2, 0))详解:
1、ax: 这是一个matplotlib的轴对象(axis),用于在图形上放置图像。通常,它用于创建子图。
2、 make_grid(images.detach(), nrow=10): 这是一个函数调用。make_grid函数的作用是将一组图像拼接成一个网格。它接受两个参数:images和nrow。images是一个包含图像的张量,nrow是可选参数,表示每行显示的图像数量。在这里,它将图像进行拼接,并设置每行显示10个图像。
3、 permute(1, 2, 0): 这是一个张量的操作,用于交换维度的顺序。在这里,对于一个3维的张量(假设图像维度为(C,H,W),其中C是通道数,H是高度,W是宽度),permute(1, 2, 0)将把通道维度(C)移动到最后,而将高度和宽度维度(H,W)放在前面。这样做是为了符合matplotlib对图像的要求,因为matplotlib要求图像的维度为(H,W,C)。
4、 imshow(…): 这是matplotlib的一个函数,用于显示图像。在这里,它接受一个拼接好并且维度已经调整好的图像张量,并将其显示在之前创建的轴对象(ax)上。
show_batch(train_loader)
输出结果:
image_shape = (3, 128, 128)
image_dim = int(np.prod(image_shape))
latent_dim = 100
n_classes = 3
embedding_dim = 100
三、构建模型
函数weights_init(m),其中m表示神经网络中的一层。函数的作用是对神经网络的权重进行初始化。通过函数判断类名:
● Conv:使用正态分布初始化权重数据,均值为0,标准差为0.02
● BatchNorm:使用正态分布初始化权重数据,均值为1,标准差为0.02,同时使用常数初始化偏置项数据,值为0。
通过将每一层的权重初始化为符合正态分布的数据,提高网络的泛化能力和训练效果。
# 自定义权重初始化函数,用于初始化生成器和判别器的权重
def weights_init(m):
# 获取当前层的类名
classname = m.__class__.__name__
# 如果当前层是卷积层(类名中包含 'Conv' )
if classname.find('Conv') != -1:
# 使用正态分布随机初始化权重,均值为0,标准差为0.02
torch.nn.init.normal_(m.weight, 0.0, 0.02)
# 如果当前层是批归一化层(类名中包含 'BatchNorm' )
elif classname.find('BatchNorm') != -1:
# 使用正态分布随机初始化权重,均值为1,标准差为0.02
torch.nn.init.normal_(m.weight, 1.0, 0.02)
# 将偏置项初始化为全零
torch.nn.init.zeros_(m.bias)
- 构建生成器
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
# 定义条件标签的生成器部分,用于将标签映射到嵌入空间中
# n_classes:条件标签的总数
# embedding_dim:嵌入空间的维度
self.label_conditioned_generator = nn.Sequential(
nn.Embedding(n_classes, embedding_dim), # 使用Embedding层将条件标签映射为稠密向量
nn.Linear(embedding_dim, 16) # 使用线性层将稠密向量转换为更高维度
)
# 定义潜在向量的生成器部分,用于将噪声向量映射到图像空间中
# latent_dim:潜在向量的维度
self.latent = nn.Sequential(
nn.Linear(latent_dim, 4*4*512), # 使用线性层将潜在向量转换为更高维度
nn.LeakyReLU(0.2, inplace=True) # 使用LeakyReLU激活函数进行非线性映射
)
# 定义生成器的主要结构,将条件标签和潜在向量合并成生成的图像
self.model = nn.Sequential(
# 反卷积层1:将合并后的向量映射为64x8x8的特征图
nn.ConvTranspose2d(513, 64*8, 4, 2, 1, bias=False),
nn.BatchNorm2d(64*8, momentum=0.1, eps=0.8), # 批标准化
nn.ReLU(True), # ReLU激活函数
# 反卷积层2:将64x8x8的特征图映射为64x4x4的特征图
nn.ConvTranspose2d(64*8, 64*4, 4, 2, 1, bias=False),
nn.BatchNorm2d(64*4, momentum=0.1, eps=0.8),
nn.ReLU(True),
# 反卷积层3:将64x4x4的特征图映射为64x2x2的特征图
nn.ConvTranspose2d(64*4, 64*2, 4, 2, 1, bias=False),
nn.BatchNorm2d(64*2, momentum=0.1, eps=0.8),
nn.ReLU(True),
# 反卷积层4:将64x2x2的特征图映射为64x1x1的特征图
nn.ConvTranspose2d(64*2, 64*1, 4, 2, 1, bias=False),
nn.BatchNorm2d(64*1, momentum=0.1, eps=0.8),
nn.ReLU(True),
# 反卷积层5:将64x1x1的特征图映射为3x64x64的RGB图像
nn.ConvTranspose2d(64*1, 3, 4, 2, 1, bias=False),
nn.Tanh() # 使用Tanh激活函数将生成的图像像素值映射到[-1, 1]范围内
)
def forward(self, inputs):
noise_vector, label = inputs
# 通过条件标签生成器将标签映射为嵌入向量
label_output = self.label_conditioned_generator(label)
# 将嵌入向量的形状变为(batch_size, 1, 4, 4),以便与潜在向量进行合并
label_output = label_output.view(-1, 1, 4, 4)
# 通过潜在向量生成器将噪声向量映射为潜在向量
latent_output = self.latent(noise_vector)
# 将潜在向量的形状变为(batch_size, 512, 4, 4),以便与条件标签进行合并
latent_output = latent_output.view(-1, 512, 4, 4)
# 将条件标签和潜在向量在通道维度上进行合并,得到合并后的特征图
concat = torch.cat((latent_output, label_output), dim=1)
# 通过生成器的主要结构将合并后的特征图生成为RGB图像
image = self.model(concat)
return image
generator = Generator().to(device)
generator.apply(weights_init)
print(generator)
输出结果:
Generator(
(label_conditioned_generator): Sequential(
(0): Embedding(3, 100)
(1): Linear(in_features=100, out_features=16, bias=True)
)
(latent): Sequential(
(0): Linear(in_features=100, out_features=8192, bias=True)
(1): LeakyReLU(negative_slope=0.2, inplace=True)
)
(model): Sequential(
(0): ConvTranspose2d(513, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(512, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): ConvTranspose2d(512, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
(6): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(10): BatchNorm2d(64, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(13): Tanh()
)
)
from torchinfo import summary
summary(generator)
输出结果:
=================================================================
Layer (type:depth-idx) Param #
=================================================================
Generator --
├─Sequential: 1-1 --
│ └─Embedding: 2-1 300
│ └─Linear: 2-2 1,616
├─Sequential: 1-2 --
│ └─Linear: 2-3 827,392
│ └─LeakyReLU: 2-4 --
├─Sequential: 1-3 --
│ └─ConvTranspose2d: 2-5 4,202,496
│ └─BatchNorm2d: 2-6 1,024
│ └─ReLU: 2-7 --
│ └─ConvTranspose2d: 2-8 2,097,152
│ └─BatchNorm2d: 2-9 512
│ └─ReLU: 2-10 --
│ └─ConvTranspose2d: 2-11 524,288
│ └─BatchNorm2d: 2-12 256
│ └─ReLU: 2-13 --
│ └─ConvTranspose2d: 2-14 131,072
│ └─BatchNorm2d: 2-15 128
│ └─ReLU: 2-16 --
│ └─ConvTranspose2d: 2-17 3,072
│ └─Tanh: 2-18 --
=================================================================
Total params: 7,789,308
Trainable params: 7,789,308
Non-trainable params: 0
=================================================================
a = torch.ones(100)
b = torch.ones(1)
b = b.long()
a = a.to(device)
b = b.to(device)
# generator((a,b))
- 构建鉴别器
import torch
import torch.nn as nn
class 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), # 输入通道为6(包含图像和标签的通道数),输出通道为64,4x4的卷积核,步长为2,padding为1
nn.LeakyReLU(0.2, inplace=True), # LeakyReLU激活函数,带有负斜率,增加模型对输入中的负值的感知能力
nn.Conv2d(64, 64*2, 4, 3, 2, bias=False), # 输入通道为64,输出通道为64*2,4x4的卷积核,步长为3,padding为2
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), # 输入通道为64*2,输出通道为64*4,4x4的卷积核,步长为3,padding为2
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), # 输入通道为64*4,输出通道为64*8,4x4的卷积核,步长为3,padding为2
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), # 全连接层,将特征向量映射到输出维度为1的向量
nn.Sigmoid() # Sigmoid激活函数,用于输出范围限制在0到1之间的概率值
)
def forward(self, inputs):
img, label = inputs
# 将类别标签转换为特征向量
label_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)
输出结果:
Discriminator(
(label_condition_disc): Sequential(
(0): Embedding(3, 100)
(1): Linear(in_features=100, out_features=49152, bias=True)
)
(model): Sequential(
(0): Conv2d(6, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(1): LeakyReLU(negative_slope=0.2, inplace=True)
(2): Conv2d(64, 128, kernel_size=(4, 4), stride=(3, 3), padding=(2, 2), bias=False)
(3): BatchNorm2d(128, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(4): LeakyReLU(negative_slope=0.2, inplace=True)
(5): Conv2d(128, 256, kernel_size=(4, 4), stride=(3, 3), padding=(2, 2), bias=False)
(6): BatchNorm2d(256, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(7): LeakyReLU(negative_slope=0.2, inplace=True)
(8): Conv2d(256, 512, kernel_size=(4, 4), stride=(3, 3), padding=(2, 2), bias=False)
(9): BatchNorm2d(512, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(10): LeakyReLU(negative_slope=0.2, inplace=True)
(11): Flatten(start_dim=1, end_dim=-1)
(12): Dropout(p=0.4, inplace=False)
(13): Linear(in_features=4608, out_features=1, bias=True)
(14): Sigmoid()
)
)
summary(discriminator)
输出结果:
=================================================================
Layer (type:depth-idx) Param #
=================================================================
Discriminator --
├─Sequential: 1-1 --
│ └─Embedding: 2-1 300
│ └─Linear: 2-2 4,964,352
├─Sequential: 1-2 --
│ └─Conv2d: 2-3 6,144
│ └─LeakyReLU: 2-4 --
│ └─Conv2d: 2-5 131,072
│ └─BatchNorm2d: 2-6 256
│ └─LeakyReLU: 2-7 --
│ └─Conv2d: 2-8 524,288
│ └─BatchNorm2d: 2-9 512
│ └─LeakyReLU: 2-10 --
│ └─Conv2d: 2-11 2,097,152
│ └─BatchNorm2d: 2-12 1,024
│ └─LeakyReLU: 2-13 --
│ └─Flatten: 2-14 --
│ └─Dropout: 2-15 --
│ └─Linear: 2-16 4,609
│ └─Sigmoid: 2-17 --
=================================================================
Total params: 7,729,709
Trainable params: 7,729,709
Non-trainable params: 0
=================================================================
a = torch.ones(2,3,128,128) # 创建大小为2x3x128x128的张量a,元素值都为1
b = torch.ones(2,1) # 创建大小为2x1的张量b,元素值都为1
b = b.long() # 将张量b的数据类型转换为long型
a = a.to(device) # 将张量a移动到指定的设备上
b = b.to(device) # 将张量b移动到指定的设备上
c = discriminator((a,b)) # 对a和b进行前向传播,得到输出结果
c.size() # 打印输出结果c的大小
输出结果:
torch.Size([2, 1])
四、训练模型
- 定义损失函数
adversarial_loss = nn.BCELoss()
def generator_loss(fake_output, label):
gen_loss = adversarial_loss(fake_output, label)
return gen_loss
def discriminator_loss(output, label):
disc_loss = adversarial_loss(output, label)
return disc_loss
- 定义优化器
learning_rate = 0.0002
G_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))
- 训练模型
代码逻辑结构图:
1、首先设置了训练的总轮数和用于存储每轮训练中判别器和生成器损失的列表。
2、然后进行GAN模型的训练。在每轮训练中,它首先从数据加载器中加载真实图像和标签,然后计算判别器对真实图像的损失,接着从噪声向量中生成假图像,计算判别器对假图像的损失,计算判别器总体损失并反向传播更新判别器的参数,然后计算生成器的损失并反向传播更新生成器的参数。
3、 最后,它打印当前轮次的判别器和生成器的平均损失,并将当前轮次的判别器和生成器的平均损失保存到列表中 。
4、 在每10轮训练后,它会将生成的假图像保存为图片文件,并将当前轮次的生成器和判别器的权重保存到文件。
# 设置训练的总轮数
num_epochs = 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()
# 将真实图像数据和标签转移到GPU(如果可用)
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))
# 计算判别器对假图像的损失(注意detach()函数用于分离生成器梯度计算图)
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:
# 将生成的假图像保存为图片文件
save_image(generated_image.data[:50], './G3/images/sample_%d' % epoch + '.png', nrow=5, normalize=True)
# 将当前轮次的生成器和判别器的权重保存到文件
torch.save(generator.state_dict(), './G3/training_weights/generator_epoch_%d.pth' % (epoch))
torch.save(discriminator.state_dict(), './G3/training_weights/discriminator_epoch_%d.pth' % (epoch))
输出结果:
Epoch: [1/100]: D_loss: 0.506, G_loss: 1.971
Epoch: [2/100]: D_loss: 0.463, G_loss: 1.983
Epoch: [3/100]: D_loss: 0.480, G_loss: 1.766
Epoch: [4/100]: D_loss: 0.418, G_loss: 1.786
Epoch: [5/100]: D_loss: 0.433, G_loss: 1.772
Epoch: [6/100]: D_loss: 0.490, G_loss: 1.931
Epoch: [7/100]: D_loss: 0.430, G_loss: 1.692
Epoch: [8/100]: D_loss: 0.459, G_loss: 1.828
Epoch: [9/100]: D_loss: 0.413, G_loss: 1.916
Epoch: [10/100]: D_loss: 0.438, G_loss: 1.909
Epoch: [11/100]: D_loss: 0.378, G_loss: 2.068
Epoch: [12/100]: D_loss: 0.359, G_loss: 2.239
Epoch: [13/100]: D_loss: 0.350, G_loss: 2.236
Epoch: [14/100]: D_loss: 0.355, G_loss: 2.210
Epoch: [15/100]: D_loss: 0.403, G_loss: 2.222
Epoch: [16/100]: D_loss: 0.333, G_loss: 2.164
Epoch: [17/100]: D_loss: 0.407, G_loss: 2.180
Epoch: [18/100]: D_loss: 0.424, G_loss: 1.925
Epoch: [19/100]: D_loss: 0.427, G_loss: 1.782
Epoch: [20/100]: D_loss: 0.448, G_loss: 1.705
Epoch: [21/100]: D_loss: 0.457, G_loss: 1.709
Epoch: [22/100]: D_loss: 0.420, G_loss: 1.632
Epoch: [23/100]: D_loss: 0.422, G_loss: 1.572
Epoch: [24/100]: D_loss: 0.436, G_loss: 1.645
Epoch: [25/100]: D_loss: 0.418, G_loss: 1.643
Epoch: [26/100]: D_loss: 0.403, G_loss: 1.682
Epoch: [27/100]: D_loss: 0.413, G_loss: 1.672
Epoch: [28/100]: D_loss: 0.428, G_loss: 1.714
Epoch: [29/100]: D_loss: 0.382, G_loss: 1.669
Epoch: [30/100]: D_loss: 0.440, G_loss: 1.740
Epoch: [31/100]: D_loss: 0.384, G_loss: 1.822
Epoch: [32/100]: D_loss: 0.354, G_loss: 1.767
Epoch: [33/100]: D_loss: 0.619, G_loss: 2.270
Epoch: [34/100]: D_loss: 0.336, G_loss: 1.846
Epoch: [35/100]: D_loss: 0.339, G_loss: 1.789
Epoch: [36/100]: D_loss: 0.348, G_loss: 1.814
Epoch: [37/100]: D_loss: 0.360, G_loss: 1.845
Epoch: [38/100]: D_loss: 0.361, G_loss: 1.951
Epoch: [39/100]: D_loss: 0.356, G_loss: 1.939
Epoch: [40/100]: D_loss: 0.476, G_loss: 2.059
Epoch: [41/100]: D_loss: 0.337, G_loss: 1.936
Epoch: [42/100]: D_loss: 0.373, G_loss: 1.994
Epoch: [43/100]: D_loss: 0.324, G_loss: 1.913
Epoch: [44/100]: D_loss: 0.355, G_loss: 2.121
Epoch: [45/100]: D_loss: 0.390, G_loss: 2.111
Epoch: [46/100]: D_loss: 0.329, G_loss: 2.057
Epoch: [47/100]: D_loss: 0.346, G_loss: 2.103
Epoch: [48/100]: D_loss: 0.420, G_loss: 2.175
Epoch: [49/100]: D_loss: 0.332, G_loss: 2.130
Epoch: [50/100]: D_loss: 0.299, G_loss: 2.141
Epoch: [51/100]: D_loss: 0.331, G_loss: 2.103
Epoch: [52/100]: D_loss: 0.285, G_loss: 2.221
Epoch: [53/100]: D_loss: 0.747, G_loss: 2.666
Epoch: [54/100]: D_loss: 0.324, G_loss: 2.297
Epoch: [55/100]: D_loss: 0.273, G_loss: 2.102
Epoch: [56/100]: D_loss: 0.294, G_loss: 2.147
Epoch: [57/100]: D_loss: 0.282, G_loss: 2.212
Epoch: [58/100]: D_loss: 0.307, G_loss: 2.223
Epoch: [59/100]: D_loss: 0.280, G_loss: 2.266
Epoch: [60/100]: D_loss: 0.432, G_loss: 2.417
Epoch: [61/100]: D_loss: 0.287, G_loss: 2.280
Epoch: [62/100]: D_loss: 0.259, G_loss: 2.314
Epoch: [63/100]: D_loss: 0.332, G_loss: 2.448
Epoch: [64/100]: D_loss: 0.405, G_loss: 2.455
Epoch: [65/100]: D_loss: 0.261, G_loss: 2.330
Epoch: [66/100]: D_loss: 0.555, G_loss: 2.469
Epoch: [67/100]: D_loss: 0.322, G_loss: 2.374
Epoch: [68/100]: D_loss: 0.272, G_loss: 2.382
Epoch: [69/100]: D_loss: 0.275, G_loss: 2.374
Epoch: [70/100]: D_loss: 0.260, G_loss: 2.409
Epoch: [71/100]: D_loss: 0.266, G_loss: 2.430
Epoch: [72/100]: D_loss: 0.261, G_loss: 2.490
Epoch: [73/100]: D_loss: 0.265, G_loss: 2.510
Epoch: [74/100]: D_loss: 0.281, G_loss: 2.610
Epoch: [75/100]: D_loss: 0.272, G_loss: 2.607
Epoch: [76/100]: D_loss: 0.315, G_loss: 2.574
Epoch: [77/100]: D_loss: 0.258, G_loss: 2.602
Epoch: [78/100]: D_loss: 0.562, G_loss: 2.995
Epoch: [79/100]: D_loss: 0.266, G_loss: 2.542
Epoch: [80/100]: D_loss: 0.258, G_loss: 2.576
Epoch: [81/100]: D_loss: 0.269, G_loss: 2.581
Epoch: [82/100]: D_loss: 0.258, G_loss: 2.635
Epoch: [83/100]: D_loss: 0.225, G_loss: 2.652
Epoch: [84/100]: D_loss: 0.231, G_loss: 2.713
Epoch: [85/100]: D_loss: 0.229, G_loss: 2.706
Epoch: [86/100]: D_loss: 0.365, G_loss: 2.991
Epoch: [87/100]: D_loss: 0.402, G_loss: 2.788
Epoch: [88/100]: D_loss: 0.235, G_loss: 2.726
Epoch: [89/100]: D_loss: 0.245, G_loss: 2.771
Epoch: [90/100]: D_loss: 0.220, G_loss: 2.759
Epoch: [91/100]: D_loss: 0.214, G_loss: 2.809
Epoch: [92/100]: D_loss: 0.503, G_loss: 2.962
Epoch: [93/100]: D_loss: 0.219, G_loss: 2.769
Epoch: [94/100]: D_loss: 0.248, G_loss: 2.859
Epoch: [95/100]: D_loss: 0.246, G_loss: 2.814
Epoch: [96/100]: D_loss: 0.231, G_loss: 2.829
Epoch: [97/100]: D_loss: 0.210, G_loss: 2.905
Epoch: [98/100]: D_loss: 0.219, G_loss: 2.967
Epoch: [99/100]: D_loss: 0.243, G_loss: 3.005
Epoch: [100/100]: D_loss: 0.222, G_loss: 2.992
五、模型分析
- 加载模型
generator.load_state_dict(torch.load('./G3/training_weights/generator_epoch_100.pth'), strict=False)
generator.eval()
输出结果:
Generator(
(label_conditioned_generator): Sequential(
(0): Embedding(3, 100)
(1): Linear(in_features=100, out_features=16, bias=True)
)
(latent): Sequential(
(0): Linear(in_features=100, out_features=8192, bias=True)
(1): LeakyReLU(negative_slope=0.2, inplace=True)
)
(model): Sequential(
(0): ConvTranspose2d(513, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(512, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): ConvTranspose2d(512, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
(6): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(10): BatchNorm2d(64, eps=0.8, momentum=0.1, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
(13): Tanh()
)
)
# 导入所需的库
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 gridspec
# 生成潜在空间的点,作为生成器的输入
def 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_input
# 在两个潜在空间点之间进行均匀插值
def 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 * p2
vectors.append(v)
return asarray(vectors)
# 生成两个潜在空间的点
pts = generate_latent_points(100, 2)
# 在两个潜在空间点之间进行插值
interpolated = interpolate_points(pts[0], pts[1])
# 将数据转换为torch张量并将其移至GPU(假设device已正确声明为GPU)
interpolated = torch.tensor(interpolated).to(device).type(torch.float32)
output = None
# 对于三个类别的循环,分别进行插值和生成图片
for label in range(3):
# 创建包含相同类别标签的张量
labels = torch.ones(10) * label
labels = 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 = pred
else:
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))
gs = gridspec.GridSpec(nrow, ncol)
k = 0
for i in range(nrow):
for j in range(ncol):
pred = (output[k, :, :, :] + 1 ) * 127.5
pred = 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()
输出结果: