{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": "true" }, "source": [ "

Table of Contents

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 简介\n", "\n", "生成对抗网络由 Ian Goodfellow 于 2014 年提出。GAN 不是神经网络应用在无监督学习中的唯一途径,还有玻尔兹曼机(Geoffrey Hinton 和 Terry Sejnowski,1985)和自动解码器(Dana H. Ballard,1987)。三者皆致力于通过学习恒等函数 f(x)= x 从数据中提取特征,且都依赖马尔可夫链来训练或生成样本。\n", "\n", "GAN 设计之初衷就是避免使用马尔可夫链,因为后者的计算成本很高。相对于玻尔兹曼机的另一个优点是 GAN 的限制要少得多(只有几个概率分布适用于马尔可夫链抽样)。\n", "\n", "![](http://ohjwan9tm.bkt.clouddn.com/lesson-gan-pic-1.jpeg)\n", "\n", "\n", "判别算法和生成算法有何不同?简单地说:判别算法学习类之间的边界(如判别器做的那样),而生成算法学习类的分布(如生成器做的那样)。 \n", "\n", ">思考:如果 Discriminator 只是做区分的话,其实在文本中是没有什么意义的,因为文本能区分开并不意味着文本本身很通畅。所以 Gan 在自然语言处理中的关键是如何生成有效通畅的文本。也就是说,我们其实要解决文本生成的问题,而不是对抗训练的问题。 \n", "而且这很像强化学习接一层 CNN。\n", "\n", "\n", "![](http://ohjwan9tm.bkt.clouddn.com/lesson-gan-pic-2.jpeg)\n", "当判别器不能区分 `p_g` 和 `p_data`,即 `D(x,θ_d)= 1/2` 时,训练过程停止。达成生成器与判别器之间判定误差的平衡。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 证明\n", "\n", "来自:[机器之心 GitHub 项目:GAN 完整理论推导与实现,Perfect! | 机器之心](https://www.jiqizhixin.com/articles/2017-10-1-1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 概念与过程形式化" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 本质:生成模型不断生成与真实数据一致的分布,直到判别模型不能区分为止。其实是一种端到端的映射,一种从自己身上学习的算法。 \n", "- 基本要素: \n", " - 生成模型:根据随机噪声 `P_z` 生成 `P_G(z;θ)`\n", " - 判别模型:`P_G(z;θ)` 与 `P_data(x)` 做分类\n", " - 判别模型要尽可能地识别出真实数据为正类,而生成模型则要尽可能伪装成真实数据。 \n", "- 模型设计: \n", " - 正类:$$E_x \\sim p_{data}(x) \\log(D(x))\\ (1) $$\n", " - 负类:$$E_z \\sim p_z(z) \\log(1-D(G(z)))\\ (2)$$\n", " - 最大化(1)就相当于 x~p_data 时 D(x) = 1;最大化(2)就相当于最小化 D(G(z))。\n", " - 判别模型要最大化识别真实和伪造数据:`D(X)=1, D(G(z))=0`,最大化: \n", " $$E_x \\sim p_{data}(x) \\log(D(x)) + E_z \\sim p_z(z) \\log(1-D(G(z)))$$ \n", " - 给定 G,最优判别器可以表示为 `D_G*`,定义价值函数为: \n", " $$V(G,D) := E_x \\sim p_{data}(x) \\log(D(x)) + E_z \\sim p_z(z) \\log(1-D(G(z)))$$\n", " - 最优化问题为: \n", " $$D_G^{*} = argmax_DV(G,D)D_G^{*} = argmax_DV(G,D)$$ \n", " - 当 `D=D_G*` 时,V 达到极大值,此时,G 的目标是让 V 最小(这样 D 就分不出来啦,哈哈),因此最优的 G 为最小化 V,写成我们熟知的极小极大化博弈形式: \n", " $$min_G max_D V(D,G) = \\mathbb{E}_x \\sim p_{data}(x) [\\log(D(x))] + \\mathbb{E}_z \\sim p_z(z) [\\log(1-D(G(z)))]$$\n", " - 最后,优化问题为: \n", " $$G^* = argmin_G V(G, D_G^*)$$\n", " - 整个训练是一个迭代过程。其实极小极大化博弈可以分开理解,即在**给定 G 的情况下先最大化 V(D,G) 而取 D,然后固定 D,并最小化 V(D,G) 而得到 G**。其中,给定 G,最大化 V(D,G) 评估了 `P_G` 和 `P_data` 之间的差异或距离。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 理论推导" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 目标:若给定一个样本数据的分布 `P_data(x)` 和生成的数据分布 `P_G(z;θ)`,那么 GAN 希望能找到一组参数 θ 使分布 `P_G(z;θ)` 和 `P_data(x)` 之间的距离最短,也就是找到一组生成器参数而使得生成器能生成十分逼真的图片。\n", "\n", "- 从 `P_data(x)` 中抽取 m 个真实样本 `{𝑥^1,𝑥^2,…,𝑥^𝑚}`,计算 `P_G(x^i;θ)`,即在由 θ 确定的生成分布中,`x^i` 样本所出现的概率,以此训练 θ 使 P_G 逼近真实分布。抽取的 m 个真实样本在 `P_G(x;θ)` 分布中全部出现的概率值可以表达为 L: \n", " $$L = \\prod_{i=1}^m P_G(x^i;\\theta)$$ \n", "\n", "- 最大化 L 求的离真实分布最近的生成分布(即最优的 θ): \n", " $$\\theta^* = argmax_{\\theta} \\prod_{i=1}^m P_G(x^i;\\theta) = argmax_{\\theta} \\log\\prod_{i=1}^m P_G(x^i;\\theta) = argmax_{\\theta} \\sum_{i=1}^m \\log P_G(x^i;\\theta)$$ \n", " $$\\approx argmax_{\\theta} E_x \\sim P_{data} [\\log P_G(x^i;\\theta)] = argmax_{\\theta} \\int_x P_{data}(x)logP_G(x;\\theta)dx$$ \n", " $$\\theta^* = argmax_{\\theta} \\int_x P_{data}(x)logP_G(x;\\theta)dx - \\int_x P_{data}(x)\\log P_{data}(x)dx$$ \n", " $$=argmin_{\\theta} (\\int_x P_{data}(x)\\log P_{data}(x)dx - \\int_x P_{data}(x)\\log P_G(x;\\theta)dx)$$ \n", " $$=argmin_{\\theta} KL(P_{data} \\parallel P_G(x;\\theta))$$\n", " 约等于是因为 m 个只是取样,不是全体。 \n", " 只要最小化 KL 散度就能取得最优参数 θ。 \n", "\n", "- 推导的问题:G 不需要满足可逆条件(也就是说默认 G(z) 可以生成 x)。证明能够成立需要满足: \n", " $$E_z \\sim p_z(z) \\log(1-D(G(z))) = E_x \\sim p_G(x) \\log(1-D(x))$$ \n", " 放到表达式中: \n", " $$V = \\int_x p_{data}(x)\\log(D(x)) + \\int_zp(z)\\log(1-D(G(z)))$$ \n", " $$= \\int_x p_{data}(x)\\log(D(x)) + \\int_x p_G(x)\\log(1-D(x))$$ \n", " 这里使用了积分换元公式,但进行积分换元就必须计算 `G^(-1)`,而 G 的逆却并没有假定为存在。并且在神经网络的实践中,它也并不存在。 \n", " >知识卡:设函数 y=f(x)(x∈A) 的值域是 C,若找得到一个函数 g(y) 在每一处 g(y) 都等于 x,这样的函数 x= g(y)(y∈C) 叫做函数 y=f(x)(x∈A) 的反函数,记作 y=f^(-1)(x) 。若一函数有反函数,此函数便称为可逆的(invertible)。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 证明" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 最优判别器\n", "\n", "$$V(G,D) = \\int_x p_{data}(x)\\log(D(x)) + \\int_x p_G(x)\\log(1-D(x)) dx$$ \n", "求积分的最大值可以转化为求被积函数的最大值: \n", "$$P_{data}(x) log (D(x)) + P_G(x) log (1-D(x))$$\n", "令 `a = P_data, b = P_G, D(x) = y`: \n", "$$a\\log(y) + b\\log(1-y)$$ \n", "为了求极值,如果 a+b≠0,一阶导: \n", "$$f'(y) = 0 => \\frac{a}{y} - \\frac{b}{1-y} = 0 => y = \\frac{a}{a+b}$$ \n", "继续求 f(y) 在驻点的二阶导: \n", "$$f''(\\frac{a}{a+b}) = -\\frac{a}{y^2} - \\frac{b}{(1-y)^2} < 0, \\ a,b \\in (0,1)$$ \n", "因为一阶导等于零、二阶导小于零,所以我们知道 a/(a+b) 为极大值: \n", "$$D(x) = \\frac{a}{a+b} = \\frac{P_{data}(x)} {P_{data}(x)+P_G(x)}$$ \n", "所以: \n", "$$V(G,D) = \\int_{x} p_{data}(x)\\log D(x) + \\int_x p_G(x)\\log(1-D(x)) dx$$ \n", "$$\\le \\int_x max_y p_{data}(x)\\log y + \\int_x p_G(x)\\log(1-y) dx$$ \n", "y 为上式的 D(x) \n", "\n", "如果我们令 `D(x)=P_data/(P_data+p_G)`,那么我们就可以令价值函数 V(G,D) 取极大值。因为 f(y) 在定义域内有唯一的极大值,最优 D 也是唯一的,并且没有其它的 D 能实现极大值。 \n", "其实该最优的 D 在实践中并不是可计算的,但在数学上十分重要。我们并**不知道先验的 P_data(x)**,所以我们在训练中永远不会用到它。另一方面,它的存在令我们**可以证明最优的 G(D?) 是存在的,并且在训练中我们只需要逼近 D**。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 最优生成器 \n", "\n", "最终的目标是 `P_G = P_data`,代入 `D_G*`: \n", "$$D_G^* = \\frac{P_{data}(x)}{P_{data}(x)+P_G(x)} = \\frac{1}{2}$$ \n", "此时,判别器无法分辨 `P_data` 与 `P_G`,基于此可以证明:【当且仅当 `P_G=P_data,Cost(G)=maxV(G,D)` 的全局最小点可以达到。】这就是极大极小博弈的第二步,求令 `V(G,D*)` 最小的生成器 G,此时 D 固定。 \n", "之所以当 `P_G(x)=P_data(x)` 可以令价值函数最小化,是因为这时候两个分布的 JS 散度 `[JSD(P_data(x) || P_G(x))]` 等于零。\n", "\n", "- 先证明反向(反向指预先知道最优条件并做推导),设 `P_G=P_data`: \n", " $$V(G, D_G^*) = \\int_x p_{data}(x)\\log\\frac{1}{2} + P_G(x) \\log (1-\\frac{1}{2})dx$$ \n", " $$= -\\log2 \\int_x P_{data}(x) dx - \\log2 \\int_x P_G(x) dx$$ \n", " $$= -\\log2 (\\int_x P_{data}(x )dx + \\int_x P_G(x) dx) = -2\\log2 = -\\log4$$ \n", " 该值是**全局最小值的候选**,因为它只有在 `P_G=P_data` 的时候才出现。 \n", "\n", "- 现在需要从正向证明这一个值**常常为最小值**,也就是同时满足「当」和「仅当」的条件。放弃 `P_G=P_data` 的假设,对**任意一个 G**,我们可以将上一步求出的最优判别器 `D*` 代入到 Cost(G)=maxV(G,D) 中: \n", " $$Cost(G) = \\int_x p_{data}(x) \\log \\frac{P_{data}(x)}{P_{data}(x)+P_G(x)} + p_{G}(x) \\log \\frac{P_{G}(x)}{P_{data}(x)+P_G(x)} dx$$ \n", " 因为已知 -log4 为全局最小候选值,所以我们希望构造某个值以使方程式中出现 log2。因此我们可以在每个积分中加上或减去 log2,并乘上概率密度:\n", " $$Cost(G) = \\int_x (\\log2 - \\log2)P_{data}(x) + p_{data}(x) \\log \\frac{P_{data}(x)}{P_{data}(x)+P_G(x)} + (\\log2 - \\log2)P_{G}(x) + p_{G}(x) \\log \\frac{P_{G}(x)}{P_{data}(x)+P_G(x)} dx$$ \n", " $$= \\int_x P_{data}(x)(\\log2 + \\log \\frac{P_{data}(x)}{P_{data}(x)+P_G(x)}) + P_{G}(x)(\\log2 + \\log \\frac{P_{G}(x)}{P_{data}(x)+P_G(x)}) dx - \\log2 \\int_x P_{data}(x)+P_G(x) dx$$ \n", " 因为概率密度的定义,`P_G` 和 `P_data` 在它们积分域上的积分等于 1,即: \n", " $$- \\log2 \\int_x P_{data}(x)+P_G(x) dx = - \\log2(1+1) = -\\log4$$ \n", " 代入 Cost(G): \n", " $$Cost(G) = -\\log4 + \\int_x P_{data}(x)(\\log \\frac{P_{data}(x)}{(P_{data}(x)+P_G(x))/2}) + P_{G}(x)(\\log \\frac{P_{G}(x)}{(P_{data}(x)+P_G(x))/2}) dx$$ \n", " $$= -\\log4 + KL(P_{data} \\parallel \\frac{P_{data} + P_G}{2}) + KL(P_{G} \\parallel \\frac{P_{data} + P_G}{2})$$ \n", " KL 散度非负,所以 -log4 就是 Cost(G) 全局最小值。如果进一步证明只有一个 G 能达到这一个值,因为 `P_G=P_data` 将会成为令 Cost(G)=−log4 的唯一点,所以整个证明就能完成了。 \n", " 由于 KL 散度是非对称的,所以 C(G) 中的 `KL(P_data || (P_data+P_G)/2)` 左右两项是不能交换的,但如果同时加上另一项 `KL(P_G || (P_data+P_G)/2)`,它们的和就能变成对称项。这两项 KL 散度的和即可以表示为 JS 散度(Jenson-Shannon divergence):\n", " $$JSD(P \\parallel Q) = \\frac{1}{2}KL(P \\parallel M) + \\frac{1}{2}KL(Q \\parallel M), \\ M = \\frac{1}{2} (P+Q)$$ \n", " JS 散度的取值为 0 到 log2。若两个分布完全没有交集,那么 JS 散度取最大值 log2;若两个分布完全一样,那么 JS 散度取最小值 0。 \n", " 所以,Cost(G) 可以根据 JS 散度改写为: \n", " $$Cost(G) = -\\log4 + 2*JSD(P_{data} \\parallel P_G)$$ \n", " 这一散度其实就是 Jenson-Shannon 距离度量的平方。根据它的属性:当 `P_G=P_data` 时,`JSD(P_data||P_G)` 为 0,Cost(G) 取得极小值。 \n", " \n", "\n", "综上所述,**生成分布当且仅当等于真实数据分布式时,我们可以取得最优生成器**。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 重述训练过程" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 参数优化过程\n", "\n", "若我们需要寻找最优的生成器,那么给定一个判别器 D,我们可以将 maxV(G,D) 看作训练生成器的损失函数 L(G)。 \n", "既然设定了损失函数,那么我们就能使用 SGD、Adam 等优化算法更新生成器 G 的参数,梯度下降的参数优化过程如下: \n", "$$\\theta_G \\gets \\theta_G - \\eta \\partial L(G)/\\partial \\theta_G$$\n", "\n", "\n", "- 给定 `G_0`,最大化 `V(G_0,D)` 求得 `D_0*`,将最优的 `D_0*` 代入 `V(G_0,D)` 得到 `- log4+2*JSD`\n", "- 固定 `D_0*`,最小化 JSD,求得更新后的 `G_1` \n", "- 固定 `G_1`,最大化 `V(G_1,D_0*)` 求得 `D_1*`,将最优的 `D_1*` 代入 `V(G_1,D)` 得到更新后的 `- log4+2*JSD` \n", "- 固定 `D_1*`,最小化 更新后的 JSD,求得更新后的 `G_2`\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 实际训练过程\n", "\n", "根据前面价值函数 V(G,D) 的定义,我们需要求两个数学期望,即 `E[log(D(x))]` 和 `E[log(1-D(G(z)))]`,其中 x 服从真实数据分布,z 服从初始化分布。但在实践中,我们是没有办法利用积分求这两个数学期望的,所以一般我们能从无穷的真实数据和无穷的生成器中做采样以逼近真实的数学期望。 \n", "\n", "若现在给定生成器 G,并希望计算 maxV(G,D) 以求得判别器 D。那么我们首先需要从 `P_data(x)` 采样 m 个样本 `{𝑥^1,𝑥^2,…,𝑥^𝑚}`,从生成器 `P_G(x)` 采样 m 个样本 `{𝑥^1',𝑥^2',…,𝑥^𝑚'}`。因此最大化价值函数 V(G,D) 就可以使用以下表达式近似替代: \n", "$$Maximize\\ V = \\frac{1}{m} \\sum_{i=1}^m \\log D(x^i) + \\frac{1}{m} \\sum_{i=1}^m \\log (1-D(x'^i))$$ \n", "\n", "若我们需要计算上述的极大化过程,可以采用等价形式的训练方法。**若我们有一个二元分类器 D(参数为θ_d),当然该分类器可以是深度神经网络,那么极大化过程的输出就为该分类器 D(x)**。 \n", "\n", "现在我们从 `P_data(x)` 抽取样本作为正样本,从 `P_G(x)` 抽取样本作为负样本,同时将逼近负 V(G,D) 的函数作为损失函数,因此我们就将其表述为一个标准的二元分类器的训练过程(有疑惑,已改): \n", "\n", "$$Minimize\\ L = \\frac{1}{m} \\sum_{i=1}^m \\log(1-D(x'^i))$$ \n", "\n", "在实践中,我们必须使用迭代和数值计算的方法实现极小极大化博弈过程。在训练的内部循环中完整地优化 D 在计算上是不允许的,并且有限的数据集也会导致过拟合。因此我们可以在 k 个优化 D 的步骤和一个优化 G 的步骤间交替进行。那么我们只需慢慢地更新 G,D 就会一直处于最优解的附近,这种策略类似于 SML/PCD 训练的方式。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 最终过程 \n", "\n", "对每一次迭代: \n", "\n", "- 从真实数据分布 `P_data` 中抽取 m 个样本\n", "- 从先验分布 `P_prior(z)` 中抽取 m 个噪声样本\n", "- 将噪声样本投入 G 生成数据 `{𝑥^1',𝑥^2',…,𝑥^𝑚'}`,通过最大化 V 的近似而更新判别器参数 `θ_d`,即极大化: \n", " $$V = \\frac{1}{m} \\sum_{i=1}^m \\log D(x^i) + \\frac{1}{m} \\sum_{i=1}^m \\log (1-D(x'^i))$$ \n", " 判别器参数迭代: \n", " $$\\theta_d \\gets \\theta_d - \\eta \\nabla V(\\theta_d)$$ \n", "\n", "以上是学习判别器 D 的过程。因为学习 D 的过程是计算 JS 散度的过程,并且我们希望能最大化价值函数,所以该步骤会重复 k 次。 \n", "- 从先验分布 `P_prior(z)` 中抽取另外 m 个噪声样本 `{z^1,...z^m}` \n", "- 通过极小化 V 更新参数 `θ_g`,即极**小**化: \n", " $$V = \\frac{1}{m} \\sum_{i=1}^m \\log(1-D(G(z^i)))$$ \n", " 对这项可以这么理解:**D 是固定的,因此这个表达式是要把 G(z) 分成 负类 的,所以要让分类效果变差,也就是表达式变小**。 \n", " 生成器参数迭代: \n", " $$\\theta_g \\gets \\theta_g - \\eta \\nabla V(\\theta_g)$$ \n", "\n", "以上是学习生成器参数的过程,这一过程在一次迭代中只会进行一次,因此可以避免更新太多而令 JS 散度上升。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 代码\n", "\n", "来自[ML-Tutorial-Experiment/tf\\_GAN.ipynb at master · jiqizhixin/ML-Tutorial-Experiment](https://github.com/jiqizhixin/ML-Tutorial-Experiment/blob/master/Experiments/tf_GAN.ipynb)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import matplotlib.gridspec as gridspec\n", "\n", "import tensorflow as tf\n", "from tensorflow.examples.tutorials.mnist import input_data" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "#该函数将给出权重初始化的方法\n", "def variable_init(size):\n", " in_dim = size[0]\n", "\n", " #计算随机生成变量所服从的正态分布标准差\n", " w_stddev = 1. / tf.sqrt(in_dim / 2.)\n", " return tf.random_normal(shape=size, stddev=w_stddev)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "#定义输入矩阵的占位符,输入层单元为784,None代表批量大小的占位,X代表输入的真实图片。占位符的数值类型为32位浮点型\n", "X = tf.placeholder(tf.float32, shape=[None, 784])\n", "\n", "#定义判别器的权重矩阵和偏置项向量,由此可知判别网络为三层全连接网络\n", "D_W1 = tf.Variable(variable_init([784, 128]))\n", "D_b1 = tf.Variable(tf.zeros(shape=[128]))\n", "\n", "D_W2 = tf.Variable(variable_init([128, 1]))\n", "D_b2 = tf.Variable(tf.zeros(shape=[1]))\n", "\n", "theta_D = [D_W1, D_W2, D_b1, D_b2]\n", "\n", "#定义生成器的输入噪声为100维度的向量组,None根据批量大小确定\n", "Z = tf.placeholder(tf.float32, shape=[None, 100])\n", "\n", "#定义生成器的权重与偏置项。输入层为100个神经元且接受随机噪声,\n", "#输出层为784个神经元,并输出手写字体图片。生成网络根据原论文为三层全连接网络\n", "G_W1 = tf.Variable(variable_init([100, 128]))\n", "G_b1 = tf.Variable(tf.zeros(shape=[128]))\n", "\n", "G_W2 = tf.Variable(variable_init([128, 784]))\n", "G_b2 = tf.Variable(tf.zeros(shape=[784]))\n", "\n", "theta_G = [G_W1, G_W2, G_b1, G_b2]" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "#定义一个可以生成m*n阶随机矩阵的函数,该矩阵的元素服从均匀分布,随机生成的z就为生成器的输入\n", "def sample_Z(m, n):\n", " return np.random.uniform(-1., 1., size=[m, n])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "#定义生成器\n", "def generator(z):\n", " \n", " #第一层先计算 y=z*G_W1+G-b1,然后投入激活函数计算G_h1=ReLU(y),G_h1 为第二次层神经网络的输出激活值\n", " G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)\n", " \n", " #以下两个语句计算第二层传播到第三层的激活结果,第三层的激活结果是含有784个元素的向量,该向量转化28×28就可以表示图像\n", " G_log_prob = tf.matmul(G_h1, G_W2) + G_b2\n", " G_prob = tf.nn.sigmoid(G_log_prob)\n", " return G_prob" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "#定义判别器\n", "def discriminator(x):\n", " \n", " #计算D_h1=ReLU(x*D_W1+D_b1),该层的输入为含784个元素的向量\n", " D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1)\n", " \n", " #计算第三层的输出结果。因为使用的是Sigmoid函数,则该输出结果是一个取值为[0,1]间的标量(见上述权重定义)\n", " #即判别输入的图像到底是真(=1)还是假(=0)\n", " D_logit = tf.matmul(D_h1, D_W2) + D_b2\n", " D_prob = tf.nn.sigmoid(D_logit)\n", " \n", " #返回判别为真的概率和第三层的输入值,输出D_logit是为了将其输入tf.nn.sigmoid_cross_entropy_with_logits()\n", " # 以构建损失函数\n", " return D_prob, D_logit" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "#该函数用于输出生成图片\n", "def plot(samples):\n", " fig = plt.figure(figsize=(4, 4))\n", " gs = gridspec.GridSpec(4, 4)\n", " gs.update(wspace=0.05, hspace=0.05)\n", "\n", " for i, sample in enumerate(samples):\n", " ax = plt.subplot(gs[i])\n", " plt.axis('off')\n", " ax.set_xticklabels([])\n", " ax.set_yticklabels([])\n", " ax.set_aspect('equal')\n", " plt.imshow(sample.reshape(28, 28), cmap='Greys_r')\n", "\n", " return fig" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "#输入随机噪声z而输出生成样本\n", "G_sample = generator(Z)\n", "\n", "#分别输入真实图片和生成的图片,并投入判别器以判断真伪\n", "D_real, D_logit_real = discriminator(X)\n", "D_fake, D_logit_fake = discriminator(G_sample)\n", "\n", "#以下为原论文的判别器损失和生成器损失,但本实现并没有使用该损失函数\n", "# D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1. - D_fake))\n", "# G_loss = -tf.reduce_mean(tf.log(D_fake))\n", "\n", "# 我们使用交叉熵作为判别器和生成器的损失函数,因为sigmoid_cross_entropy_with_logits内部会对预测输入执行Sigmoid函数,\n", "#所以我们取判别器最后一层未投入激活函数的值,即D_h1*D_W2+D_b2。\n", "#tf.ones_like(D_logit_real)创建维度和D_logit_real相等的全是1的标注,真实图片。\n", "D_loss_real = tf.reduce_mean(\n", " tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real)))\n", "D_loss_fake = tf.reduce_mean(\n", " tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.zeros_like(D_logit_fake)))\n", "\n", "#损失函数为两部分,即E[log(D(x))]+E[log(1-D(G(z)))],将真的判别为假和将假的判别为真\n", "D_loss = D_loss_real + D_loss_fake\n", "\n", "#同样使用交叉熵构建生成器损失函数\n", "G_loss = tf.reduce_mean(\n", " tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake)))\n", "\n", "#定义判别器和生成器的优化方法为Adam算法,关键字var_list表明最小化损失函数所更新的权重矩阵\n", "D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)\n", "G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Extracting ./data/MNIST/train-images-idx3-ubyte.gz\n", "Extracting ./data/MNIST/train-labels-idx1-ubyte.gz\n", "Extracting ./data/MNIST/t10k-images-idx3-ubyte.gz\n", "Extracting ./data/MNIST/t10k-labels-idx1-ubyte.gz\n" ] } ], "source": [ "#选择训练的批量大小和随机生成噪声的维度\n", "mb_size = 128\n", "Z_dim = 100\n", "\n", "#读取数据集MNIST,并放在当前目录data文件夹下MNIST文件夹中,如果该地址没有数据,则下载数据至该文件夹\n", "mnist = input_data.read_data_sets(\"./data/MNIST/\", one_hot=True)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Iter: 0 D_loss: 1.446, G_loss: 2.288\n", "Iter: 2000 D_loss: 0.0645, G_loss: 6.974\n", "Iter: 4000 D_loss: 0.08388, G_loss: 4.614\n", "Iter: 6000 D_loss: 0.254, G_loss: 4.711\n", "Iter: 8000 D_loss: 0.7669, G_loss: 3.501\n", "Iter: 10000 D_loss: 0.7431, G_loss: 2.662\n", "Iter: 12000 D_loss: 0.65, G_loss: 3.146\n", "Iter: 14000 D_loss: 0.73, G_loss: 2.457\n", "Iter: 16000 D_loss: 0.7591, G_loss: 2.304\n", "Iter: 18000 D_loss: 0.6435, G_loss: 2.266\n" ] } ], "source": [ "#打开一个会话运行计算图\n", "sess = tf.Session()\n", "\n", "#初始化所有定义的变量\n", "sess.run(tf.global_variables_initializer())\n", "\n", "#如果当前目录下不存在out文件夹,则创建该文件夹\n", "if not os.path.exists('out/'):\n", " os.makedirs('out/')\n", "\n", "#初始化,并开始迭代训练,100W次\n", "i = 0\n", "for it in range(20000):\n", " \n", " #每2000次输出一张生成器生成的图片\n", " if it % 2000 == 0:\n", " samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)})\n", "\n", " fig = plot(samples)\n", " plt.savefig('out/{}.png'.format(str(i).zfill(3)), bbox_inches='tight')\n", " i += 1\n", " plt.close(fig)\n", " \n", " #next_batch抽取下一个批量的图片,该方法返回一个矩阵,即shape=[mb_size,784],每一行是一张图片,共批量大小行\n", " X_mb, _ = mnist.train.next_batch(mb_size)\n", " \n", " #投入数据并根据优化方法迭代一次,计算损失后返回损失值\n", " _, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})\n", " _, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(mb_size, Z_dim)})\n", "\n", "\n", " #每迭代2000次输出迭代数、生成器损失和判别器损失\n", " if it % 2000 == 0:\n", " print('Iter: {} D_loss: {:.4}, G_loss: {:.4}'.format(it, D_loss_curr, G_loss_curr))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 代码学习\n", "\n", "主要看其设计和过程。\n", "\n", "- 定义并初始化变量: \n", " - placeholder: X, Z\n", " - variables: θ (即 W,b)\n", "- 一个随机噪声产生器\n", " - 生成均匀分布的噪声,用于生成 Z\n", "- 一个生成器\n", " - 根据噪声生成矩阵 G(Z),也就是能够产生出图片(本质就是个矩阵……)\n", "- 一个判别器\n", " - 判别正负例的\n", "- 构造损失函数\n", " - 判别器需要对正负例进行判断,用 交叉熵 损失函数\n", " - 生成器需要对生成的进行判断,用 交叉熵 损失函数\n", " - 优化方法选择:Adam\n", "- 训练\n", " - run adam 和 loss\n", " - 判别器 feed X and Z\n", " - 生成器 feed Z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 应用\n", "\n", "- 历史档案图像检索:一个有趣的 GAN 应用实例是在「Prize Papers」中检索相似标记,Prize Papers 是海洋史上最具价值的档案之一。对抗网络使得处理这些具有历史意义的文件更加容易,这些文件还包括海上扣留船只是否合法的信息。\n", "- 文本翻译成图像:图片生成的主要问题在于图像分布是多模态的。例如,有太多的例子完美契合文本描述的内容。GAN 有助于解决这一问题。**GAN 不直接使用输入和输出对。相反,它们学习如何给输入和输出配对**。\n", "- 药物匹配:目标是训练生成器,以尽可能精确地从一个药物数据库中对现有药物进行按病取药的操作。\n", "- 肿瘤分子生物学的应用:目的是预测具有抗癌作用的药物反应和化合物。使用基于现有生化数据的用于识别和生成新化合物的对抗自编码器(AAE)模型。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 数据集\n", "\n", "- Caltech-UCSD-200-2011 是一个具有 200 种鸟类照片、总数为 11,788 的图像数据集。\n", "- Oxford-102 花数据集由 102 个花的类别组成,每个类别包含 40 到 258 张图片不等。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 其他资料\n", "\n", "- [GAN学习指南:从原理入门到制作生成Demo - 知乎专栏](https://zhuanlan.zhihu.com/p/24767059)\n", " - 简单、清晰\n", "- [GAN之父NIPS 2016演讲现场直击:全方位解读生成对抗网络的原理及未来(附PPT) | 机器之心](https://www.jiqizhixin.com/articles/22bc25c3-6e3c-4960-b86c-786813d0afac)\n", "- [GitHub - zhangqianhui/AdversarialNetsPapers: The classical papers and codes about generative adversarial nets](https://github.com/zhangqianhui/AdversarialNetsPapers)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 参考文献\n", "\n", "- [Generative Adversarial Networks (GANs): Engine and Applications](https://blog.statsbot.co/generative-adversarial-networks-gans-engine-and-applications-f96291965b47)\n", "- [机器之心 GitHub 项目:GAN 完整理论推导与实现,Perfect! | 机器之心](https://www.jiqizhixin.com/articles/2017-10-1-1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "toc": { "base_numbering": 1, "nav_menu": { "height": "84px", "width": "252px" }, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": true, "toc_position": {}, "toc_section_display": "block", "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }