最简单条件生成对抗网络学习(CGAN)

最简单条件生成对抗网络(CGAN)学习:通俗讲解

欢迎点赞和收藏,后续会继续分享文章,记得关注
本文为365天深度学习训练营中的博客学习总结
原作者:k同学呀
文章来源:K同学的学习圈

模型提出介绍

我们都知道GAN可以生成很逼真的人脸,它是随机生成人脸呀,如果我指定要生成一张女生或男生的脸怎么办,如果我想指定生成一张迪丽热巴的脸怎么办,是不是它就不行了。再比如它可以生成手势图,比如剪刀手,里面有阴影的,有真人的,那如果我们指定要生成阴影的,它也做不到。因为最初的GAN只是以假乱真,还没有监督信号,所以做不到有条件的生成。

模型升级方法

既然GAN没有监督信号,那我们直接加入就好了。
生成网络: G(x)最初只有输入一个噪声。那我们再加入一个监督信号并编码就好了。以黑白手势为例。监督信号为阴影手势,还是真人手势。然后通过一个编码器进行编码,再把输出结果与噪声同时输入就好了。

在这里插入图片描述
同理,判别器也只需要加一个是对什么条件下的输出进行判别即可。
在这里插入图片描述

条件映射方法

上面提到了把条件进行编码输入就可以。那我们怎么编码呢?这是核心问题,其实你仔细想一想是有现成方法的。我们只需要把word3vector 中的embeddings层拿过来就能用。比如一个嵌入层加一个全连接。
talking is cheap, show me code .下面我们就一起学习和复现代码。🙌

代码

导包

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

设置超参

dataroot = "./data/GAN/"  # 数据路径
batch_size = 128  # 训练过程中的批次大小
image_size = 128   # 图像的尺寸(宽度和高度)
image_shape = (3, 128, 128)
image_dim = int(np.prod(image_shape))
latent_dim = 100
n_classes = 3     # 条件标签的总数
embedding_dim = 100
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

数据集后面上传,可以在我的主页免费下载

导入数据

# 创建数据集
train_dataset = datasets.ImageFolder(root=dataroot,
                           transform=transforms.Compose([
                           transforms.Resize(image_size),        # 调整图像大小
                           transforms.ToTensor(),                # 将图像转换为张量
                           transforms.Normalize((0.5, 0.5, 0.5), # 标准化图像张量
                                                (0.5, 0.5, 0.5)),
                           ]))

# 创建数据加载器
train_loader = torch.utils.data.DataLoader(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
        
show_batch(train_loader)

在这里插入图片描述

定义模型

参数初始化函数
# 自定义权重初始化函数,用于初始化生成器和判别器的权重
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)
定义判别器
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)

模型训练

定义损失、优化器
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))
开始训练
# 设置训练的总轮数
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], './data/images_GAN3/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))
...
Epoch: [89/100]: D_loss: 0.204, G_loss: 2.987
Epoch: [90/100]: D_loss: 0.278, G_loss: 3.057
Epoch: [91/100]: D_loss: 0.245, G_loss: 3.064
Epoch: [92/100]: D_loss: 0.260, G_loss: 2.949
Epoch: [93/100]: D_loss: 0.242, G_loss: 3.050
Epoch: [94/100]: D_loss: 0.183, G_loss: 3.086
Epoch: [95/100]: D_loss: 0.205, G_loss: 3.114
Epoch: [96/100]: D_loss: 0.241, G_loss: 3.200
Epoch: [97/100]: D_loss: 0.866, G_loss: 3.776
Epoch: [98/100]: D_loss: 0.844, G_loss: 2.618
Epoch: [99/100]: D_loss: 0.293, G_loss: 2.459
Epoch: [100/100]: D_loss: 0.232, G_loss: 2.673

结果分析

模型加载
generator.load_state_dict(torch.load('./training_weights/generator_epoch_100.pth'), strict=False)
generator.eval()
模型调用
# 导入所需的库
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))


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()

在这里插入图片描述

好的,我可以为你提供一个基于PyTorch的CGAN代码框架。首先,让我们定义一些必要的库和参数: ``` python import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader from torchvision import transforms from torchvision.datasets import MNIST # Define hyperparameters batch_size = 128 lr = 0.0002 z_dim = 100 num_epochs = 50 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') ``` 接下来,我们定义生成器和判别器网络: ``` python class Generator(nn.Module): def __init__(self, z_dim=100, img_dim=784, hidden_dim=256): super(Generator, self).__init__() self.fc1 = nn.Linear(z_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.fc3 = nn.Linear(hidden_dim, img_dim) self.relu = nn.ReLU() self.sigmoid = nn.Sigmoid() def forward(self, z): x = self.relu(self.fc1(z)) x = self.relu(self.fc2(x)) x = self.sigmoid(self.fc3(x)) return x class Discriminator(nn.Module): def __init__(self, img_dim=784, hidden_dim=256): super(Discriminator, self).__init__() self.fc1 = nn.Linear(img_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.fc3 = nn.Linear(hidden_dim, 1) self.relu = nn.ReLU() self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.sigmoid(self.fc3(x)) return x ``` 然后,我们定义损失函数和优化器: ``` python criterion = nn.BCELoss() G = Generator(z_dim).to(device) D = Discriminator().to(device) g_optimizer = optim.Adam(G.parameters(), lr=lr) d_optimizer = optim.Adam(D.parameters(), lr=lr) ``` 接下来,我们定义训练循环: ``` python def train_GAN(dataloader, num_epochs): G.train() D.train() for epoch in range(num_epochs): for i, (real_imgs, _) in enumerate(dataloader): real_imgs = real_imgs.to(device) batch_size = real_imgs.size(0) # Train discriminator d_optimizer.zero_grad() # Train on real data real_labels = torch.ones(batch_size, 1).to(device) real_outputs = D(real_imgs) d_loss_real = criterion(real_outputs, real_labels) # Train on fake data z = torch.randn(batch_size, z_dim).to(device) fake_imgs = G(z) fake_labels = torch.zeros(batch_size, 1).to(device) fake_outputs = D(fake_imgs) d_loss_fake = criterion(fake_outputs, fake_labels) d_loss = d_loss_real + d_loss_fake d_loss.backward() d_optimizer.step() # Train generator g_optimizer.zero_grad() z = torch.randn(batch_size, z_dim).to(device) fake_imgs = G(z) fake_labels = torch.ones(batch_size, 1).to(device) fake_outputs = D(fake_imgs) g_loss = criterion(fake_outputs, fake_labels) g_loss.backward() g_optimizer.step() if i % 100 == 0: print("Epoch [{}/{}], Step [{}/{}], Discriminator Loss: {:.4f}, Generator Loss: {:.4f}" .format(epoch, num_epochs, i, len(dataloader), d_loss.item(), g_loss.item())) ``` 最后,我们加载数据集并开始训练: ``` python transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=(0.5,), std=(0.5,)) ]) train_dataset = MNIST(root='./data', train=True, download=True, transform=transform) train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True) train_GAN(train_dataloader, num_epochs) ``` 这是一个简单的CGAN代码框架,你可以根据自己的需求进行修改和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值