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

2020-07-17 15:44:36

在本代码实验室中,您将学习如何构建和训练识别手写数字的神经网络。在此过程中,当您增强您的神经网络以达到99%的准确率时,您还将发现深度学习专业人员用来高效训练其模型的行业工具。

这个代码室使用的是MNIST数据集,这是一个由6万个标记数字组成的集合,已经让几代博士忙碌了近20年。只需不到100行Python/TensorFlow代码即可解决该问题。

如果您觉得这个实验室有什么不对劲的地方,或者您认为应该改进,请告诉我们。我们通过GitHub问题[反馈链接]处理反馈。

本实验使用Google Colboratory,不需要您进行任何设置。你可以在Chromebook上运行它。请打开下面的文件,并执行单元格以熟悉Colab笔记本。

在Colab菜单中,选择Runtime>;更改运行时类型,然后选择GPU。到运行时的连接将在第一次执行时自动进行,或者您可以使用右上角的";Connect";按钮。

通过单击单元格并使用Shift-Enter键一次执行一个单元格。您还可以使用Runtime>;Run All运行整个笔记本。

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

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

我们先来看一节神经网络训练。请打开下面的笔记本,浏览所有的单元格。暂时不要注意代码,我们稍后会开始解释。

我们有一个手写数字的数据集,这些数字已标记,以便我们知道每张图片代表什么,即0到9之间的数字。在笔记本中,您将看到摘录:

我们将要构建的神经网络将手写数字分类为10类(0,..,9)。它基于内部参数执行此操作,这些内部参数需要具有正确的值才能使分类正常工作。这个正确值是通过培训过程学习的,培训过程需要一个标有图像和相关正确答案的数据集。

我们怎么知道训练好的神经网络运行得好不好呢?使用训练数据集来测试网络将是作弊。在训练期间,它已经多次查看过该数据集,并且在该数据集上的性能肯定非常好。我们需要另一个在训练过程中从未见过的标签数据集来评估网络的真实性能。它被称为验证数据集";

该数据集中有50,000个训练数字。我们在每次迭代中将大小为128的一批数字输入到训练循环中,这样系统在391次迭代之后就会看到所有的训练数字。我们称这是一个新时代。

随着训练的进行,一次一批训练数据,内部模型参数不断更新,模型识别手写数字的能力越来越强。您可以在培训图表上看到它:

在右侧,准确度仅是正确识别数字的百分比。随着训练的进行,它会上升,这很好。

在左边,我们可以看到损失的金额。为了推动培训,我们将定义一个损失函数,该函数表示系统识别数字的糟糕程度,并试图将其降至最低。您在这里看到的是,随着培训的进行,培训和验证数据的损失都会下降:这很好。这意味着神经网络正在学习。

当模型经过训练后,我们可以用它来识别手写数字。下一个可视化显示了它在从本地字体(第一行)呈现的几个数字以及验证数据集的10,000个数字上的执行情况。预测的类别显示在每个数字下面,如果是错误的,则用红色表示。

如你所见,这个最初的型号不是很好,但仍然正确地识别了一些数字。它的最终验证准确率在90%左右,这对于我们开始使用的简单模型来说并不是很糟糕,但这仍然意味着它遗漏了10000个验证数字中的1000个。这是可以显示的更多内容,这就是为什么看起来所有答案都是错误的(红色)。

数据存储在矩阵中。28x28像素灰度图像适合28x28二维矩阵。但是对于彩色图像,我们需要更多的维度。每个像素有3个颜色值(红、绿、蓝),因此需要一个具有维度[28,28,3]的三维表。并且为了存储一批128个彩色图像,需要具有维度[128,28,28,3]的四维表。

这些多维表格被称为张量,它们的维度列表就是它们的形状。

如果您已经知道下一段中所有粗体的术语,您可以进入下一个练习。如果您刚刚开始深度学习,那么欢迎您,请继续阅读。

神经网络分类器由多层神经元组成。对于图像分类,这些层可以是密集的,或者更常见的是卷积层。它们通常使用REU激活功能来激活。最后一层使用与类一样多的神经元,并由Softmax激活。对于分类,交叉熵是最常用的损失函数,将一热编码标签(即正确答案)与神经网络预测的概率进行比较。为了将损失降到最低,最好选择一个有动量的优化器,例如ADAM,并对批量的训练图像和标签进行训练。

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

Model=tf.keras.Sequential([tf.keras.layers.flatten(input_Shape=[28,28,1]),tf.keras.layers.Dense(200,activate=";relu";),tf.keras.layers.Dense(60,activate=";relu";),tf.keras.layers.Dense(10,activate=';Softmax&#。凯拉斯称其为#34;编译";模型。编译(优化器=#39;亚当';,损失=#39;分类_交叉标记=#39;,指标=[';精度#39;])#%的正确答案#训练模型.fit(数据集,...)

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

神经网络中的每个神经元对其所有输入进行加权求和,添加一个称为偏差的常数,然后通过某个非线性激活函数反馈结果。权重和偏差是将通过培训确定的参数。它们首先用随机值初始化。

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

下面是如何用矩阵乘法表示处理图像集合的神经网络层:

使用权重矩阵W中的第一列权重,我们计算第一个图像的所有像素的加权和。这个和相当于第一个神经元。使用第二列权重,我们对第二个神经元进行同样的操作,以此类推,直到第10个神经元。然后,我们可以对其余99张图像重复该操作。如果我们称X为包含100个图像的矩阵,那么在100个图像上计算的10个神经元的所有加权和都是简单的X.W,即矩阵乘法。

每个神经元现在必须加上它的偏置(一个常数)。因为我们有10个神经元,所以我们有10个偏置常数。我们将这个10个值的向量称为b。它必须加到前面计算的矩阵的每一行上。使用一种名为广播的魔术,我们将用一个简单的加号来写这段代码。

广播是Python和其科学计算库Numpy中使用的标准技巧。它扩展了常规运算在维数不相容的矩阵上的工作方式。";广播添加意味着";如果您要添加两个矩阵,但因为它们的尺寸不兼容而无法添加,请尝试尽可能多地复制较小的矩阵以使其正常工作。";

最后,我们应用激活函数,例如Softmax&34;(解释如下),并获得描述应用于100幅图像的1层神经网络的公式:

有了像Kera这样的高级神经网络库,我们就不需要实施这个公式了。然而,重要的是要理解,神经网络层只是一堆乘法和加法。在KERAS中,致密层将写为:

链接神经网络层是微不足道的。第一层计算像素的加权和。后续层计算前几层输出的加权和。

除了神经元的数量,唯一的区别将是激活函数的选择。

您通常会对除最后一层之外的所有层使用激活函数。分类器中的最后一层将使用Softmax;激活。

同样,神经元计算其所有输入的加权和,添加称为偏差的值,并通过激活函数反馈结果。

最流行的激活函数被称为整流线性单元的RELU&34;。这是一个非常简单的函数,如上图所示。

在神经网络中,传统的激活函数是Sigmoid函数,但是RELU函数几乎处处都具有更好的收敛特性,现在是首选。

我们的神经网络的最后一层有10个神经元,因为我们想把手写数字分成10个类别(0,..9)。它应该输出10个介于0和1之间的数字,表示此数字为0、1、2的概率,依此类推。为此,在最后一层,我们将使用一个名为";Softmax";的激活函数。

在向量上应用Softmax是通过取每个元素的指数,然后对向量进行归一化来完成的,通常通过将其除以其范数(即绝对值的和),使得归一化值加起来为1,并且可以被解释为概率。

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

为什么将";Softmax";称为Softmax?指数是一个急剧增加的函数。这将增加神经元输出之间的差异。然后,当您规格化向量时,主宰范数的最大元素将被规格化为接近1的值,而所有其他元素最终将除以一个较大的值,并规格化为接近0的值。生成的向量清楚地显示了获胜的类,最大值,但保留了其值的原始相对顺序,因此是软的。

既然我们的神经网络从输入图像中产生预测,我们就需要衡量它们有多好,也就是网络告诉我们的和正确答案之间的距离,通常被称为“标签”。请记住,我们对数据集中的所有图像都有正确的标签。

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

一对一编码表示您使用包含10个值的矢量表示标签";这是数字3";,除第三个值为1外,其余均为零。此矢量表示成为数字3";的概率为100%。(";1-HOT&34;编码表示您使用10个值的矢量表示";数字3";的概率为100%,除第三个值为1外,其余均为零)。我们的神经网络还将其预测输出为10个概率值的向量。它们很容易比较。

训练神经网络实际上意味着使用训练图像和标签来调整权重和偏差,以最小化交叉熵损失函数。下面是它的工作原理。

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

如果我们计算交叉熵相对于所有权重和所有偏差的偏导数,我们就得到了针对给定图像、标签和权重和偏差的现值计算的梯度。记住,我们可能有数百万的权重和偏差,所以计算梯度听起来像是一项很大的工作。幸运的是,TensorFlow为我们做到了这一点。渐变的数学特性是它向上指向。因为我们想去交叉熵低的地方,所以我们走相反的方向。我们通过渐变的一小部分来更新权重和偏差。然后,我们在训练循环中使用下一批训练图像和标签一次又一次地做同样的事情。希望这会收敛到交叉熵最小的地方,尽管没有什么能保证这个最小值是唯一的。

学习速率";:您不能在每次迭代中根据整个渐变长度来更新权重和偏差。这就像穿着七个联赛的靴子试图到达谷底一样。你会从山谷的一边跳到另一边。要达到底部,您需要执行较小的步骤,即仅使用渐变的一小部分,通常在1/1000的范围内。这部分被称为学习率。

您可以只在一个示例图像上计算渐变,并立即更新权重和偏移,但对一批(例如,128个)图像执行此操作会产生一个更好地表示不同示例图像施加的约束的渐变,因此可能会更快地收敛到解决方案。小批量的大小是一个可调参数。

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

但是,收敛仍然可能有一点混乱,如果梯度向量全为零,它甚至可能停止。这是不是意味着我们已经找到了最低限度?不总是这样。梯度分量在最小值或最大值上可以为零。对于一个有数百万个元素的梯度向量,如果它们都是零,那么每个零都对应于一个最小值,并且没有一个对应于最大值的概率是相当小的。在一个多维的空间里,鞍点是很常见的,我们不想止步于它们。

插图:鞍点。渐变为0,但不是所有方向的最小值。(图像归属维基媒体:Nicoguaro-Owner Work,CC by 3.0)。

解决方案是给优化算法增加一些动量,这样它就可以不间断地驶过鞍点。

TensorFlow库提供了一整套优化器,从基本的梯度下降tf.keras.Optimizers.sgd开始,现在它有一个可选的动量参数。具有内置势头的更高级的流行优化器是tf.keras.Optimizers.RMSprop或tf.keras.Optimizers.Adam。

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

致密层:神经元的一层,每个神经元都与前一层中的所有神经元相连。

特征:神经网络的输入有时被称为特征。计算出数据集的哪些部分(或部分的组合)应该输入神经网络以获得良好预测的技术称为特征工程。

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

LOGITS:在应用激活函数之前的一层神经元的输出称为LOGITS。这一术语来自逻辑函数,也就是“逻辑函数”(Logistic Function";又名“Log Function”)。Sigmoid函数曾经是最流行的激活函数。逻辑函数&34;之前的神经元输出缩短为";logits";。

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

一次热编码:5个类中的3个被编码为5个元素的向量,除了第3个元素是1之外,所有的元素都是0。

Sigmoid:另一种激活功能,过去很流行,在特殊情况下仍然有用。

Softmax:一种特殊的激活函数,作用于向量,增加最大分量与所有其他分量之间的差异,并将向量归一化为和为1,以便可以将其解释为概率向量。用作量词的最后一步。

张量:张量就像一个矩阵,但维数是任意的。一维张量是一个向量。二维张量是一个矩阵。然后你可以有3,4,5或更多维的张量。

本节的任务是阅读并理解代码,以便以后可以改进它。

这里定义了数据文件的批量大小、训练周期数和位置。数据文件托管在Google Cloud Storage(GCS)存储桶中,这就是它们的地址以gs://开头的原因。

所有必要的Python库都导入到这里,包括TensorFlow和用于可视化的matplotlib。

此单元格包含无趣的可视化代码。它在默认情况下是折叠的,但您可以打开它,在有时间的时候双击它来查看代码。

您必须运行此单元格(Shift-Enter),否则将不会解析其中定义的函数,并且稍后会出现错误。

此单元使用tf.data.Dataset API从数据文件加载MNIST数据集。没有必要在这个单元格上花费太多时间。如果您对tf.data.Dataset API感兴趣,这里有一个教程对其进行了解释:TPU速度数据管道。目前,基本情况是:

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

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

Def read_image(Tf_Bytestring):image=tf.io.decode_raw(tf_bytestring,tf.uint8)image=tf.cast(image,tf.float32)/256.0 image=tf.reshape(image,[28*28])返回image。

我们使用.map将此函数应用于数据集,并获得图像的数据集:

我们对标签执行相同类型的读取和解码,并将图像和标签压缩在一起:

现在我们有了配对(图像、标签)的数据集。这正是我们的模型所期望的。我们还没有完全准备好在培训功能中使用它:

.cache在RAM中缓存数据集。这是一个很小的数据集,所以它可以工作。.shffle使用包含5000个元素的缓冲区对其进行置乱。很重要的一点是,训练数据要被很好地洗牌。.Repeat循环数据集。我们将就此进行多次培训(多个纪元)。.Batch将多个图像和标签放在一起,形成一个迷你抓图。最后,当当前批处理正在GPU上训练时,.prefetch可以使用CPU准备下一批处理。

验证数据集是以类似的方式准备的。我们现在准备好了

.