第G3周:CGAN入门|生成手势图像

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊
    这段程序实现了一个条件生成对抗网络(Conditional GAN,简称CGAN),用于生成指定类别的石头剪刀布手势图像。与标准GAN不同,CGAN在生成器和判别器中都加入了条件标签信息(这里是三种手势类别),使得生成过程可以受控于特定类别。程序使用PyTorch框架,包含生成器和判别器两个核心组件:生成器将随机噪声向量和类别标签映射为128×128的RGB图像;判别器则同时接收图像和标签,判断图像的真实性。训练过程中,两者相互对抗优化------生成器试图生成以假乱真的图像欺骗判别器,而判别器则努力区分真假图像。训练完成后,程序还展示了通过在潜在空间插值生成的图像序列,直观展示了不同类别间图像的平滑过渡。整体上,这是一个完整的深度生成模型实现案例,涵盖了数据准备、模型构建、训练循环和结果可视化全流程。思维导图如下:
python 复制代码
# 这是一个生成手势图像的程序,使用了CGAN(条件生成对抗网络)技术
# 我们将用最简单易懂的方式解释每个部分,就像给初中生讲道理一样

# 第一步:导入所有需要的工具(就像准备做菜需要各种厨具一样)
import torch  # 一个强大的AI计算库,就像计算器但更厉害
import numpy as np  # 一个处理数字和数组的工具
import torch.nn as nn  # 用于构建神经网络的工具
import torch.optim as optim  # 用于优化神经网络的工具
from torchvision import datasets, transforms  # 用于获取图像数据和处理图像
from torch.autograd import Variable  # 用于自动计算梯度(AI学习时的"思考"过程)
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)

# 设置设备:如果电脑有GPU(图形处理器),就用GPU加速计算;否则用CPU
# GPU就像超级计算器,能更快地处理AI任务
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
batch_size = 128  # 每次处理128张图片(就像一次处理128个苹果)

# 第二步:准备图像数据(就像准备做菜的食材)
# 定义图像处理步骤:
# 1. 将图像调整为128x128像素(就像把照片裁剪成统一大小)
# 2. 将图像转换为数字数组(电脑能理解的形式)
# 3. 将每个像素值归一化到-1到1之间(让数据更"整齐")
train_transform = transforms.Compose([
    transforms.Resize(128),  # 调整大小
    transforms.ToTensor(),  # 转换为张量(数字数组)
    transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])  # 归一化
])

# 加载手势图像数据集(从文件夹中读取)
# 这里假设我们有一个名为"rps"的文件夹,里面有石头、剪刀、布的手势图片
train_dataset = datasets.ImageFolder(root='./data/rps/', transform=train_transform)
# 创建数据加载器,可以方便地一次取一批图片
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True,  # 打乱顺序,避免AI只记住特定顺序
                                           num_workers=6)  # 使用6个进程同时处理数据

# 第三步:定义一些实用函数(就像给AI准备一些工具)
# 显示多张图片的函数
def show_images(images):
    # 创建一个大画布(20x20英寸)
    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  # 只显示一批就停止

# 第四步:定义模型参数(就像定义菜谱的食材和调料)
image_shape = (3, 128, 128)  # 图像的形状:3个颜色通道(RGB),128x128像素
image_dim = int(np.prod(image_shape))  # 计算图像总像素数(3*128*128)
latent_dim = 100  # 潜在空间的维度(AI用来生成图片的"创意空间"大小)
n_classes = 3  # 手势类别数:石头、剪刀、布
embedding_dim = 100  # 类别嵌入维度(将类别标签转换为数字向量)

# 第五步:定义权重初始化函数(就像给AI的"大脑"做准备)
# 这个函数会为神经网络的每个层设置合适的初始值
def weights_init(m):
    # 获取当前层的名称
    classname = m.__class__.__name__

    # 如果是卷积层(负责识别图像特征的层)
    if classname.find('Conv') != -1:
        # 用正态分布初始化权重(让AI开始时有点"随机性")
        torch.nn.init.normal_(m.weight, 0.0, 0.02)

    # 如果是批归一化层(负责让AI训练更稳定)
    elif classname.find('BatchNorm') != -1:
        # 用正态分布初始化权重(均值为1,标准差为0.02)
        torch.nn.init.normal_(m.weight, 1.0, 0.02)
        # 将偏置项初始化为0(让AI从"零"开始)
        torch.nn.init.zeros_(m.bias)

# 第六步:定义生成器(Generator) - 生成图片的"艺术家"
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        # 定义条件标签生成器:将类别标签转换为向量
        # 例如:将"石头"转换为一个100维的数字向量
        self.label_conditioned_generator = nn.Sequential(
            nn.Embedding(n_classes, embedding_dim),  # 类别嵌入层
            nn.Linear(embedding_dim, 16)  # 将嵌入向量转换为16维向量
        )

        # 定义潜在向量生成器:将随机噪声转换为图像特征
        # 潜在向量就像AI的"创意种子"
        self.latent = nn.Sequential(
            nn.Linear(latent_dim, 4 * 4 * 512),  # 将100维噪声转换为512x4x4的特征图
            nn.LeakyReLU(0.2, inplace=True)  # 激活函数,让AI更"聪明"
        )

        # 定义生成器的主要结构:将条件信息和潜在向量转换为图像
        self.model = nn.Sequential(
            # 反卷积层1:将513维输入转换为64x8x8的特征图
            nn.ConvTranspose2d(513, 64 * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(64 * 8, momentum=0.1, eps=0.8),  # 批归一化
            nn.ReLU(True),  # 激活函数
            
            # 反卷积层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()  # 激活函数,将像素值限制在[-1, 1]之间
        )

    # 前向传播:定义输入如何转换为输出
    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

# 创建生成器并移动到GPU(如果可用)
generator = Generator().to(device)
# 应用权重初始化
generator.apply(weights_init)
print(generator)  # 打印生成器结构

# 使用torchinfo显示生成器结构(更详细)
from torchinfo import summary
summary(generator)

# 第七步:定义判别器(Discriminator) - 评判图片真假的"裁判"
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(
            # 卷积层1:输入6通道(3通道图像+3通道标签),输出64通道
            nn.Conv2d(6, 64, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),  # 激活函数
            
            # 卷积层2:输入64通道,输出128通道
            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),
            
            # 卷积层3:输入128通道,输出256通道
            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),
            
            # 卷积层4:输入256通道,输出512通道
            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),  # 随机丢弃40%的神经元,防止过拟合
            nn.Linear(4608, 1),  # 全连接层,将特征映射到输出
            nn.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

# 创建判别器并移动到GPU
discriminator = Discriminator().to(device)
# 应用权重初始化
discriminator.apply(weights_init)
print(discriminator)  # 打印判别器结构

# 使用torchinfo显示判别器结构
summary(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

# 设置学习率(AI学习的速度)
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))

# 第九步:训练循环(AI学习的过程)
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()
        
        # 将数据移动到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)
        
        # 生成假图片(AI的"创作")
        noise_vector = torch.randn(real_images.size(0), latent_dim, device=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)))
    
    # 每10轮保存一次结果
    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))

# 第十步:加载训练好的模型并生成图片
generator.load_state_dict(torch.load('./training_weights/generator_epoch_100.pth'), strict=False)
generator.eval()  # 设置为评估模式

# 第十一步:生成插值图片(从一种手势过渡到另一种)
# 生成潜在空间的点(AI的"创意种子")
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])

# 将数据转换为张量
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()
    
    # 生成图片
    predictions = generator((interpolated, labels))
    predictions = predictions.permute(0,2,3,1)  # 调整维度
    pred = predictions.detach().cpu()  # 移回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):
        # 将像素值从[-1, 1]转换回[0, 255]
        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()  # 显示最终结果

最终显示如下:

相关推荐
bubiyoushang8882 小时前
基于LSTM神经网络的短期风速预测实现方案
人工智能·神经网络·lstm
中烟创新2 小时前
烟草专卖文书生成智能体与法规案卷评查智能体获评“年度技术最佳实践奖”
人工智能
得一录2 小时前
大模型中的多模态知识
人工智能·aigc
Github掘金计划3 小时前
Claude Work 开源平替来了:让 AI 代理从“终端命令“变成“产品体验“
人工智能·开源
ghgxm5203 小时前
Fastapi_00_学习方向 ——无编程基础如何用AI实现APP生成
人工智能·学习·fastapi
余俊晖3 小时前
3秒实现语音克隆的Qwen3-TTS的Qwen-TTS-Tokenizer和方法架构概览
人工智能·语音识别
森屿~~3 小时前
AI 手势识别系统:踩坑与实现全记录 (PyTorch + MediaPipe)
人工智能·pytorch·python
运维行者_4 小时前
2026 技术升级,OpManager 新增 AI 网络拓扑与带宽预测功能
运维·网络·数据库·人工智能·安全·web安全·自动化
淬炼之火4 小时前
图文跨模态融合基础:大语言模型(LLM)
人工智能·语言模型·自然语言处理