电子书/动手学 CV-PyTorch

22 / 28

5.2 GAN实战: 手写数字生成

约 0.1 万字 · 0 阅读

🎉🎉 在接下来的内容中,我们将结合代码,深入了解GAN的模型架构,并动手实现第一个手写数字生成的GAN网络。

为便于说明GAN的关键环节,这里我们弱化了网络和数据集的复杂度。数据集为 MNIST、网络用全连接层。后续将用一些卷积层的实例来说明。

导入相关库

首先导入numpy、torch等模块。

import os
import torch
import torchvision
import torch.nn as nn
from torchvision import transforms
from torchvision.utils import save_image

数据加载和参数定义

pytorch内置集成了MNIST数据集。

# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# Hyper-parameters 
latent_size = 64  
hidden_size = 256
image_size = 784
num_epochs = 200
batch_size = 100
sample_dir = 'samples'

# Create a directory if not exists
if not os.path.exists(sample_dir):
    os.makedirs(sample_dir)

# Image processing
transform = transforms.Compose([
                transforms.ToTensor(),
                transforms.Normalize(mean=0.5,std=0.5)])

# MNIST dataset
mnist = torchvision.datasets.MNIST('./data',
                                   train=True,
                                   transform=transform,
                                   download=True)

# Data loader
data_loader = torch.utils.data.DataLoader(dataset=mnist,
                                          batch_size=batch_size, 
                                          shuffle=True)

判别器模型

定义判别器网络结构,这里使用LeakyReLU为激活函数,输出一个节点并经过 Sigmoid后输出,用于真假二分类。

# 构建判断器 
D = nn.Sequential( 
    nn.Linear(image_size, hidden_size), 
    nn.LeakyReLU(0.2), 
    nn.Linear(hidden_size, hidden_size), 
    nn.LeakyReLU(0.2), 
    nn.Linear(hidden_size, 1), 
    nn.Sigmoid())

D = D.to(device)

生成器模型

使用nn.tanh将使数据分布 在[-1,1]之间。其输入是潜在空间的向量z,输出维度与真图像相同。

# 构建生成器
G = nn.Sequential(
    nn.Linear(latent_size, hidden_size), 
    nn.ReLU(), 
    nn.Linear(hidden_size, hidden_size), 
    nn.ReLU(), 
    nn.Linear(hidden_size, image_size), 
    nn.Tanh())

G = G.to(device)

训练模型

# 定义损失函数以及优化器
# 二值交叉熵损失函数和优化器设置
# Binary cross entropy loss and optimizer
criterion = nn.BCELoss()
d_optimizer = torch.optim.Adam(D.parameters(), lr=0.0002)
g_optimizer = torch.optim.Adam(G.parameters(), lr=0.0002)

# 规范化处理
# 在Image processing中以mean,std=0.5进行规范化,out=2*input-1
# 故input=(out+1)/2
def denorm(x):
    out = (x + 1) / 2
    # 将out张量每个元素的范围限制到区间 [min,max]
    return out.clamp(0, 1)

# 梯度清零    
def reset_grad():
    d_optimizer.zero_grad()
    g_optimizer.zero_grad()

# Start training
total_step = len(data_loader)
for epoch in range(num_epochs):
    for i, (images, _) in enumerate(data_loader):
        images = images.reshape(batch_size, -1).to(device) 

        # 定义图像是真或假的标签           
        real_labels = torch.ones(batch_size, 1).to(device) 
        fake_labels = torch.zeros(batch_size, 1).to(device) 
        # ================================================================== # 
        #                          训练判别器  K=1                            # 
        # ================================================================== # 
        # 定义判别器对真图像的损失函数
        outputs = D(images) # 真图像输入给鉴别器,并产生判别器输出
        # 计算由真图像输入给判别器产生的输出与真的label间的二值交叉熵损失
        d_loss_real = criterion(outputs, real_labels)
        real_score = outputs 

        # 定义判别器对假图像(即由潜在空间点生成的图像)的损失函数 
        z = torch.randn(batch_size, latent_size).to(device) 
        # 随机向量输入给生成器,并产生生成器输出假图像
        fake_images = G(z)
        outputs = D(fake_images) # 假图像输入给鉴别器,并产生判别器输出
        # 计算由假图像输入给判别器产生的输出与虚的label间的二值交叉熵损失
        d_loss_fake = criterion(outputs, fake_labels)
        fake_score = outputs

        # 得到判别器总的损失函数
        d_loss = d_loss_real + d_loss_fake 

        # 对生成器、判别器的梯度清零
        # 进行反向传播及运行判别器的优化器 
        reset_grad()
        d_loss.backward()
        d_optimizer.step() 
        # ================================================================== # 
        #                           训练生成器                                # 
        # ================================================================== # 
        # 定义生成器对假图像的损失函数
        z = torch.randn(batch_size, latent_size).to(device)
        # 随机向量输入给生成器,并产生生成器输出假图像
        fake_images = G(z)
        outputs = D(fake_images) # 假图像输入给鉴别器,并产生判别器输出
        # 计算生成器生成的假图输入给鉴别器鉴别输出与真的label间的二值交叉熵损失
        g_loss = criterion(outputs, real_labels) 

        # 对生成器、判别器的梯度清零 
        # 进行反向传播及运行生成器的优化器 
        reset_grad()
        g_loss.backward()
        g_optimizer.step()
        # 每迭代一定步骤,打印结果值
        if (i + 1) % 200 == 0:
            print(
                "Epoch [{}/{}], Step [{}/{}], d_loss: {:.4f}, g_loss: {:.4f}, D(x): {:.2f}, D(G(z)): {:.2f}".format(
                    epoch,
                    num_epochs,
                    i + 1,
                    total_step,
                    d_loss.item(),
                    g_loss.item(),
                    real_score.mean().item(),
                    fake_score.mean().item(),
                )
            )

    # 保存真图像
    if (epoch + 1) == 1:
          images = images.reshape(images.size(0), 1, 28, 28)
          save_image(denorm(images), os.path.join(sample_dir, 'real_images.png')) 

    # 保存假图像 
    fake_images = fake_images.reshape(fake_images.size(0), 1, 28, 28) 
    save_image(denorm(fake_images), os.path.join(sample_dir, 'fake_images-{}.png'.format(epoch+1))) 

# 保存模型
torch.save(G.state_dict(), 'G.ckpt')
torch.save(D.state_dict(), 'D.ckpt')

可视化结果

import matplotlib.image as mpimg
import matplotlib.pyplot as plt

reconsPath = './samples/fake_images-200.png' 
Image = mpimg.imread(reconsPath) 
plt.imshow(Image)
plt.axis('off')
plt.show() 

fake_images

手写数字图片

小结

在本节内容中,我们从代码的角度直观清晰的了解了损失函数以及模型训练过程的pytorch实现过程。动手亲自完成了手写数字生成的GAN项目实践。虽然这个项目可能在读者看起来比较简单,但却是整个GAN模型的核心所在。而例如CGAN,DCGAN,CycleGAN等等也都是基于这段简单的代码,在网络层方面进行改进,形成新的模型。

本文转载自开源项目 动手学 CV-PyTorch(作者 Datawhale),按 GPL-3.0 许可刊载,正文未改动。

医疗、法律、政策类内容仅供参考,具体以当地部门、医生、律师的说法为准。