对抗生成网络架构原理解析


之前已经讲解了如何使用经典的卷积神经网络做分类和回归任务,相信大家对深度学习已经有了一定的了解,那么这一节开始,我们就要一起学习另一个深度学习中很火的模块——对抗生成网络。

对抗生成网络原理

所谓的对抗生成网络同,最主要的组成部分就是生成器和判别器。以银行鉴别假钞为例,假钞的出现会扰乱市场,所以需要验钞,而有了验钞器,假钞就无法花出,于是假钞就会升级,骗过当前的验钞机制,随后验钞器也会升级,鉴定出升级的假钞。对抗生成网络中,生成器可以看做这个例子中的假钞,而判别器就相当于这个例子中的验钞机,随着生成器和判别器的迭代,最终的结果就是生成器生成的“假钞”几乎可以达到与“真钞”几乎相同,此时我们便可以只保留生成器的最终模型。对抗生成网络可以完成的任务有很多,比如修改图像的季节:
在这里插入图片描述
或者对模糊图像进行超分辨率重建:
在这里插入图片描述

损失函数的解释说明

在传统的神经网络之中,我们介绍过损失函数的作用,忘记的小伙伴可以复习一下~
在对抗生成网络中,我们的判别器会针对生成器生成的结果给出一个评价,这个评价介于0和1之间,越接近于1,说明生成器的输出越逼真,相反,接近0则证明生成器输出结果差。损失函数是用来描述判别结果与实际的差距,其计算公式为:
在这里插入图片描述
其中,t代表了实际值,在该网络下只有0和1之分(由生成器生成的结果会被标记为0,由数据集中读取到的结果则标记为1),o则代表了判别器判定的结果。假设生成器输出的结果,判别器判断其为0.87,就可以说生成效果较好(因为判别器判断其为数据集中读到的内容),损失值为:-1×[1×log(0.87)+0×log(0.23)]=0.0605。下面我们用一段代码给大家解释一下损失函数如何使用:

import torch
from torch import autograd
input = autograd.Variable(torch.tensor([[ 1.9072,  1.1079,  1.4906],
        [-0.6584, -0.0512,  0.7608],
        [-0.0614,  0.6583,  0.1095]]), requires_grad=True) # 模拟判定值
print(input)
print('-'*100)

from torch import nn
m = nn.Sigmoid() # 将判定值转化成0到1之间的对应值
print(m(input)) # y=f(x)=sigmoid(imput)
print('-'*100)

target = torch.FloatTensor([[0, 1, 1], [1, 1, 1], [0, 0, 0]]) # 模拟标记值,0代表生成器生成,1代表数据集读入
print(target)
print('-'*100)

import math
## 手动计算损失
r11 = 0 * math.log(0.8707) + (1-0) * math.log((1 - 0.8707))
r12 = 1 * math.log(0.7517) + (1-1) * math.log((1 - 0.7517))
r13 = 1 * math.log(0.8162) + (1-1) * math.log((1 - 0.8162))

r21 = 1 * math.log(0.3411) + (1-1) * math.log((1 - 0.3411))
r22 = 1 * math.log(0.4872) + (1-1) * math.log((1 - 0.4872))
r23 = 1 * math.log(0.6815) + (1-1) * math.log((1 - 0.6815))

r31 = 0 * math.log(0.4847) + (1-0) * math.log((1 - 0.4847))
r32 = 0 * math.log(0.6589) + (1-0) * math.log((1 - 0.6589))
r33 = 0 * math.log(0.5273) + (1-0) * math.log((1 - 0.5273))

r1 = -(r11 + r12 + r13) / 3
#0.8447112733378236
r2 = -(r21 + r22 + r23) / 3
#0.7260397266631787
r3 = -(r31 + r32 + r33) / 3
#0.8292933181294807
bceloss = (r1 + r2 + r3) / 3 
print(bceloss)
print('-'*100)
## 调包,自动进行计算
loss = nn.BCELoss()
# 使用BCELoss()计算损失时需要手动对输入进行sigmoid激活
print(loss(m(input), target))
print('-'*100)
# 使用BCEWithLogitsLoss()可以自动对输入进行激活
loss = nn.BCEWithLogitsLoss() 
print(loss(input, target))

小伙伴可以运行查看哦,这里只展示部分结果:
在这里插入图片描述
可以看到我们手动计算的损失值确实和调包得到的结果相同,大家懂得其中的原理了吧~

生成器构建

在这个模块中我会给大家介绍生成绩的具体组成,需要注意的是我们还没有制作数据,因此这一节和下一节都只是理论解释,大家县要做到能看懂就可以啦,完整的代码会在代码整理中展现。
我们用到的数据集依然是minist,由于手写字体较容易生成,因此我们只用100个随机特征值来进行模拟输入。随着日后任务的复杂度提升,我们的输入内容也会发生变化,代码的解释都放在注释里了:

class Generator(nn.Module): # 制作生成器,由100维随机向量生成一个784(1*28*28)维特征图像
    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)] # in_feat为特征内容,最开始是随机初始化的
                                                    # 我们需要把生成内容变成有价值的特征,最后输出成
                                                    # 和数据集一样的数字
                                                    # 在这里定义每一层网络有out_feat个隐层
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8)) # 随机失活
            layers.append(nn.LeakyReLU(0.2, inplace=True)) # 激活函数
            return layers

        self.model = nn.Sequential( # 定义每一层网络
            *block(opt.latent_dim, 128, normalize=False), # 第一层网络输入由数据集决定,输出为128,不进行随机失活
            *block(128, 256), # 第二层网络输入为128,输出为256,进行随机失活
            *block(256, 512), # 第三层网络输入为256,输出为512,进行随机失活
            *block(512, 1024),
            nn.Linear(1024, int(np.prod(img_shape))), # 输出层,输入为1024,输出为原始图像所有特征(1*28*28)
            nn.Tanh()
        )

    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), *img_shape)
        return img

判别器构建

判别器与生成器最大的区别其实在输入上,其他的结构都是大体相同的:

class Discriminator(nn.Module): # 制作判别器
    def __init__(self):
        super(Discriminator, self).__init__()

        self.model = nn.Sequential( # 与生成器的定义大体相同
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1), # 只需要得到一个最终的预测结果
            nn.Sigmoid(),
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validity = self.model(img_flat)
        return validity

代码整理

首先我们已经把最关键的三个部分给大家介绍完了,接下来就是处理数据、设置优化器以及执行训练了。首先是处理数据:

os.makedirs("images", exist_ok=True)

parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=100, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=128, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--n_cpu", type=int, default=8, help="number of cpu threads to use during batch generation")
# 模拟使用100个随机特征作为生成器的初始输入
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=400, help="interval betwen image samples")
opt = parser.parse_args()
print(opt)

img_shape = (opt.channels, opt.img_size, opt.img_size) # 颜色通道*长*宽

cuda = True if torch.cuda.is_available() else False

接下来是初始化网络、配置数据以及优化器:

# 损失函数
adversarial_loss = torch.nn.BCELoss()

# 实例化生成器和判别器
generator = Generator()
discriminator = Discriminator()

if cuda:
    generator.cuda()
    discriminator.cuda()
    adversarial_loss.cuda()

# 配置数据
os.makedirs("./data/mnist", exist_ok=True)
dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "./data/mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])] # 转换成transform并标准化
        ),
    ),
    batch_size=opt.batch_size,
    shuffle=True,
)

# 优化
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))


Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor

最后就是训练和保存了:

for epoch in range(opt.n_epochs):
    for i, (imgs, _) in enumerate(dataloader): # img维度为batch_size*channel*wide*high

        # 用真假作为标签。把生成器生成的结果标记为假,数据集中拿到的数据标记为真
        # valid代表有batch个数据从数据集中取出,每个都标记成1
        # fake代表有batch个数据由生成器生成,每个都标记成0
        # imgs.size(0)=batch_size
        valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False)
        fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)

        # 配置输入
        real_imgs = Variable(imgs.type(Tensor))

        # 训练生成器
        optimizer_G.zero_grad() # 梯度清零


        # 随机构建batch_size个latent_dim维的标准正态分布噪声向量
        z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

        # 生成一批图像
        gen_imgs = generator(z) # 通过生成器生成batch_size个拥有channel*wide*high个特征的矩阵

        # 将生成结果传入判别器,希望骗过判别器
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)

        g_loss.backward()
        optimizer_G.step()

        # 训练判别器
        optimizer_D.zero_grad()

        #  判别器首先接收的是实际的数字图像
        # 计算真实值损失
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        # 计算生成值损失
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
        # 用平均值反应判别器从生成的样本中分类真实样本的综合能力
        d_loss = (real_loss + fake_loss) / 2
        d_loss.backward()
        optimizer_D.step()

        print(
            "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
            % (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())
        )

        batches_done = epoch * len(dataloader) + i
        if batches_done % opt.sample_interval == 0:
            save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

代码的解释已经附加成注释了,小伙伴们可以自行阅读。最后我们把所有的代码整理起来,就能够运行了:

import argparse
import os
import numpy as np
import math
import torchvision.transforms as transforms
from torchvision.utils import save_image
from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch

os.makedirs("images", exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=100, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=128, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--n_cpu", type=int, default=8, help="number of cpu threads to use during batch generation")
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=400, help="interval betwen image samples")
opt = parser.parse_args()
print(opt)

img_shape = (opt.channels, opt.img_size, opt.img_size)

cuda = True if torch.cuda.is_available() else False


class Generator(nn.Module):
    
    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)] 
            
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *block(opt.latent_dim, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            nn.Linear(1024, int(np.prod(img_shape))), 
            nn.Tanh()
        )

    def forward(self, z):
        print('foward')
        img = self.model(z)
        img = img.view(img.size(0), *img_shape)
        return img


class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        self.model = nn.Sequential(
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1), 
            nn.Sigmoid(),
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validity = self.model(img_flat)
        return validity

adversarial_loss = torch.nn.BCELoss()

generator = Generator()
discriminator = Discriminator()

if cuda:
    generator.cuda()
    discriminator.cuda()
    adversarial_loss.cuda()

os.makedirs("./data/mnist", exist_ok=True)
dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "./data/mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])] # 转换成transform并标准化
        ),
    ),
    batch_size=opt.batch_size,
    shuffle=True,
)

optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))


Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor

for epoch in range(opt.n_epochs):
    for i, (imgs, _) in enumerate(dataloader):

        valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False)
        fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)

        real_imgs = Variable(imgs.type(Tensor))
        
        optimizer_G.zero_grad() 
        
        z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

        gen_imgs = generator(z)
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)

        g_loss.backward()
        optimizer_G.step()
        optimizer_D.zero_grad()
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2
        d_loss.backward()
        optimizer_D.step()

        print(
            "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
            % (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())
        )

        batches_done = epoch * len(dataloader) + i
        if batches_done % opt.sample_interval == 0:
            save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

运行完后,我们就可以看到一个新生成的文件夹,存储了每一个epoch的训练结果:
在这里插入图片描述
可以看到,经过不断训练后,生成器的生成结果真的从纯粹的噪声信息变成了几可乱真。后面,我们还会介绍更多的GAN网络变种,有兴趣或看完还有些迷糊的小伙伴可以追更哟~

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值