04.md 40.9 KB
Newer Older
W
wizardforcel 已提交
1
# 四、使用 GAN 的时尚行业样式迁移
W
wizardforcel 已提交
2

W
wizardforcel 已提交
3
**样式迁移**的概念是指将产品样式渲染为另一种产品的过程。 想象一下,您的一位时尚狂朋友买了一个蓝色的手袋,想买一双类似印花的鞋子。 直到 2016 年,这还是不可能实现的,除非他们与一位时装设计师成为朋友,他们必须首先设计一款鞋子,然后才能批准生产。 然而,随着生成对抗网络的最新进展,这种设计过程可以很容易地进行。
W
wizardforcel 已提交
4

W
wizardforcel 已提交
5
生成对抗网络是通过在生成器网络和判别器网络之间进行零和游戏来学习的网络。 假设一位时装设计师想要设计一种特定结构的手袋,并且正在探索不同的印花。 设计人员可以绘制手提包的结构草图,然后将草图图像输入到生成的对抗网络中,以得出手提包的几种可能的最终印刷品。 这种样式迁移过程可以使客户自己绘制产品设计和图案,而无需征集大量设计师的意见,从而对时尚行业产生巨大影响。 通过推荐具有类似设计和风格的产品来补充客户已经拥有的产品,时装屋也可以从中受益。
W
wizardforcel 已提交
6

W
wizardforcel 已提交
7
在这个项目中,我们将构建一个智能人工智能系统,该系统将生成与给定手提袋样式相似的鞋子,反之亦然。 我们之前讨论的原始 GAN 不足以实现这个项目。 我们需要的是 GAN 的定制版本,例如 DiscoGAN 和 CycleGAN。
W
wizardforcel 已提交
8 9 10 11 12 13

在本章中,我们将介绍以下主题:

*   我们将讨论 DiscoGAN 背后的工作原理和数学基础
*   我们将比较和对比 DiscoGAN 与 CycleGAN,后者在架构和工作原理上非常相似
*   我们将训练一个 DiscoGAN,该系统学习从给定的袋子素描中生成袋子的图像
W
wizardforcel 已提交
14
*   最后,我们将讨论与训练 DiscoGAN 有关的复杂性
W
wizardforcel 已提交
15 16 17 18 19

# 技术要求

读者应具有 Python 3 和人工智能的基础知识,才能完成本章中的项目。

W
wizardforcel 已提交
20
[本章的代码文件可以在 GitHub 上找到](https://github.com/PacktPublishing/Intelligent-Projects-using-Python/tree/master/Chapter04)
W
wizardforcel 已提交
21

W
wizardforcel 已提交
22
[观看以下视频,查看运行中的代码](http://bit.ly/2CUZLQb)
W
wizardforcel 已提交
23 24 25

# DiscoGAN

W
wizardforcel 已提交
26
**DiscoGAN** 是一个生成的对抗网络,它在给定域`A`中的图像的情况下生成域`B`中产品的图像。下图说明了 DisoGAN 网络的架构图:
W
wizardforcel 已提交
27 28 29 30 31

![](img/ad2f3c06-0854-4f44-871f-d0916c1f0980.png)

图 4.1:DiscoGAN 的架构图

W
wizardforcel 已提交
32
`B`中生成的图像在样式和样式上都类似于域`A`中的图像。 无需在训练过程中显式配对来自两个域的图像就可以学习这种关系。 鉴于项目的配对是一项耗时的任务,因此这是一项非常强大的功能。 在较高的水平上,它尝试学习神经网络`G[AB]``G[BA]`形式的两个生成器函数。 图像`x[A]`,当通过生成器馈入时`G[AB]`,产生图像`x[AB]`,在域`B`中看起来很真实。此外,当此图像`x[AB]`通过其他生成器网络`G[BA]`馈送时,它应产生图像`x[ABA]`,理想情况下应与原始图像`x[A]`相同。 关于生成器函数,以下关系应成立:
W
wizardforcel 已提交
33 34 35

![](img/b12bb426-7c2a-4116-be5b-98a133536497.png)

W
wizardforcel 已提交
36
但是实际上,生成器函数`G[AB]``G[BA]`不可能彼此相反,因此我们尝试通过选择 L1 或 L2 归一化的损失来尽量减少重建图像和原始图像之间的损失。 L1 规范损失基本上是每个数据点的绝对误差之和,而 L2 规范损失表示每个数据点的平方误差的和。 我们可以如下表示单个图像的 L2 范数损失:
W
wizardforcel 已提交
37 38 39

![](img/09ada4ef-4d12-45db-a6b8-19064db767e9.png)

W
wizardforcel 已提交
40
仅使前面的损失最小化是不够的。 我们必须确保在域`B`中创建的图像`x[B]`看起来逼真。例如,如果我们将域`A`中的衣服映射到域`B`中的鞋子,我们将确保`x[B]`类似于鞋子。 如果图像不够真实,则在域`B`侧的判别器`D[B]`将检测为`x[B]`为假。 鞋子,因此也要考虑与此有关的损失。 通常,在训练过程中,向判别器提供生成的域`B`图像`x[AB] = G[AB](X[A])`,我们选择在这里用`y[B]`表示,以便它学习从假图像中对真实图像进行分类。 您可能还记得,在 GAN 中,生成器和判别器相互进行*零和最小最大值游戏*,以便不断变得更好,直到达到平衡为止。 如果伪造的图像看起来不够逼真,则判别器将对其进行惩罚,这意味着生成器必须学习产生更好的图像`x[AB]`,如果输入图像`x[A]`。 考虑到所有这些因素,我们可以将我们希望最小化的生成器损失公式化为重建损失,以及判别器将`x[AB]`识别为假冒的损失。 第二种损失将试图使生成器在域`B`中生成逼真的图像。将域`A`中的图像`x[A]`映射到域`B`中的图像的生成器损失可以表示如下:
W
wizardforcel 已提交
41 42 43 44 45 46 47

![](img/f2cdd9c3-d4d4-4c89-a533-e5044ea37592.png)

L2 范数下的重建损失可以表示为:

![](img/db0f4732-8246-4e5f-ab9f-d680b444e478.png)

W
wizardforcel 已提交
48
由于我们正在处理图像,因此可以假设`x[A]`是所有像素的扁平向量,以符合 L2 规范项。 如果我们假设`x[A]`是矩阵,则最好将`||·||_2^2`称为 **Frobenius 范数**。 但是,这些只是数学术语,实质上,我们只是将原始图像和重建图像之间的像素值差的平方和求和。
W
wizardforcel 已提交
49

W
wizardforcel 已提交
50
让我们考虑一下生成器在使变换后的图像`x[AB]`追求时要尽量降低成本的做法。 判别器将尝试将图像标记为伪图像,因此生成器`G[AB]`应当在这种情况下产生`x[AB]`使其成为假图片的对数损失的方式尽可能小。 如果域`B`中的判别器`D[B]`将真实图像标记为`1`,将伪图像标记为`0`,则图像真实的概率由`D[B](.)`,则生成器应使`x[AB]`在判别器网络下极有可能出现,从而使`D[B](x[B]) = D[B](G[AB](x[A]))`接近`1`)。 就对数损失而言,生成器应使先前概率的负对数最小化,这基本上使我们得到`C[D(AB)]`,如下所示:
W
wizardforcel 已提交
51 52 53

![](img/549bd16c-1bd7-4f76-b4c1-6179369f92c7.png)

W
wizardforcel 已提交
54
结合`(3)``(4)`,我们可以获得将镜像从域`A`映射到域`A`的总生成器成本`C_G[AB]``B`,如下所示:
W
wizardforcel 已提交
55 56 57

![](img/581b9be4-661a-41e9-a9f3-597d1116c2c7.png) ![](img/7d74f982-9649-4c22-a18e-d20e9b27d51c.png)

W
wizardforcel 已提交
58
最大的问题是,我们可以在这里停下来吗? 由于我们有来自两个域的图像,因此要获得更好的映射,我们也可以从域`B`拍摄图像,并通过生成器`G[BA]`将它们映射到域`A`。 如果我们在域`B`中拍摄`x[B]`图像,并通过生成器`G[BA]`将其转换为图像`x[BA]`,而域`A`上的标识符由`D[A]`给出,则与这种转换相关的成本函数由以下给出:
W
wizardforcel 已提交
59 60 61

![](img/e8a983da-5a03-453d-8317-eff82e4164e6.png) ![](img/0812b062-6e92-4e91-b725-747ac4b61f31.png)

W
wizardforcel 已提交
62
如果我们对两个域中的全部图像总数求和,则生成器损失将由`(5)``(6)`之和给出,如下所示:
W
wizardforcel 已提交
63 64 65

![](img/bd4d5890-a506-4180-b680-4c842cca1286.png) ![](img/bcb0c1db-84a7-44ac-864c-4e7545fb6016.png)

W
wizardforcel 已提交
66
现在,让我们构建成本函数,这些判别器将尝试最小化以建立零和最小/最大游戏。 每个域中的判别器都会尝试将真实图像与伪图像区分开,因此判别器`G[B]`会尝试将成本降到最低`C_D[B]`,如下所示:
W
wizardforcel 已提交
67 68 69

![](img/c8b74f9b-c8cb-47d4-99e1-00c9b35eef05.png)

W
wizardforcel 已提交
70
同样,判别器`D[A]`会尝试将成本降到最低。`C_D[A]`如下所示:
W
wizardforcel 已提交
71 72 73

![](img/78578465-7cc8-40c5-9662-21aa263baa81.png)

W
wizardforcel 已提交
74
结合`(8)``(9)`的总判别器成本由`C[D]`给出,如下:
W
wizardforcel 已提交
75 76 77

![](img/8907ca7c-ae87-4a4b-80e3-37c4bfb678cc.png)

W
wizardforcel 已提交
78
如果我们表示`G[AB]`的参数,则`G[BA]``D[A]``D[B]`设为`θ[GAB]``θ[GBA]``θ[DA]``θ[DB]`,则网络的优化参数可以表示为:
W
wizardforcel 已提交
79 80 81 82 83 84 85

![](img/3d609715-cecb-4823-8ce5-87fda4d8846f.png)

![](img/4937092d-eabc-4bc6-80ec-9e836b4f128e.png)

对成本函数执行随机梯度下降(例如 Adam),以得出最优解。 请注意,如前所述,生成对抗网络的解决方案是优化成本函数的一个障碍。

W
wizardforcel 已提交
86
# CycleGAN
W
wizardforcel 已提交
87

W
wizardforcel 已提交
88
**CycleGAN** 从根本上类似于 DiscoGAN,但有一个小的修改。 在 CycleGAN 中,我们可以灵活地确定相对于 GAN 损失或归因于判别器的损失,为重建损失分配多少权重。 该参数有助于根据眼前的问题按正确比例平衡损失,以帮助网络在训练时更快地收敛。 CycleGAN 的其余实现与 DiscoGAN 相同。
W
wizardforcel 已提交
89

W
wizardforcel 已提交
90
# 学习从草绘的轮廓生成自然手袋
W
wizardforcel 已提交
91

W
wizardforcel 已提交
92
在本章中,我们将使用草绘的轮廓生成手袋,而无需使用 DiscoGAN 进行显式配对。 我们将草图图像表示为属于域`A`,而将自然手袋图像表示为属于域`B`。将有两种生成器:一种生成器,用于获取域`A`的图像并将其映射到在域`B`下看起来逼真的图像,以及另一个与此相反:将域`B`中的手袋图像映射到在域`A`下看起来很逼真的图像。判别器将尝试从每个域中真实图像的生成器中识别生成器生成的虚假图像。 生成器和判别器将相互进行 minimax 零和游戏。
W
wizardforcel 已提交
93

W
wizardforcel 已提交
94
要训​​练该网络,我们将需要两套图像,手袋的草图或轮廓以及手袋的自然图像。 [可以从以下链接下载图像](https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/edges2handbags.tar.gz)
W
wizardforcel 已提交
95

W
wizardforcel 已提交
96
在接下来的几节中,我们将完成在 TensorFlow 中定义 DiscoGAN 网络的过程,然后训练它使用充当图像边缘的手提包草图来生成逼真的手提包图像。 我们将从定义生成器网络的架构开始。
W
wizardforcel 已提交
97 98 99

# 预处理图像

W
wizardforcel 已提交
100
`edges2handbags`数据集文件夹中的每个图像在同一图像中包含`bag`的图片和`bag edges`的图片。 为了训练网络,我们需要将它们分离为属于我们在 DiscoGAN 架构中讨论过的两个域`A`和 B 的图像。 通过使用以下代码(`image_split.py`),可以将图像分为域`A`和域`B`图像:
W
wizardforcel 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

```py
# -*- coding: utf-8 -*-
"""
Created on Fri Apr 13 00:10:12 2018

@author: santanu
"""

import numpy as np
import os
from scipy.misc import imread
from scipy.misc import imsave
import fire
from elapsedtimer import ElapsedTimer
from pathlib import Path
import shutil 
'''
Process the images in Domain A and Domain and resize appropriately
Inputs contain the Domain A and Domain B image in the same image
This program will break them up and store them in their respecective folder

'''

def process_data(path,_dir_):
    os.chdir(path)
    try: 
        os.makedirs('trainA')
    except:
        print(f'Folder trainA already present, cleaning up and recreating empty folder trainA')
        try:
            os.rmdir('trainA')
        except:
            shutil.rmtree('trainA')

        os.makedirs('trainA')

    try: 
        os.makedirs('trainB')
    except:
        print(f'Folder trainA already present, cleaning up and recreating empty folder trainB')
        try:
            os.rmdir('trainB')
        except:
            shutil.rmtree('trainB')
        os.makedirs('trainB')
    path = Path(path) 
    files = os.listdir(path /_dir_)
    print('Images to process:', len(files))
    i = 0
    for f in files:
        i+=1 
        img = imread(path / _dir_ / str(f))
        w,h,d = img.shape
        h_ = int(h/2)
        img_A = img[:,:h_]
        img_B = img[:,h_:]
        imsave(f'{path}/trainA/{str(f)}_A.jpg',img_A)
        imsave(f'{path}/trainB/{str(f)}_B.jpg',img_A)
        if ((i % 10000) == 0 & (i >= 10000)):
            print(f'the number of input images processed : {i}')
    files_A = os.listdir(path / 'trainA')
    files_B = os.listdir(path / 'trainB')
    print(f'No of images written to {path}/trainA is {len(files_A)}')
    print(f'No of images written to {path}/trainA is {len(files_B)}')

with ElapsedTimer('process Domain A and Domain B Images'):
    fire.Fire(process_data)

```

`image_split.py`代码可以按以下方式调用:

```py
python image_split.py --path /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ --_dir_ train
```

输出日志如下:

```py
Folder trainA already present, cleaning up and recreating empty folder trainA
Folder trainA already present, cleaning up and recreating empty folder trainB
Images to process: 138569 the number of input images processed : 10000
the number of input images processed : 20000
the number of input images processed : 30000

.....
```

W
wizardforcel 已提交
190
# DiscoGAN 的生成器
W
wizardforcel 已提交
191

W
wizardforcel 已提交
192
DiscoGAN 的生成器是前馈卷积神经网络,其中输入和输出是图像。 在网络的第一部分中,图像在空间维度上按比例缩小,而输出特征映射的数量随层的进展而增加。 在网络的第二部分中,图像沿空间维度按比例放大,而输出特征映射的数量则逐层减少。 在最终输出层中,将生成具有与输入相同的空间大小的图像。 如果生成器将图像`x[A]`转换为`x[AB]`从域`A`到域`B`表示为`G[AB]`,则我们有`x[AB] = G[AB](x[A])`
W
wizardforcel 已提交
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284

此处显示的是`build_generator`函数,我们可以使用它来构建 DiscoGAN 网络的生成器:

```py
def build_generator(self,image,reuse=False,name='generator'):
    with tf.variable_scope(name):
        if reuse:
            tf.get_variable_scope().reuse_variables()
        else:
            assert tf.get_variable_scope().reuse is False

            """U-Net generator"""
        def lrelu(x, alpha,name='lrelu'):
            with tf.variable_scope(name):
                return tf.nn.relu(x) - alpha * tf.nn.relu(-x)

    """Layers used during downsampling"""
        def common_conv2d(layer_input,filters,f_size=4,
                          stride=2,padding='SAME',norm=True,
                          name='common_conv2d'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()

                else:
                    assert tf.get_variable_scope().reuse is False

                d = 
               tf.contrib.layers.conv2d(layer_input,filters,
                                        kernel_size=f_size,
                                        stride=stride,padding=padding)
                if norm:
                    d = tf.contrib.layers.batch_norm(d)

                d = lrelu(d,alpha=0.2)
                return d

         """Layers used during upsampling"""

       def common_deconv2d(layer_input,filters,f_size=4,
                           stride=2,padding='SAME',dropout_rate=0,
                           name='common_deconv2d'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()

                else:
                    assert tf.get_variable_scope().reuse is False

                u = 
                tf.contrib.layers.conv2d_transpose(layer_input,
                                                   filters,f_size,
                                                   stride=stride,
                                                   padding=padding)
                if dropout_rate:
                    u = tf.contrib.layers.dropout(u,keep_prob=dropout_rate)

                u = tf.contrib.layers.batch_norm(u)
                u = tf.nn.relu(u)
                return u 

        # Downsampling
        #  64x64 -> 32x32
        dwn1 = common_conv2d(image,self.gf,stride=2,norm=False,name='dwn1') 
        #  32x32 -> 16x16
       dwn2 = common_conv2d(dwn1,self.gf*2,stride=2,name='dwn2')           
        #  16x16   -> 8x8
       dwn3 = common_conv2d(dwn2,self.gf*4,stride=2,name='dwn3')           
        #  8x8   -> 4x4 
       dwn4 = common_conv2d(dwn3,self.gf*8,stride=2,name='dwn4')            
        #  4x4   -> 1x1 
       dwn5 = common_conv2d(dwn4,100,stride=1,padding='valid',name='dwn5') 

        # Upsampling
        #  4x4    -> 4x4
        up1 = 
       common_deconv2d(dwn5,self.gf*8,stride=1,
                       padding='valid',name='up1')      
        #  4x4    -> 8x8
        up2 = common_deconv2d(up1,self.gf*4,name='up2')                  
        #  8x8    -> 16x16
        up3 = common_deconv2d(up2,self.gf*2,name='up3')                  
        #  16x16    -> 32x32 
        up4 = common_deconv2d(up3,self.gf,name='up4')                    
       out_img = tf.contrib.layers.conv2d_transpose(up4,self.channels,
                                                    kernel_size=4,stride=2,                                                                                                                                      
                                                    padding='SAME',
                                                    activation_fn=tf.nn.tanh) 
       # 32x32 -> 64x64
        return out_img
```

W
wizardforcel 已提交
285
在生成器函数中,我们定义了 LReLU 激活函数,并使用`0.2`的泄漏因子。 我们还定义了卷积层生成函数`common_conv2d`(用于对图像进行下采样)和`common_deconv2d`(用于将经降采样的图像上采样至其原始空间大小)。
W
wizardforcel 已提交
286

W
wizardforcel 已提交
287
我们通过使用`tf.get_variable_scope().reuse_variables()`使用`reuse`选项定义生成器函数。 当多次调用同一个生成器函数时,重用选项可确保我们重用特定生成器使用的相同变量。 当我们删除重用选项时,我们为生成器创建了一组新的变量。
W
wizardforcel 已提交
288

W
wizardforcel 已提交
289
例如,我们可能使用生成器函数创建了两个生成器网络,因此在第一次创建这些网络时不会使用`reuse`选项。 如果再次引用该生成器函数,则使用`reuse`选项。 卷积(下采样)和解卷积(上采样)期间的激活函数是 LReLU,然后进行批量归一化,以实现稳定和快速的收敛。
W
wizardforcel 已提交
290

W
wizardforcel 已提交
291
网络不同层中的输出特征映射的数量可以是`self.gf` 或其倍数。 对于我们的 DiscoGAN 网络,我们选择了`self.gf`作为`64`
W
wizardforcel 已提交
292

W
wizardforcel 已提交
293
生成器中要注意的一件事是输出层的`tanh`激活函数。 这样可以确保生成器生成的图像的像素值在`[-1, +1]`的范围内。 这对于输入图像具有`[-1, +1]`范围内的像素强度非常重要,这可以通过对像素强度进行简单的逐元素变换来实现,如下所示:
W
wizardforcel 已提交
294 295 296 297 298 299 300

![](img/7d1812c6-e792-454e-a693-76421c5d5d91.png)

同样,要将图像转换为可显示的 0-255 像素强度格式,我们需要应用逆变换,如下所示:

![](img/118fba60-e8de-4a9c-8ffa-bcd85c456a21.png)

W
wizardforcel 已提交
301
# DiscoGAN 的判别器
W
wizardforcel 已提交
302

W
wizardforcel 已提交
303
DiscoGAN 的判别器将学会在特定域中将真实图像与假图像区分开。 我们将有两个判别器:一个用于域`A`,一个用于域`B`。这些判别器也是可以执行二分类的卷积网络。 与传统的基于分类的卷积网络不同,判别器没有任何全连接层。 使用步长为 2 的卷积对输入图像进行下采样,直到最终层(输出为`1 x 1`)为止。同样,我们使用 LReLU 作为激活函数并使用批量归一化以实现稳定和快速的收敛。 以下代码显示了 TensorFlow 中判别器构建函数的实现:
W
wizardforcel 已提交
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

```py
def build_discriminator(self,image,reuse=False,name='discriminator'):
    with tf.variable_scope(name):
        if reuse:
            tf.get_variable_scope().reuse_variables()
        else:
            assert tf.get_variable_scope().reuse is False

        def lrelu(x, alpha,name='lrelu'):

            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False

            return tf.nn.relu(x) - alpha * tf.nn.relu(-x)

                """Discriminator layer"""

        def d_layer(layer_input,filters,f_size=4,stride=2,norm=True,
                    name='d_layer'):
            with tf.variable_scope(name):
                if reuse:
                    tf.get_variable_scope().reuse_variables()
                else:
                    assert tf.get_variable_scope().reuse is False

                d = 
                tf.contrib.layers.conv2d(layer_input,
                                         filters,kernel_size=f_size,
                                         stride=2, padding='SAME')
                if norm:
                    d = tf.contrib.layers.batch_norm(d)

                d = lrelu(d,alpha=0.2)
                return d

        #64x64 -> 32x32        
        down1 = d_layer(image,self.df, norm=False,name='down1')  
        #32x32 -> 16x16
        down2 = d_layer(down1,self.df*2,name='down2')         
        #16x16 -> 8x8
        down3 = d_layer(down2,self.df*4,name='down3')         
        #8x8 -> 4x4
        down4 = d_layer(down3,self.df*8,name='down4')        
        #4x4 -> 1x1
        down5  = 
       tf.contrib.layers.conv2d(down4,1,kernel_size=4,stride=1,
                                padding='valid')

        return down5
```

W
wizardforcel 已提交
359
判别器网络不同层中输出特征映射的数量为`self.df`或其倍数。 对于我们的网络,我们将`self.df`设为`64`
W
wizardforcel 已提交
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466

# 建立网络并定义成本函数

在本节中,我们将使用生成器和鉴别函数来构建整个网络,并定义在训练过程中要优化的成本函数。 TensorFlow 代码如下:

```py
def build_network(self):
    def squared_loss(y_pred,labels):
        return tf.reduce_mean((y_pred - labels)**2)
   def abs_loss(y_pred,labels):
        return tf.reduce_mean(tf.abs(y_pred - labels))  
   def binary_cross_entropy_loss(logits,labels):
        return tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
                                        labels=labels,logits=logits))
     self.images_real = tf.placeholder(tf.float32,[None,self.image_size,self.image_size,self.input_dim + self.output_dim])

    self.image_real_A = self.images_real[:,:,:,:self.input_dim]
    self.image_real_B = 
    self.images_real[:,:,:,self.input_dim:self.input_dim + self.output_dim]
    self.images_fake_B = 
    self.build_generator(self.image_real_A,
                         reuse=False,name='generator_AB')
    self.images_fake_A = 
    self.build_generator(self.images_fake_B,
                         reuse=False,name='generator_BA')
    self.images_fake_A_ = 
    self.build_generator(self.image_real_B,
                         reuse=True,name='generator_BA')
    self.images_fake_B_ = 
    self.build_generator(self.images_fake_A_,
                         reuse=True,name='generator_AB')

    self.D_B_fake = 
    self.build_discriminator(self.images_fake_B ,
                             reuse=False, name="discriminatorB")
    self.D_A_fake = 
    self.build_discriminator(self.images_fake_A_,
                             reuse=False, name="discriminatorA") 

    self.D_B_real = 
    self.build_discriminator(self.image_real_B,
                             reuse=True, name="discriminatorB")
    self.D_A_real = 
    self.build_discriminator(self.image_real_A,
                             reuse=True, name="discriminatorA")
    self.loss_GABA = 
    self.lambda_l2*squared_loss(self.images_fake_A,self.image_real_A) +
    binary_cross_entropy_loss(labels=tf.ones_like(self.D_B_fake),
    logits=self.D_B_fake)
    self.loss_GBAB = 
    self.lambda_l2*squared_loss(self.images_fake_B_,
    self.image_real_B) + 
    binary_cross_entropy_loss(labels=tf.ones_like(self.D_A_fake),
    logits=self.D_A_fake)
    self.generator_loss = self.loss_GABA + self.loss_GBAB

    self.D_B_loss_real =     
    binary_cross_entropy_loss(tf.ones_like(self.D_B_real),self.D_B_real)
    self.D_B_loss_fake = 
    binary_cross_entropy_loss(tf.zeros_like(self.D_B_fake),self.D_B_fake)
    self.D_B_loss = (self.D_B_loss_real + self.D_B_loss_fake) / 2.0

    self.D_A_loss_real = 
    binary_cross_entropy_loss(tf.ones_like(self.D_A_real),self.D_A_real)
    self.D_A_loss_fake = 
    binary_cross_entropy_loss(tf.zeros_like(self.D_A_fake),self.D_A_fake)
    self.D_A_loss = (self.D_A_loss_real + self.D_A_loss_fake) / 2.0

    self.discriminator_loss = self.D_B_loss + self.D_A_loss
    self.loss_GABA_sum = tf.summary.scalar("g_loss_a2b", self.loss_GABA)
    self.loss_GBAB_sum = tf.summary.scalar("g_loss_b2a", self.loss_GBAB)
    self.g_total_loss_sum = tf.summary.scalar("g_loss", self.generator_loss)
    self.g_sum = tf.summary.merge([self.loss_GABA_sum,
                                   self.loss_GBAB_sum,self.g_total_loss_sum])
    self.loss_db_sum = tf.summary.scalar("db_loss", self.D_B_loss)
    self.loss_da_sum = tf.summary.scalar("da_loss", self.D_A_loss)
    self.loss_d_sum = tf.summary.scalar("d_loss",self.discriminator_loss)

    self.db_loss_real_sum = tf.summary.scalar("db_loss_real", self.D_B_loss_real)
    self.db_loss_fake_sum = tf.summary.scalar("db_loss_fake", self.D_B_loss_fake)
    self.da_loss_real_sum = tf.summary.scalar("da_loss_real", self.D_A_loss_real)
    self.da_loss_fake_sum = tf.summary.scalar("da_loss_fake", self.D_A_loss_fake)
    self.d_sum = tf.summary.merge(
            [self.loss_da_sum, self.da_loss_real_sum, self.da_loss_fake_sum,
             self.loss_db_sum, self.db_loss_real_sum, self.db_loss_fake_sum,
             self.loss_d_sum]
        )

    trainable_variables = tf.trainable_variables()
    self.d_variables = 
    [var for var in trainable_variables if 'discriminator' in var.name]
    self.g_variables =
    [var for var in trainable_variables if 'generator' in var.name]

    print ('Variable printing start :'  )
    for var in self.d_variables: 
        print(var.name)

    self.test_image_A = 
    tf.placeholder(tf.float32,[None, self.image_size,
                   self.image_size,self.input_dim], name='test_A')
    self.test_image_B =
    tf.placeholder(tf.float32,[None, self.image_size,
                   self.image_size,self.output_c_dim], name='test_B')
    self.saver = tf.train.Saver()
```

W
wizardforcel 已提交
467
在构建网络中,我们首先定义 L2 范数误差和二进制交叉熵误差的成本函数。 L2 范数误差将用作重建损失,而二元互熵将用作判别器损失。 然后,我们使用生成器函数为两个域中的图像定义占位符,并为每个域中的伪图像定义 TensorFlow 操作。 我们还通过传递特定于域的伪造和真实图像来定义判别器输出的操作。 除此之外,我们为两个域中的每个域中的重建图像定义 TensorFlow 操作。
W
wizardforcel 已提交
468

W
wizardforcel 已提交
469
一旦定义了操作,我们就可以使用它们来计算损失函数,同时考虑图像的重建损失和归因于判别器的损失。 请注意,我们使用了相同的生成器函数来定义域`A``B`的生成器,也定义了从`B``A`的生成器。唯一不同的是为这两个网络提供了不同的名称:`generator_AB``generator_BA`。 由于变量作用域定义为`name`,所以这两个生成器都将具有不同的权重集,并以提供的名称为前缀。
W
wizardforcel 已提交
470

W
wizardforcel 已提交
471
下表显示了我们需要跟踪的不同损失变量。 就生成器或判别器的参数而言,所有这些损失都需要最小化:
W
wizardforcel 已提交
472 473

| **不同损失的变量** | **说明** |
W
wizardforcel 已提交
474
| --- | --- |
W
wizardforcel 已提交
475 476 477 478
| `self.D_B_loss_real` | 在对域`B`中的真实图像进行分类时,判别器`D[B]`的二进制交叉熵损失。(相对于判别器`D[B]`的参数,该损失应最小化) |
| `self.D_B_loss_fake` | 在对域`B`中的伪造图像进行分类时,判别器`D[B]`的二进制交叉熵损失。(相对于判别器`D[B]`的参数,该损失应最小化) |
| `self.D_A_loss_real` | 在对域`A`中的真实图像进行分类时,判别器`D[A]`的二进制交叉熵损失。(相对于判别器`D[A]`的参数,该损失应最小化) |
| `self.D_A_loss_fake` | 在对域`A`中的伪造图像进行分类时,判别器`D[A]`的二进制交叉熵损失。(相对于判别器`D[A]`的参数,该损失应最小化) |
W
wizardforcel 已提交
479 480
| `self.loss_GABA` | 通过两个生成器`G[AB]``G[BA]`将域`A`中的图像映射到`B`,然后再映射回`A`的重建损失,加上假图片`G[AB](x[A])`的二进制交叉熵,由域`B`中的判别器标记为真实图像。(相对于生成器`G[AB]``G[BA]`的参数,该损失应最小化) |
| `self.loss_GBAB` | 通过两个生成器`G[BA]``G[AB]`将域`B`中的图像映射到`A`,然后再映射回`B`的重建损失,加上伪图片`G[BA](x[B])`的二元交叉熵,由域`A`中的判别器标记为真实图像。(相对于生成器`G[AB]``G[BA]`的参数,该损失应最小化) |
W
wizardforcel 已提交
481

W
wizardforcel 已提交
482
前四个损失组成了判别器损失,需要根据判别器的参数将其最小化。 ]。 最后两个损失组成了生成器损失,需要根据生成器的参数将其最小化。 。
W
wizardforcel 已提交
483

W
wizardforcel 已提交
484
损失变量通过`tf.summary.scaler` 与 TensorBoard 绑定,以便可以在训练过程中监控这些损失,以确保以期望的​​方式减少损失。 稍后,我们将在训练进行时在 TensorBoard 中看到这些损失痕迹。
W
wizardforcel 已提交
485

W
wizardforcel 已提交
486
# 建立训练流程
W
wizardforcel 已提交
487

W
wizardforcel 已提交
488
`train_network`函数中,我们首先为生成器和判别器损失函数定义优化器。 我们将 Adam 优化器用于生成器和判别器,因为这是随机梯度下降优化器的高级版本,在训练 GAN 方面非常有效。 亚当使用梯度的衰减平均值(非常类似于稳定梯度的动量)和平方梯度的衰减平均值,以提供有关成本函数曲率的信息。 与`tf.summary`定义的不同损失有关的变量将写入日志文件,因此可以通过 TensorBoard 进行监视。 以下是`train`函数的详细代码:
W
wizardforcel 已提交
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581

```py
def train_network(self):

        self.learning_rate = tf.placeholder(tf.float32)
        self.d_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.discriminator_loss,var_list=self.d_variables)
        self.g_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.generator_loss,var_list=self.g_variables) 

        self.init_op = tf.global_variables_initializer()
        self.sess = tf.Session()
        self.sess.run(self.init_op)
        #self.dataset_dir = '/home/santanu/Downloads/DiscoGAN/edges2handbags/train/'
        self.writer = tf.summary.FileWriter("./logs", self.sess.graph)
        count = 1
        start_time = time.time()

        for epoch in range(self.epoch):
            data_A = os.listdir(self.dataset_dir + 'trainA/')
            data_B = os.listdir(self.dataset_dir + 'trainB/')
            data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for file_name in data_A ] 

            data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for file_name in data_B ] 
            np.random.shuffle(data_A)
            np.random.shuffle(data_B)
            batch_ids = min(min(len(data_A), len(data_B)), self.train_size) // self.batch_size
            lr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step)

            for id_ in range(0, batch_ids):
                batch_files = list(zip(data_A[id_ * self.batch_size:(id_ + 1) * self.batch_size],
                                      data_B[id_ * self.batch_size:(id_ + 1) * self.batch_size]))
                batch_images = [load_train_data(batch_file, self.load_size, self.fine_size) for batch_file in batch_files]
                batch_images = np.array(batch_images).astype(np.float32)

                    # Update G network and record fake outputs
                fake_A, fake_B, _, summary_str = self.sess.run(
                        [self.images_fake_A_,self.images_fake_B,self.g_optimizer,self.g_sum],
                        feed_dict={self.images_real: batch_images, self.learning_rate:lr})
                self.writer.add_summary(summary_str, count)
                [fake_A,fake_B] = self.pool([fake_A, fake_B])

                    # Update D network
                _, summary_str = self.sess.run(
                        [self.d_optimizer,self.d_sum],
                        feed_dict={self.images_real: batch_images,
                               # self.fake_A_sample: fake_A,
                               # self.fake_B_sample: fake_B,
                                   self.learning_rate: lr})
                self.writer.add_summary(summary_str, count)

                count += 1
                print(("Epoch: [%2d] [%4d/%4d] time: %4.4f" % (
                        epoch, id_, batch_ids, time.time() - start_time)))

                if count % self.print_freq == 1:
                    self.sample_model(self.sample_dir, epoch, id_)

                if count % self.save_freq == 2:
                    self.save_model(self.checkpoint_dir, count)
```

正如我们在代码末尾看到的那样,在训练期间会不时调用`sample_model`函数,以根据来自另一个域的输入图像来检查在一个域中生成的图像的质量。 还基于`save_freq`定期保存模型。

我们在前面的代码中引用了`sample_model`函数和`save_model`函数,以供参考:

```py
def sample_model(self, sample_dir, epoch, id_):
    if not os.path.exists(sample_dir):
        os.makedirs(sample_dir)
    data_A = os.listdir(self.dataset_dir + 'trainA/')
    data_B = os.listdir(self.dataset_dir + 'trainB/') 
    data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for 
              file_name in data_A ]
    data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for 
              file_name in data_B ]
   np.random.shuffle(data_A)
    np.random.shuffle(data_B)
    batch_files = 
    list(zip(data_A[:self.batch_size], data_B[:self.batch_size]))
    sample_images = 
    [load_train_data(batch_file, is_testing=True) for 
     batch_file in batch_files]
    sample_images = np.array(sample_images).astype(np.float32)

    fake_A, fake_B = self.sess.run(
            [self.images_fake_A_,self.images_fake_B],
            feed_dict={self.images_real: sample_images}
        )
    save_images(fake_A, [self.batch_size, 1],
                    './{}/A_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))
    save_images(fake_B, [self.batch_size, 1],
                    './{}/B_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))
```

W
wizardforcel 已提交
582
在此`sample_model`函数中,从域`A`随机选择的图像被拍摄并馈送到生成器`G[AB]`,以在域`B`中生成图像。类似地,从域`B`随机选择的图像馈送到生成器`G[BA]`中,以在域`A`中生成图像。这些输出图像由两个生成器在不同周期生成,并将批量保存在样本文件夹中,来查看生成器在训练过程中是否随着时间的推移而不断改进,以产生更好的图像质量。
W
wizardforcel 已提交
583

W
wizardforcel 已提交
584
使用 TensorFlow 保存器功能保存模型的`save_model`函数如下所示:
W
wizardforcel 已提交
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

```py
def save_model(self,checkpoint_dir,step):
    model_name = "discogan.model"
    model_dir = "%s_%s" % (self.dataset_dir, self.image_size)
    checkpoint_dir = os.path.join(checkpoint_dir, model_dir)

    if not os.path.exists(checkpoint_dir):
        os.makedirs(checkpoint_dir)

    self.t(self.sess,
                    os.path.join(checkpoint_dir, model_name),
                    global_step=step)
```

# GAN 训练的重要参数值

在本节中,我们将讨论用于训练 DiscoGAN 的不同参数值。 下表中列出了这些:

| **参数名称** | **变量名称和值集** | **原理** |
| Adam 优化器的学习率 | `self.l_r = 2e-4` | 我们应该始终训练学习率较低的 GAN 网络以获得更好的稳定性,而 DiscoGAN 也不例外。 |
| Adam 优化器的衰减率 | `self.beta1 = 0.5` `self.beta2 = 0.99` | 参数`beta1`定义梯度的衰减平均值,而参数`beta2`定义梯度平方的衰减平均值。 |
W
wizardforcel 已提交
607
| 周期 | `self.epoch = 200` | 在此实现中,`200`周期足以满足 DiscoGAN 网络的收敛要求。 |
W
wizardforcel 已提交
608
| 批量大小 | `self.batch_size = 64` | `64`的批量大小非常适合此实现。 但是,由于资源限制,我们可能不得不选择较小的批量大小。 |
W
wizardforcel 已提交
609
| 学习率线性下降的周期 | `epoch_step = 10` | 在`epoch_step`指定的周期数之后,学习率呈线性下降,由以下方案确定:`lr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step)` |
W
wizardforcel 已提交
610

W
wizardforcel 已提交
611
# 调用训练
W
wizardforcel 已提交
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685

我们前面说明的所有函数都是在`DiscoGAN()`类内创建的,并在`__init__` 函数中声明了重要的参数值,如以下代码块所示。 训练网络时仅需要传递的两个参数是`dataset_dir`和需要对其进行训练的`epochs`的数量

```py
   def __init__(self,dataset_dir,epochs=200):
        # Input shape
        self.dataset_dir = dataset_dir
        self.lambda_l2 = 1.0
        self.image_size = 64
        self.input_dim = 3
        self.output_dim = 3
        self.batch_size = 64 
        self.df = 64
        self.gf = 64
        self.channels = 3
        self.output_c_dim = 3
        self.l_r = 2e-4
        self.beta1 = 0.5
        self.beta2 = 0.99
        self.weight_decay = 0.00001
        self.epoch = epochs
        self.train_size = 10000
        self.epoch_step = 10
        self.load_size = 64
        self.fine_size = 64 
        self.checkpoint_dir = 'checkpoint'
        self.sample_dir = 'sample'
        self.print_freq = 5
        self.save_freq = 10 
        self.pool = ImagePool()

        return None
```

现在我们已经定义了训练模型所需的所有内容,我们可以通过`process_main `函数调用训练,如下所示:

```py
   def process_main(self):
        self.build_network()
        self.train_network()
```

我们之前为训练所展示的端到端代码在脚本`cycledGAN_edges_to_bags.py`中。 我们可以通过运行 python 脚本`cycledGAN_edges_to_bags.py`来训练模型,如下所示:

```py
python cycledGAN_edges_to_bags.py process_main  --dataset_dir /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ epochs 100
```

脚本`cycledGAN_edges_to_bags.py`执行的输出日志如下:

```py
Epoch: [ 0] [ 0/ 156] time: 3.0835
Epoch: [ 0] [ 1/ 156] time: 3.9093
Epoch: [ 0] [ 2/ 156] time: 4.3661
Epoch: [ 0] [ 3/ 156] time: 4.8208
Epoch: [ 0] [ 4/ 156] time: 5.2821
Epoch: [ 0] [ 5/ 156] time: 6.2380
Epoch: [ 0] [ 6/ 156] time: 6.6960
Epoch: [ 0] [ 7/ 156] time: 7.1528
Epoch: [ 0] [ 8/ 156] time: 7.6138
Epoch: [ 0] [ 9/ 156] time: 8.0732
Epoch: [ 0] [ 10/ 156] time: 8.8163
Epoch: [ 0] [ 11/ 156] time: 9.6669
Epoch: [ 0] [ 12/ 156] time: 10.1256
Epoch: [ 0] [ 13/ 156] time: 10.5846
Epoch: [ 0] [ 14/ 156] time: 11.0427
Epoch: [ 0] [ 15/ 156] time: 11.9135
Epoch: [ 0] [ 16/ 156] time: 12.3712
Epoch: [ 0] [ 17/ 156] time: 12.8290
Epoch: [ 0] [ 18/ 156] time: 13.2899
Epoch: [ 0] [ 19/ 156] time: 13.7525
.......
```

W
wizardforcel 已提交
686
# 监控生成器和判别器损失
W
wizardforcel 已提交
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704

可以在 TensorBoard 仪表板中监控损失。 TensorBoard 仪表板可以按以下方式调用:

1.  在终端上,运行以下命令:

```py
 tensorboard --logdir=./logs
```

`./logs`是特定于该程序的 Tensorboard 日志的存储目标,应在程序中定义如下:

```py
      self.writer = tf.summary.FileWriter("./logs", self.sess.graph)

```

2.  执行完步骤 1 中的命令后,导航到 TensorBoard 的`localhost:6006`站点:

W
wizardforcel 已提交
705
以下屏幕快照中展示了在项目中实现的 DiscoGAN 训练期间在 TensorBoard 中查看的一些生成器和判别器损失的痕迹:
W
wizardforcel 已提交
706 707 708

![](img/8c1362f9-bf37-4c56-ac05-30bb6d00a579.png)

W
wizardforcel 已提交
709
图 4.2:Tensorboard `Scalars`部分包含不同损失的迹线
W
wizardforcel 已提交
710

W
wizardforcel 已提交
711
以下屏幕截图显示了随着训练的进行,域`A`中判别器的损失成分:
W
wizardforcel 已提交
712 713 714

![](img/a02c2ba3-678a-4f01-8f16-0addf12d379e.png)

W
wizardforcel 已提交
715
图 4.3:域`A`中的判别器损失
W
wizardforcel 已提交
716

W
wizardforcel 已提交
717
从前面的屏幕截图中,我们可以看到不同批量中域`A`中判别器的损失。 `da_loss``da_loss_real``da_loss_fake`损失的总和。 `da_loss_real`稳步下降,这是因为判别器易于学会识别域`A`中的真实图像,而虚假图像的损失则稳定在 0.69 左右,这是二分类器输出时您可以期望的`logloss` 具有`1/2`概率的类。 发生这种情况是因为生成器也在同时学习以使伪图像看起来逼真,因此使鉴别人员很难轻松地将生成器图像分类为伪图像。 域`B`上的判别器的损失曲线看起来与域`A`的上一幅屏幕快照所示的相似。
W
wizardforcel 已提交
718

W
wizardforcel 已提交
719
现在让我们看一下生成器的损失曲线,如下所示:
W
wizardforcel 已提交
720 721 722

![](img/70ea99da-3268-4b40-a8df-225aed87f8b3.png)

W
wizardforcel 已提交
723
图 4.4:DiscoGAN 生成器的损失曲线
W
wizardforcel 已提交
724

W
wizardforcel 已提交
725
`g_loss_a2b` 是从域`A`到域`B`以及从域`B`反向重构图像的组合生成器损失,也是与使变换后的图像在`B`域中看起来逼真相关的二进制交叉熵损失。 `g_loss_b2a`是从域`B`到域`A`以及从域`A`重建图像的组合生成器损失,也是与使变换后的图像在`A`域中看起来逼真相关的二进制交叉熵损失。这两种损失曲线及其总和随着批量的进行不断减少,正如我们从上一个屏幕快照中的 TensorBoard 视觉图中看到的那样。
W
wizardforcel 已提交
726 727 728 729 730 731 732 733 734 735 736

由于训练生成对抗网络通常非常棘手,因此监视其损失概况的进度以了解训练是否按预期进行是有意义的。

# DiscoGAN 生成的样本图像

在本章结束时,让我们看一下 DiscoGAN 在两个域中生成的一些图像:

![](img/4feced13-0f3d-4f42-b954-41c050233276.png)

图 4.5:根据草图生成的手提包图像

W
wizardforcel 已提交
737
以下屏幕截图包含**手提包草图的生成图像(域`A`)**
W
wizardforcel 已提交
738 739 740 741 742 743 744

![](img/b4acc286-1586-405f-b2a9-52123d6b7c5a.png)

图 4.6:根据手提包图像生成的草图

我们可以看到 DiscoGAN 在将任一域中的图像转换为另一域中的高质量逼真的图像方面做得非常出色。

W
wizardforcel 已提交
745
# 总结
W
wizardforcel 已提交
746

W
wizardforcel 已提交
747
现在,我们到了本章的结尾。 您现在应该精通 DiscoGAN 的技术知识和实现复杂性。 我们在本章中探讨的概念可用于实现各种生成性对抗性网络,这些网络具有适合当前问题的细微变化。 DiscoGAN 网络的端到端实现位于 GitHub 存储库中,位于[这里](https://github.com/PacktPublishing/Intelligent-Projects-using-Python/tree/master/Chapter04)
W
wizardforcel 已提交
748

W
wizardforcel 已提交
749
在第 5 章,“视频字幕应用”中,我们将研究视频到文本翻译应用,它们属于人工智能领域的专家系统。