GAN(Generative Adversarial Nets)

GAN(Generative Adversarial Nets)

引言

GAN由Ian J. Goodfellow等人提出,是Ian J. Goodfellow的代表作之一,他还出版了大家耳熟能详的花书(Deep Learning深度学习),GAN主要的思想是同时训练两个模型,生成模型G用于获取数据分布,判别模型D用于估计样本来自训练数据而不是G的概率。G的训练过程是最大化D犯错误的概率。这个过程对应于一个极小极大两人博弈。在任意函数G和D的空间中,存在唯一解,其中G恢复训练数据分布,并且D处处等于1/2。

用一个警察与小偷的故事来阐述:假设一个城市里有许多小偷,在这些小偷中,部分是技艺高超的偷窃高手,另一部分则是毫无技术的新手。警察开始进行对小偷的抓捕,其中一批“学艺不精”的小偷就被捉住了。这些小偷被抓住或许是因为识别他们毫无难度,警察不需要特殊本领,但是剩下的“偷窃高手”警察就很难抓捕。于是警察们开始继续训练自己的破案技术,开始抓住那些技艺高超的小偷。随着这些他们的落网,警察们也练就了特别的本事,他们能很快能从一群人中识别终逮捕嫌犯;随着警察们的水平大大提高,为了避免被捕,小偷们努力表现得不那么“可疑”,而魔高一尺、道高一丈,警察也在不断提高自己的水平,争取将小偷和无辜的普通群众区分开。随着警察和小偷之间的这种“交流”与“切磋”,小偷们都变得非常谨慎,他们有着极高的偷窃技巧,表现得跟普通群众一模一样,而警察们都练就了“火眼金睛”,一旦发现可疑人员,就能马上发现并及时控制——最终,我们同时得到了最强的小偷和最强的警察。其中,小偷就可以视作生成模型G,警察可以视作判别模型D,通过G和D的对抗,能够获得效果较好的生成模型(判别模型也是如此)参考链接

主要架构

根据引言,一个GAN主要包含两个基础模型:生成器(G)与判别器(D)。其中,生成器用于生成新数据,其生成数据的基础往往是一组噪音或者随机数,而判别器用于判断生成的数据和真实数据哪个才是真的。生成器执行无监督任务;而判别器执行有监督任务,用于二分类,其label是“假与真”(0与1)。
在这里插入图片描述

生成器的目标是生成尽量真实的数据(这也是我们对生成对抗网络的要求),最好能够以假乱真、让判别器判断不出来,因此生成器的学习目标是让判别器上的判断准确性越来越低;相反,判别器的目标是尽量判别出真伪,因此判别器的学习目标是让自己的判断准确性越来越高。

当生成器生成的数据越来越真时,判别器为维持住自己的准确性,就必须向判别能力越来越强的方向迭代。当判别器越来越强大时,生成器为了降低判别器的判断准确性,就必须生成越来越真的数据。在这个奇妙的关系中,判别器与生成器同时训练、相互内卷,对损失函数的影响此消彼长。参考链接

理论支撑

m i n G m a x D V ( D , G ) = E x ∼ p d a t a ( x ) [ l o g D ( x ) ] + E z ∼ p z ( z ) [ l o g ( 1 − D ( G ( z ) ) ) ] \underset{G}{min}\underset{D}{max}V(D,G) = {\mathbb E_{x \sim{p}_{data}(x)} [logD(x)]} + {\mathbb E _{z \sim{p}_{z}(z)}[{log(1-D({G(z)}))}]} GminDmaxV(D,G)=Expdata(x)[logD(x)]+Ezpz(z)[log(1D(G(z)))]
V V V是一个值函数(损失函数), x x x表示真实数据, p d a t a p_{data} pdata表示数据的真实分布, z z z是与真实数据相同分布的随机数据, G ( z ) G(z) G(z)是生成器中基于 z z z生成的数据, D ( x ) D(x) D(x)是判别器在真实数据 x x x上判断的结果, D ( G ( x ) ) D(G(x)) D(G(x))表示判别器在生成器生成的数据 G ( z ) G(z) G(z)上判断出的结果。

那么需要做的就是:

1. 对于判别器D来说,尽可能找到生成器生成的数据

2. 对于生成器G来说,尽可能让生成的数据接近真实数据,使得判别器D无法判别出来

上面表达式需要做的是,首先固定G,在D的层面使得值最大(即让判别器能够精确区分真实数据和生成数据),然后固定D,在G的层面使得值最小(即在判别器能够精确区分数据的情况下,让生成器能够生成更接近真实的数据,使得判别器无法区分),从而实现了D和G的对抗,如此可以找到最好的生成器(生成模型)。
在这里插入图片描述

图(a)中展示了生成器G、判别器D以及真实数据初始状态,此时真实数据与生成数据分布明显不同,判别器此时也只是初始状态;图(b)展示了判别器经过训练后能够进行区分真实数据和生成数据;图©展示了生成器经过训练后能够更加接近真实分布;图(d)展示了经过多次循环之后,生成器和判别器的状态,此时生成数据已经无限接近真实数据分布,同时判别器难以区分出真实数据和生成数据,导致判别答案始终为1/2。

算法

在这里插入图片描述
算法存在的一个问题是需要选择一个较好的k,在算法中要保证:不能一次性让判别器就能够准确的识别出所有生成数据,这会导致生成器没有办法继续提升,生成更加接近真实分布的数据,同时也不能让生成模型一下子生成非常接近真实分布的数据,这会导致判别器难以进行识别能力的提升。

公式分析

对于判别器D

判别器的作用是尽可能找出生成器生成的数据与真实数据分布之间的差异,这是一个二分类的问题,将G固定后,公式就变为:
m a x D V ( D , G ) = E x ∼ p d a t a ( x ) [ l o g D ( x ) ] + E z ∼ p z ( z ) [ l o g ( 1 − D ( G ( z ) ) ) ] \underset{D}{max}V(D,G) = {\mathbb E_{x \sim{p}_{data}(x)} [logD(x)]} + {\mathbb E _{z \sim{p}_{z}(z)}[{log(1-D({G(z)}))}]} DmaxV(D,G)=Expdata(x)[logD(x)]+Ezpz(z)[log(1D(G(z)))]
该公式等价于交叉熵,只不过交叉熵是取负的对数。这个函数的输入一部分是真实数据,分布为 p d a t a {p_{data}} pdata,一部分是生成器的数据(噪声数据),生成器接收的数据 z z z服从分布 p ( z ) p(z) p(z),输入 z z z经过生成器的计算生产的数据分布设为 p G ( x ) {p_{G}}(x) pG(x),这个函数要取得最大值,必然是对于真实数据 D ( x ) = 1 D(x)=1 D(x)=1,对于生成数据 D ( x ) = 0 D(x)=0 D(x)=0,这一步用于优化D,因此可以简写为
D G ∗ = m a x D V ( G , D ) D_G^* = \underset D {max}V(G,D) DG=DmaxV(G,D)
此时,这是D的一元函数,进行求导,得到
在这里插入图片描述
取导数为0,算最优点得到
在这里插入图片描述

对于生成器G

当且仅当 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)时,有
D G ∗ = P d a t a ( x ) P G ( x ) + P d a t a ( x ) = 1 2 D_G^* = \frac{{{P_{data}}(x)}}{{{P_G}(x) + {P_{data}}(x)}} = \frac{1}{2} DG=PG(x)+Pdata(x)Pdata(x)=21
此时生成器无法判别数据是真实数据或者生成数据。
我们假设 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x),可以反向推出
V ( G , D G ∗ ) = ∫ x P d a t a ( x ) l o g 1 2 + P G ( x ) l o g ( 1 − 1 2 ) d x {V(G,D_G^*) = \int_x {{P_{data}}(x)log\frac{1}{2}}+ {P_G}(x)log(1 - \frac{1}{2})dx} V(G,DG)=xPdata(x)log21+PG(x)log(121)dx
⇔ V ( G , D G ∗ ) = − log ⁡ 2 ∫ x P G ( x ) d x − log ⁡ 2 ∫ x P d a t a ( x ) d x = − 2 log ⁡ 2 = − log ⁡ 4 \Leftrightarrow {V(G,D_G^*) = - \log 2\int\limits_x {{P_G}(x)} dx - \log 2\int\limits_x {{P_{data}}(x)} dx = - 2\log 2 = - \log 4} V(G,DG)=log2xPG(x)dxlog2xPdata(x)dx=2log2=log4
该值是全局最小值的候选,因为它只有在 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)
的时候才出现。
对于任意一个G,将 D ∗ {D^*} D带入到 V ( G , D ) V(G,D) V(G,D)中:
在这里插入图片描述

结合KL散度得到:
= − 2 l o g 2 + K L ( P d a t a ( x ) ∣ ∣ P d a t a ( x ) + P G ( x ) 2 ) + K L ( P G ( x ) ∣ ∣ P d a t a ( x ) + P G ( x ) 2 ) { = - 2log2 + KL({P_{data}}(x)||\frac{{{P_{data}}(x) + {P_G}(x)}}{2}) + KL({P_G}(x)||\frac{{{P_{data}}(x) + {P_G}(x)}}{2})} =2log2+KL(Pdata(x)∣∣2Pdata(x)+PG(x))+KL(PG(x)∣∣2Pdata(x)+PG(x))
最后根据JS散度得到:
V ( G , D ) = − log ⁡ 4 + 2 ∗ J S D ( P d a t a ( x ) ∣ P G ( x ) ) V(G,D) = - \log 4 + 2*JSD({P_{data}}(x)|{P_G}(x)) V(G,D)=log4+2JSD(Pdata(x)PG(x))
根据他的属性:当 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)
时,
为0。综上所述,生成分布当前仅当等于真实数据分布式时,我们可以取得最优生成器。前后逻辑自洽。

注:
对于判别器D的优化:这是一个二分类,满足 y l o g q + ( 1 − y ) l o g ( 1 − q ) ylogq+(1-y)log(1-q) ylogq+(1y)log(1q),对于x,标签只会为1,因此只有log(D(x))这一项;对于g(z),其标签只会为0,因此只有log(1-D(G(z)))这一项,因此可以有损失函数:
l o s s = c r o s s E n t r o p y L o s s ( D ( x ) , 1 ) + c r o s s E n t r o p y L o s s ( D ( x ) , 0 ) loss = crossEntropyLoss(D(x),1)+crossEntropyLoss(D(x),0) loss=crossEntropyLoss(D(x),1)+crossEntropyLoss(D(x),0)
对于生成器G的优化:因为D(x)这一项,并不包含生成器的优化参数,因此在求梯度的时候D(x)这一项为0,因此只有log(1-D(G(z)))这一项,损失函数:
l o s s = c r o s s E n t r o p y L o s s ( D ( G ( z ) ) , 1 ) loss = crossEntropyLoss(D(G(z)),1) loss=crossEntropyLoss(D(G(z)),1)

代码

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=50, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=64, 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")
#使用jupyter时需要传入list,
opt = parser.parse_args(args=[])
#opt = parser.parse_args()
#print(opt)

#图像形状为:1*28*28,图像大小为784
img_shape = (opt.channels, opt.img_size, opt.img_size)

#这里提前做了一下cuda的判断
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

#生成器G,用于进行数据的生成
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
        
        #多个模型块进行组合(MLP),输入维度的映射过程为:input_dim->128->256->512->1024->1*28*28
        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() #Tanh取值范围为-1,1,即将输出映射到-1,1
        )

    def forward(self, z):
        img = self.model(z) #对噪声数据处理进行数据生成
        img = img.view(img.size(0), *img_shape) #生成数据为(batch_size,1,28,28)
        return img


#判别器D,用于区分真实数据和生成数据
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        #维度映射过程:1*28*28->512->256->1,使用sigmoid进行二分类激活,映射为0,1之间的数
        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


# Loss function
adversarial_loss = torch.nn.BCELoss() #损失函数使用BCE(二分类交叉熵)

# Initialize generator and discriminator
generator = Generator()
discriminator = Discriminator()

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

# Configure data loader
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])]
        ),
    ),
    batch_size=opt.batch_size,
    shuffle=True,
)

# Optimizers
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))


# ----------
#  Training
# ----------

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

        # Adversarial ground truths
        valid = torch.tensor([[1.0]] * imgs.size(0), requires_grad=False).to(device) #定义真实数据标号为1
        fake = torch.tensor([[0.0]] * imgs.size(0), requires_grad=False).to(device)  #定义虚假数据标号为0

        # Configure input
        real_imgs = torch.tensor(imgs.type(torch.Tensor)).to(device)

        # -----------------
        #  Train Generator
        # -----------------

        optimizer_G.zero_grad()

        # Sample noise as generator input
        z = torch.tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim)), dtype=torch.float32).to(device) #随机生成噪声


        # Generate a batch of images
        gen_imgs = generator(z) #生成数据

        # Loss measures generator's ability to fool the discriminator
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)

        g_loss.backward()
        optimizer_G.step()

        # ---------------------
        #  Train Discriminator
        # ---------------------

        optimizer_D.zero_grad()

        # Measure discriminator's ability to classify real from generated samples
        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)

参考

参考:
文章1
文章2
文章3
代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/890201.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯刷题--幸运数字

幸运数字 题目: 解析: 我们由题目可以知道,某个进制的哈沙德数就是该数和各个位的和取整为0.然后一个幸运数字就是满足所有进制的哈沙德数之和.然后具体就是分为以下几个步骤 1. 我们先写一个方法,里面主要是用来判断,这个数在该进制下是否是哈沙德数 2. 我们在main方法里面调用…

推荐几本编程入门书目

对于编程入门,推荐以下几本书籍,这些书籍覆盖了不同的编程语言,适合零基础的学习者逐步掌握编程基础: 1. 《Python编程快速上手——让繁琐工作自动化》 特点:以简单易懂的方式介绍了Python的基础知识和编程概念&#…

【最新华为OD机试E卷-支持在线评测】构成正方形的数量(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

vue实现列表自动滚动(纯与原生方式)

Vue实现列表自动滚动(纯与原生方式) 源码放在最后!1.效果展示: 2.功能说明: 该滚动可能存在的Bug: 1.如果你写的大屏不是使用的接口轮询的方式可能会存在也页面空白的情况(需要手动刷新才能触发列表滚动),因为我使用的是监听数据的变化然后…

【C++】关联式容器——map和set的使用

文章目录 一、 序列式容器和关联式容器二、set的介绍1.set的构造和迭代器2.set的增删查3.接口lower_bound和upper_bound4.multiset和set的差异 三、map的介绍1.map的构造2.map的增删查3.multimap和map的差异 四、map和set相关OJ 一、 序列式容器和关联式容器 序列式容器&#…

WordPress添加meta标签做seo优化

一、使用function.php文件添加钩子函数添加 方法1、使用is_page()判断不同页面的page_id进行辨别添加不同页面keyword和description (1)通过页面前台源码查看对应页面的id (2)或者通过wordpress后台,点击页面列表&…

【网易云音乐】--源代码分享

最近写了一个网易云音乐的音乐实现部分,是通过JavaScript和jQuery实现的,具体效果大家可以参照下面的视频 源代码分享 - git地址: 网易云音乐源代码 下面将着重讲解一下音乐实现部分 视频有点模糊,不好意思,在b站上添加视频的时候…

Ping32:专业的终端安全管理解决方案

在当今数字化转型迅速发展的时代,终端安全管理已成为企业信息安全的重要环节。随着远程办公和移动设备的普及,企业面临着越来越多的网络安全挑战。Ping32作为一款专业的终端安全管理解决方案,以其卓越的性能和易用性,成为众多企业…

[Linux] Linux 进程程序替换

标题:[Linux] Linux 进程程序替换 个人主页水墨不写bug (图片来源于网络) 目录 O、前言 一、进程程序替换的直观现象(什么是进程程序替换?) 二、进程程序替换的原理 三、进程程序替换的函数&#xff08…

几种Word Embedding技术详解

NLP 中的词嵌入是一个重要术语,用于以实值向量的形式表示用于文本分析的单词。这是 NLP 的一项进步,提高了计算机更好地理解基于文本的内容的能力。它被认为是深度学习在解决具有挑战性的自然语言处理问题方面最重要的突破之一。 在这种方法中&#xff…

有了WPF后Winform还有活路吗?

近年来,随着技术的不断发展,Windows Presentation Foundation(WPF)和Windows Forms(WinForms)这两种技术在开发桌面应用程序方面一直备受关注。虽然WPF以其强大的功能和灵活性吸引了众多开发者,…

快速上手C语言【上】(非常详细!!!)

目录 1. 基本数据类型 2. 变量 2.1 定义格式 和 命名规范 2.2 格式化输入和输出(scanf 和 printf) ​编辑 2.3 作用域和生命周期 3. 常量 4. 字符串转义字符注释 5. 操作符 5.1 双目操作符 5.1.1 算数操作符 5.1.2 移位操作符 5.1.3 位操作符…

架构设计笔记-7-系统架构设计基础知识

目录 知识要点 单选 案例分析 1.质量属性 / 管道过滤器 / 数据仓库风格 2.面向对象风格 / 控制环路风格 3.软件架构风格 / 架构风格选择 4.体系结构方案对比 5.面向对象风格 / 基于规则风格 6.解释器风格 / 管道过滤器风格 7.面向对象风格 / 解释器风格 8.软件架构复…

【宝可梦】游戏

pokemmo https://pokemmo.com/zh/ 写在最后:若本文章对您有帮助,请点个赞啦 ٩(๑•̀ω•́๑)۶

OpenCV 环境配置

首先下载opencv,在opencv官网进行下载。 按照上面的步骤,点击进去 滑至底部,不切换至不同页,选择合适的版本进行下载(Window系统选择Windows版本进行下载)。 接下来以4.1.2版本为例: 点击之后会进入这个页面&#xff…

linux源码安装slurm以及mung和openssl

一、源码安装munge 1、编译安装munge (1)下载munge地址:https://github.com/dun/munge/releases (2)解压编译安装: 1 2 3 4 5 6 7 8 创建/data目录 复制文件munge-0.5.15.tar.xz 到/data目录下 tar -Jx…

闭着眼学机器学习——朴素贝叶斯分类

引言: 在正文开始之前,首先给大家介绍一个不错的人工智能学习教程:https://www.captainbed.cn/bbs。其中包含了机器学习、深度学习、强化学习等系列教程,感兴趣的读者可以自行查阅。 1. 算法介绍 朴素贝叶斯是一种基于贝叶斯定理…

C# 屏幕录制工具

屏幕录制工具 开发语音:C# vb.net 下载地址:https://download.csdn.net/download/polloo2012/89879996 功能:屏幕录制,声卡采集,麦克风采集。 屏幕录制:录制屏幕所有操作,并转换视频格式&…

uniapp-小程序开发0-1笔记大全

uniapp官网: https://uniapp.dcloud.net.cn/tutorial/syntax-js.html uniapp插件市场: https://ext.dcloud.net.cn/ uviewui类库: https://www.uviewui.com/ 柱状、扇形、仪表盘库: https://www.ucharts.cn/v2/#/ CSS样式&…

Springboot 接入 WebSocket 实战

Springboot 接入 WebSocket 实战 前言: WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。 简单理解: 1,常见开发过程中我们知道 Http协议,客户端…