目录

1 Divergence

  1.1 Kullback–Leibler divergence

  1.2 Jensen–Shannon divergence

  1.3 Wasserstein distance

2 GAN

  2.1 Theory

  2.2 Algorithm

    Objective function for generator in real implementation

    Code

    运行结果

1 Divergence

这是一些比较重要的前置知识。

1.1 Kullback–Leibler divergence

假设 $P(x), Q(x)$ 是随机变量 $X$ 的两个分布,在离散和连续随机变量的情形下,KL divergence 分别定义为:

非负性:$D_{KL} (P \parallel Q)$ 恒为非负的,且在 $P,Q$ 为同一分布时 $D_{KL} (P||Q) = 0$。

不对称性:$D_{KL} (P \parallel Q) \neq D_{KL} (Q||P)$。

1.2 Jensen–Shannon divergence

假设 $P(x), Q(x)$ 是随机变量 $X$ 的两个分布,Jensen–Shannon divergence 定义为:

其中 $M={\frac {1}{2}}(P+Q)$。

JS divergence 解决了 KL divergence 不对称性的问题,一般地,JS divergence 是对称的,且取值 $0\leq {\rm {JSD}}(P\parallel Q)\leq \log(2)$,注意这里的 $\log$ 即 $\ln$。

KL divergence 和 JS divergence 有一个同样的问题:如果两个分布 $P,Q$ 完全没有重叠,那么 KL divergence 是没有意义的,而 JS divergence 是一个常数 $\log (2)$。

关于两个分布无重叠时 JS divergence 为 $\log (2)$ 的证明也很简单:

显然对于第一个积分,在 $p(x) \neq 0$ 时必然有 $q(x) = 0$,所以第一个积分值为零。对第二个积分也是同理。所以两个分布无重叠时 JS divergence 为 $\log (2)$。

1.3 Wasserstein distance

假设 $P, Q$ 是两个概率分布,则 Wasserstein distance 定义为:

其中,$\gamma \in \Pi(x, y)$ 表示 $\gamma$ 是一个联合分布,而它的边缘分布即 $P$ 和 $Q$。

如果 $P, Q$ 是连续型的概率分布,那么就有 $W(P,Q) = \inf_{\gamma \in \Pi[P,Q]} \iint \gamma(x,y)d(x,y)dxdy$。$d(x,y)$ 即 $\left \| x-y \right \|$ 代表 $x,y$ 间的某种距离。

根据《从Wasserstein距离、对偶理论到WGAN》的说法,事实上 $\gamma$ 描述了一种运输方案。假设 $P$ 是原始分布,$Q$ 是目标分布,$p(x)$ 的意思是原来在位置 $x$ 处有 $p(x)$ 数量的货物,而 $q(x)$ 是指最终 $x$ 处要存放的货物数量,如果某处 $x$ 的 $p(x)>q(x)$,那么就要把 $x$ 处的一部分货物运到别处,反之,如果 $p(x)<q(x)$,那么就要从别的地方运一些货物到 $x$ 处。而 $\gamma(x,y)$ 的意思是指,要从 $x$ 处搬 $γ(x,y)$ 数量的东西到 $y$ 处。

最后是 $\inf$,表示下确界,简单来说就是取最小,也就是说,要从所有的运输方案中,找出总运输成本 $\iint \gamma(x,y)d(x,y)dxdy$ 最小的方案,这个方案的成本,就是我们要算的 $W(P,Q)$。如果将上述比喻中的“货物”换成“沙土”,那么Wasserstein距离就是在求最省力的“搬土”方案了,所以Wasserstein距离也被称为“推土机距离”(Earth Mover's Distance)。更加形象的讲解可以参考李宏毅老师的GAN课程中关于WGAN的那一节。

2 GAN

2.1 Theory

We want to find data distribution $P_{data}(x)$,$x$ 是一张图片(或者说是a high-dimensional vector), $P_{data}(x)$ 是一个固定的分布,而我们的database中的图片,都是来自 $P_{data}(x)$ 的一个个sample,如下图

为了方便,图中的 $x$ 是二维空间中的一个点(一个向量)。

如果我们的database是二次元人物头像的,那么就有一个对应的固定的 $P_{data}(x)$,database里二次元人物头像图片,就是data points from distribution $P_{data}(x)$。很显然,往往这个分布中高概率的区域只占整个image space的很小很小的一部分。

(假设蓝色区域就是高概率区域,而剩余的就是低概率区域)

显然, 我们不可能知道 $P_{data}(x)$ 的公式是怎么样的,我们唯一能做的事情就是sample from $P_{data}(x)$。

我们能做的事情就是:我们有一个distribution $P_G (x;\theta)$ parameterized by $\theta$,通过调整参数 $\theta$ 使得 $P_G (x;\theta)$ 接近 $P_{data}(x)$。

很自然,我们就能想到maximum likelihood estimation (MLE):

  假设我们有样本 ${x_1, \cdots, x_m}$ 来自 $P_{data}(x)$,那么likelihood function

  log-likelihood function为

  那么

  也就是说,我们用MLE来估计 $\theta$,就约等于在minimize KL divergence。

  (由于 $\int_{x} \log(P_{data}(x)) \cdot P_{data}(x) dx$ 与 $\theta$ 无关,所以加上这一项并不影响 $\arg\max\limits_{\theta}$)

  (关于上面的约等于号怎么来的,参考伯努利大数定律,假设 $x$ 只有 $n$ 个可能的取值,表示成 $x^{(1)}, x^{(2)}, \cdots, x^{(n)}$,当 $m$ 很大时,$m$ 个样本中取值为 $x^{(k)}$ 的样本,其数目占总样本数的比例 $\frac{count(x^{(k)})}{m}$,就约等于 $P_{data}(x^{(k)})$,所以 $\sum_{i=1}^{m} f(x_i) = \sum_{k=1}^{n} \frac{count(x^{(k)})}{m} f(x^{(k)}) \approx \sum_{k=1}^{n} P_{data}(x^{(k)}) f(x^{(k)}) = E_{x \sim P_{data}}[f(x)]$。当然,这不是严格证明,这仅仅是我在思考这个约等于号时的一点思路。)

上面这个经典的MLE思路当然是可行的,如果我们可以先确定 $P_G(x;\theta)$ 的表达式,那么就可以通过MLE求出 $\hat{\theta}$,进而得到一个确定的 $P_G(x;\hat{\theta})$,就可以sample from $P_G(x;\hat{\theta})$ 来生成图片了,但实际上这样的效果并不好,因为 $P_{data}$ 其实是非常复杂的,我们需要更加复杂的 $P_G$ 来接近 $P_{data}$。

我们令 $G$ 是一个mapping,输入一个随机噪声 $z$,输出一个高维向量(图片) $x = G(z)$,随机噪声 $z$ 可能服从Gaussian distribution,也可能服从uniform distribution,关系不大,但是经过 $G$ 之后,$x$ 就可以服从一个非常复杂的distribution $P_{G}$。

所以有

即寻找一个 $G^{*}$ 使得 $P_{data}$ 和 $P_G$ 之间的某种divergence最小。这个divergence可以是KL divergence,也可以是别的divergence。minimize KL divergence只不过是正好近似等价于MLE罢了。

然后问题就来了,由于 $P_{data}$ 是不可知的,而且如果mapping $G$ 很复杂的话,那么 $P_{G}$ 其实也是不可知的,所以我们其实没办法直接去算 $P_{data}$ 和 $P_G$ 之间的divergence,这就引出了discriminator的作用。

discriminator其实也是一个mapping,记作做 $D$,输入是一个高维向量(图像)$x$,输出是一个标量,$D$ 的作用是,分辨输入的图像到底是来自 $P_{data}$,还是来自 $P_G$。我们训练discriminator的做法如下:

Objective function for $D$:

注意,这里的 $G$ 是固定的,也就是说此时对于 $D$ 来说 $P_G$ 是固定的。

训练:

给定 $G$,最优的 $D^{*}$ 会最大化

我们需要假设 $D(x)$ 是可以是任意函数,那么对于任意的 $x_1 \neq x_2$,$D(x_1)$ 和 $D(x_2)$ 之间其实没有任何的相互限制,所以可以把每个 $x$ 分开来看待,

所以进一步给定 $x$,最优的 $D^{*}$ 会最大化

记 $a = P_{data}(x), b = P_G(x)$,记 $f(D) = a \log(D) + b \log(1-D)$,则令 $\frac{df}{dD}$ 等于 $0$ 得到

如果我们绘制 $f(D) = 0.5 \log(D) + 0.5 \log(1-D)$ 的图像

其实无论 $a,b$ 在 $(0,1)$ 之间如何变动,该函数始终只有一个最大值,因此上面的方法是可行的。

因此,我们找到了 $D^{*}$,将其回代就可以得到

因此,我们现在有一种divergence $D(P_{data}, P_{G}) = 2JSD(P_{data} \parallel P_{G}) - 2\log2 = \max\limits_{D}V(D,G)$,所以将这个 $D(P_{data}, P_{G}) = \max\limits_{D}V(D,G)$ 代回到 $G^{*} = \arg\min\limits_{G} D(P_{data}, P_{G})$ 中即可得

这就是《Generative Adversarial Nets》中式(1)

这里的 $p_{z}(z)$ 是随机噪声所服从的分布。

这里的 $\min\limits_{G}\max\limits_{D}V(D,G)$ 似乎有些绕,其实并不难理解。从朴素的思想来看,我要寻找一个最优的 $G^{*}$ 使得divergence $D(P_{data}, P_{G})$ 最小,那就枚举所有可能的 $G$ 好了,看看哪个算出来的divergence $D(P_{data}, P_{G})$ 最小不就好了。那么对于一个给定的 $G = G'$,我们不会算 $D(P_{data}, P_{G})$,但是我知道 $D(P_{data}, P_{G}) = \max\limits_{D}V(D,G)$,注意此时的 $G'$ 是给定的,所以 $D(P_{data}, P_{G}) = \max\limits_{D}V(D,G') = \max\limits_{D}V(D)$,这就很简单了,我们也会算了,不就是找一个自变量为 $D$ 的一元函数 $V(D)$ 的最大值嘛,找到这个函数的最大值,这个值就是当 $G = G'$ 时的divergence $D(P_{data}, P_{G})$。然后我们就可以去枚举下一个 $G = G''$ 了。

2.2 Algorithm

上面我说的那种朴素的枚举所有可能的 $G$ 的思路显然是不可能真的去实现的,求最大值、最小值的一个经典方法就是梯度下降法。

首先记 $\theta_{G}$ 是 mapping $G$ 的参数,$\theta_{D}$ 是 mapping $D$ 的参数。

上述算法存在的一个问题是,例如,当我找到了 $D_{0}^{*}$ 使得 $V(D,G_0)$ 取得最大值,但是经过 $\theta_G \leftarrow \theta_G - \eta \cdot \partial V(D_{0}^{*},G) / \partial \theta_G$ 更新之后,$G$ 已经变成了 $G_1$,$V(D,G_0)$ 和 $V(D,G_1)$ 这两个自变量为 $D$ 的函数,很可能差别比较大,例如下图

那么 $V(D,G_1)$ 的最大值很有可能反而比 $V(D,G_0)$ 的最大值还要大,换句话说,当我的 $G$ 从 $G_0$ 更新到 $G_1$,使得 $P_{data}$ 和 $P_G$ 之间的divergence反而增大了,这些然不是我们想要的情况。因此我们必须使得 $\theta_G$ 的更新尽量小一些,这样 $V(D,G_1)$ 和 $V(D,G_0)$ 这两个自变量为 $D$ 的函数的形状就会比较相似,就不会出现使得divergence反而增大的情况。

训练 $D$ 是在计算divergence,而训练 $G$ 是在降低divergence。

Objective function for generator in real implementation

对于generator的objective function $V = E_{x \sim P_G}[\log(1-D(x))]$,由于在一开始discriminator很容易区分图片的真假,所以它对于 $x \sim P_G$ 给出的 $D(x)$ 值是很小的,这就导致 $log(1-D(x))$ 的导数值很小,使得训练速度偏慢。

所以就把generator的objective function改成了 $V = E_{x \sim P_G}[-\log(D(x))]$。仅仅是因为两者的趋势是一致的,仅仅是因为斜率不一样,所以作者认为这样是可以的。

两者分别有命名

Code

import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets
from torchvision import transforms
from torchvision.utils import save_image
from torch.autograd import Variable
import os if not os.path.exists('./img'):
os.mkdir('./img') def to_img(x):
out = 0.5 * (x + 1)
out = out.clamp(0, 1)
out = out.view(-1, 1, 28, 28)
return out batch_size = 128
num_epoch = 100
z_dimension = 100 # Image processing
img_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])
])
# MNIST dataset
mnist = datasets.MNIST(
root='./data/', train=True, transform=img_transform, download=True)
# Data loader
dataloader = torch.utils.data.DataLoader(
dataset=mnist, batch_size=batch_size, shuffle=True) class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.dis = nn.Sequential(
nn.Linear(784, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 1),
nn.Sigmoid()) def forward(self, x):
return self.dis(x) class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.gen = nn.Sequential(
nn.Linear(100, 256),
nn.ReLU(True),
nn.Linear(256, 256),
nn.ReLU(True),
nn.Linear(256, 784),
nn.Tanh()) def forward(self, x):
return self.gen(x) D = Discriminator().cuda()
G = Generator().cuda() # Binary cross entropy loss and optimizer
criterion = nn.BCELoss()
d_optimizer = torch.optim.Adam(D.parameters(), lr=0.0003)
g_optimizer = torch.optim.Adam(G.parameters(), lr=0.0003) # Start training
for epoch in range(num_epoch):
for i, (img, _) in enumerate(dataloader):
num_img = img.size(0) # region Train discriminator img = img.view(num_img, -1)
real_img = img.cuda()
real_label = torch.ones([num_img, 1]).cuda()
fake_label = torch.zeros([num_img, 1]).cuda() # compute loss of real_img
real_out = D(real_img)
d_loss_real = criterion(real_out, real_label)
real_scores = real_out # closer to 1 means better # compute loss of fake_img
z = torch.randn(num_img, z_dimension).cuda()
fake_img = G(z)
fake_out = D(fake_img)
d_loss_fake = criterion(fake_out, fake_label)
fake_scores = fake_out # closer to 0 means better # bp and optimize
d_loss = d_loss_real + d_loss_fake
d_optimizer.zero_grad()
d_loss.backward()
d_optimizer.step() # endregion # region train generator # compute loss of fake_img
z = torch.randn(num_img, z_dimension).cuda()
fake_img = G(z)
output = D(fake_img)
g_loss = criterion(output, real_label) # bp and optimize
g_optimizer.zero_grad()
g_loss.backward()
g_optimizer.step() # endregion if (i + 1) % 100 == 0:
print('Epoch [{}/{}], d_loss: {:.6f}, g_loss: {:.6f}, '
'D real: {:.6f}, D fake: {:.6f}.'
.format(epoch, num_epoch, d_loss.item(),
g_loss.item(), real_scores.data.mean(),
fake_scores.data.mean())) if epoch == 0:
real_images = to_img(real_img.cpu().data)
save_image(real_images, './img/real_images.png') fake_images = to_img(fake_img.cpu().data)
save_image(fake_images, './img/fake_images-{}.png'.format(epoch + 1))

关于BCELoss,即binary cross entropy loss,计算公式如下:

其中 $y_i$ 是真值的第 $i$ 项(注意取值是 $0$ 或者 $1$),而 $\hat{y}_i$ 是对应的第 $i$ 项估计值(取值为 $[0,1]$)。而 $l_i$ 即对应的第 $i$ 项loss值。

而 nn.BCELoss() 中有一个参数 reduction='mean',可以取值为 'mean' 或者 'sum' 或者 'none',默认取值 'mean',分别代表对上面的 $l_i$ 求均值、求和、不进一步操作。

所以上面的代码中,对于 $Dloss$ 有

所以梯度下降最小化 $Dloss$ 和之前的算法描述(Update discriminator parameters to maximize $\widetilde{V} = \frac{1}{m} \sum_{i=1}^{m}\log D(x^i) + \frac{1}{m} \sum_{i=1}^{m}\log(1-D(\widetilde{x}^i))$)是一致的。

而对于代码中的 $Gloss$ 有

梯度下降这也之前描述的在实际代码实现中用NSGAN而非MMGAN一致。

运行结果

这是运行了100 epochs中某几代的生成结果:

  

  

关于GAN的一些笔记的更多相关文章

  1. 关于Wasserstein GAN的一些笔记

    这篇笔记基于上一篇<关于GAN的一些笔记>. 1 GAN的缺陷 由于 $P_G$ 和 $P_{data}$ 它们实际上是 high-dim space 中的 low-dim manifol ...

  2. 生成式对抗网络(GAN)学习笔记

    图像识别和自然语言处理是目前应用极为广泛的AI技术,这些技术不管是速度还是准确度都已经达到了相当的高度,具体应用例如智能手机的人脸解锁.内置的语音助手.这些技术的实现和发展都离不开神经网络,可是传统的 ...

  3. 《StackGAN: Text to Photo-realistic Image Synthesis with Stacked GAN》论文笔记

    出处:arxiv 2016 尚未出版 Motivation 根据文字描述来合成相片级真实感的图片是一项极具挑战性的任务.现有的生成手段,往往只能合成大体的目标,而丢失了生动的细节信息.StackGAN ...

  4. 深度学习-Wasserstein GAN论文理解笔记

    GAN存在问题 训练困难,G和D多次尝试没有稳定性,Loss无法知道能否优化,生成样本单一,改进方案靠暴力尝试 WGAN GAN的Loss函数选择不合适,使模型容易面临梯度消失,梯度不稳定,优化目标不 ...

  5. GAN 生成mnist数据

    参考资料 GAN原理学习笔记 生成式对抗网络GAN汇总 GAN的理解与TensorFlow的实现 TensorFlow小试牛刀(2):GAN生成手写数字 参考代码之一 #coding=utf-8 #h ...

  6. GAN网络从入门教程(二)之GAN原理

    在一篇博客GAN网络从入门教程(一)之GAN网络介绍中,简单的对GAN网络进行了一些介绍,介绍了其是什么,然后大概的流程是什么. 在这篇博客中,主要是介绍其数学公式,以及其算法流程.当然数学公式只是简 ...

  7. 论文笔记:Towards Diverse and Natural Image Descriptions via a Conditional GAN

    论文笔记:Towards Diverse and Natural Image Descriptions via a Conditional GAN ICCV 2017 Paper: http://op ...

  8. 深度学习课程笔记(八)GAN 公式推导

    深度学习课程笔记(八)GAN 公式推导 2018-07-10  16:15:07

  9. GAN算法笔记

    本篇文章为Goodfellow提出的GAN算法的开山之作"Generative Adversarial Nets"的学习笔记,若有错误,欢迎留言或私信指正. 1. Introduc ...

随机推荐

  1. swoole之建立 websocket server

    一.代码部分 <?php /** * 为什么用WebSocket? * HTTP的通信只能由客户端发起 * * WebSocket 协议是基于TCP的一种新的网络协议.实现了浏览器与服务器全双工 ...

  2. LVS负载均衡基本原理

    负载均衡基本原理与lvs 基本介绍 1.1 负载均衡的由来 在业务初期,我们一般会先使用单台服务器对外提供服务.随着业务流量越来越大,单台服务器无论如何优化,无论采用多好的硬件,总会有性能天花板,当单 ...

  3. esxi命令行强行关闭虚拟机

    目的:强行关闭通过前端界面无法关闭的ESXI虚拟机 环境:esxi5.1-esxi6.5 背景:如果esxi下面某一台vm死机了,并且esxi的控制台卡死不能用,为了不影响同一个esx下其他的vm正常 ...

  4. Arch系统软件列表

    1. 安装统计 2. 安装列表 3. 安装说明 4. 作为依赖项的安装列表 5. 更正 mangaro使用减的方式安装系统.开箱即用的豪华版本,大部分人需要的都有了,同样包括个别用户不需要的,配置方面 ...

  5. 向量容器vector操作

    1.向量容器vector 1.1 vector说明 进行vector操作前应添加头文件#include<vector>: vector是向量类型,可以容纳许多类型的数据,因此也被称为容器: ...

  6. Java基础学习总结(一)——Java开发学习介绍

    Java平台: 1.J2SE java开发平台标准版 2.J2EE java开发费平台企业版 Java程序需要在虚拟机上才可以运行,换言之只要有虚拟机的系统都可以运行java程序.不同的系统上要安装对 ...

  7. 基于Hadoop3.1.2集群的Hive3.1.2安装(有不少坑)

    前置条件: 已经安装好了带有HDFS, MapReduce, Yarn 功能的 Hadoop集群 链接: ubuntu18.04.2 hadoop3.1.2+zookeeper3.5.5高可用完全分布 ...

  8. IDEA启动Tomcat报错Address localhost:1099 is already in use解决办法

    问题:Error running 'lugia-web': Address loaclhost:1099 is already in use如下图 解决方法:cmd输入下面命令: netstat -a ...

  9. 【LeetCode】226. 翻转二叉树

    题目 翻转一棵二叉树. 示例: 输入: 4 / \ 2 7 / \ / \ 1 3 6 9 输出: 4 / \ 7 2 / \ / \ 9 6 3 1 本题同[剑指Offer]面试题27. 二叉树的镜 ...

  10. C++ 根据日期判断星期几

    int CaculateWeekDay(int y,int m, int d) { ||m==) { m+=; y--; } *m+*(m+)/+y+y/-y/+y/)%; ; }