1. 概览
在本实验中,您将学习如何使用 Keras 和 TensorFlow 2 从头开始构建、训练和调优您自己的卷积神经网络。现在,借助 TPU 的强大功能,您可以在几分钟内完成此操作。您还将探索多种方法,从非常简单的迁移学习到现代卷积架构(例如 Squeezenet)。本实验包含有关神经网络的理论说明,是开发者学习深度学习的良好起点。
阅读深度学习论文可能既困难又令人困惑。让我们亲自动手了解一下现代卷积神经网络架构。

学习内容
- 使用 Keras 和张量处理单元 (TPU) 更快地构建自定义模型。
- 使用 tf.data.Dataset API 和 TFRecord 格式高效加载训练数据。
- 作弊方法 😈:使用迁移学习,而不是构建自己的模型。
- 使用 Keras 顺序模型和功能模型样式。
- 构建自己的 Keras 分类器,其中包含 softmax 层和交叉熵损失。
- 通过选择合适的卷积层来微调模型。
- 探索模块、全局平均池化等现代卷积网络架构理念。
- 使用 Squeezenet 架构构建简单的现代卷积神经网络。
反馈
如果您在此 Codelab 中发现任何问题,请告诉我们。您可以通过 GitHub 问题 [反馈链接] 提供反馈。
2. Google Colaboratory 快速入门
本实验使用 Google Colaboratory,无需您进行任何设置。您可以在 Chromebook 上运行该应用。请打开下面的文件,并执行相应单元格,以便熟悉 Colab 笔记本。
选择 TPU 后端

在 Colab 菜单中,依次选择运行时 > 更改运行时类型,然后选择 TPU。在此代码实验中,您将使用强大的 TPU(张量处理单元)来支持硬件加速训练。首次执行时,系统会自动连接到运行时,您也可以使用右上角的“连接”按钮。
笔记本执行

点击某个单元格,然后使用 Shift-Enter 逐个执行单元格。您还可以通过依次选择运行时 > 全部运行来运行整个笔记本
目录

所有笔记本都有目录。您可以使用左侧的黑色箭头打开该菜单。
隐藏单元格

部分单元格将仅显示其标题。这是 Colab 特有的笔记本功能。您可以双击它们来查看其中的代码,但通常不会很有趣。通常是支持或可视化函数。您仍需运行这些单元格,以便定义其中的函数。
Authentication

只要您使用已获授权的账号进行身份验证,Colab 就可以访问您的私有 Google Cloud Storage 存储分区。上述代码段将触发身份验证流程。
3. [信息] 什么是张量处理单元 (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 脉动阵列架构在密度和能耗方面具有显著优势,在速度方面也具有不可忽略的优势。
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 的二维环面网状网 HPC 网络上,使用 all-reduce 算法在训练期间同步梯度。
软件
大批次大小训练
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是实现分布式和“全归约”梯度同步算法的部分。- 策略通过范围应用。必须在 strategy 范围() 内定义模型。
tpu_model.fit函数需要一个 tf.data.Dataset 对象作为 TPU 训练的输入。
常见的 TPU 移植任务
- 虽然有很多方法可以在 TensorFlow 模型中加载数据,但对于 TPU,必须使用
tf.data.DatasetAPI。 - TPU 的运行速度非常快,因此在 TPU 上运行时,提取数据往往会成为瓶颈。您可以使用一些工具来检测数据瓶颈,并参考 TPU 性能指南中的其他性能提示。
- int8 或 int16 数字会被视为 int32。TPU 没有以小于 32 位运行的整数硬件。
- 不支持某些 TensorFlow 操作。点击此处可查看列表。好消息是,此限制仅适用于训练代码,即模型的前向和后向传递。您仍然可以在数据输入流水线中使用所有 TensorFlow 操作,因为这些操作将在 CPU 上执行。
- TPU 不支持
tf.py_func。
4. 加载数据

我们将使用一个鲜花图片数据集。目标是学习将它们分为 5 种花卉类型。数据加载是使用 tf.data.Dataset API 执行的。首先,让我们了解一下该 API。
具备实践性
请打开以下笔记本,执行单元格(按 Shift-Enter 键),然后按照标有“需要完成的工作”的说明操作。
Fun with tf.data.Dataset (playground).ipynb
其他信息
“flowers”数据集简介
数据集整理在 5 个文件夹中。每个文件夹都包含一种花。这些文件夹的名称分别为 sunflowers、daisy、dandelion、tulips 和 roses。数据托管在 Google Cloud Storage 的公共存储分区中。摘录:
gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg
为什么使用 tf.data.Dataset?
Keras 和 TensorFlow 在其所有训练和评估函数中都接受数据集。在数据集中加载数据后,该 API 会提供所有对神经网络训练数据有用的常见功能:
dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training
您可以在这篇文章中找到性能提示和数据集最佳实践。点击此处查看参考文档。
tf.data.Dataset 基础知识
数据通常以多个文件(此处为图片)的形式呈现。您可以通过调用以下函数来创建文件名数据集:
filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.
然后,您将一个函数“映射”到每个文件名,该函数通常会将文件加载并解码为内存中的实际数据:
def decode_jpeg(filename):
bits = tf.io.read_file(filename)
image = tf.io.decode_jpeg(bits)
return image
image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)
如需迭代处理数据集,请执行以下操作:
for data in my_dataset:
print(data)
元组数据集
在监督学习中,训练数据集通常由训练数据和正确答案对组成。为了实现这一点,解码函数可以返回元组。然后,您将获得一个元组数据集,当您对其进行迭代时,系统会返回元组。返回的值是 TensorFlow 张量,可供模型直接使用。您可以对它们调用 .numpy() 以查看原始值:
def decode_jpeg_and_label(filename):
bits = tf.read_file(filename)
image = tf.io.decode_jpeg(bits)
label = ... # extract flower name from folder name
return image, label
image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs
for image, label in dataset:
print(image.numpy().shape, label.numpy())
结论:逐个加载图片速度很慢!
随着您对此数据集进行迭代,您会发现每秒可以加载大约 1-2 张图片。这太慢了!我们将用于训练的硬件加速器可以维持比此速率高出许多倍的速率。请前往下一部分,了解我们将如何实现此目标。
解决方案
以下是解决方案笔记本。如果您遇到困难,可以使用它。
Fun with tf.data.Dataset (solution).ipynb
所学内容
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 元组数据集
- 😀 遍历数据集
请花点时间在脑海中过一遍此核对清单。
5. 快速加载数据
我们将在本实验中使用的张量处理单元 (TPU) 硬件加速器速度非常快。挑战往往在于如何快速地向它们提供数据,以确保它们始终处于忙碌状态。Google Cloud Storage (GCS) 能够维持非常高的吞吐量,但与所有云存储系统一样,建立连接需要一些网络往返时间。因此,将数据存储为数千个单独的文件并不理想。我们将它们分批放入数量较少的文件中,并利用 tf.data.Dataset 的强大功能并行读取多个文件。
读穿
以下笔记本中包含用于加载图片文件、将它们调整为共同大小,然后将它们存储到 16 个 TFRecord 文件中的代码。请快速浏览一下。由于本 Codelab 的其余部分将提供格式正确的 TFRecord 数据,因此无需执行此操作。
Flower pictures to TFRecords.ipynb
实现最佳 GCS 吞吐量的理想数据布局
TFRecord 文件格式
TensorFlow 存储数据的首选文件格式是基于 protobuf 的 TFRecord 格式。其他序列化格式也适用,但您可以直接通过以下代码从 TFRecord 文件加载数据集:
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
为获得最佳性能,建议使用以下更复杂的代码同时从多个 TFRecord 文件中读取数据。此代码将并行读取 N 个文件,并忽略数据顺序以提高读取速度。
AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
TFRecord 备忘单
TFRecord 中可以存储三种类型的数据:字节字符串(字节列表)、64 位整数和 32 位浮点数。它们始终以列表形式存储,单个数据元素将是一个大小为 1 的列表。您可以使用以下辅助函数将数据存储到 TFRecord 中。
写入字节串
# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))
写入整数
def _int_feature(list_of_ints): # int64
return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))
写入浮点数
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
使用上述辅助函数写入 TFRecord
# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
feature = {
"image": _bytestring_feature([my_img_bytes]), # one image in the list
"class": _int_feature([my_class]), # one class in the list
"size": _int_feature([my_height, my_width]), # fixed length (2) list of ints
"float_data": _float_feature(my_floats) # variable length list of floats
}
tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
out_file.write(tf_record.SerializeToString())
如需从 TFRecord 读取数据,您必须先声明所存储记录的布局。在声明中,您可以将任何已命名字段作为固定长度列表或可变长度列表进行访问:
从 TFRecord 读取
def read_tfrecord(data):
features = {
# tf.string = byte string (not text string)
"image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
"class": tf.io.FixedLenFeature([], tf.int64), # shape [] means scalar, i.e. a single item
"size": tf.io.FixedLenFeature([2], tf.int64), # two integers
"float_data": tf.io.VarLenFeature(tf.float32) # a variable number of floats
}
# decode the TFRecord
tf_record = tf.io.parse_single_example(data, features)
# FixedLenFeature fields are now ready to use
sz = tf_record['size']
# Typical code for decoding compressed images
image = tf.io.decode_jpeg(tf_record['image'], channels=3)
# VarLenFeature fields require additional sparse.to_dense decoding
float_data = tf.sparse.to_dense(tf_record['float_data'])
return image, sz, float_data
# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)
有用的代码段:
读取单个数据元素
tf.io.FixedLenFeature([], tf.string) # for one byte string
tf.io.FixedLenFeature([], tf.int64) # for one int
tf.io.FixedLenFeature([], tf.float32) # for one float
读取固定大小的元素列表
tf.io.FixedLenFeature([N], tf.string) # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64) # list of N ints
tf.io.FixedLenFeature([N], tf.float32) # list of N floats
读取数量不定的数据项
tf.io.VarLenFeature(tf.string) # list of byte strings
tf.io.VarLenFeature(tf.int64) # list of ints
tf.io.VarLenFeature(tf.float32) # list of floats
VarLenFeature 会返回稀疏向量,并且在解码 TFRecord 后需要执行额外的步骤:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
还可以在 TFRecord 中添加可选字段。如果您在读取字段时指定了默认值,那么如果该字段缺失,系统会返回默认值,而不是错误。
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
所学内容
- 🤔 对数据文件进行分片,以便从 GCS 快速访问
- 😓 如何编写 TFRecord。(您已经忘记了语法?没关系,您可以将此页面添加为书签,以便日后参考)
- 🤔 使用 TFRecordDataset 从 TFRecord 加载数据集
请花点时间在脑海中过一遍此核对清单。
6. [信息] 神经网络分类器 101
简而言之
如果您已经了解下一段中所有粗体字词,则可以继续进行下一个练习。如果您刚刚开始学习深度学习,欢迎您,请继续阅读。
对于构建为层序列的模型,Keras 提供了 Sequential API。例如,使用三个密集层的图片分类器可以在 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)
解决方案是为优化算法增加一些动量,使其能够顺利通过鞍点而不停止。
术语库
批次或小批次:始终基于批次的训练数据和标签进行训练。这样做有助于算法收敛。“批次”维度通常是数据张量的第一个维度。例如,形状为 [100, 192, 192, 3] 的张量包含 100 张 192x192 像素的图片,每张图片包含三个值(RGB)。
交叉熵损失:一种常用于分类器的特殊损失函数。
密集层:一种神经元层,其中每个神经元都与上一层中的所有神经元相连。
特征:神经网络的输入有时称为“特征”。确定将数据集的哪些部分(或部分组合)馈送到神经网络以获得良好预测结果的技巧称为“特征工程”。
标签:监督式分类问题中“类别”或正确答案的另一种名称
学习速率:在训练循环的每次迭代中,权重和偏差更新所依据的梯度分数。
logits:在应用激活函数之前,神经元层的输出称为“logits”。该术语源自“logistic 函数”(也称为“S 型函数”),后者曾经是最热门的激活函数。“Neuron outputs before logistic function”(逻辑函数之前的神经元输出)缩短为“logits”。
损失:将神经网络输出与正确答案进行比较的误差函数
神经元:计算输入的加权和,添加偏差,并通过激活函数馈送结果。
独热编码:5 个类别中的第 3 个类别编码为包含 5 个元素的向量,其中第 3 个元素为 1,其余元素均为 0。
relu:修正线性单元。一种常用的神经元激活函数。
sigmoid:另一种曾经很受欢迎的激活函数,在特殊情况下仍然有用。
softmax:一种特殊的激活函数,可作用于向量,增大最大分量与所有其他分量之间的差值,还会将向量归一化为总和为 1,以便将其解读为概率向量。用作分类器的最后一步。
张量:“张量”类似于矩阵,但具有任意数量的维度。一维张量是向量。二维张量是矩阵。然后,您可以拥有 3 维、4 维、5 维或更多维度的张量。
7. 迁移学习
对于图像分类问题,密集层可能不够。我们必须了解卷积层以及排列卷积层的多种方式。
但我们也可以走捷径!您可以下载经过完整训练的卷积神经网络。您可以截断它们的最后一层(即 softmax 分类头),并将其替换为您自己的层。所有训练后的权重和偏差保持不变,您只需重新训练添加的 softmax 层。这种技术称为迁移学习。令人惊讶的是,只要预训练神经网络所用的数据集与您的数据集“足够接近”,这种技术就能发挥作用。
动手
请打开以下笔记本,执行单元格(按 Shift-Enter 键),然后按照标有“需要完成的工作”的说明操作。
Keras Flowers transfer learning (playground).ipynb
其他信息
借助迁移学习,您可以同时受益于顶尖研究人员开发的高级卷积神经网络架构,以及在庞大的图片数据集上进行的预训练。在本例中,我们将从在 ImageNet(一个包含许多植物和户外场景的图片数据库)上训练的网络进行迁移学习,这与花卉足够接近。

图示:使用已训练的复杂卷积神经网络作为黑盒,仅重新训练分类头。这就是迁移学习。我们稍后将了解这些复杂的卷积层排列方式是如何运作的。目前,这是其他人的问题。
Keras 中的迁移学习
在 Keras 中,您可以从 tf.keras.applications.* 集合实例化预训练模型。例如,MobileNet V2 是一种非常出色的卷积架构,其大小合理。选择 include_top=False 可获取不含最终 softmax 层的预训练模型,以便您添加自己的 softmax 层:
pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False
model = tf.keras.Sequential([
pretrained_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(5, activation='softmax')
])
另请注意 pretrained_model.trainable = False 设置。它会冻结预训练模型的权重和偏差,以便您仅训练 softmax 层。这通常涉及相对较少的权重,可以快速完成,并且不需要非常大的数据集。不过,如果您有大量数据,迁移学习与 pretrained_model.trainable = True 搭配使用效果会更好。预训练的权重随后会提供出色的初始值,并且仍可通过训练进行调整,以更好地适应您的问题。
最后,请注意插入到密集 softmax 层之前的 Flatten() 层。密集层可处理扁平数据向量,但我们不知道预训练模型是否会返回这种向量。因此,我们需要展平。在下一章中,我们将深入探讨卷积架构,并说明卷积层返回的数据格式。
采用这种方法,准确率应该接近 75%。
解决方案
以下是解决方案笔记本。如果您遇到困难,可以使用它。
Keras Flowers transfer learning (solution).ipynb
所学内容
- 🤔 如何在 Keras 中编写分类器
- 🤓 配置了 softmax 最后一层和交叉熵损失
- 😈 迁移学习
- 🤔 训练您的首个模型
- 🧐 跟踪模型在训练期间的损失和准确率
请花点时间在脑海中过一遍此核对清单。
8. [信息] 卷积神经网络
简而言之
如果您已经了解下一段中所有粗体字词,则可以继续进行下一个练习。如果您刚刚开始使用卷积神经网络,请继续阅读。

示例:使用两个连续的滤波器(每个滤波器包含 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 窗口的最大值)是一种在水平维度上缩小数据立方体的方法。
Convolutional 分类器
最后,我们通过以下方式附加分类头:将最后一个数据立方体展平,然后将其馈送到密集的 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'])
9. 您的自定义卷积神经网络
动手
让我们从头开始构建和训练卷积神经网络。使用 TPU 可以让我们非常快速地进行迭代。请打开以下笔记本,执行单元格(按 Shift-Enter 键),然后按照标有“需要完成的工作”的说明操作。
Keras_Flowers_TPU (playground).ipynb
目标是超越迁移学习模型 75% 的准确率。该模型具有优势,因为它已在包含数百万张图片的数据集上进行过预训练,而我们这里只有 3, 670 张图片。你们至少能匹配一下吗?
其他信息
有多少层,每层有多大?
选择层大小更像是一门艺术,而不是一门科学。您必须在参数(权重和偏差)过少和过多之间找到适当的平衡。如果权重过少,神经网络就无法表示花朵形状的复杂性。如果数量过多,模型可能会出现“过拟合”现象,即过于专注于训练图片,而无法进行泛化。如果参数过多,模型训练速度也会变慢。在 Keras 中,model.summary() 函数会显示模型的结构和形参数量:
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 192, 192, 16) 448
_________________________________________________________________
conv2d_1 (Conv2D) (None, 192, 192, 30) 4350
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 96, 96, 60) 16260
_________________________________________________________________
...
_________________________________________________________________
global_average_pooling2d (Gl (None, 130) 0
_________________________________________________________________
dense (Dense) (None, 90) 11790
_________________________________________________________________
dense_1 (Dense) (None, 5) 455
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________
以下是一些提示:
- 正是因为有多个层,深度神经网络才能发挥效用。对于这个简单的花卉识别问题,5 到 10 层是合理的。
- 使用小过滤器。通常,3x3 滤镜在任何地方都表现良好。
- 您也可以使用 1x1 过滤条件,这种过滤条件的费用较低。它们实际上并不“过滤”任何内容,而是计算渠道的线性组合。将它们与真实过滤条件交替使用。(有关“1x1 卷积”的更多信息,请参阅下一部分。)
- 对于此类分类问题,请使用最大池化层(或步长大于 1 的卷积)频繁进行下采样。您并不关心花朵的位置,只关心它是玫瑰还是蒲公英,因此丢失 x 和 y 信息并不重要,过滤较小区域的成本也更低。
- 过滤器的数量通常会与网络末端的类数量相似(原因?请参阅下方的“全局平均池化”技巧)。如果您要分类到数百个类别,请在连续的层中逐步增加过滤器的数量。对于具有 5 个类别的花卉数据集,仅使用 5 个过滤条件进行过滤是不够的。您可以在大多数层中使用相同的过滤条件数量(例如 32),并在接近末尾时减少该数量。
- 最终密集层成本高昂。它的权重数量可能比所有卷积层的总和还要多。例如,即使最后一个数据立方体的输出非常合理,只有 24x24x10 个数据点,一个 100 个神经元的密集层也会产生 24x24x10x100=576,000 个权重!请仔细考虑,或尝试全局平均池化(见下文)。
全局平均池化
您无需在卷积神经网络的末尾使用昂贵的密集层,而是可以将传入的数据“立方体”拆分为与类别数量相同的多个部分,计算这些部分的平均值,然后通过 softmax 激活函数馈送这些平均值。这种构建分类头的方式不需要任何权重。在 Keras 中,语法为 tf.keras.layers.GlobalAveragePooling2D().

解决方案
以下是解决方案笔记本。如果您遇到困难,可以使用它。
Keras_Flowers_TPU (solution).ipynb
所学内容
- 🤔 尝试了卷积层
- 🤓 尝试过最大池化、步幅、全局平均池化等
- 😀 在 TPU 上快速迭代了真实世界模型
请花点时间在脑海中过一遍此核对清单。
10. [信息] 现代卷积架构
简而言之

图示:卷积“模块”。目前最好的做法是什么?一个最大池化层,后跟一个 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”卷积就是乘以一个常数,并不是一个很有用的概念。不过,在卷积神经网络中,请注意,滤镜会应用于数据立方体,而不仅仅是二维图片。因此,“1x1”滤波器会计算 1x1 数据列的加权和(见图),当您在数据中滑动该滤波器时,会获得输入通道的线性组合。这实际上很有用。如果您将渠道视为各个过滤操作的结果(例如,一个过滤条件是“尖耳朵”,另一个是“胡须”,第三个是“细长的眼睛”),那么“1x1”卷积层将计算这些特征的多种可能的线性组合,这在寻找“猫”时可能很有用。此外,1x1 层使用的权重更少。
11. Squeezenet
《Squeezenet》论文中展示了一种将这些想法结合在一起的简单方法。作者建议采用非常简单的卷积模块设计,仅使用 1x1 和 3x3 卷积层。

图示:基于“fire 模块”的 SqueezeNet 架构。它们交替使用一个在垂直维度上“挤压”传入数据的 1x1 层,然后是两个并行的 1x1 和 3x3 卷积层,再次“扩展”数据的深度。
动手
在之前的笔记本中继续操作,构建一个受 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 模块。
在 Fire 模块中,“squeeze”形参通常应小于“expand”形参。这些参数实际上是过滤器的数量。它们通常介于 8 到 196 之间。您可以尝试使用过滤器数量在整个网络中逐渐增加的架构,也可以使用所有 Fire 模块都具有相同数量过滤器的简单架构。
示例如下:
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。暂时不必理会此细节。
数据增强
通过简单的转换(例如左右翻转饱和度变化)来扩充数据,您可以将准确率再提高几个百分点:


在 TensorFlow 中,使用 tf.data.Dataset API 可以非常轻松地实现这一点。为数据定义新的转换函数:
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.dataset 进行数据增强
请花点时间在脑海中过一遍此核对清单。
12. 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 notebook 重新开始,但这次选择 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 中,学习速率是通过回调指定的,您可以在其中计算每个周期的适当学习速率。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
所学内容
- 🤔 深度可分离卷积
- 🤓 学习速率时间表
- 😈 对预训练模型进行微调。
请花点时间在脑海中过一遍此核对清单。
13. 恭喜!
您已构建了第一个现代卷积神经网络,并将其训练到 90% 以上的准确率,借助 TPU,只需几分钟即可完成连续的训练运行。
TPU 实践
Google Cloud 的 Vertex AI 提供 TPU 和 GPU:
- 在 Deep Learning VM 上
- 在 Vertex AI Notebooks 中
- 在 Vertex AI Training作业中
最后,我们非常欢迎您提供反馈。如果您发现此实验有任何问题,或者认为此实验应进行改进,请告诉我们。您可以通过 GitHub 问题 [反馈链接] 提供反馈。

|
|

