GAN(Generative Adversarial Nets)

ops/2024/10/18 14:23:02/

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 mathimport torchvision.transforms as transforms
from torchvision.utils import save_imagefrom torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variableimport torch.nn as nn
import torch.nn.functional as F
import torchos.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*28self.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 truthsvalid = torch.tensor([[1.0]] * imgs.size(0), requires_grad=False).to(device) #定义真实数据标号为1fake = torch.tensor([[0.0]] * imgs.size(0), requires_grad=False).to(device)  #定义虚假数据标号为0# Configure inputreal_imgs = torch.tensor(imgs.type(torch.Tensor)).to(device)# -----------------#  Train Generator# -----------------optimizer_G.zero_grad()# Sample noise as generator inputz = torch.tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim)), dtype=torch.float32).to(device) #随机生成噪声# Generate a batch of imagesgen_imgs = generator(z) #生成数据# Loss measures generator's ability to fool the discriminatorg_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 samplesreal_loss = adversarial_loss(discriminator(real_imgs), valid)fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)d_loss = (real_loss + fake_loss) / 2d_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) + iif batches_done % opt.sample_interval == 0:save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

参考

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


http://www.ppmy.cn/ops/124768.html

相关文章

JAVA基础

1.数据类型 变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。 1.1 基本数据类型 Java语言提供了八种基本类型。六种数字类型…

封装代码片段语法 vue2语法

关于函数导入 1.在untils写一个pdfService.js 关于pdf预览和下载的方法 export const previewPdf async (record) > {const pdfUrln record.url; // 直接使用 PDF 文件的 URL// const pdfUrln indexConfig.VITE_GLOB_VIEW_URL static/pdf/web/viewer.html?file reco…

使用频率最高的 opencv 基础绘图操作 - python 实现

以下是 opencv-python 基本操作绘制示例,绘制: 1)圆,2)矩形,3)线段,4)文本。 安装 opencv-python pip install opencv-python 在图上绘制圆的操作,示例如…

C语言题目练习2

前面我们知道了单链表的结构及其一些数据操作,今天我们来看看有关于单链表的题目~ 移除链表元素 移除链表元素: https://leetcode.cn/problems/remove-linked-list-elements/description/ 这个题目要求我们删除链表中是指定数据的结点,最终返…

第十五届蓝桥杯C/C++学B组(解)

1.握手问题 解题思路一 数学方法 50个人互相握手 (491)*49/2 ,减去7个人没有互相握手(61)*6/2 答案:1024 解题思路二 package 十五届;public class Min {public static void main(String[] args) {i…

【GaussDB】产品简介

产品定位 GaussDB 200是一款具备分析及混合负载能力的分布式数据库,支持x86和Kunpeng硬件架构,支持行存储与列存储,提供PB(Petabyte)级数据分析能力、多模分析能力和实时处理能力,用于数据仓库、数据集市、实时分析、实时决策和混…

FFmpeg的简单使用【Windows】--- 简单的视频混合拼接

实现功能 点击【选择文件】按钮在弹出的对话框中选择多个视频,这些视频就是一会将要混剪的视频素材,点击【开始处理】按钮之后就会开始对视频进行处理,处理完毕之后会将处理后的文件路径返回,并在页面展示处理后的视频。 视频所…

《机器学习与数据挖掘综合实践》实训课程教学解决方案

一、引言 随着信息技术的飞速发展,人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一,机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台,帮助学生掌握机器学习…