5.md 24.9 KB
Newer Older
W
wizardforcel 已提交
1 2 3 4
# VGG,启动模块,残差和 MobileNets

到目前为止,我们已经讨论了所有必要的构建块,以便能够实现常见问题(例如图像分类和检测)的解决方案。 在本章中,我们将讨论一些通用模型体系结构的实现,这些体系结构在许多常见任务中都表现出了很高的性能。 自从最初创建以来,这些体系结构一直很流行,并且在今天继续被广泛使用。

W
wizardforcel 已提交
5
在本章的最后,您将对现有的不同类型的 CNN 模型及其在各种不同的计算机视觉问题中的用例进行了解。 在实现这些模型时,您将学习如何设计这些模型以及它们各自的优点。 最后,我们将讨论如何修改这些架构,以使训练和性能/效率更好。
W
wizardforcel 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

总之,本章将涵盖以下主题:

*   如何提高参数效率
*   如何在 TensorFlow 中实现 VGG 网络
*   如何在 TensorFlow 中实现 Inception 网络
*   如何在 TensorFlow 中实施残留网络
*   如何实现对移动设备更友好的体系结构

# 替代大卷积

在开始之前,我们将首先学习可以减少模型使用的参数数量的技术。 首先,这很重要,因为它可以提高网络的泛化能力,因为使用该模型中存在的参数数量将需要较少的训练数据。 其次,较少的参数意味着更高的硬件效率,因为将需要更少的内存。

在这里,我们将从解释减少模型参数的重要技术入手,将几个小卷积级联在一起。 在下图中,我们有两个 3x3 卷积层。 如果回头看图右侧的第二层,可以看到第二层中的一个神经元具有 3x3 的接收场:

![](img/5eaf2681-cfa7-4907-b95f-c6204b5c4721.png)

当我们说“感受野”时,是指它可以从上一层看到的区域。 在此示例中,需要一个 3x3 区域来创建一个输出,因此需要一个 3x3 的接收场。

回溯到另一层,该 3x3 区域的每个元素在输入端也具有 3x3 接收场。 因此,如果我们将所有这 9 个元素的接受场组合在一起,那么我们可以看到在输入上创建的总接受场大小为 5x5。

因此,用简单的话来说,将较小的卷积级联在一起可以获得与使用较大卷积相同的接收场。 这意味着我们可以用级联的小卷积代替大卷积。

请注意,由于第一卷积层和输入文件深度之间的深度不匹配(输出的深度需要保持一致),因此无法在作用于输入图像的第一个卷积层上进行此替换:还应在图像上观察我们如何 计算每层参数的数量。

![](img/b41b8fa0-33ef-41f1-bf8e-7b9d3a4ede88.png)

在上图中,我们用三个 3x3 卷积替换了一个 7x7 卷积。 让我们自己计算一下,以减少使用的参数。

想象一下,在形状为 WxHxC 的输入体积上使用 C 滤波器进行 7x7 大小的卷积。 我们可以计算过滤器中的权数,如下所示:

![](img/65e0fde3-6ba1-4588-93b6-a945414cdfef.png)

现在,相反,如果我们层叠三个 3x3 卷积(代之以 7x7 卷积),我们可以如下计算其权重数:

![](img/6f819b52-e9db-4868-b320-cbbeb722b935.png)

在这里,我们可以看到参数比以前更少!

还要注意,在这三个卷积层的每一个之间,我们放置了 ReLu 激活。 与仅使用单个大卷积层相比,这样做会给模型带来更多的非线性。 增加的深度(和非线性)是一件好事,因为这意味着网络可以将更多的概念组合在一起,并提高其学习能力!

大多数新的成功模型的趋势是用许多级联在一起的较小卷积(通常为 3x3 大小)替换所有大型滤波器。 如前所述,这样做有两个巨大的好处。 它不仅减少了参数的数量,而且还增加了网络中非线性的深度和数量,这对于增加其学习能力是一件好事。

# 替代 3x3 卷积

也可以通过称为瓶颈的机制来简化 3x3 卷积。 与早期相似,这将具有正常 3x3 卷积的相同表示,但参数更少,非线性更多。

瓶颈通过使用以下 C 滤镜替换 3x3 卷积层而起作用:

*   具有 C / 2 滤波器的 1x1 卷积
*   具有 C / 2 滤波器的 3x3 卷积
*   带 C 滤镜的 1x1 卷积

这里给出一个实际的例子:

![](img/31cf352c-348d-47c5-8e03-5a7b48f04120.png)

从此示例中,我们将计算参数数量以显示该瓶颈的减少量。 我们得到以下内容:

![](img/d3fa1a7c-dbd1-47c7-bd95-9df99f61b68b.png)

这比仅使用 3x3 卷积层时得到的参数要少:

![](img/2b7ce629-e563-477b-9552-cf8a85339dfb.png)

一些网络体系结构,例如残差网络(我们将在后面看到),使用瓶颈技术再次减少了参数数量并增加了非线性。

# 虚拟网

W
wizardforcel 已提交
75
VGGNet 由牛津大学的**视觉几何组****VGG**)创建,是真正引入堆叠更多层的想法的首批架构之一。 虽然 AlexNet 最初以其七层出现时被认为很深,但与 VGG 和其他现代体系结构相比,这现在已经很小了。
W
wizardforcel 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

与只有 11x11 的 AlexNet 相比,VGGNet 仅使用空间大小为 3x3 的非常小的滤镜。 这些 3x3 卷积滤波器经常散布在 2x2 最大池化层中。

使用如此小的滤波器意味着可见像素的邻域也非常小。 最初,这可能给人的印象是,本地信息是模型所考虑的全部内容。 但是,有趣的是,通过依次堆叠小型滤波器,它提供了与单个大型滤波器相同的“接收场”。 例如,堆叠三批 3x3 滤镜将具有与一个 7x7 滤镜相同的接收场。

堆叠过滤器的这种洞察力带来了能够拥有更深的结构(我们通常会看到更好的结构)的优点,该结构保留了相同的接收场大小,同时还减少了参数数量。 本章后面将进一步探讨这个想法。

# 建筑

接下来,我们将看到 VGGNet 的体系结构,特别是包含 16 层的 VGG-16 风味。 所有卷积层都有空间大小为 3x3 的滤镜,并且随着我们深入网络,卷积层中滤镜的数量从 64 个增加到 512 个。

堆叠两个或三个卷积层然后合并的简单模块化设计使网络的大小易于增加或减小。 结果,VGG 成功创建并测试了具有 11、13 和 19 层的版本:

![](img/57103ce4-379c-47c9-914b-ab5c12c4b9f6.png)

# 参数和内存计算

W
wizardforcel 已提交
93
VGG 最酷的功能之一是,由于其在 conv 层中的内核较小,因此使用的参数数量很少。 如果我们从第 2 章,“深度学习和卷积神经网络”记住,卷积层中的参数数量(减去偏差)可以计算如下:
W
wizardforcel 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

![](img/ec08db79-cccc-4e58-88d5-167580308795.png)

因此,例如,第一层将具有以下参数:

![](img/5aa777f7-072b-48ea-a7cd-f3447c9f6347.png)

但是要注意,当涉及到模型末端的完全连接(密集)层时,这种数量很少的参数并不是这种情况,通常这是我们可以找到许多模型参数的地方。 如果像在 VGGNet 中一样,一个接一个地堆叠多个密集层,则尤其如此。

例如,第一个密集层将具有以下数量的参数:

![](img/33beb295-8ebe-4b4a-85c9-c104704b2ed2.png)

![](img/4ca076a4-7112-4d07-bdc1-044bf5fc8740.png)

到那时为止,这是所有参数的六倍以上!

W
wizardforcel 已提交
111
如前所述,您需要在训练数据集中使用大量样本来消耗模型参数,因此最好避免过度使用全连接层来避免参数爆炸。 幸运的是,人们发现,如果最后只有一层而不是三层,那么 VGGNet 的工作原理几乎相同。 因此,删除这些全连接层会从模型中删除大量参数,而不会大大降低性能。 因此,如果您决定实施 VGGNet,我们建议您也这样做。
W
wizardforcel 已提交
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 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

# 码

接下来,我们介绍负责在 Tensorflow 中构建 VGG-16 模型图的函数。 像本章中的所有模型一样,VGGNet 旨在对 Imagenet 挑战的 1,000 个类别进行分类,这就是为什么该模型输出大小为 1000 的向量的原因。 显然,可以为您自己的数据集轻松更改此设置,如下所示:

```py
   def build_graph(self): 

   self.__x_ = tf.placeholder("float", shape=[None, 224, 224, 3], name='X') 

   self.__y_ = tf.placeholder("float", shape=[None, 1000], name='Y') 

   with tf.name_scope("model") as scope: 

       conv1_1 = tf.layers.conv2d(inputs=self.__x_, filters=64, kernel_size=[3, 3], 

                                padding="same", activation=tf.nn.relu) 

       conv2_1 = tf.layers.conv2d(inputs=conv1_1, filters=64, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       pool1 = tf.layers.max_pooling2d(inputs=conv2_1, pool_size=[2, 2], strides=2) 

       conv2_1 = tf.layers.conv2d(inputs=pool1, filters=128, kernel_size=[3, 3], 

                                padding="same", activation=tf.nn.relu) 

       conv2_2 = tf.layers.conv2d(inputs=conv2_1, filters=128, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       pool2 = tf.layers.max_pooling2d(inputs=conv2_2, pool_size=[2, 2], strides=2) 

      conv3_1 = tf.layers.conv2d(inputs=pool2, filters=256, kernel_size=[3, 3], 

                                padding="same", activation=tf.nn.relu) 

       conv3_2 = tf.layers.conv2d(inputs=conv3_1, filters=256, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       conv3_3 = tf.layers.conv2d(inputs=conv3_2, filters=256, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       pool3 = tf.layers.max_pooling2d(inputs=conv3_3, pool_size=[2, 2], strides=2) 

       conv4_1 = tf.layers.conv2d(inputs=pool3, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       conv4_2 = tf.layers.conv2d(inputs=conv4_1, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       conv4_3 = tf.layers.conv2d(inputs=conv4_2, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       pool4 = tf.layers.max_pooling2d(inputs=conv4_3, pool_size=[2, 2], strides=2) 

       conv5_1 = tf.layers.conv2d(inputs=pool4, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       conv5_2 = tf.layers.conv2d(inputs=conv5_1, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       conv5_3 = tf.layers.conv2d(inputs=conv5_2, filters=512, kernel_size=[3, 3], 

                                  padding="same", activation=tf.nn.relu) 

       pool5 = tf.layers.max_pooling2d(inputs=conv5_3, pool_size=[2, 2], strides=2) 

       pool5_flat = tf.reshape(pool5, [-1, 7 * 7 * 512]) 

       # FC Layers (can be removed) 

       fc6 = tf.layers.dense(inputs=pool5_flat, units=4096, activation=tf.nn.relu) 

       fc7 = tf.layers.dense(inputs=fc6, units=4096, activation=tf.nn.relu) 

       # Imagenet has 1000 classes 

       fc8 = tf.layers.dense(inputs=fc7, units=1000) 

       self.predictions = tf.nn.softmax(self.fc8, name='predictions')
```

# 关于 VGG 的更多信息

W
wizardforcel 已提交
205
2014 年,VGG 在 Imagenet 分类挑战中获得第二名,在 Imagenet 本地化挑战中获得第一名。 正如我们所看到的,VGGNet 的设计选择是堆叠许多小的卷积层,从而可以实现更深的结构,同时具有更少的参数(如果我们删除了不必要的全连接层),则性能更好。 这种设计选择在创建强大而高效的网络方面非常有效,以至于几乎所有现代体系结构都复制了这种想法,并且很少(如果有的话)使用大型过滤器。
W
wizardforcel 已提交
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 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303

事实证明,VGG 模型可以在许多任务中很好地工作,并且由于其简单的体系结构,它是开始尝试或适应问题需求的理想模型。 但是,它确实有以下问题需要注意:

*   通过仅使用 3x3 层,尤其是在第一层,计算量不适用于移动解决方案
*   如前几章所述,由于逐渐消失的梯度问题,甚至更深的 VGG 结构也无法正常工作
*   原始设计中大量的 FC 层在参数方面是过大的,这不仅减慢了模型的速度,而且更容易出现过拟合的问题
*   使用许多池化层,目前认为这不是好的设计

# GoogLeNet

虽然 VGGNet 在 2014 年 Imagenet 分类挑战赛中排名第二,但我们将要讨论的下一个模型 GoogLeNet 在那一年是赢家。 它是由 Google 创建的,它引入了一种重要的方法来使网络更深,同时减少参数数量。 他们称他们提出了`Inception` 模块。 该模块填充了大部分 GoogLeNet 模型。

GoogLeNet 具有 22 层,参数几乎比 AlexNet 少 12 倍。 因此,除了更加精确之外,它还比 AlexNet 快得多。 创建`Inception`模块的动机是制作更深的 CNN,以便获得高度准确的结果,并使该模型可在智能手机中使用。 为此,在预测阶段,计算预算大约需要增加 15 亿次:

![](img/a792bf21-afb7-4617-90ee-9eaf988cddd9.png)

# 起始模块

起始模块(或层的块)旨在覆盖较大的区域,但也保持较高的分辨率,以便也可以在图像中查看重要的本地信息。 除了创建更深的网络外,起始块还引入了并行卷积的思想。 我们的意思是在前一层的输出上执行不同大小的并行卷积。

初始层的幼稚视图可以在这里看到:

![](img/5ea6732d-fe36-4d0d-8272-8e15beb3ac2c.png)

基本上,初始块的想法是使用所有可用的内核大小和操作来覆盖尽可能多的信息,并让反向传播根据您的数据决定使用什么。 在上图中看到的唯一问题是计算成本,因此该图在实践中会有所不同。

考虑我们之前看到的 5x5 分支,让我们检查一下它的计算成本:

![](img/b9e05229-a800-45dc-b675-4fad355b3c54.png)

![](img/564ae665-af37-4088-8ab8-2975d8c16ec8.png)

现在,考虑以下更改; 我们添加一个 1x1 CONV 来将 5x5 CONV 输入深度从 192 转换为 16:

![](img/8cc4d625-1193-4cd4-a91f-55adc41c642a.png)

![](img/a0d5631a-32bc-4136-ac2a-a12452ce341c.png)

如果您观察到,现在计算效率提高了 10 倍。 1x1 层会挤压大量深度(瓶颈),然后发送到 5x5 CONV 层。

考虑到这一瓶颈变化,真正的初始层要复杂一些:

![](img/ca3da890-3a5d-4c96-8320-af7cbc197cfd.png)

此外,在某些实现中,您可能会注意到有人试图在初始代码块中使用 Batchnorm 或 Dropout。

Googlenet 将只是许多级联的启动块。 在这段代码中,我们展示了如何创建一个起始块:

```py
# Reference: https://github.com/khanrc/mnist/blob/master/inception.py 

import tensorflow as tf 

def inception_block_a(x, name='inception_a'): 

   # num of channels: 384 = 96*4 

   with tf.variable_scope(name): 

       # Pooling part 

       b1 = tf.layers.average_pooling2d(x, [3,3], 1, padding='SAME') 

       b1 = tf.layers.conv2d(inputs=b1, filters=96, kernel_size=[1, 1], padding="same", activation=tf.nn.relu) 

       # 1x1 part 

       b2 = tf.layers.conv2d(inputs=x, filters=96, kernel_size=[1, 1], padding="same", activation=tf.nn.relu) 

       # 3x3 part 

       b3 = tf.layers.conv2d(inputs=x, filters=64, kernel_size=[1, 1], padding="same", activation=tf.nn.relu) 

       b3 = tf.layers.conv2d(inputs=b3, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu) 

       # 5x5 part 

       b4 = tf.layers.conv2d(inputs=x, filters=64, kernel_size=[1, 1], padding="same", activation=tf.nn.relu) 

       # 2 3x3 in cascade with same depth is the same as 5x5 but with less parameters 

       # b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[5, 5], padding="same", activation=tf.nn.relu) 

       b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu) 

       b4 = tf.layers.conv2d(inputs=b4, filters=96, kernel_size=[3, 3], padding="same", activation=tf.nn.relu) 

       concat = tf.concat([b1, b2, b3, b4], axis=-1) 

       return concat 
```

# 有关 GoogLeNet 的更多信息

GoogLeNet 的主要优点是,它比 VGG 更为准确,同时使用的参数更少,计算能力也更低。 主要的缺点仍然是,如果我们开始堆叠很多初始层,梯度将消失,而且整个网络具有多个分支和多个损耗的设计相当复杂。

# 残留网络

W
wizardforcel 已提交
304
在前面的部分中,已经证明了网络的深度是有助于提高准确性的关键因素(请参见 VGG)。 TensorFlow 中的第 3 章和*图像分类中也显示,可以通过正确的权重初始化和批处理归一化来缓解深度网络中梯度消失或爆炸的问题。 但是,这是否意味着我们添加的层越多,我们得到的系统就越准确? 亚洲研究机构 Microsoft 的*用于图像识别的深度残差学习*的作者发现,只要网络深度达到 30 层,准确性就会达到饱和。 为了解决此问题,他们引入了一个称为残差块的新层块,该块将上一层的输出添加到下一层的输出中(请参见下图)。 Residual Net 或 ResNet 在非常深的网络(甚至超过 100 层!)中都显示了出色的结果,例如 152 层的 ResNet 赢得了 2015 LRVC 图像识别挑战,其前 5 个测试错误为 3.57。 事实证明,诸如 ResNets 之类的更深层网络要比包括 Inception 模块(例如 GoogLeNet)在内的更广泛的网络更好地工作。*
W
wizardforcel 已提交
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 359 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

![](img/d8ad63df-15dd-4d20-b918-a3f18767a9c8.png)

让我们更详细地了解残差块的外观以及其功能背后的直觉。 如果我们有一个输入![](img/54cb6769-dfa9-4ed2-8a98-04904ad5ed2e.png)和一个输出![](img/0df834df-790e-4fd2-b5fd-f6d8afbd04fe.png),则存在一个将![](img/ac3e4947-4de7-4f5d-9b05-9a892b4aa2b8.png)映射到![](img/429cb4f4-0cb2-4408-80d7-8185e46ae29c.png)的非线性函数![](img/2bc69477-f997-406b-9f65-4cd1d59170df.png)。 假设函数![](img/2bc69477-f997-406b-9f65-4cd1d59170df.png)可以由两个堆叠的非线性卷积层近似。 然后,残差函数![](img/04c577da-ec4e-42e8-8a2e-659b7a2570bd.png)也可以近似。 我们可以等效地写为![](img/6ff3c055-c8f0-41e4-adbb-655f94f1f14f.png),其中![](img/076d8c2c-5fd2-4cd0-90ee-002c1b80fd2b.png)表示两个堆叠的非线性层,![](img/8b38461a-1ec6-4f8f-a0d2-0977201c727c.png)标识函数(输入=输出)。

更正式地说,对于通过网络的前向传递,如果![](img/48890ce8-4d40-4ced-9c20-0f0fe7258f49.png)是来自![](img/3cbd4bac-33c6-44e9-855c-4559561e7400.png)层的张量,并且![](img/cf39e814-f34f-4ee6-8669-04d386f69236.png)和![](img/290b2b0d-9cc0-433f-9797-31ecf9cf0db5.png)是当前层和先前层的权重矩阵,则输入![](img/bca942f2-dddf-4e8d-aaf4-48722f648d63.png)到 下一层![](img/32d5f07f-1c7c-40ab-9cbf-695177d3cfd2.png)

![](img/2a75b154-8239-4fce-968d-441d25d69a27.png)

其中![](img/9848c713-134c-4eb9-9f35-3d3076854a6d.png)是非线性激活函数,例如 ReLu 和![](img/75a30ef9-ee69-49eb-92e5-738eeef18b10.png),即两层堆叠卷积。 ReLu 函数可以在添加 x 之前或之后添加。 剩余的块应由 2 层或更多层组成,因为一层的块没有明显的好处。

为了理解该概念背后的直觉,我们假设我们有一个经过训练的浅层 CNN,其更深的对应层具有与浅层 CNN 相同的层,并且在它们之间随机插入了一些层。 为了拥有一个与浅层模型至少具有相似性能的深层模型,附加层必须近似标识函数。 但是,要学习具有 CONV 层堆栈的标识函数比将残差函数推为零要困难得多。 换句话说,如果单位函数是最优解,则很容易实现![](img/54b2d5db-424c-4399-b2d3-081897501f57.png),因此很容易实现![](img/ffc658b8-7e88-4b94-8f69-53d3904a01fc.png)

另一种思考的方式是,在训练期间,特定的层不仅会从上一层学习一个概念,还会从它之前的其他层学习一个概念。 这比只从上一层学习概念要好。

在实现方面,我们应注意确保![](img/269bb145-9df7-475f-b3cb-5bdd3993537b.png)和![](img/47cbd75c-bae1-4ed3-86b7-ad75747293b1.png)的大小相同。

查看残差块的重要性的另一种方法是,我们将为梯度创建一个“高速公路”(加法块),以避免随着梯度的增加而消失的梯度问题!

以下代码将向您展示如何创建残差块,它是残差网络的主要构建块:

```py
# Reference
# https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/learn/resnet.py
import tensorflow as tf
from collections import namedtuple

# Configurations for each bottleneck group.
BottleneckGroup = namedtuple('BottleneckGroup',
                            ['num_blocks', 'num_filters', 'bottleneck_size'])
groups = [
   BottleneckGroup(3, 128, 32), BottleneckGroup(3, 256, 64),
   BottleneckGroup(3, 512, 128), BottleneckGroup(3, 1024, 256)
]

# Create the bottleneck groups, each of which contains `num_blocks`
# bottleneck groups.
for group_i, group in enumerate(groups):
   for block_i in range(group.num_blocks):
       name = 'group_%d/block_%d' % (group_i, block_i)

       # 1x1 convolution responsible for reducing dimension
       with tf.variable_scope(name + '/conv_in'):
           conv = tf.layers.conv2d(
               net,
               filters=group.num_filters,
               kernel_size=1,
               padding='valid',
               activation=tf.nn.relu)
           conv = tf.layers.batch_normalization(conv, training=training)

       with tf.variable_scope(name + '/conv_bottleneck'):
           conv = tf.layers.conv2d(
               conv,
               filters=group.bottleneck_size,
               kernel_size=3,
               padding='same',
               activation=tf.nn.relu)
           conv = tf.layers.batch_normalization(conv, training=training)

       # 1x1 convolution responsible for restoring dimension
       with tf.variable_scope(name + '/conv_out'):
           input_dim = net.get_shape()[-1].value
           conv = tf.layers.conv2d(
               conv,
               filters=input_dim,
               kernel_size=1,
               padding='valid',
               activation=tf.nn.relu)
           conv = tf.layers.batch_normalization(conv, training=training)

       # shortcut connections that turn the network into its counterpart
       # residual function (identity shortcut)
       net = conv + net
```

# 移动网

我们将以一个新的 CNN 系列结束本章,该系列不仅具有较高的准确性,而且更轻巧,并且在移动设备上的运行速度更快。

由 Google 创建的 MobileNet 的关键功能是它使用了不同的“三明治”形式的卷积块。 它不是通常的(`CONV``BATCH_NORM,RELU`),而是将 3x3 卷积拆分为 3x3 深度卷积,然后是 1x1 点向卷积。他们称此块为深度可分离卷积。

这种分解可以减少计算量和模型大小:

![](img/2e2654a7-06d4-4019-8fbc-91babc5c5ce4.png)

# 深度可分离卷积

这个新的卷积块(`tf.layers.separable_conv2d`)由两个主要部分组成:深度卷积层,然后是 1x1 点式卷积层。 该块与普通卷积有以下几种不同:

*   在正常卷积层中,每个滤波器 F 将同时应用于输入通道上的所有通道(F 应用于每个通道然后求和)
*   这个新的卷积 F 分别应用于每个通道,并且结果被级联到某个中间张量(多少由深度倍数 DM 参数控制)

相对于标准卷积,深度卷积非常有效。 但是,它仅过滤输入通道,并且不将它们组合以创建新功能。

现在,将使用 1x1 转换层将深度输出张量映射到某些所需的输出通道深度,该转换层将在通常在标准卷积层中发生的通道之间进行混合。 区别在于 DM 参数可用于丢弃一些信息。 同样,1x1 转换仅用于调整音量大小。

# 控制参数

MobileNets 使用两个超参数来帮助控制精度和速度之间的折衷,从而使网络适合您要定位的任何设备。 这两个超参数如下:

*   **宽度倍增器**:通过统一减少整个网络中使用的滤波器数量,控制深度 CONV 精度
*   **分辨率倍增器**:只需将输入图像缩小到不同大小

# 有关 MobileNets 的更多信息

对于任何神经网络设计,MobileNets 都具有一些最佳的精度,速度和参数比率。

但是,目前尚无良好(快速)的深度卷积实现可在 GPU 上运行。 结果,训练可能会比使用正常的卷积运算慢。 但是,此网络目前真正发挥作用的地方是小型 CPU 设计,提高的效率更加明显。

# 摘要

在本章中,我们向您介绍了各种卷积神经网络设计,这些设计已经证明了它们的有效性,因此被广泛使用。 我们首先介绍牛津大学 VGG 的 VGGNet 模型。 接下来,在最终讨论微软的 Residual Net 之前,我们先使用 Google 的 GoogLeNet。 此外,我们还向您展示了一种更高级的新型卷积,该模型在名为 MobileNet 的模型设计中具有特色。 在整个过程中,我们讨论了使每个网络如此出色的不同属性和设计选择,例如跳过连接,堆叠小型过滤器或启动模块。 最后,给出了代码,向您展示了如何在 TensorFlow 中写出这些网络。

在下一章中,我们将讨论一种称为生成模型的新型模型,该模型将使我们能够生成数据。