梳理GAN

我参考训练生成动漫头像运行了一次GAN的代码,虽然对于GAN的模型略有了解,但是对细节理解仍然不够充分。

问题小白

代码结构?

GAN包括生成器与判别器,上述代码包括两部分,model.pytrain.pymodel.py中定义了两个网络,分别是生成器NetG、鉴别器NetD。训练GAN的时候将model.py中的NetG NetD引入到train.py中。

model.py如何构建网络模型?

首先将torch import一下

import torch.nn as nn

然后定义生成器NetG,并且规定第一层的输入内容和输出尺寸,以及以后各层的输出尺寸。

class NetG(nn.Module):
    def __init__(self, ngf, nz):
        super(NetG, self).__init__()
        # layer1输入的是一个100x1x1的随机噪声, 输出尺寸(ngf*8)x4x4
        self.layer1 = nn.Sequential(
            nn.ConvTranspose2d(nz, ngf * 8, kernel_size=4, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(inplace=True)
        )
        # layer2输出尺寸(ngf*4)x8x8
        self.layer2 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(inplace=True)
        )
        # layer3输出尺寸(ngf*2)x16x16
        self.layer3 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(inplace=True)
        )
        # layer4输出尺寸(ngf)x32x32
        self.layer4 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(inplace=True)
        )
        # layer5输出尺寸 3x96x96
        self.layer5 = nn.Sequential(
            nn.ConvTranspose2d(ngf, 3, 5, 3, 1, bias=False),
            nn.Tanh()
        )

    # 定义NetG的前向传播
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        return out

定义鉴别器netD

class NetD(nn.Module):
    def __init__(self, ndf):
        super(NetD, self).__init__()
        # layer1 输入 3 x 96 x 96, 输出 (ndf) x 32 x 32
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, ndf, kernel_size=5, stride=3, padding=1, bias=False),
            nn.BatchNorm2d(ndf),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer2 输出 (ndf*2) x 16 x 16
        self.layer2 = nn.Sequential(
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer3 输出 (ndf*4) x 8 x 8
        self.layer3 = nn.Sequential(
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer4 输出 (ndf*8) x 4 x 4
        self.layer4 = nn.Sequential(
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer5 输出一个数(概率)
        self.layer5 = nn.Sequential(
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    # 定义NetD的前向传播
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        return out


  1. nn.Sequential()用于定义顺序连接模型,一般地使用该函数可以定义神经网络模型,而在该示例中,Sequential()用于包装层将几个层包装起来像一个块一样,(layer1,layer2等);
  2. nn.ConvTranspose2d()是一次上采样卷积操作,与之对应的nn.conv2d()是卷积操作;
  3. nn.Batchnorm2d()常用于卷积神经网络中防止梯度消失或爆炸,设置的参数是卷积的输出通道数;
  4. 剩下的如nn.ReLU(),nn.Tanh(),nn.LeakyReLU()是各类不同的激活函数,激活函数输入参数的含义:如ReLU函数中的inplace-选择是否进行覆盖运算nn.LeakyReLU(0.2,inplace=True)表示f(x) = max(0, x) + 0.2*min(0, x)
  5. forward()定义前向传播,通俗理解就是顺序计算
  6. backward()定义反向传播,通俗理解为梯度下降,根据代价函数对神经元权重求导等。
  7. 不同层传递参数的维度变化规律:参考反卷积ConvTranspose2d()参数理解
  class torch.nn.ConvTranspose2d(in_channels,out_channels,kernel_size,stride=1,
  padding=0,output_padding=0, groups=1,bias=True,dilation=1)
  参数含义:
   in_channels(int)  ---输入信号的通道数
   out_channels(int)  ----卷积产生的通道数
   kernel_size(int or tuple)  ---卷积核的大小
   stride(int or tuple,optional)  ---卷积步长,即要将输入扩大的倍数
   padding(int or tuple,optional)  ---输入的每一条边补充0的层数,高宽都增加2*padding
   output_padding(int or tuple,optional)  ---输出边补充ing0de层数,高宽都增加2*padding
   groups(int ,optional)  ---从输入通道到输出通道的阻塞连接数
   bias(bool, optional)  ---若bias=True,添加偏置
   dilation(int or tuple,optional)---卷积核元素之间的间距
对于每一条边输入输出的尺寸的公式:
output=(input-1)*stride+output_padding -2*padding+kernel_size                                                                                                                                        

查看NetG和NetD的定义发现,netG的最初输入是10011的随机噪声,经过5个layers之后输出为39696的数据;而netD的输入是39696的数据,经过5个layers后得到一个概率(判断输入的数据为真的概率)。NetG和NetD的卷积参数也是对称的

有了简单的模型之后,如何训练?理解train.py

该参考代码添加了一些库,每个库的用法暂时不做讨论(因为我还不懂)

import argparse
import torch
import torchvision
import torchvision.utils as vutils
import torch.nn as nn
from random import randint
from model import NetD, NetG

为训练做准备:定义参数,以及是否使用GPU加速

parser = argparse.ArgumentParser()
parser.add_argument('--batchSize', type=int, default=64)
parser.add_argument('--imageSize', type=int, default=96)
parser.add_argument('--nz', type=int, default=100, help='size of the latent z vector')
parser.add_argument('--ngf', type=int, default=64)
parser.add_argument('--ndf', type=int, default=64)
parser.add_argument('--epoch', type=int, default=25, help='number of epochs to train for')
parser.add_argument('--lr', type=float, default=0.0002, help='learning rate, default=0.0002')
parser.add_argument('--beta1', type=float, default=0.5, help='beta1 for adam. default=0.5')
parser.add_argument('--data_path', default='data/', help='folder to train data')
parser.add_argument('--outf', default='imgs/', help='folder to output images and model checkpoints')
opt = parser.parse_args()#解析参数
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

argparse.ArgumentParser()用法解析
基本用法就是添加参数(名字,类型,数值)和解析参数

然后定义数据预处理函数以及数据读入

#图像读入与预处理
transforms = torchvision.transforms.Compose([
    torchvision.transforms.Scale(opt.imageSize),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ])

dataset = torchvision.datasets.ImageFolder(opt.data_path, transform=transforms)
#数据所在文件夹
dataloader = torch.utils.data.DataLoader(
    dataset=dataset,
    batch_size=opt.batchSize,
    shuffle=True,
    drop_last=True,
)

接下来依旧是开始训练前的准备,直接在代码上理解

netG = NetG(opt.ngf, opt.nz).to(device)
#.to(device) device copy tensor变量到指定运行位置(CPU或者GPU上)
netD = NetD(opt.ndf).to(device)

criterion = nn.BCELoss()
#pytorch给出的18种损失函数的一种:BCELoss()表示二分类任务时的交叉熵计算函数
optimizerG = torch.optim.Adam(netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))
optimizerD = torch.optim.Adam(netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))
#torch.optim 是实现了多种优化算法的包,torch.optim.Adam()是其中一种,使用前要做如上定义
label = torch.FloatTensor(opt.batchSize)
real_label = 1
fake_label = 0
#指定类别标签

以上准备工作完成后,便开始迭代了

for epoch in range(1, opt.epoch + 1):
    for i, (imgs,_) in enumerate(dataloader):
        # 固定生成器G,训练鉴别器D
        optimizerD.zero_grad()
        ## 让D尽可能的把真图片判别为1
        imgs=imgs.to(device)
        output = netD(imgs)
        label.data.fill_(real_label)
        label=label.to(device)
        errD_real = criterion(output, label)
        errD_real.backward()
        ## 让D尽可能把假图片判别为0
        label.data.fill_(fake_label)
        noise = torch.randn(opt.batchSize, opt.nz, 1, 1)
        noise=noise.to(device)
        fake = netG(noise)  # 生成假图
        output = netD(fake.detach()) #避免梯度传到G,因为G不用更新
        errD_fake = criterion(output, label)
        errD_fake.backward()
        errD = errD_fake + errD_real
        optimizerD.step()

        # 固定鉴别器D,训练生成器G
        optimizerG.zero_grad()
        # 让D尽可能把G生成的假图判别为1
        label.data.fill_(real_label)
        label = label.to(device)
        output = netD(fake)
        errG = criterion(output, label)
        errG.backward()
        optimizerG.step()

        print('[%d/%d][%d/%d] Loss_D: %.3f Loss_G %.3f'
              % (epoch, opt.epoch, i, len(dataloader), errD.item(), errG.item()))

    vutils.save_image(fake.data,
                      '%s/fake_samples_epoch_%03d.png' % (opt.outf, epoch),
                      normalize=True)
    torch.save(netG.state_dict(), '%s/netG_%03d.pth' % (opt.outf, epoch))
    torch.save(netD.state_dict(), '%s/netD_%03d.pth' % (opt.outf, epoch))


第一个poech结束时得到的
在这里插入图片描述
第23次peoch结束时得到的
在这里插入图片描述

训练完成后呢?

pth文件?

以上代码在运行时,把每次迭代都保存了==.pth==文件,其实保存的就是模型,且存在两种保存方式:保存整个模型或者只保存模型参数。参考链接

torch.save(model.state_dict(), "my_model.pth") # 只保存模型的参数
torch.save(model, "my_model.pth") # 保存整个模型

保存的模型参数为字典类型,即key-value的形式。

测试时将上述训练得到的数据读入赋值给模型即可。

import torch
import torchvision.utils as vutils
from model import NetD, NetG
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
netG = NetG(64, 100).to(device)
netD = NetD(64).to(device)
checkpoint = torch.load('imgs/netG_020.pth')
# optimizerG.load_state_dict(checkpoint['optimizer'])
netG.load_state_dict(checkpoint)
noise = torch.randn(64, 100, 1, 1)
noise=noise.to(device)
fake = netG(noise)  # 生成假图
vutils.save_image(fake.data,
                      '%s/fake_samples_epoch_%03d.png' % ('imgs/', 26),
                      normalize=True)
# epoch = checkpoint(['epoch'])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值