1. 概览
在本实验中,您将了解现代卷积架构,并利用所学知识实现一个简单但有效的卷积网络(称为“squeezenet”)。
本实验包含关于卷积神经网络的必要理论解释,非常适合开发者学习深度学习。
本实验是“TPU 上的 Keras”系列教程的第 4 部分。您可以按以下顺序执行这些操作,也可以单独执行。
- TPU 速度数据流水线:tf.data.Dataset 和 TFRecords
- 您的第一个 Keras 模型(使用迁移学习)
- 包含 Keras 和 TPU 的卷积神经网络
- [本实验] 现代卷积神经网络、squeezenet、Xception 以及 Keras 和 TPU
学习内容
- 掌握 Keras 函数式风格
- 使用 SqueezeNet 架构构建模型
- 使用 TPU 对您的架构进行快速训练和迭代
- 使用 tf.data.dataset 实现数据增强
- 如需在 TPU 上微调预训练的大型模型 (Xception)
反馈
如果您在此 Codelab 中发现任何问题,请告诉我们。您可以通过 GitHub 问题 [反馈链接] 提供反馈。
2. Google Colaboratory 快速入门
本实验使用 Google 协作工具,您无需进行任何设置。Colaboratory 是一个用于教育目的的在线笔记本平台。它提供免费的 CPU、GPU 和 TPU 训练。
您可以打开此示例笔记本,运行其中的几个单元格,以熟悉 Colaboratory。
选择 TPU 后端
在 Colab 菜单中,依次选择运行时 > 更改运行时类型,然后选择 TPU。在此 Codelab 中,您将使用一个支持硬件加速训练的强大 TPU(张量处理单元)。系统会在首次执行时自动连接到运行时,您也可以使用右上角的“连接”按钮。
笔记本执行
点击单元格并按 Shift-Enter 可逐个执行单元格。您还可以依次选择 Runtime > Run all 来运行整个笔记本
目录
所有笔记本都有目录。您可以使用左侧的黑色箭头将其打开。
隐藏单元格
部分单元格将仅显示标题。这是 Colab 特有的笔记本功能。您可以双击它们查看内部代码,但通常这些代码并不太有趣。通常是支持函数或可视化函数。您仍然需要运行这些单元才能定义其中的函数。
Authentication
如果您使用已获授权的账号进行身份验证,则 Colab 可以访问您的私有 Google Cloud Storage 存储分区。上面的代码段将触发身份验证过程。
3. [INFO] 什么是张量处理单元 (TPU)?
简要说明
在 Keras 中在 TPU 上训练模型的代码(如果 TPU 不可用,则回退到 GPU 或 CPU):
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
我们今天将使用 TPU 以交互速度(每次训练运行的分钟数)构建和优化花卉分类器。
为何选择 TPU?
现代 GPU 以可编程的“核心”为组织结构,这种架构非常灵活,可处理 3D 渲染、深度学习、物理模拟等各种任务。另一方面,TPU 将传统矢量处理器与专用矩阵乘法单元搭配使用,非常适合执行大量矩阵乘法操作的任务,例如神经网络。
插图:将密集神经网络层用作矩阵乘法,一次通过神经网络处理一批八张图片。请执行一行 x 列乘法运算,以验证这是否确实对图片的所有像素值进行加权和。卷积层也可以表示为矩阵乘法,尽管它有点复杂( 此处的说明,第 1 部分)。
硬件
MXU 和 VPU
TPU v2 核心由一个矩阵乘法单元 (MXU) 组成,该单元针对所有其他任务(例如激活、softmax 等)运行矩阵乘法和矢量处理单元 (VPU)。VPU 负责处理 float32 和 int32 计算。另一方面,MXU 以混合精度 16-32 位浮点格式运行。
混合精度浮点和 bfloat16
MXU 使用 bfloat16 输入和 float32 输出计算矩阵乘法。中间累积以 float32 精度执行。
神经网络训练通常能抵抗因浮点精度降低而引入的噪声。在某些情况下,噪声甚至有助于优化器收敛。16 位浮点精度一直以来都用于加快计算速度,但 float16 和 float32 格式却截然不同。将精度从 float32 降低至 float16,通常会导致上溢和下溢。虽然存在解决方案,但通常需要执行额外的操作才能使 float16 正常运行。
因此,Google 在 TPU 中引入了 bfloat16 格式。bfloat16 是经过截断的 float32,其指数位和范围与 float32 完全相同。再加上 TPU 使用 bfloat16 输入但 float32 输出以混合精度计算矩阵乘法这一事实,这意味着通常无需进行任何代码更改,即可受益于降低精度带来的性能提升。
脉冲阵列
MXU 使用所谓的“脉动阵列”架构在硬件中实现矩阵乘法,其中数据元素流经一组硬件计算单元。(在医学中,“收缩性”是指心脏收缩和血流,这里指的是数据的流动。)
矩阵乘法的基本元素是两个矩阵中的一条直线与另一个矩阵的列之间的点积(请参阅本节顶部的图示)。对于矩阵乘法 Y=X*W,结果的一个元素将为:
Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]
在 GPU 上,可以将此点积编程到 GPU“核心”,然后在可用的尽可能多的“核心”上并行执行该点积,以尝试一次性计算生成矩阵的每个值。如果生成的矩阵大小为 128x128,则需要 128x128=16K 个“核心”,这通常是不可能的。最大的 GPU 大约有 4000 个核心。另一方面,TPU 在 MXU 中的计算单元中使用了极少的硬件:仅使用 bfloat16 x bfloat16 => float32
乘法累加器,没有其他任何硬件。这些矩阵非常小,因此 TPU 可以在 128x128 MXU 中实现 16K 并一次性处理此矩阵乘法。
插图:MXU 脉动阵列。计算元素是乘法累加器。一个矩阵的值会加载到数组(红点)中。另一个矩阵的值会流经数组(灰点)。垂直线会向上传播值。水平线会传播部分和。用户可以自行验证,当数据流经数组时,您是否会从右侧获得矩阵乘法结果。
除此之外,虽然点积是根据 MXU 计算的,但中间求和只是在相邻的计算单元之间流动。它们无需存储到/从内存或寄存器文件中检索。最终结果是,在计算矩阵乘法时,TPU 脉动阵列架构具有明显的密度和功率优势,并且在计算矩阵乘法时,也比 GPU 具有明显的速度优势。
Cloud TPU
在 Google Cloud Platform 上申请一个“Cloud TPU v2”时,您会得到一个配有 PCI 连接 TPU 板的虚拟机 (VM)。TPU 板有四个双核 TPU 芯片。每个 TPU 核心都有一个 VPU(矢量处理单元)和 128x128 MXU(矩阵乘法单元)。然后,此“Cloud TPU”通常会通过网络连接到请求它的虚拟机。因此,完整的画面如下所示:
插图:您的虚拟机连接到网络附加的“Cloud TPU”加速器。“Cloud TPU”本身是由一个虚拟机制成,该虚拟机配有一个连接了 PCI 的 TPU 板以及四个双核 TPU 芯片。
TPU Pod
在 Google 的数据中心中,TPU 连接到高性能计算 (HPC) 互连,这使得它们看起来就像一个超大加速器。Google 将其称为 Pod,它们最多可包含 512 个 TPU v2 核心或 2048 个 TPU v3 核心。
插图:TPU v3 Pod。通过 HPC 互连连接的 TPU 板和机架。
在训练期间,梯度会使用全局求和算法在 TPU 核心之间交换(点击此处可查看对全局求和的详细说明)。正在训练的模型可以通过针对大批量大小进行训练来充分利用硬件。
插图:在 Google TPU 的 2D 环形网格 HPC 网络上使用全局求和算法在训练期间同步梯度。
软件
大批量训练
理想的 TPU 批次大小是每个 TPU 核心 128 个数据项,但硬件已经可以显示出每个 TPU 核心 8 个数据项的良好利用率。请注意,一个 Cloud TPU 有 8 个核心。
在此 Codelab 中,我们将使用 Keras API。在 Keras 中,您指定的批次是整个 TPU 的全局批次大小。您的批次将自动拆分为 8 个,并在 TPU 的 8 个核心上运行。
如需了解其他性能提示,请参阅 TPU 性能指南。对于非常大的批处理大小,某些模型可能需要特别注意,如需了解详情,请参阅 LARSOptimizer。
幕后探秘:XLA
TensorFlow 程序定义计算图。TPU 不会直接运行 Python 代码,而是运行由 TensorFlow 程序定义的计算图。在底层,一个名为 XLA(加速线性代数编译器)的编译器会将计算节点的 TensorFlow 图转换为 TPU 机器码。此编译器还会对您的代码和内存布局执行许多高级优化。在将工作发送到 TPU 时,系统会自动进行编译。您无需在构建链中明确添加 XLA。
示例:为了在 TPU 上运行,TensorFlow 程序定义的计算图会先转换为 XLA(加速线性代数编译器)表示法,然后由 XLA 编译为 TPU 机器码。
在 Keras 中使用 TPU
从 Tensorflow 2.1 开始,可通过 Keras API 支持 TPU。Keras 支持适用于 TPU 和 TPU Pod。下面的示例适用于 TPU、GPU 和 CPU:
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
在以下代码段中:
TPUClusterResolver().connect()
会在网络上查找 TPU。它无需参数即可在大多数 Google Cloud 系统(AI Platform 作业、Colaboratory、Kubeflow、通过“ctpu up”实用程序创建的深度学习虚拟机)上运行。由于 TPU_NAME 环境变量,这些系统知道其 TPU 的位置。如果您手动创建 TPU,请在使用它的虚拟机上设置 TPU_NAME 环境变量,或使用显式参数调用TPUClusterResolver
:TPUClusterResolver(tp_uname, zone, project)
TPUStrategy
是实现分布和“全局求和”梯度同步算法的部分。- 该策略通过镜重应用。必须在策略 scope() 中定义该模型。
tpu_model.fit
函数需要 tf.data.Dataset 对象作为 TPU 训练的输入。
常见的 TPU 移植任务
- 虽然有许多方法可以在 TensorFlow 模型中加载数据,但对于 TPU,必须使用
tf.data.Dataset
API。 - TPU 的运行速度非常快,在 TPU 上运行时,提取数据通常会成为瓶颈。TPU 性能指南中提供了可用于检测数据瓶颈的工具和其他性能提示。
- int8 或 int16 数字被视为 int32。TPU 没有在少于 32 位上运行的整数硬件。
- 不支持某些 TensorFlow 操作。点击此处查看相关列表。好消息是,此限制仅适用于训练代码,即模型的正向和反向传递。您仍然可以在数据输入流水线中使用所有 Tensorflow 操作,因为这些操作将在 CPU 上执行。
- TPU 不支持
tf.py_func
。
4. [INFO] 神经网络分类器 101
简要说明
如果您已经了解下一段中所有粗体字体的术语,可以直接进入下一个练习。如果您刚开始接触深度学习,欢迎继续阅读。
对于构建为一系列层的模型,Keras 可提供 Sequential API。例如,使用 3 个密集层的图片分类器可以在 Keras 中编写为:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
tf.keras.layers.Dense(500, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])
# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy']) # % of correct answers
# train the model
model.fit(dataset, ... )
密集神经网络
这是最简单的图像分类神经网络。它由分层排列的“神经元”组成。第一层处理输入数据,并将其输出馈送到其他层。之所以称为“密集”,是因为每个神经元都与前一层中的所有神经元相连。
您可以将图片的所有像素的 RGB 值展平为一个长向量,并将其用作输入,以便将图片馈送到此类网络。这不是图片识别的最佳技术,但我们稍后会对其进行改进。
神经元、激活函数、RELU
“神经元”会计算其所有输入的加权和,添加一个名为“偏差”的值,并通过所谓的“激活函数”馈送结果。权重和偏差一开始是未知的。它们会随机初始化,并通过大量已知数据训练神经网络“学习”。
最常用的激活函数称为修正线性单元的 RELU。如上图所示,这是一个非常简单的函数。
Softmax 激活
上述网络以 5 神经元层结尾,因为我们将花卉分为 5 类(玫瑰、郁金香、蒲公英、雏菊、向日葵)。中间层中的神经元使用传统的 RELU 激活函数进行激活。不过,在最后一层中,我们需要计算 0 到 1 之间的数字,表示这朵花是玫瑰、郁金香等的概率。为此,我们将使用一个名为“softmax”的激活函数。
对向量应用 softmax 的方法是,对每个元素求指数,然后对向量进行归一化(通常使用 L1 范数 [绝对值的总和]),以便值的总和为 1,并且可以被解释为概率。
交叉熵损失
现在,我们的神经网络可以根据输入图片生成预测,接下来我们需要衡量预测的好坏,即网络告诉我们的信息与正确答案之间的距离,通常称为“标签”。请记住,数据集中的所有图片都有正确的标签。
任何距离都可以,但对于分类问题,所谓的“交叉熵距离”是最有效的。我们将其称为错误或“损失”函数:
梯度下降法
对神经网络进行“训练”实际上是指使用训练图片和标签来调整权重和偏差,以最大限度地减少交叉熵损失函数。具体流程如下。
交叉熵是训练图片的权重、偏差、像素及其已知类别的函数。
如果我们计算交叉熵相对于所有权重和所有偏置的偏导数,就会得到一个“梯度”,该梯度针对给定图像、标签以及权重和偏差的现值进行了计算。请记住,我们可以有数百万个权重和偏差,因此计算梯度声就像是大量的工作。幸运的是,Tensorflow 可以为我们做到这一点。梯度的数学性质是,它指向“上方”。由于我们想去交叉熵较低的地方,因此会前往相反的方向。我们会按梯度分数更新权重和偏差。然后,我们在训练循环中使用下一批训练图片和标签重复执行相同的操作。希望它会收敛到交叉熵最小的位置,但没有任何保证此最小值是唯一的。
小批量和动量
您可以仅针对一个示例图片计算梯度,并立即更新权重和偏差,但如果针对一批(例如 128 张)图片执行此操作,则得到的梯度会更好地代表不同示例图片施加的约束条件,因此更有可能更快地收敛到解决方案。小批次的大小是一个可调节的参数。
这种技术有时称为“随机梯度下降”,还有另一个更实用的优势:使用批量还意味着处理更大的矩阵,这些矩阵通常更易于在 GPU 和 TPU 上进行优化。
不过,收敛过程可能仍然有些混乱,如果梯度矢量全为零,甚至可能会停止。这是否意味着我们找到了一个最低值?不一定。在最小值或最大值处,梯度分量可以为零。对于包含数百万个元素的梯度矢量,如果这些元素全部为零,则每个零对应于一个最小值而没有一个对应于最大点的概率非常小。在多维空间中,鞍点非常常见,我们不希望在鞍点处停留。
插图:鞍点。梯度为 0,但并非在所有方向上都是最小值。(图片出处 维基媒体:Nicoguaro - 自创内容,CC BY 3.0)
解决方法是向优化算法添加一些动量,以便它能够不停顿地越过鞍点。
术语库
batch 或 mini-batch:始终对批量训练数据和标签执行训练。这样做有助于算法收敛。“批次”维度通常是数据张量的第一个维度。例如,形状为 [100, 192, 192, 3] 的张量包含 100 张 192x192 像素的图片,每个像素有三个值 (RGB)。
交叉熵损失函数:分类器中常用的特殊损失函数。
密集层:一个神经元层,其中每个神经元都与前一层中的所有神经元连接。
特征:神经网络的输入有时称为“特征”。弄清楚将数据集的哪些部分(或部分组合)馈送到神经网络以获得良好预测这一艺术称为“特征工程”。
标签:监督式分类问题中“类别”或正确答案的别名
学习速率:在训练循环的每次迭代中用于更新权重和偏差的梯度的一部分。
logits:在应用激活函数之前,一层神经元的输出称为“logits”。该术语源自“逻辑函数”(也称为“sigmoid 函数”),该函数曾经是最流行的激活函数。“逻辑函数之前的神经元输出”已缩写为“logits”。
loss:将神经网络输出与正确答案进行比较的误差函数
神经元:计算其输入的加权和,添加偏差,并通过激活函数馈送结果。
one-hot 编码:第 3 个类(占 5 个类中的 3 个)编码为 5 个元素的矢量,其中除了第 3 个元素为 1 之外,所有元素均为 0。
relu:修正的线性单元。一种常用的神经元激活函数。
S 型函数:另一种过去很流行的激活函数,在特殊情况下仍然有用。
softmax:一个作用于向量的特殊激活函数,用于增大最大分量与所有其他分量之间的差值,并将此向量归一化,使其总和为 1,以便将其解释为概率向量。用作分类器中的最后一步。
tensor:“张量”类似于矩阵,但具有任意数量的维度。一维张量是一个向量。二维张量是矩阵。然后,您可以得到具有 3 个、4 个、5 个或更多维度的张量。
5. [INFO] 卷积神经网络
简要说明
如果您已经了解下一段中所有粗体字体的术语,可以直接进入下一个练习。如果您刚刚开始接触卷积神经网络,请继续阅读。
插图:使用两个由 4x4x3=48 个可学习权重组成的连续过滤器来过滤图片。
下面是简单的卷积神经网络在 Keras 中的样子:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
卷积神经网络基础知识
在卷积网络的某一层中,一个“神经元”只会对图像中一小区域内紧挨其上的像素进行加权求和。它会添加偏差并通过激活函数向和馈送总和,就像常规密集层中的神经元一样。然后,使用相同的权重在整个图片中重复此操作。请记住,在密集层中,每个神经元都有自己的权重。在这里,单个权重“补丁”会在两个方向上滑动(“卷积”)。输出的值数量与图片中的像素数量相同(不过,边缘需要一些内边距)。这是一个使用 4x4x3=48 权重的过滤器的过滤操作。
但是,48 个权重远远不够。为了增加自由度,我们使用一组新的权重重复相同的操作。这样会生成一组新的过滤器输出。通过与输入图片中的 R、G、B 通道进行类比,将其称为输出“通道”。
通过添加新维度,将两组(或更多)权重求和为一个张量。这为我们提供了卷积层权重张量的通用形状。由于输入和输出通道的数量是参数,因此我们可以开始堆叠和串联卷积层。
插图:卷积神经网络将数据“立方体”转换为其他数据“立方体”。
步幅卷积、最大池化
通过以 2 或 3 的步长执行卷积,我们还可以缩减生成的数据立方体的横向维度。有两种常用方法可以实现此目的:
- 步长卷积:与上文中的滑动过滤器相同,但步长大于 1
- 最大池化:应用 MAX 运算的滑动窗口(通常在 2x2 补丁上,每 2 个像素重复一次)
示例:将计算窗口向右滑动 3 个像素会导致输出值减少。步长卷积或最大池化(在步长为 2 的 2x2 滑动窗口上执行最大值运算)是一种在水平维度上缩减数据立方体的做法。
卷积分类器
最后,我们通过展平最后一个数据立方体并将其馈送到具有 softmax 激活函数的密集层来附加分类头。典型的卷积分类器可能如下所示:
插图:使用卷积层和 softmax 层的图像分类器。它使用 3x3 和 1x1 过滤条件。maxpool 层会取 2x2 个数据点组中的最大值。分类头是使用具有 softmax 激活的密集层实现的。
在 Keras 中
上述卷积堆栈可在 Keras 中编写为:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
6. [NEW INFO] 现代卷积架构
简要说明
插图:卷积“模块”。这时什么方法最好?最大池化层后跟 1x1 卷积层,还是其他层的组合?尝试所有这些方法,将结果串联起来,然后让网络做出决定。右图:使用此类模块的“ inception”卷积架构。
在 Keras 中,要创建数据流可以传入和传出的模型,您必须使用“函数式”模型样式。示例如下:
l = tf.keras.layers # syntax shortcut
y = l.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', input_shape=[192, 192, 3])(x) # x=input image
# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation
# many more layers ...
# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)
其他低成本技巧
小型 3x3 过滤器
在下图中,您可以看到连续应用两个 3x3 过滤器的结果。尝试追溯是哪些数据点导致了结果:这两个连续的 3x3 过滤器计算 5x5 区域的某种组合。这与 5x5 过滤器计算的组合并不完全相同,但值得尝试,因为两个连续的 3x3 过滤器的计算开销比单个 5x5 过滤器要低。
1x1 卷积?
从数学角度来看,“1x1”卷积是与常数相乘,这不是一个非常实用的概念。不过,请注意,在卷积神经网络中,滤镜会应用于数据立方体,而不仅仅是 2D 图片。因此,“1x1”过滤器会计算 1x1 列数据(见图)的加权和,当您在数据中滑动它时,您将获得输入渠道的线性组合。这实际上很有用。如果将通道视为各个过滤操作的结果,例如针对“尖耳朵”的过滤器、针对“胡须”的过滤器,以及针对“狭窄的眼睛”的第三个过滤器,则“1x1”卷积层将计算这些特征的多个可能的线性组合,这在查找“猫”时可能很有用。除此之外,1x1 层使用的权重也更少。
7. Squeezenet
《Squeezenet》论文展示了一种整合上述理念的简单方法。作者建议使用非常简单的卷积模块设计,仅使用 1x1 和 3x3 卷积层。
插图:基于“火焰模块”的 SqueezeNet 架构。
动手实践
继续使用上一个笔记本,构建一个受 SqueezeNet 启发的卷积神经网络。您必须将模型代码更改为 Keras“函数式风格”。
Keras_Flowers_TPU (playground).ipynb
其他信息
在本练习中为 squeezenet 模块定义一个辅助函数会很有用:
def fire(x, squeeze, expand):
y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
return tf.keras.layers.concatenate([y1, y3])
# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
return lambda x: fire(x, squeeze, expand)
# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)
这次的目标是达到 80% 的准确率。
可尝试的操作
先从单个卷积层开始,然后是“fire_modules
”,交替使用 MaxPooling2D(pool_size=2)
层。您可以在网络中尝试最多 2 到 4 个池化层,也可以在最大池化层之间使用 1、2 或 3 个连续触发模块。
在 Fire 模块中,“squeeze”参数通常应小于“expand”参数。这些参数实际上是滤镜数量。通常,范围为 8 至 196。您可以尝试使用以下架构:滤镜数量在网络中逐渐增加;或者所有火焰模块的滤镜数量相同的简单架构。
示例如下:
x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB
y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, y)
此时,您可能会注意到,您的实验进展不顺利,80% 的准确率目标似乎偏远。我们再来介绍几个小技巧。
批量归一化
批量归一化有助于解决你遇到的收敛问题。下一个研讨会将详细介绍此技术,目前,请将其用作黑盒“魔法”帮助程序,方法是在网络中的每个卷积层后(包括 fire_module 函数内的层)添加以下代码行:
y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context
由于数据集较小,因此必须将动量参数从默认值 0.99 降低到 0.9。暂时不用管这个细节。
数据增强
通过使用简单的转换(例如饱和度变化的左右翻转)来增强数据,您可以再提高几个百分点:
使用 tf.data.Dataset API 在 Tensorflow 中可以非常轻松地做到这一点。为数据定义新的转换函数:
def data_augment(image, label):
image = tf.image.random_flip_left_right(image)
image = tf.image.random_saturation(image, lower=0, upper=2)
return image, label
然后在最终的数据转换中使用它(“训练和验证数据集”单元格,函数“get_batched_dataset”):
dataset = dataset.repeat() # existing line
# insert this
if augment_data:
dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line
别忘了将数据增强设为可选,并添加必要的代码,以确保仅对训练数据集进行增强。对验证数据集进行增强毫无意义。
35 个周期内 80% 的准确率现在都触手可及了。
解决方案
以下是解决方案笔记本。如果您遇到问题,可以使用此方法。
Keras_Flowers_TPU_squeezenet.ipynb
所学内容
- ❌ Keras“函数样式”模型
- 🤓? Squeezenet 架构
- 🤓? 使用 tf.data.datset 进行数据增强
请花点时间在脑海中过一遍此核对清单。
8. Xception 微调
可分离卷积
最近,一种实现卷积层的其他方式越来越受欢迎:可分离深度的卷积。我知道这听起来很令人费解,但概念其实很简单。它们在 TensorFlow 和 Keras 中以 tf.keras.layers.SeparableConv2D
的形式实现。
可分离卷积也会对图片运行过滤器,但会对输入图片的每个通道使用一组不同的权重。接下来是“1x1 卷积”,即一系列点积,其结果是过滤通道的加权和。每次都使用新的权重时,可以根据需要计算任意数量的频道加权重组。
插图:可分离的卷积。第 1 阶段:为每个通道使用单独的滤波器进行卷积。第 2 阶段:渠道的线性重组。使用一组新的权重重复上述步骤,直到达到所需的输出通道数。第 1 阶段也可以重复,每次都使用新的权重,但在实践中很少会这样做。
可分离卷积用于最新的卷积网络架构:MobileNetV2、Xception、EfficientNet。顺便提一下,你之前用于迁移学习的 MobileNetV2 就是这个。
它们比常规卷积更经济,并且在实践中也被证明同样有效。下面是上述示例的权重计数:
卷积层:4 x 4 x 3 x 5 = 240
可分离的卷积层:4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
读者可以将计算以类似方式应用每种卷积层缩放所需的乘法次数作为一项练习。可分离卷积的大小更小,计算效率也更高。
动手实践
从“转移学习”Playground 笔记本重新开始,但这次选择 Xception 作为预训练模型。Xception 仅使用可分离的卷积。让所有权重保持可训练。我们将根据自己的数据对预训练权重进行微调,而不是直接使用预训练层。
Keras Flowers transfer learning (playground).ipynb
目标:准确率 > 95%(不,说真的,这是有可能的!)
这是最后一个练习,需要编写更多代码和数据科学工作。
有关微调的更多信息
Xception 可用于 tf.keras.application 中的标准预训练模型。*这次别忘了让所有权重保持可训练。
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
为了在微调模型时获得良好的结果,您需要注意学习速率,并使用具有磨合期的学习速率计划。示例如下:
从标准学习速率入手会破坏模型的预训练权重。从头开始逐步保留这些数据,直到模型锁定到您的数据并能够以合理的方式修改这些数据。在完成增量后,您可以继续使用恒定学习速率或指数衰减学习速率。
在 Keras 中,学习速率是通过回调指定的,您可以在其中计算每个 epoch 的适当学习速率。Keras 会针对每个周期将正确的学习速率传递给优化器。
def lr_fn(epoch):
lr = ...
return lr
lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)
model.fit(..., callbacks=[lr_callback])
解决方案
以下是解决方案笔记本。如果您遇到问题,可以使用此方法。
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
所学内容
- 🤔 深度可分离卷积
- 🤓 学习速率时间表
- 😈 微调预训练模型。
请花点时间在脑海中过一遍此核对清单。
9. 恭喜!
您已经构建了自己的第一个现代卷积神经网络,并将其训练准确率达到 90% 以上。得益于 TPU,您可以在几分钟内对连续训练进行迭代。至此,4 个“Keras on TPU Codelab”到此结束:
- TPU 速度数据流水线:tf.data.Dataset 和 TFRecords
- 您的第一个具有迁移学习功能的 Keras 模型
- 包含 Keras 和 TPU 的卷积神经网络
- [此实验]现代卷积神经网络、squeezenet、Xception 以及 Keras 和 TPU
TPU 使用实例
Cloud AI Platform 上提供 TPU 和 GPU:
- 在 Deep Learning VM 上
- 在 AI Platform Notebooks 中
- 在 AI Platform Training 作业中
最后,欢迎您提供反馈。如果您发现此实验中存在错误,或者您认为需要改进,请告诉我们。您可以通过 GitHub 问题 [反馈链接] 提供反馈。
|