TensorFlow、Keras 和深度学习,无博士学位

在此 Codelab 中,您将学习如何构建和识别可识别数字的神经网络。在此过程中,通过增强神经网络来实现 99% 的准确率,你还会发现深度学习专家可以高效利用各种工具来训练自己的模型。

此 Codelab 使用 MNIST 数据集,该数据集包含 60000 个带标签的数字,连续生成几代博士,近二十年一直以来都处于忙碌状态。您只需使用少于 100 行 Python / TensorFlow 代码即可解决此问题。

学习内容

  • 什么是神经网络以及如何训练它
  • 如何使用 tf.keras 构建基本的 1 层神经网络
  • 如何添加更多图层
  • 如何设置学习速率时间表
  • 如何构建卷积神经网络
  • 如何使用正则化技术:丢弃、批量归一化
  • 什么是过拟合

您需要满足的条件

只需一个浏览器。本研讨会完全可以借助 Google Colaboratory 开展。

反馈

如果您认为此实验中出现了问题,或者您认为此实验有待改进,请告诉我们。我们通过 GitHub 问题处理反馈 [反馈链接]。

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

Welcome to Colab.ipynb

其他说明如下:

选择 GPU 后端

在 Colab 菜单中,依次选择运行时 > 更改运行时类型,然后选择 GPU。首次运行时,系统会自动连接到运行时。您也可以使用右上角的“连接”按钮。

笔记本执行

点击单元格并使用 Shift-ENTER 可逐个执行单元格。您也可以使用 Runtime > Run all 运行整个笔记本

目录

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

隐藏单元格

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

我们首先看一下神经网络训练。请打开下面的笔记本并运行所有单元格。先不要关注代码,我们稍后会讲解。

keras_01_mnist.ipynb

执行笔记本时,将焦点集中在可视化图表上。有关说明,请参阅下文。

训练数据

我们有一个带标签的手写数字数据集,以便我们知道每张图片代表什么,例如 0 到 9 之间的数字。在笔记本中,您会看到以下摘录:

我们将构建的神经网络将手写数字分为 10 个类别(0、..、9)。它基于需要正确值的内部参数,以便分类正确。此“正确的值”是通过训练过程学习的,该过程需要具有图像和相关正确答案的“已加标签数据集”。

如何知道经过训练的神经网络是否表现良好?使用训练数据集来测试网络是作弊。它在训练期间已多次见过该数据集,毫无疑问,它的表现非常好。我们需要另一个在训练期间从未见过的带标签数据集来评估网络的“现实”性能。这称为“验证数据集”。

训练

随着训练的进行,一次一个批次的训练数据,内部模型参数会更新,模型会越来越好识别手写数字。您可以在训练图中看到它:

右边的“准确率”只是正确识别的数字所占的百分比。随着训练的进行,效果会越来越好。

在左侧,我们可以看到“损失”。为了推动训练,我们将定义一个“损失”函数,它表示系统识别数字的准确性,并尽量减少。您在这里看到,随着训练的进行,训练和验证数据的损失都会下降:这很好。这表示神经网络正在学习。

X 轴表示整个数据集的“周期数”或迭代次数。

预测

训练模型时,我们可以使用它来识别手写数字。下一次可视化会显示使用本地字体(第 1 行)的几位数以及验证数据集的 10000 位数的表现。预测的类别显示在每个数字下方,如果数字不正确,则显示为红色。

如您所见,这种初始模型不太好,但仍然可以正确识别某些数字。其最终验证准确率约为 90%,这对我们开始采用的简化模型而言并没有什么不利之处,但这仍意味着,它会遗漏 10000 中的 1000 个验证数字。可以显示的内容远远不止这些,因此所有答案都显示为错误(红色)。

张量

数据存储在矩阵中。28x28 像素的灰度图片可容纳到 28x28 的二维矩阵中。但对于彩色图片,我们需要更多维度。每个像素有 3 个颜色值(红色、绿色、蓝色),因此需要一个三维表,尺寸为 [28, 28, 3]。此外,为了存储一批 128 张彩色图片,需要使用四维表 [128, 28, 28, 3]。

这些多维表格称为“张量”,其维度列表是它们的“形状”

简述

如果您已知下一个段落中的所有粗体字词,则可以转到下一个练习。如果您刚开始接触深度学习,欢迎学习本文。

女巫.png

对于以层序列构建的模型,Keras 提供 Sequential API。例如,采用三个密集层的图片分类器可以采用 Keras 编写为:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 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, ... )

单个密集层

MNIST 数据集中的手写数字为 28x28 像素的灰度图片。对此类进行分类的最简单方法是使用 28x28=784 像素作为单层神经网络的输入。

2016 年 7 月 26 日屏幕截图,12.32.24.png

神经网络中的每个“神经元”都会对其所有输入加权求和,添加一个名为“偏差”的常量,然后通过一些非线性“激活函数”馈送结果。权重偏差是通过训练确定的参数。它们最初是用随机值初始化的。

上图表示的是具有 10 个输出神经元的 1 层神经网络,因为我们希望将数字分为 10 个类别(0 到 9)。

使用矩阵乘法

处理矩阵图片集的神经网络层如何用矩阵乘法表示:

matmul.gif

使用权重矩阵 W 中的第一列权重,计算第一张图片的所有像素的加权和。该总和对应于第一个神经元。使用第二列权重,我们对第二个神经元执行相同的操作,依此类推,直到第 10 个神经元。然后,我们可以对其余 99 张图片重复此操作。如果我们将 X 称为包含 100 张图片的矩阵,则根据 100 张图片计算的 10 个神经元的所有加权和仅是 X.W,即矩阵乘法。

现在,每个神经元都必须添加自己的偏差(常量)。由于我们有 10 个神经元,因此有 10 个偏差常量。我们将这个包含 10 个值的向量称为 b。必须将其添加到之前计算的矩阵的每一行。我们将使用一个名为“广播”的神奇功能来编写一个简单加号。

最后,我们最终应用了一个激活函数,例如“softmax”(解释如下),并获取描述应用于 100 张图片的 1 层神经网络的公式:

2016 年 7 月 26 日屏幕截图 (16.02.36.png)

在 Keras 中

借助 Keras 等高级神经网络库,我们无需实现此公式。但务必了解,神经网络层只是一些乘法和加法。在 Keras 中,密集层可以写为:

tf.keras.layers.Dense(10, activation='softmax')

深入了解

此操作对链式神经网络层来说无关紧要。第一层计算像素的加权总和。后续层会计算前一层输出的加权和。

除了激活函数的数量外,唯一的区别在于激活函数选择。

激活函数:relu、softmax 和 sigmoid

您通常对除最后一个图层之外的所有其他图层使用“relu”激活函数。在分类器中,最后一层将使用“softmax”激活函数。

同样,“神经元”会计算其所有输入的加权和,添加一个名为“偏差”的值,并通过激活函数提供结果。

对于最热门的线性单元,最常用的激活函数是 REQUU;RELU&quot。如上图所示,这是一个非常简单的函数。

神经网络中的传统激活函数是 “sigmoid”,但“relu”类算法几乎可以实现更好的收敛属性,现在是首选。

用于分类的 Softmax 激活

神经网络的最后一层有 10 个神经元,因为我们想要将手写数字归为 10 个类别 (0,..9)。它应该输出 0 到 1 之间的 10 个数字,表示该数字的概率为 0、1、2 等。为此,在最后一层,我们将使用名为 "softmax" 的激活函数。

对向量应用 softmax 的方法为:对每个元素使用指数,然后对向量进行归一化,通常将其除以“L1”范数(即绝对值总和),这样归一化的值之和可等于 1,从而可解释为概率。

激活前最后一层的输出有时称为 logout。如果向量为 L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9],则:

交叉熵损失

现在,我们的神经网络根据输入图片生成预测,接下来,我们需要衡量它们的效果,即网络告诉我们的内容与正确答案之间的距离(通常称为“标签”)。请记住,数据集中的所有图片都有正确的标签。

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

梯度下降法

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

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

如果我们计算交叉熵相对于所有权重和所有偏差的偏导数,我们会获得一个“梯度”,即针对给定图像、标签、权重和偏差的当前值计算得出的“梯度”。请注意,我们可能有数百万的权重和偏差,因此计算梯度似乎是一项繁重的工作。幸运的是,TensorFlow 帮助我们实现了这一点。渐变的数学属性是,它指向“向上”。因为我们想要前往交叉熵较低的位置,所以我们朝着相反的方向。我们会按照一定比例的梯度更新权重和偏差。然后,我们在训练循环中使用下一批训练图像和标签重复执行相同的操作。希望这能使结果接近交叉熵,尽管这无法保证该最小值的唯一性。

梯度下降法 2.png

小批量处理和动量计算

您可以只计算一张示例图像的梯度,并立即更新权重和偏差,例如,在一批图像中,渐变可更好地表示不同示例图像施加的约束,因此有可能更快地收敛到解决方案。小批次的大小是可调整的参数。

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

然而,收敛可能仍然有点混乱,并且如果渐变矢量全为零,甚至会停止。这是否意味着我们已经确定了一个最低金额?不一定。渐变分量可以为零或最大值为零。对于包含数百万个元素的渐变矢量,如果所有这些元素全都是零,则每个零都对应于最小值,且均不对应到最高点。在许多维度中,马鞍点很常见,我们不想停下来。

插图:马鞍点。渐变为 0,但不是所有方向的最小值。(图像来源Wikimedia:由 Nicoguaro - 拥有作品,CC BY 3.0

解决方案是为优化算法增加一些动力,使其能够顺利渡过马鞍点。

术语库

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

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

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

特征:神经网络的输入有时称为“特征”。算出数据集的哪些部分(或部分组合)要提供给神经网络以获得良好的预测的过程称为“特征工程”。

labels:类别的“类别”或正确答案的正确名称

学习速率:在训练循环的每次迭代中更新权重和偏差的梯度比例。

logit:在应用激活函数之前,神经元层的输出称为“logit”。该术语来自“逻辑函数”(又称“sigmoid 函数”),它过去是最受欢迎的激活函数。“逻辑函数之前的 Neurron 输出”缩短为“logit”

loss - 将神经网络输出与正确答案进行比较的错误函数

neuron:计算输入的加权和,添加偏差,并通过激活函数传递结果。

独热编码:第 3 个类(共 5 个)被编码为 5 个元素的向量,除第 1 个元素(1 除外)之外,所有零都编码。

relu:校正线性单位。一种适用于神经元的热门激活函数。

sigmoid:一种曾经激活的热门函数,在特殊情况下仍然有用。

softmax:一种对矢量进行作用的特殊激活函数,可增大最大分量与所有其他分量之间的差值,并对矢量进行归一化,使总和为 1,以使其可解释为概率矢量。作为分类器的最后一步。

Tensor:一个“张量”就像一个矩阵,但维度数量没有限制。一维张量是一种矢量。二维张量是一个矩阵。然后,就可以有 3 个、4 个、5 个或更多维度了。

返回学习笔记本,这次我们读一下代码。

keras_01_mnist.ipynb

让我们浏览一下笔记本中的所有单元格。

单元格“参数”

此处定义了批次大小、训练周期数以及数据文件的位置。数据文件托管在 Google Cloud Storage (GCS) 存储分区中,因此其地址以 gs:// 开头

单元格“导入”

所有必要的 Python 库都会导入此处,包括 TensorFlow 以及用于可视化的 matplotlib。

单元格可视化实用程序 [RUN ME]

此单元格包含无意义的可视化代码。默认情况下,该代码处于合拢状态,但您可以将其打开,并在双击时查看代码。

单元格 tf.data.Dataset:解析文件并准备训练和验证数据集"

此单元使用 tf.data.Dataset API 从数据文件加载 MNIST 数据集。无需在此单元格上花费太多时间。如果您对 tf.data.Dataset API 感兴趣,请查看以下教程:TPU 速度数据流水线。目前,基础知识如下:

MNIST 数据集中的图片和标签(正确答案)存储在 4 个文件中的固定长度记录中。可以使用专用固定记录函数加载文件:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

现在,我们有了图片字节数据集。它们需要解码为图像。为此,我们定义了一个函数。图片是未压缩的,因此函数无需解码任何内容(decode_raw 基本上什么都不会做)。然后,图像被转换为介于 0 和 1 之间的浮点值。我们可以在此处将其重塑为 2D 图像,但实际上,我们将它保留为大小为 28*28 的平面像素数组,因为这是我们最初的密集层所期望的结果。

def read_image(tf_bytestring):
    image = tf.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

我们使用 .map 将此函数应用于数据集,并获取图片数据集:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

我们对标签执行相同类型的读取和解码,并将图片和标签一起 .zip

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

现在,我们有了一对数据集(图片、标签)。这是我们的模型所期望的结果。我们尚未准备好在训练函数中使用它:

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

tf.data.Dataset API 具有准备数据集所需的所有必要实用函数:

.cache 将数据集缓存在 RAM 中。这是一个小型数据集,因此可以正常使用。.shuffle 使用包含 5000 个元素的缓冲区对其进行重排。请务必妥善安排训练数据。.repeat 将循环播放该数据集。我们将多次对其进行训练(多个周期)。.batch 将多个图片和标签合并为一个迷你图片。最后,.prefetch 可在 CPU 上训练当前批次时使用 CPU 准备下一个批次。

验证数据集的准备工作类似。我们现在可以定义模型并使用此数据集训练模型。

单元格“Keras 模型”

我们的所有模型都将是层的直接序列,因此我们可以使用 tf.keras.Sequential 样式来创建它们。最初,它只是一个密集层。它有 10 个神经元,因为我们将手写数字分为 10 个类别。它使用“softmax”激活函数,因为它是分类器中的最后一层。

Keras 模型还需要知道其输入的形状。tf.keras.layers.Input 可用于定义它。在此示例中,输入向量是长度为 28*28 的像素值的平面矢量。

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

在 Keras 中,使用 model.compile 函数配置模型。我们使用基本优化器 'sgd'(随机梯度下降法)。分类模型需要一个交叉熵损失函数,在 Keras 中称为 'categorical_crossentropy'。最后,我们要求模型计算 'accuracy' 指标,即正确分类的图片所占的百分比。

Keras 提供了一种非常实用的 model.summary() 实用程序,可以输出您创建的模型的详细信息。您的种类教师添加了 PlotTraining 实用程序(在“可视化实用程序”单元格中定义),它将在训练期间显示各种训练曲线。

单元格“训练并验证模型”

训练过程就是通过调用 model.fit 并传入训练数据集和验证数据集完成的。默认情况下,Keras 在每个周期结束时运行一轮验证。

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

在 Keras 中,可以在训练期间使用回调添加自定义行为。这就是为此研讨会实现动态更新的训练图。

单元格“直观呈现预测”

训练模型后,我们可以通过调用 model.predict() 从中获取预测结果:

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

我们准备了一组使用本地字体渲染的打印数字进行测试。请注意,神经网络会从其最终“softmax”返回包含 10 个概率的向量。要获取标签,我们必须找出哪个概率最高。Numpy 库中的 np.argmax 可以执行此操作。

为了理解为什么需要 axis=1 参数,请记住我们处理了一批 128 张图片,因此该模型会返回 128 个概率向量。输出张量的形状为 [128, 10]。我们计算每张图片返回的 10 个概率的 argmax,因此 axis=1(第一个轴为 0)。

这个简单的模型已经可以识别 90% 的数字了。这还不错,但您现在将显著改善。

.godeep.png

为了提高识别准确率,我们将向神经网络添加更多层。

2016 年 7 月 27 日屏幕截图 (15.36.55.png)

我们将 softmax 作为最后一层的激活函数,因为这对分类最有效。不过,对于中间层,我们将使用最经典的激活函数:sigmoid:

例如,您的模型可能如下所示(别忘了逗号,tf.keras.Sequential 接受以英文逗号分隔的图层列表):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

查看模型的“摘要”。现在,该参数至少具有 10 倍参数。效果应该好 10 倍!但出于某种原因,并不是 ...

失落似乎也在屋顶上袭来了。出了点问题。

您刚刚经历了神经网络,因为人们曾经设计过 80 年代和 90 年代的神经网络。难怪他们放弃了这个想法,带来了所谓的“AI 冬季”。事实上,随着层的增加,神经网络变得越来越难收敛。

事实证明,只要提供几个数学脏脏圈技巧,让它们拥有很多层(20、50,甚至今天 100 个),效果就很出色。2010 年代,文艺复兴重新兴起的原因之一就是发现这些简单的技巧。

RELU 激活

Relu.png

S 型激活函数在深度网络中实际上非常有问题。它会压缩所有介于 0 和 1 之间的值,当您重复执行此操作时,神经元输出及其梯度可能会完全消失。历史上提到过它,但现代网络使用 RELU(修正线性单元),如下所示:

而 reL 的导数为 1,至少位于右侧。通过 RELU 激活,即使来自某些神经元的梯度可以为零,总会有其他变体提供明确的非零梯度,并且训练可以继续良好地进行。

更好的优化器

在像这里这样的高度维度空间(约 10K 权重和偏差)中,“马鞍点”非常频繁。这些点不是局部最小值,但其梯度始终为零,且梯度下降法优化器会一直停留在那里。TensorFlow 拥有一整套可用的优化器,其中包括一些具有一定惯性并且能够安全通过马鞍点的优化器。

随机初始化

在训练之前初始化权重偏差这一艺术本身就是一个研究领域,有大量关于该主题的论文发表。您可以在此处查看 Keras 中提供的所有初始化函数。幸运的是,Keras 在默认情况下会执行正确操作,并使用 'glorot_uniform' 初始化程序,在几乎所有情况下,该初始化程序都是最好的。

您无需执行任何操作,因为 Keras 已正确执行的操作。

NaN ???

交叉熵公式涉及对数,log(0) 不是数字(NaN,如果您愿意,也可以是数字崩溃)。交叉熵的输入是否可以为 0?输入来自 softmax,它本质上是一个指数,指数从不为零。我们很安全!

真的吗?在美好的数学世界中,我们可以安心,但在计算机世界中,exp(-150)(以 float32 格式表示)像它一样保持零,并且出现交叉熵崩溃。

幸运的是,您无需执行任何操作,因为 Keras 会处理这一问题,并以一种特别谨慎的方式计算交叉熵和交叉熵,以确保数值稳定性并避免出现可怕的 NaN。

成功?

现在,准确率应该达到了 97%。本次研讨会的目标是大幅提升 99% 以上的水平,所以请再接再厉。

如果遇到问题,目前的解决方法如下:

keras_02_mnist_dense.ipynb

也许我们可以试试更快地训练?Adam 优化器中的默认学习速率为 0.001。让我们尝试提高这一比例。

加快速度似乎并没有什么帮助,但所有噪音都是什么?

训练曲线会非常嘈杂,并会查看两个验证曲线:它们会上下跳跃。这就意味着我们的速度过快。我们可以恢复到之前的速度,但有更好的方式。

放慢速度.png

良好的解决方案是快速开始并以指数方式衰减学习速率。在 Keras 中,您可以通过 tf.keras.callbacks.LearningRateScheduler 回调来执行此操作。

用于复制粘贴的实用代码:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

请务必使用您创建的 lr_decay_callback。将其添加到 model.fit 的回调列表中:

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

这一小变化带来的影响非常惊人。您会发现大部分噪声都消失了,测试准确率现在持续超过了 98%。

现在,该模型看起来已收敛。让我们尝试更深入地探究。

是否有帮助?

实际上,准确率仍然维持在 98%,并且需要考虑验证损失。它正在上升!学习算法仅适用于训练数据,并相应地优化训练损失。它从不会看到验证数据,因此在稍后工作时,不再对验证损失(不再下降甚至有时会反弹)产生影响,这不足为奇。

这不会立即影响模型的现实世界识别能力,但会阻止您运行多次迭代,通常表明训练不再产生积极影响。

Dropout.png

这种断开连接通常称为“过拟合”,当您看到这种情况时,可以尝试应用名为“丢弃”的正则化技术。丢弃技术在每次训练迭代时射击随机神经元。

效果如何?

噪声再次出现(出乎意料地表现下去)。验证损失似乎没有再上升,但总体比没有丢弃要高。验证准确率略有下降。这让我们感到非常失望。

似乎漏失是正确的解决方案,或者“过拟合”是一个更复杂的概念,并且它的一些原因导致无法解决“漏失”问题?

什么是“过拟合”?如果神经网络学习“糟糕”的方式(对训练样本有效果,但对实际数据不太有效),就会发生过拟合。有些正则化技术(例如丢弃)可以强制其以更好的方式学习,但过拟合也有更深层的根源。

overfitting.png

当神经网络针对手头的问题具有过多的自由度时,就会发生基本的过拟合。想象一下,我们拥有太多神经元,网络可以将所有训练图像存储在其中,然后通过模式匹配对其进行识别。它将无法完全处理实际数据。神经网络在一定程度上会受到限制,这样它就必须被迫在训练过程中泛化其学到的知识。

如果训练数据非常少,即使是小型网络也能通过心脏学习来了解数据,您会看到“过拟合”。一般来说,您始终需要大量数据来训练神经网络。

最后,如果您按教养完成所有任务,尝试不同规模的网络,以确保其自由度受到约束,且应用丢弃和基于大量数据进行训练,那么您可能仍陷入无法改进的任何性能水平。这意味着,按照当前形状,神经网络无法从您的数据中提取更多信息,如本例所示。

还记得我们是如何使用图像的,并将其拆分为一个矢量吗?这是个坏主意。手写数字由形状组成,我们在展平像素时舍弃了形状信息。不过,有一种神经网络可以利用形状信息:卷积网络。我们来试一试。

如果遇到问题,目前的解决方法如下:

keras_03_mnist_dense_lrdecay_dropout.ipynb

简述

如果您已知下一个段落中的所有粗体字词,则可以转到下一个练习。如果您刚开始使用卷积神经网络,请继续阅读。

conversionolutional.gif

图示:使用两个连续的过滤器过滤图像,每个过滤器的权重分别为 4x4x3=48。

简单的卷积神经网络在 Keras 中如下所示:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

在卷积网络的层中,一个“神经元”正好对着它上面的像素的加权和(整个图像的一小部分区域)。它添加一个偏差,并通过激活函数提供总和,就像常规密集层中的神经元一样。然后,使用相同的权重对整个图片执行此操作。请注意,在密集层中,每个神经元都有自己的权重。在图中,一个权重“补丁程序”在图片上双向滑动(即“卷积”)。输出包含的值与图片中的像素一样(但边缘需要填充)。是一种过滤操作。在上图中,它使用 4x4x3=48 的权重过滤器。

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

2016 年 7 月 29 日屏幕截图 (16.02.37.png)

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

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

连续卷积,最大池化

通过以 2 或 3 的步长执行卷积,我们还能够以水平维度缩小生成的数据立方体。方法有以下两种:

  • 步长卷积:上面包含滑动滑块,但步长为 1>
  • 最大池化:应用 MAX 操作的滑动窗口(通常在 2x2 补丁程序上,每 2 个像素重复)

图示:将计算窗口滑动 3 像素会使输出值减少。步积卷积或最大池化(在一张步长为 2x2 的窗口上的最大步长为 2)是一种在水平维度中缩小数据立方体的方式。

最后一层

在最后一个卷积层之后,数据将采用“立方体”形式。有两种方法可以将其穿过最终密集层。

第一种方法是将数据块拆分为矢量,然后提供给 softmax 层。有时,您甚至可以在 softmax 层之前添加密集层。权重的开销往往很高。卷积网络末尾的密集层可以包含整个神经网络一半以上的权重。

我们不使用昂贵的密集层,而是将传入的数据“立方体”拆分为具有类别数量的多部分,计算其值,并通过 softmax 激活函数为其提供值。此方式构建分类头需要 0 个权重。在 Keras 中,有一个如下层:tf.keras.layers.GlobalAveragePooling2D()

跳转到下一部分,为当前的问题构建卷积网络。

让我们构建一个用于手写数字识别的卷积网络。我们将在顶部使用三个卷积层,在底部使用传统的 softmax 读数层,并将其与一个全连接层连接:

请注意,第二个和第三个卷积层的步长为 2,这就是为什么它们将输出值的数量从 28x28 减少到 14x14,然后是 7x7。

我们来编写 Keras 代码。

在第一个卷积层之前需要特别注意。实际上,它应该获得 3D“立方体”数据,但我们的数据集目前针对密集层进行了设置,并且图像的所有像素都扁平化为矢量。我们需要将其变形为 28x28x1 的图片(针对灰度图片创建 1 个渠道):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

您可以使用此代码行代替到目前为止的 tf.keras.layers.Input 层。

在 Keras 中,“relu' 激活卷积层”的语法为:

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

对于步长卷积,您可以写出:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

如需将数据立方体扁平化为向量,以供密集层使用,请执行以下操作:

tf.keras.layers.Flatten()

对于密集层,语法没有变化:

tf.keras.layers.Dense(200, activation='relu')

您的模型是否突破了 99% 的准确率障碍?非常接近... 请查看验证损失曲线。这个会响铃吗?

另请查看预测结果。第一次,您应该可以看到 10000 个测试数字中的大部分都可以正确识别。系统只保留大约 41⁄2 行检测错误(共 10000 行,大约 110 位数字)

如果遇到问题,目前的解决方法如下:

keras_04_mnist_convolutional.ipynb

先前的训练有明显的过拟合迹象(准确率仍然达不到 99%)。我们是否应该再次尝试取消?

这次感觉怎么样?

这次似乎漏失了项目。验证损失不再持续增加,最终准确率应高于 99%。恭喜!

第一次尝试应用丢弃时,我们一直认为出现过拟合问题,事实上问题出在神经网络的架构上。如果没有卷积层,我们就无从深入研究,并且对于丢失的情况也毫无办法。

这一次,过拟合似乎是造成问题的原因,漏失实际上也有所帮助。请注意,有许多因素会导致训练和验证损失曲线断开连接,导致验证损失不断上升。过拟合(自由度太大,网络用得不好)只是其中之一。如果您的数据集过小或神经网络的架构不足,您在损失曲线上可能会看到类似的行为,但漏失不会有任何帮助。

最后,我们来尝试添加批量归一化。

实际上,理论上请记住几条规则:

现在,我们使用图书,并在每个神经网络层(但最后一个)中添加批处理范式层。不要将其添加到最后一个 "softmax" 层。对那里没有用。

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

现在的准确度如何?

稍微调整一下(BATCH_SIZE=64,学习速率衰减参数为 0.666,密集层 0.3 上的丢弃率)稍微运气,就可以达到 99.5%。学习率和漏失调整是遵循使用批量范例的“最佳做法”完成的:

  • 批处理规范有助于神经网络收敛,并且通常可让您更快地进行训练。
  • 批处理规范是一种正则化器。您通常可以减少使用的丢弃量,甚至根本不使用丢弃量。

该解决方案笔记本的训练运行率为 99.5%:

keras_05_mnist_batch_norm.ipynb

您可以在 GitHub 上的 mlengine 文件夹中找到支持云技术的版本代码,并了解如何在 Google Cloud AI Platform 上运行该代码。您必须先创建一个 Google Cloud 帐号并启用结算功能,然后才能运行此部分。完成实验所需的资源应少于几美元(假设在一个 GPU 上训练 1 小时)。如需准备帐号,请执行以下操作:

  1. 创建 Google Cloud Platform 项目 (http://cloud.google.com/console)。
  2. 启用结算功能。
  3. 安装 GCP 命令行工具(点击此处查看 GCP SDK)。
  4. 创建一个 Google Cloud Storage 存储分区(位于 us-central1 区域中)。该存储分区将用于暂存训练代码和存储经过训练的模型。
  5. 启用必要的 API 并请求必要的配额(运行一次训练命令,您应该会收到提示您要启用的错误消息)。

您已构建自己的首个神经网络,并训练使其达到 99% 的准确率。在此过程中学到的技术并不是专门针对 MNIST 数据集,实际上它们在处理神经网络时得到了广泛应用。送给朋友的这份礼物是这张卡通图,里面是卡通风格的“悬崖”笔记。您可以用此笔记来记住所学内容:

悬崖边上的 TensorFlow 实验.png

后续步骤

  • 在完全连接和卷积之后,您应了解循环神经网络
  • 为了在分布式基础架构上云端运行训练或推断,Google Cloud 提供了 AI Platform
  • 最后,欢迎您提供反馈意见。如果您认为此实验中出现了问题,或者您认为此实验有待改进,请告诉我们。我们通过 GitHub 问题处理反馈 [反馈链接]。

HR.png

Martin Görner ID 小.jpg

作者:Martin Görner

Twitter:@martin_gorner

本实验版权中的所有卡通图片:alexpokusay / 123RF 图库照片