机器学习实战:模型构建与应用
上QQ阅读APP看书,第一时间看更新

2.3 设计神经网络

现在让我们探索这在代码中的样子。首先看看图2-5中神经网络的设计:

我们在第1章中已经有了一个Sequential模型来表示有很多层。之前它只有一层,而在现在的示例中有多个层。

第一层Flatten不是一层神经元,而是一个输入层规格。我们的输入是28×28像素,的图像,但我们希望它们被视为一系列数值,就像图2-5顶部的灰色框。Flatten把那个“正方形”的值(一个二维数组)变成一条线(一个一维数组)。

下一层Dense是一层神经元,并且我们指定想要128个神经元。这就是图2-5所示的中间层。这些层被描述为隐藏层。输入和输出之间的层无法被调用者看到,因此我们使用“隐藏”一词来描述它们。我们要求这128个神经元的内部参数被随机初始化。通常在这个时候,我会被问到的一个问题是:“为什么是128?”这完全是任意的—关于所使用的神经元的数量并没有固定的规则。当你设计这些层时,需要选择适当的数量以使你的模型能够真正地学习。更多神经元意味着神经网络会运行得更慢,因为它必须学习更多的参数。更多神经元也可能导致一个非常擅长识别训练数据却不擅长识别以前没有见过的数据的网络(这称为过拟合,我们将在本章稍后讨论它)。另一方面,较少的神经元意味着模型可能没有足够的参数来学习。

选择正确的值需要进行一些实验。这个过程通常称为超参数调整。在机器学习中,超参数是用于控制训练的值,而不是训练/学习到的神经元内部值,它们称为参数。

你可能会注意到该层中还指定了一个激活函数。这个激活函数的代码将在层中的每个神经元上都执行。TensorFlow支持很多激活函数,但是在中间层中很常见的一个是relu,它代表整流线性单元。这是一个简单的函数,如果一个值大于0,它返回这个值。在这种情况下,我们不希望将负值传递给下一层,因为这可能会影响求和函数。因此,我们可以简单地用relu来激活层,而不是编写很多if-then代码。

最后还有另一个Dense层,它是输出层。这里有10个神经元,因为有10个类别。每个神经元最终都有一个用来表示输入像素与该类别匹配的概率,因此,我们的工作是确定哪个神经元的值最大。我们可以遍历它们来选择那个值,但是softmax激活函数为我们做到了这一点。

所以,现在当我们训练神经网络时,目标是可以输入一个28×28的像素数组,并且中间层神经元将会有权重和偏差(mc值),它们将一起把这些像素与10个输出值的其中一个进行匹配。

完整的代码

现在我们已经探索了神经网络的架构,下面看一下用Fashion MNIST数据训练一个神经网络的完整代码:

让我们来一步一步地仔细解析这段代码。首先是一个访问数据的捷径:

Keras有许多内置数据集,你可以像这样通过一行代码访问它们。此时你不必处理下载70 000个图像(将它们分别放入训练集和测试集,等等),所有这些只需要一行代码。这个方法已经被通过使用一个名为TensorFlow Datasets的API(https://oreil.ly/gM-Cq)改进了,但是为了减少早期章节中你需要学习的新概念的数量,我们将只使用tf.keras.datasets

我们可以调用load_data方法来返回训练集和测试集,就像这样:

Fashion MNIST被设计成含有60 000张训练图像和10 000张测试图像。因此,从data.load_data返回的结果将为你提供60 000个28×28像素并被称为training_images的数组,以及一个包含60 000个值(0~9)并被称为training_labels的数组。同样,test_images数组将包含10 000个28×28像素的数组,而test_labels数组将包含10 000个值(0~9)。

我们的工作将是用在第1章中将Y拟合到X的方式来匹配训练图像与训练标签。

我们将保留测试图像和测试标签,以便在训练时网络看不到它们。这些数据可用于测试网络在目前为止没有见过的数据上的有效性。

下面的代码可能看起来有点不太寻常:

Python允许你使用这种方式对整个数组进行操作。回想一下,我们的图像中的所有像素都是灰度的,它们的值在0~255之间,除以255可以确保每像素都由0和1之间的一个数字表示。这个过程称为归一化图像。

关于为什么归一化数据更适合训练神经网络的数学原理超出了本书的范围,但请记住,在TensorFlow中训练神经网络时,归一化可以提高性能。当处理没有归一化的数据时,通常你的网络不会学习,而且会出现大量错误。第1章中Y=2X-1的例子不需要对数据进行归一化,因为它很简单,但为了有趣,你可以尝试用不同的X值和Y值训练这个网络,当X非常大的时候,你会发现训练很快就会失败!

接下来,我们定义构成模型的神经网络,如前所述:

当编译模型时,我们指定损失函数和优化器:

这种情况下的损失函数称为稀疏类别交叉熵。它是TensorFlow中内置的大量损失函数库的其中之一。再重复一次,选择使用哪个损失函数本身就是一门艺术,随着时间的推移,你将了解在哪些场景下使用哪些损失函数是最好的。此模型与我们在第1章中创建的模型之间的一个主要区别是:我们不是试图预测一个数字,而是选择一个类别。我们的服装将属于10类服装中的1类,因此使用类别损失函数是可行的方法。稀疏类别交叉熵是一个不错的选择。

这同样适用于选择优化器。adam优化器是我们在第1章中使用的随机梯度下降(sgd)优化器的优化版本,它更快且更有效。当我们处理60 000张训练图像时,任何能得到的性能提升都将有帮助,因此在这里选择它。

你可能会注意到,在这个代码中出现了一行新的代码来指定我们要报告的性能指标。在这里,我们要在训练时报告网络的准确率。第1章中的简单示例仅仅报告了损失,并且我们通过观察损失如何减少可以了解到网络正在学习。对于现在的情况,通过查看准确率来了解网络如何学习对我们更有用—它将返回有多少次它可以将输入像素正确匹配到输出标签。

接下来,我们将通过把训练图像拟合到训练标签来训练网络5个回合:

最后,我们可以做一些新的事情—使用一行代码评估模型。我们有一组10 000张图像和标签用于测试,而且可以将它们传递给训练好的模型让它预测每个图像是什么,将预测结果与图像的实际标签相比较,并把结果相加: