TPU 上的 Keras 和现代卷积

1. 概览

在本实验中,您将学习如何使用 Keras 和 Tensorflow 2 从头开始构建、训练和调整您自己的卷积神经网络。现在,利用 TPU 的功能,在几分钟内即可完成这一操作。您还将探索多种方法,从非常简单的迁移学习到 Squeezenet 等现代卷积架构。本实验包含有关神经网络的理论解释,是开发者学习深度学习的一个很好的切入点。

阅读深度学习论文可能很难,而且会让人感到困惑。我们来亲自体验一下现代卷积神经网络架构。

ca8cc21f6838eccc.png

学习内容

  • 使用 Keras 和张量处理单元 (TPU) 更快地构建自定义模型。
  • 使用 tf.data.Dataset API 和 TFRecord 格式高效地加载训练数据。
  • 用迁移学习而不是构建自己的模型来作弊 😈?。
  • 使用 Keras 序列和功能模型样式。
  • 使用 softmax 层和交叉熵损失构建您自己的 Keras 分类器。
  • 使用合适的卷积层微调模型。
  • 探索现代 convnet 架构理念,例如模块、全局平均池化等
  • 使用 Squeezenet 架构构建一个简单的现代 convnet。

反馈

如果您发现此 Codelab 中存在错误,请告诉我们。您可以通过 GitHub 问题 [ feedback link] 提供反馈。

2. Google Colaboratory 快速入门

本实验使用 Google Colaboratory,您无需进行任何设置。您可以在 Chromebook 上运行该应用。请打开下面的文件,然后执行其中的单元,以熟悉 Colab 笔记本。

c3df49e90e5a654f.png Welcome to Colab.ipynb

选择 TPU 后端

8832c6208c99687d.png

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

笔记本执行

76d05caa8b4db6da.png

通过点击单元格并使用 Shift-ENTER 一次执行一个单元格。您也可以依次选择运行时 > 全部运行来运行整个笔记本

目录

429f106990037ec4

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

隐藏单元格

edc3dba45d26f12a.png

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

Authentication

cdd4b41413100543.png

只要您使用已获授权的账号进行身份验证,Colab 便可以访问您的私有 Google Cloud Storage 存储桶。上述代码段将触发身份验证流程。

3. [INFO] 什么是张量处理单元 (TPU)?

简要说明

f88cf6facfc70166.png

使用 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 以交互速度(每次训练运行的分钟数)构建和优化花卉分类器。

688858c21e3beff2

为什么选择 TPU?

现代 GPU 是围绕可编程“核心”组织的,这是一种非常灵活的架构,可处理各种任务,如 3D 渲染、深度学习、物理模拟等。另一方面,TPU 将传统矢量处理器与专用矩阵乘法单元搭配使用,非常适合执行大量矩阵乘法操作的任务,例如神经网络。

8eb3e718b8e2ed08

插图:以矩阵乘法表示的密集神经网络层,通过神经网络同时处理一批 8 张图片。请运行一行 x 列乘法,验证它是否确实对图像的所有像素值进行了加权求和。卷积层也可以表示为矩阵乘法,尽管它有点复杂( 此处的说明,第 1 部分)。

硬件

MXU 和 VPU

TPU v2 核心由矩阵乘法单元 (MXU) 和矢量处理单元 (VPU) 组成,前者用于运行矩阵乘法,后者用于执行所有其他任务(例如激活、softmax 等)。VPU 负责处理 float32 和 int32 计算。另一方面,MXU 采用混合精度 16-32 位浮点格式。

7d68944718f76b18.png

混合精度浮点和 bfloat16

MXU 使用 bfloat16 输入和 float32 输出计算矩阵乘法。中间累积以 float32 精度执行。

19c5fc432840c714.png

神经网络训练通常能抵抗因浮点精度降低而引入的噪声。在某些情况下,噪声甚至有助于优化器收敛。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 可以在 128 x 128 MXU 中实现 16K 个这样的操作,并一次性处理此矩阵乘法。

f1b283fc45966717.gif

图解:MXU 收缩阵列。计算元素是乘法累加器。一个矩阵的值被加载到数组中(红点)。另一个矩阵的值会流经数组(灰点)。垂直线会向上传播值。水平线会传播部分总和。用户可以自行验证,当数据流经数组时,您是否会从右侧获得矩阵乘法结果。

此外,在 MXU 中计算点积时,中间求和只会在相邻的计算单元之间流动。它们无需存储到/从内存或寄存器文件中检索。最终结果是,在计算矩阵乘法时,TPU 脉动阵列架构具有明显的密度和功率优势,并且在计算矩阵乘法时,也比 GPU 具有明显的速度优势。

Cloud TPU

在 Google Cloud Platform 上请求一个“Cloud TPU v2”后,您会获得一个配备 PCI 连接的 TPU 板的虚拟机 (VM)。TPU 板有四个双核 TPU 芯片。每个 TPU 核心都具有 VPU(矢量处理单元)和 128x128 MXU(矩阵乘法单元)。然后,此“Cloud TPU”通常会通过网络连接到请求它的虚拟机。因此,整个过程如下所示:

dfce5522ed644ece.png

插图:您的虚拟机连接到网络附加的“Cloud TPU”加速器。“Cloud TPU”本身是由一个虚拟机制成,该虚拟机配有一个连接了 PCI 的 TPU 板以及四个双核 TPU 芯片。

TPU Pod

在 Google 的数据中心中,TPU 连接到高性能计算 (HPC) 互连,这可以使它们看起来像一个非常大的加速器。Google 称之为 Pod,它们最多可包含 512 个 TPU v2 核心或 2048 个 TPU v3 核心。

2ec1e0d341e7fc34.jpeg

图解:TPU v3 Pod。通过 HPC 互连连接的 TPU 板和机架。

在训练期间,梯度会使用全局求和算法在 TPU 核心之间交换(点击此处可查看对全局求和的详细说明)。正在训练的模型可以通过针对大批量大小进行训练来充分利用硬件。

d97b9cc5d40fdb1d.gif

图解:在 Google TPU 的二维环形网状网 HPC 网络上使用全约化算法在训练期间同步梯度。

软件

大批量训练

TPU 的理想批量大小为每个 TPU 核心 128 个数据项,但硬件在每个 TPU 核心 8 个数据项时也能达到良好的利用率。请注意,一个 Cloud TPU 有 8 个核心。

在此 Codelab 中,我们将使用 Keras API。在 Keras 中,您指定的批次是整个 TPU 的全局批次大小。您的批处理将自动拆分为 8 个,并在 TPU 的 8 个核心上运行。

da534407825f01e3.png

如需了解其他性能提示,请参阅 TPU 性能指南。对于非常大的批次大小,某些模型可能需要特别注意,请参阅 LARSOptimizer 了解详情。

幕后探秘:XLA

TensorFlow 程序定义计算图。TPU 不会直接运行 Python 代码,而是运行由 TensorFlow 程序定义的计算图。在底层,一个名为 XLA(加速线性代数编译器)的编译器会将计算节点的 TensorFlow 图转换为 TPU 机器码。此编译器还可以对您的代码和内存布局执行许多高级优化。在将工作发送到 TPU 时,系统会自动进行编译。您无需在 build 链中明确包含 XLA。

edce61112cd57972.png

示例:为了在 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 的位置,这得益于 TPU_NAME 环境变量。如果您手动创建 TPU,请在使用它的虚拟机上设置 TPU_NAME 环境变量,或使用显式参数调用 TPUClusterResolverTPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy 是实现分布和“all-reduce”梯度同步算法的部分。
  • 该策略通过镜重应用。必须在策略 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. 正在加载数据

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

我们将使用花卉图片数据集。我们的目标是学习如何将其分类为 5 种花卉类型。数据加载是使用 tf.data.Dataset API 执行的。首先,我们来了解一下该 API。

实践活动

请打开以下笔记本,执行单元格 (Shift-ENTER),然后按照显示“需要完成的工作”标签处的说明操作。

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

其他信息

“花”数据集简介

该数据集分为 5 个文件夹。每个文件夹都包含一种花。这些文件夹的名称为“向日葵”、“雏菊”、“蒲公英”、“郁金香”和“玫瑰”。数据托管在 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 张图片。速度太慢了!我们将用于训练的硬件加速器可以达到这个速率的数倍。请继续阅读下一部分,了解如何实现这一目标。

解决方案

以下是解决方案笔记本。如果您遇到困难,可以使用它。

c3df49e90e5a654f.png 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 格式的数据。

c3df49e90e5a654f.png 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 加载 Dataset

请花点时间回想一下这份核对清单。

6. [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, ... )

688858c21e3beff2

密集神经网络

这是用于对图像进行分类的最简单的神经网络。它由分层排列的“神经元”组成。第一层会处理输入数据,并将其输出馈送到其他层。之所以称为“密集”,是因为每个神经元都与前一层中的所有神经元相连。

c21bae6dade487bc.png

您可以将图片馈送到此类网络中,方法是将其所有像素的 RGB 值展平为长矢量并将其用作输入。这不是图片识别的最佳技术,但我们稍后会对其进行改进。

中子、激活、RELU

“神经元”会计算其所有输入的加权和,添加一个称为“偏差”的值,然后通过所谓的“激活函数”馈送结果。权重和偏差最初未知。它们会随机初始化,并通过大量已知数据训练神经网络“学习”。

644f4213a4ee70e5.png

最常用的激活函数称为 RELU(修正线性单元)。如上图所示,这是一个非常简单的函数。

Softmax 激活

上述网络以 5 神经元层结尾,因为我们将花卉分为 5 类(玫瑰、郁金香、蒲公英、雏菊、向日葵)。中间层的神经元使用经典的 RELU 激活函数进行激活。不过,在最后一层,我们希望计算介于 0 到 1 之间的数字,表示这朵花是玫瑰、郁金香等的概率。为此,我们将使用一个名为“softmax”的激活函数。

对向量应用 Softmax 的方法是取每个元素的指数,然后对向量进行归一化,通常使用 L1 范数(绝对值总和),使值加起来为 1,并且可以解释为概率。

ef0d98c0952c262d.png d51252f75894479e.gif

交叉熵损失

现在,我们的神经网络可以根据输入图像生成预测结果,我们需要衡量这些结果的准确性,即网络告诉我们的结果与正确答案(通常称为“标签”)之间的距离。请注意,我们为数据集中的所有图片都提供了正确的标签。

任何距离都有效,但对于分类问题而言,所谓的“交叉熵距离”是最有效的。我们将其称为错误函数或“损失”函数:

7bdf8753d20617fb.png

梯度下降法

“训练”神经网络实际上意味着使用训练图片和标签来调整权重和偏差,以便最大限度地降低交叉熵损失函数。其运作方式如下。

交叉熵是训练图片的权重、偏差、像素及其已知类别的函数。

如果我们计算交叉熵相对于所有权重和所有偏置的偏导数,就会得到一个“梯度”,该梯度针对给定图像、标签以及权重和偏差的现值进行了计算。请注意,我们可以有数百万个权重和偏差,因此计算梯度听起来很麻烦。幸运的是,Tensorflow 可以为我们做到这一点。梯度的数学性质是,它指向“上方”。由于我们想去交叉熵较低的地方,因此会前往相反的方向。我们会按梯度分数更新权重和偏差。然后,我们在训练循环中使用下一批训练图片和标签重复执行相同的操作。希望它会收敛到交叉熵最小的位置,但没有任何保证此最小值是唯一的。

梯度下降法 2.png

小批量和动量

您可以仅对一张样本图片计算梯度,并立即更新权重和偏差,但对一批 128 张图片(例如 128 张图片)执行此操作将产生一个梯度,该梯度可以更好地表示不同样本图片施加的限制,因此可能会更快地收敛于解决方案。小批次的大小是一个可调整的参数。

这种技术有时也称为“随机梯度下降”,还有一个更实用的优势:处理批处理也意味着处理更大的矩阵,而这些矩阵通常更容易在 GPU 和 TPU 上进行优化。

不过,收敛可能仍然有点混乱,即使梯度矢量都为零,收敛也可能会停止。这是否意味着我们找到了一个最低值?不一定。在最小值或最大值处,梯度分量可以为零。如果梯度矢量包含数百万个元素,并且这些元素全为零,那么每个零对应于最小值且没有任何零对应于最大值的概率非常小。在具有很多维度的空间中,马鞍点很常见,我们不想就此止步。

52e824fe4716c4a0

插图:鞍点。梯度为 0,但并非在所有方向上的最小值。(图片出处 维基媒体:Nicoguaro - 自创内容,CC BY 3.0

解决方法是为优化算法增加一些动力,使其不停地驶过马鞍点。

术语库

batchmini-batch:始终对批量训练数据和标签执行训练。这样做有助于算法收敛。“批量”维度通常是数据张量的第一个维度。例如,形状为 [100, 192, 192, 3] 的张量包含 100 张 192x192 像素的图片,每个像素有三个值(RGB)。

交叉熵损失:分类器中常用的一种特殊损失函数。

密集层:一个神经元层,其中每个神经元都与前一层中的所有神经元连接。

特征:神经网络的输入有时称为“特征”。确定要将数据集的哪些部分(或部分组合)输入神经网络以获得良好预测结果的艺术称为“特征工程”。

标签:监督式分类问题中“类别”或正确答案的别名

学习速率:在训练循环的每次迭代中用于更新权重和偏差的梯度的一部分。

logits:在应用激活函数之前,一层神经元的输出称为“logits”。该术语来自“逻辑函数”(也称为“S 型函数”),曾是最常用的激活函数。“逻辑函数之前的中子输出”已简化为“logits”。

loss:用于将神经网络输出与正确答案进行比较的误差函数

神经元:计算其输入的加权和,添加偏差,并通过激活函数馈送结果。

one-hot 编码:第 3 个类(占 5 个类中的 3 个)编码为 5 个元素的矢量,其中除了第 3 个元素为 1 之外,所有元素均为 0。

relu:修正的线性单元。一种热门的神经元激活函数。

S 型函数:另一种过去很流行的激活函数,在特殊情况下仍然有用。

softmax:一种特殊的激活函数,作用于向量,会增大最大分量与所有其他分量之间的差异,还会归一化向量,使其总和为 1,以便将其解读为概率向量。用作分类器中的最后一步。

张量:“张量”类似于矩阵,但具有任意数量的维度。一维张量是向量。二维张量就是一个矩阵。然后,您可以拥有 3、4、5 或更多维度的张量。

7. 迁移学习

对于图像分类问题,密集层可能不够用。我们必须了解卷积层以及排列卷积层的多种方式。

不过,我们也可以使用快捷方式!您可以下载完全训练好的卷积神经网络。您可以剪掉最后一层(softmax 分类头),并将其替换为自己的层。所有训练的权重和偏差都保持不变,您只需重新训练添加的 softmax 层即可。这项技术称为“迁移学习”,令人惊讶的是,只要预训练神经网络所使用的数据集“足够接近”您的数据集,它就可以发挥作用。

动手实践

请打开以下笔记本,执行单元格(按住 Shift 键并按 Enter 键),并按照看到“WORK REQUIRED”(需要执行的工作)标签时显示的说明操作。

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

其他信息

通过迁移学习,您既可以从顶尖研究人员开发的高级卷积神经网络架构中受益,也可以从大型图像数据集预训练中受益。在本例中,我们将从使用 ImageNet 训练的网络中迁移学习,后者是一个包含许多植物和户外场景的图像数据库,与花朵非常接近。

b8fc1efd2001f072.png

图解:使用一个已经训练过的复杂卷积神经网络,黑框只重新训练分类头。这就是迁移学习。我们稍后将看到这些复杂的卷积层排列方式。目前是别人的问题。

Keras 中的迁移学习

在 Keras 中,您可以将 tf.keras.applications.* 集合中的预训练模型实例化。以 MobileNet V2 为例,它就是一种非常棒的卷积架构,在大小方面保持合理。选择 include_top=False 后,您会获得不含最终 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%。

解决方案

以下是解决方案笔记本。如果您遇到困难,可以使用它。

c3df49e90e5a654f.png Keras Flowers transfer learning (solution).ipynb

所学内容

  • 🤔? 如何在 Keras 中编写分类器
  • 🤓 配置了 Softmax 最后一层和交叉熵损失
  • 😈 迁移学习
  • 🤔? 训练您的首个模型
  • 🧐 跟踪训练期间的损失和准确率

请花点时间回想一下这份核对清单。

8. [INFO] 卷积神经网络

简述

如果你已经知道下一段落中以粗体显示的所有术语,可以接着进行下一个练习。如果您刚开始接触卷积神经网络,请继续阅读。

convolutional.gif

插图:使用两个由 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'])

688858c21e3beff2

卷积神经网络基础知识

在卷积网络的某一层中,一个“神经元”只会对图像中一小区域内紧挨其上的像素进行加权求和。它会添加偏差,并通过激活函数馈送总和,就像常规密集层中的神经元一样。然后,使用相同的权重对整张图片重复此操作。请注意,在密集层中,每个神经元都有自己的权重。在这里,单个权重“补丁”会在两个方向上滑动(“卷积”)。输出的值数量与图片中的像素数量相同(不过,边缘需要一些内边距)。这是一个过滤操作,使用 4x4x3=48 个权重的过滤器。

但是,48 个权重远远不够。为了增加自由度,我们使用一组新的权重重复相同的操作。这样会生成一组新的过滤器输出。我们将其称为输出的“通道”,类似于输入图像中的 R、G、B 通道。

Screen Shot 2016-07-29 at 16.02.37.png

通过添加新维度,将两组(或更多)权重求和为一个张量。这为我们提供了卷积层权重张量的通用形状。由于输入和输出通道的数量是参数,因此我们可以开始堆叠和链接卷积层。

d1b557707bcd1cb9.png

插图:卷积神经网络将数据“立方体”转换为其他数据“立方体”。

步进卷积、最大池化

通过以 2 或 3 的步长执行卷积,我们还可以缩减生成的数据立方体的横向维度。有两种常用方法可以实现此目的:

  • 步进卷积:如上所示的滑动过滤器,但步长 >1
  • 最大池化:应用 MAX 操作的滑动窗口(通常采用 2x2 图块,每 2 像素重复一次)

2b2d4263bb8470b.gif

示例:将计算窗口向右滑动 3 个像素会导致输出值减少。步长卷积或最大池化(在步长为 2 的 2x2 滑动窗口上执行最大值运算)是一种在水平维度上缩减数据立方体的做法。

卷积分类器

最后,我们通过展平最后一个数据立方体并通过密集的 softmax 激活层馈送分类头来附加分类头。典型的卷积分类器可能如下所示:

4a61aaffb6cba3d1

插图:使用卷积层和 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),然后按照显示“需要完成的工作”标签处的说明操作。

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

目标是超越迁移学习模型的 75% 准确率。该模型有一个优势,它是在包含数百万张图片的数据集上预训练的,而我们这里只有 3670 张图片。你能至少配上它吗?

其他信息

有多少层,有多大?

选择图层大小更像是一门艺术,而不是一门科学。您必须在参数(权重和偏差)过少和过多之间找到适当的平衡。如果权重过少,神经网络就无法表示花形的复杂性。图像太多,则容易出现“过拟合”,即专业训练图像而无法泛化。如果参数很多,模型的训练速度也会变慢。在 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().

93240029f59df7c2.png

解决方案

以下是解决方案笔记本。如果您遇到困难,可以使用它。

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

所学内容

  • 🤔 玩转了卷积层
  • 🤓? 尝试了最大池化、步长、全局平均池化...
  • 😀? 在 TPU 上快速对真实模型进行迭代

请花点时间回想一下这份核对清单。

10. [INFO] 现代卷积神经网络架构

简述

7968830b57b708c0

插图:卷积“模块”。这时什么方法最好?一个最大池层后跟一个 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)

688858c21e3beff2.png

其他低成本技巧

小型 3x3 过滤器

40a7b15fb7dbe75c.png

在此图中,您可以看到两个连续的 3x3 过滤器的结果。尝试追溯是哪些数据点导致了结果:这两个连续的 3x3 过滤器计算 5x5 区域的某种组合。这与 5x5 过滤器计算的组合并不完全相同,但值得尝试,因为两个连续的 3x3 过滤器的计算开销比单个 5x5 过滤器要低。

1x1 卷积?

fd7cac16f8ecb423.png

从数学角度来看,“1x1”卷积是与常数相乘,这不是一个非常实用的概念。不过,请注意,在卷积神经网络中,滤镜会应用于数据立方体,而不仅仅是 2D 图片。因此,“1x1”滤镜会计算 1x1 数据列的加权和(如图所示),当您在数据上滑动时,您将获得输入通道的线性组合。这实际上很有用。如果将通道视为各个过滤操作的结果,例如针对“尖耳朵”的过滤器、针对“胡须”的过滤器,以及针对“狭窄的眼睛”的第三个过滤器,则“1x1”卷积层将计算这些特征的多个可能的线性组合,这在查找“猫”时可能很有用。除此之外,1x1 层使用的权重也更少。

11. Squeezenet

《Squeezenet》论文展示了一种整合上述理念的简单方法。作者提出了一种非常简单的卷积模块设计,仅使用 1x1 和 3x3 卷积层。

1730ac375379269b

插图:基于“火焰模块”的 SqueezeNet 架构。它们交替使用 1x1 层(用于在垂直维度上“压缩”传入数据),然后是两个并行的 1x1 和 3x3 卷积层(用于再次“扩展”数据的深度)。

动手实践

继续上一个笔记本,构建一个受 squeezenet 启发的卷积神经网络。您必须将模型代码更改为 Keras“函数式风格”。

c3df49e90e5a654f.png 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 个连续触发模块。

在火焰模块中,“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。暂时不用管这个细节。

数据增强

通过利用简单的转换(例如饱和度变化的左右翻转)来增强数据,您可以获得更高的百分点:

4ed2958e09b487ca

ad795b70334e0d6b.png

在 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% 的准确率现在都触手可及了。

解决方案

以下是解决方案笔记本。如果您遇到困难,可以使用它。

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

所学内容

  • 🤔 Keras“函数式风格”模型
  • 🤓 Squeezenet 架构
  • 🤓? 使用 tf.data.datset 进行数据增强

请花点时间在脑海中过一遍此核对清单。

12. 微调后的 Xception

可分离卷积

最近,一种实现卷积层的不同方法越来越受欢迎:深度可分离卷积。我知道这听起来很令人费解,但概念其实很简单。它们在 TensorFlow 和 Keras 中实现为 tf.keras.layers.SeparableConv2D

可分离卷积也会对图片运行过滤器,但它会为输入图片的每个通道使用一组不同的权重。后跟“1x1 卷积”,也就是一系列点积,用于生成已过滤通道的加权总和。每次都使用新的权重时,可以根据需要计算任意数量的频道加权重组。

615720b803bf8dda.gif

插图:可分离的卷积。阶段 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 仅使用可分离的卷积。让所有权重保持可训练。我们将根据自己的数据对预训练权重进行微调,而不是直接使用预训练层。

c3df49e90e5a654f.png 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

为了在微调模型时获得良好的结果,您需要注意学习速率,并使用具有磨合期的学习速率计划。示例如下:

9b1af213b2b36d47

从标准学习速率入手会破坏模型的预训练权重。开始时会逐步保留这些数据,直到模型能够根据数据进行合理修改。完成梯度后,您可以使用恒定或指数衰减的学习速率继续学习。

在 Keras 中,学习速率通过回调指定,您可以在该回调中为每个周期计算适当的学习速率。Keras 会针对每个周期将正确的学习速率传递给优化器。

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

解决方案

以下是解决方案笔记本。如果您遇到困难,可以使用它。

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

所学内容

  • 🤔? 可分离深度的卷积
  • 🤓 学习速率时间表
  • 😈 微调预训练模型。

请花点时间回想一下这份核对清单。

13. 恭喜!

您已经构建了自己的第一个现代卷积神经网络,训练后准确率达到 90% 以上。得益于 TPU,只需几分钟即可对连续训练进行迭代。

TPU 使用实例

Google Cloud 的 Vertex AI 提供 TPU 和 GPU:

最后,欢迎您提供反馈。如果您发现此实验中存在错误,或者您认为需要改进,请告诉我们。您可以通过 GitHub 问题 [ feedback link] 提供反馈。

HR.png

马丁·戈尔纳 ID small.jpg
作者:Martin Görner
Twitter:@martin_gorner