Logo
Section 1 / 6

准备数据

了解神经网络的准备步骤,包括数据处理、调整输入变量的范围以及对它们进行标准化。

导入

准备数据

了解神经网络的准备步骤,包括数据处理、调整输入变量的范围以及对它们进行标准化。

修订神经网络

我们用了若干章节来构建一个神经网络,并用更多章节深入探讨了其细节。在本章中,我们将精益求精,力求在 MNIST 数据集上达到 99% 的准确率。为了实现这一目标,我们将采用一种迭代过程,这正是机器学习领域中的“开发”模式。事实上,我们可以称之为 机器学习开发(ML development)。

与软件开发类似,完整的机器学习开发流程远比本章或本课程所能涵盖的内容更加复杂。它涉及具备不同技能的人员,从数学家到工程师不等。即便是工程部分的工作,也不仅仅是构建和调参神经网络那么简单。现实中的机器学习系统通常是复杂的流水线,由多个算法和服务组成。更困难的是,机器学习开发既是一门科学,也是一门艺术:它需要丰富的经验、合理的猜测以及一定的运气。

现在我们开始实践。我们将简要了解机器学习开发的核心流程,重点关注以下三个环节:

  1. 使用神经网络解决我们的任务。首先,我们需要为网络准备数据集。例如,我们会对输入变量进行缩放,使其更适合网络处理。
  2. 其次,进入开发循环。在每一步中,通过调整超参数(如学习率 lr、批量大小等)来提升网络的准确率。
  3. 最后,在整个流程结束时,使用测试集对模型进行最终评估。

在此过程中,请记住 “过拟合的威胁”一节 中介绍的测试策略。我们有三组样本:训练集、验证集 和 测试集。目前我们不会使用测试集,直到开发流程最后才进行最终测试。而在开发过程中,我们将使用验证集来评估网络性能。事实上,验证集有时也被称为开发集(dev set),正是因为它用于开发阶段的调优。

...
现在我们有了计划。让我们开始行动,看看能否接近那个 99% 的目标。

处理数据

如同编程一样,机器学习工作中也有不那么光鲜但耗时更多的部分。对于机器学习而言,这部分工作通常就是 数据准备。

以 MNIST 数据集为例,有人必须收集并扫描六万个手写数字。他们很可能还人工检查了所有这些数字,剔除那些不能代表真实书写情况的样本(比如过于模糊的图像)。他们还需要将图像居中、裁剪,并统一调整为相同的分辨率,同时避免出现锯齿等图形伪影。此外,还需对所有图像进行亮度和对比度的标准化处理,确保从纯白到纯黑具有一致的表现。最后但同样重要的是,每个样本都需要打上标签,并反复核对以排除标注错误。

幸运的是,MNIST 的这些繁琐工作已经由他人完成,我们无需重复。但我们仍可以进一步优化 MNIST 数据,使其更适配我们的神经网络。

数据预处理本身就是一个复杂的任务。在这里,我们将学习几种常用的技术,并直观理解它们的作用。

检查输入变量的范围

在将数据输入神经网络之前,最好确保所有输入变量处于相似的数量级范围内。试想一下,如果一个输入变量的取值范围是 0 到 10,而另一个是 1,000 到 2,000,即使两者对预测标签的重要性相同,后者由于数值更大,会对损失函数贡献更多。结果是网络会倾向于优先降低大数值变量带来的损失,而忽略小数值变量。

为了避免这个问题,我们可以将变量缩放到相近的范围。这种操作称为 特征缩放(feature scaling),其中“特征”只是“输入变量”的另一种说法。在我们的 MNIST 示例中,不需要特别做特征缩放,因为所有变量都是单字节像素值,范围固定在 0 到 255 之间。

然而,即便输入变量范围相近,我们也希望这个范围不要包含过大的数值。向网络输入大数会导致网络内部激活值也变得很大。正如我们在 “死亡神经元”一节 中所学,当输入值接近零时,神经网络表现更好,因为此时激活函数(如 S 型函数)处于最敏感的工作区间。

如果我们的输入变量分布过广(例如从 $−10,000$ 到 $+10,000$),或中心偏移(例如从 $10,000$ 到 $10,100$),我们就应该对其进行平移和缩放,使其数值较小且以零为中心。

下面我们来看一种常见的处理方法:

重点提示
重点提示

标准化输入变量

为了让输入变量接近零,机器学习从业者通常会对它们进行标准化。标准化(Standardization) 对不同的人可能略有不同的含义,但最常见的定义是:将输入重新缩放,使其均值为 0,标准差为 1。

如果你还不熟悉,标准差是用来衡量数据离散程度的指标。标准差越小,说明数据越集中在平均值附近。例如人类身高标准差较低,因为没有人比其他人高出几百倍;而植物高度的标准差较高,因为植物可以从苔藓般矮小到红杉般高大。

快速标准化一组输入的方法是:减去其均值,再除以其标准差:

1standardized_inputs = (inputs - np.average(inputs)) / np.std(inputs)

这样处理后的数据 均值为零,且不会远离零点。

以下是更新版的 mnist.py,其中包含了对数据集的标准化处理:

1def standardize(training_set, test_set): 2 average = np.average(training_set) 3 standard_deviation = np.std(training_set) 4 training_set_standardized = (training_set - average) / standard_deviation 5 test_set_standardized = (test_set - average) / standard_deviation 6 return (training_set_standardized, test_set_standardized) 7 8# X_train/X_validation/X_test: 60K/5K/5K 图像 9# 每张图像包含 784 个元素(28 * 28 像素) 10X_train_raw = load_images("/programming-machine-learning/data/mnist/train-images-idx3-ubyte.gz") 11X_test_raw = load_images("/programming-machine-learning/data/mnist/t10k-images-idx3-ubyte.gz") 12X_train, X_test_all = standardize(X_train_raw, X_test_raw) 13X_validation, X_test = np.split(X_test_all, 2)

我们可以单独标准化每个输入变量,也可以整体一起标准化。由于 MNIST 中的所有输入变量规模相近,上述函数选择整体标准化。它使用 NumPy 计算训练集的均值和标准差,然后应用如下公式:

1standardized-values = (original-values - average) / standard_deviation

最后四行代码加载两个 MNIST 数据集,进行 标准化,并将测试数据拆分为验证集和测试集,这与前一章的做法一致。我们需要理解 standardize() 函数的行为,因为这是初学者常犯错误的地方:

  1. 函数仅基于训练集计算均值和标准差,因为我们只应使用训练集的信息来进行训练。如果在计算中引入验证集或测试集,就会导致信息泄露,影响模型泛化能力。
  2. 另一方面,在计算出这些参数后,函数会用它们来标准化全部三个数据集:训练集、验证集和测试集。这是因为我们希望这三个集合具有相同的分布特性,否则模型从训练切换到测试时可能出现性能下降。将来若将模型部署到生产环境,也需要用同样的均值和标准差对新数据进行标准化。

总结:仅在训练集上计算均值和标准差,然后用它们来标准化整个数据集。这对初学者来说可能有些难以理解,但这是标准做法。

现在我们已经完成了 MNIST 的标准化处理。接下来验证一下这样做是否值得。

实际应用中的标准化效果

我们来看看标准化对神经网络准确率的影响。下面的代码将网络运行两次:一次使用原始 MNIST 数据,另一次使用标准化后的 MNIST 数据。每种配置运行两个 epoch,批量大小为 60。由于 MNIST 训练集包含 60,000 个样本,因此每个 epoch 包含 1,000 次迭代:

No configuration found for ID: 6bc37afbf696837c62edb158e3b9fe0b2a8d2d4ced46106e6bbb7946750c761e

经过几十分钟的计算后,结果如下:

原始 MNIST:

0-0 > 损失:2.28073164,准确率:18.90%
…
RuntimeWarning: overflow encountered in exp
…
1-999 > 损失:0.41537869,准确率:84.60%

标准化 MNIST:

0-0 > 损失:2.26244033,准确率:17.96%
…
1-999 > 损失:0.21364970,准确率:91.80%

我们不知道如果继续训练数天会发生什么,但这次短期实验给出了有力证据:使用标准化 MNIST 训练的网络明显更准确。此外,原始 MNIST 在训练过程中出现了指数溢出警告,而标准化版本则没有。这表明较小的输入值确实提升了网络的数值稳定性,这也符合理论预期。

因此,从现在起,我们将使用标准化版本的 MNIST 数据集。

判别机

调整超参数