跳转至

第 11 章:实践方法论(Practical Methodology)

本章引言

成功地把深度学习技术应用到具体任务上,仅有"知道存在哪些算法以及它们的工作原理"是不够的。一个好的机器学习实践者还需要知道如何为特定应用选择算法、监控实验反馈并做出反应,以便改进系统。在日常开发中,实践者必须决定:是收集更多数据、增减模型容量、增减正则化、改进模型优化、改进近似推断、还是调试软件的实现。所有这些操作至少都很耗时,因此关键在于先确定正确的行动方向、而不是盲目猜测。本书大部分内容都在讨论不同的机器学习模型、训练算法与目标函数,这可能给读者造成"成为机器学习专家的最重要条件是知道大量算法并擅长多种数学"的印象。实际经验表明,对于某个常见算法的一个正确应用,往往比一个冷门算法的草率应用效果好得多。正确地应用一个算法需要掌握一些相对简单的方法论。本章许多建议改编自 Ng (2015)。作者推荐下列实际设计流程:① 确定目标——使用哪种误差度量、目标值是多少,这些目标和误差度量应当由应用要解决的问题驱动;② 尽快搭建一个可工作的端到端 pipeline,包括对合适性能度量的估计;③ 对系统做好仪表化(instrument),找出性能瓶颈,诊断是哪些组件表现不及预期——是过拟合、欠拟合,还是数据 / 软件缺陷;④ 在仪表化所揭示的具体发现的基础上,反复做增量改动:收集新数据、调超参数、换算法。

作为贯穿全章的运行示例,作者使用街景门牌号转录系统(Street View address number transcription, Goodfellow et al., 2014d)。该应用目的是为 Google Maps 添加建筑物。街景车拍摄建筑照片并记录 GPS 坐标,一个卷积网络识别照片中的门牌号,让 Google Maps 数据库能在地图的正确位置添加该地址。开发这个商业应用的故事提供了如何遵循本章所倡导的设计方法论的实例。

11.1 性能度量(Performance Metrics)

确定目标——使用哪种误差度量——是必要的首要步骤,因为误差度量会指导你之后所有的动作。你还应当对期望达到的性能水平有一个大致概念。

对大多数应用而言,绝对零误差是无法达到的。Bayes 误差定义了即便拥有无穷训练数据、可以恢复真实概率分布时所能达到的最小误差率。其原因要么是输入特征可能不包含输出变量的完整信息,要么是系统本身具有内禀的随机性。此外,训练数据量有限也构成限制。

训练数据量有限的原因多种多样。当目标是构建最佳的现实产品或服务时,原则上可以收集更多数据,但必须权衡进一步降低误差的收益与收集更多数据的代价——数据收集会耗费时间、金钱,或带来人的痛苦(例如当数据收集过程涉及侵入性的医学检验)。当目标是在某个固定基准(benchmark)上回答"哪个算法表现更好"这一科学问题时,基准本身通常已规定了训练集,并且不允许你收集更多数据。

怎样确定一个合理的期望性能?通常在学术环境下,可以基于已发表的基准结果给出一个可达到误差率的估计。在实际场景下,对一个应用要安全、划算、对消费者有吸引力所需的误差率往往也有粗略概念。一旦确定了实际期望的误差率,设计决策就会以"达到该误差率"为指引展开。

除目标性能值之外,另一个重要考虑是选择哪种度量指标。对于一个完整的含机器学习组件的应用,通常会使用几种不同的性能度量来评估整体效果。这些性能度量一般不同于训练模型所用的代价函数。如 5.1.2 节所述,常见做法是度量系统的准确率(accuracy)或等价的错误率(error rate)。

但许多应用要求更高级的度量。有时犯一种错误的代价比另一种高得多。例如邮件反垃圾系统可能犯两种错误:把合法邮件误判为垃圾、把垃圾邮件放进收件箱。前者的代价比后者大得多。这时与其度量反垃圾分类器的错误率,不如度量某种"总代价"——屏蔽合法邮件的代价要高于放过垃圾邮件的代价。

有时我们希望训练一个二元分类器来检测某个罕见事件。例如设计一个针对罕见疾病的医学检验。假设每百万人中只有 1 人患该病。我们可以让分类器恒定输出"未患病"以轻松达到 99.9999% 的准确率。显然对这种系统来说,准确率不是衡量性能的好方式。一种解决方法是改为度量查准率(precision)和查全率(recall)。查准率是模型报告的"是事件"中被正确识别的比例;查全率是所有真实事件中被检测出来的比例。一个对所有人都说"未患病"的检测器查准率完美、查全率为零;一个对所有人都说"患病"的检测器查全率完美、查准率等于患病的比例(本例为 0.0001%)。使用 precision 和 recall 时,常以 precision 为纵轴、recall 为横轴绘制 PR 曲线。分类器输出一个分数,分数越高表示"待检测事件"越可能发生。例如一个为疾病检测设计的前馈网络输出 \(\hat{y}=P(y=1\mid x)\),估计具有特征 \(x\) 的人患病的概率。我们选择在该分数超过某阈值时报告检出。通过改变阈值,可以用 precision 换取 recall。在很多情况下希望用单数概括分类器的性能。一种做法是把 precision \(p\) 和 recall \(r\) 合并为 F-score

\[ F = \frac{2pr}{p+r} \]

另一种做法是报告 PR 曲线下面积。

在某些应用中,机器学习系统可以拒绝做决策。当机器学习算法能估计对决策应有的置信度时这特别有用——尤其当错误决策可能有害、且人类操作员可以偶尔接管时。街景转录系统提供这种情形下的一个例子:任务是从照片转录门牌号以把拍照地点和地图上的正确地址关联起来。如果地图不准确,其价值就会大大降低,所以只有当转录正确时才能加入地址。如果机器学习系统认为自己转录正确的概率比人低,最佳行动就是让人类来转录这张照片。当然,机器学习系统只有在能大幅减少人类操作员需要处理的照片数量时才有价值。这种情形下一种自然的性能度量是 coverage(覆盖率)。coverage 是机器学习系统能够给出响应的样本所占比例。可以在 coverage 和 accuracy 之间做权衡:对所有样本都拒绝处理可以达到 100% accuracy,但 coverage 降到 0%。对街景任务而言,项目目标是在保持 95% coverage 的同时达到人类水平的转录准确率。该任务的人类水平性能是 98% accuracy。

还有许多其他度量,例如点击率、用户满意度调查等。许多专门的应用领域还有特定的应用准则。重点是提前确定要改进哪种性能度量,然后专注于改进它。没有清晰定义的目标,很难判断对机器学习系统的改动是否带来进步。

11.2 默认基线模型(Default Baseline Models)

选定性能度量与目标之后,任何实际应用的下一步是尽快搭建一个合理的端到端系统。本节给出在不同情形下作为首个基线方法应该使用哪些算法的建议。要记住,深度学习研究进展迅速,因此更好的默认算法很可能在本书写作后不久出现。

根据问题的复杂度不同,你甚至可以一开始就不使用深度学习。如果问题有可能通过正确选择少量线性权重就解决,你或许希望从一个简单的统计模型如 logistic 回归开始。如果你知道问题属于"AI-complete"类(目标识别、语音识别、机器翻译等),则你很可能从一个合适的深度学习模型起步会得到好结果。

首先,根据数据的结构选择模型的大类。如果要做输入是定长向量的监督学习,使用全连接层的前馈网络。如果输入有已知的拓扑结构(例如输入是图像),使用卷积网络。在这些情形下,应使用某种分段线性单元(ReLU 或其泛化如 Leaky ReLU、PreLU、maxout)作为起点。如果输入或输出是序列,使用门控循环网络(LSTM 或 GRU)。

优化算法的一个合理选择是带动量的 SGD、学习率衰减(常见衰减方案在不同问题上各有优劣:线性衰减直到固定最小学习率、指数衰减、或在验证误差平台期把学习率除以 2-10)。另一个非常合理的备选是 Adam。Batch normalization 可以对优化性能有显著影响,特别是对卷积网络和带 sigmoid 非线性的网络。虽然在最初基线中省略 batch normalization 是合理的,但如果优化出现问题,就应当迅速引入。

除非训练集包含上千万个或更多样本,否则一开始就应该加入一些温和的正则化。Early stopping 几乎应当普遍使用。Dropout 是一种出色且易实现的正则化手段,与许多模型和训练算法兼容。Batch normalization 有时也能降低泛化误差,并允许省略 dropout,因为它在估计每个变量归一化统计量时引入的噪声也起到正则化作用。

如果你的任务与某个已被广泛研究的任务相似,你很可能可以先把在该已知任务上表现最好的模型和算法搬过来用,甚至可以拷贝一个已训练好的模型。例如使用在 ImageNet 上预训练的卷积网络的特征去解决其他视觉任务是常见做法(Girshick et al., 2015)。

一个常见问题是是否一开始就用无监督学习(在第三部分进一步讨论)。这有领域依赖性。已知某些领域(如自然语言处理)从无监督学习中受益巨大,例如学习无监督的词嵌入。在另一些领域(如计算机视觉),目前的无监督学习并不带来收益——除非在标注样本极少时的半监督设定下(Kingma et al., 2014; Rasmus et al., 2015)。如果你的应用位于已知无监督学习重要的场景,则把它纳入首个端到端基线。否则,仅当任务本身是无监督的,才在第一次尝试中使用无监督学习。后续如果观察到基线过拟合,可以再尝试加入无监督学习。

11.3 是否要收集更多数据(Determining Whether to Gather More Data)

在首个端到端系统建立起来之后,就该度量算法的性能并决定如何改进它。许多机器学习新手倾向于通过尝试多种不同算法来改进。然而通常收集更多数据比改进学习算法有效得多。

怎样决定是否收集更多数据?首先要判断在训练集上的性能是否可以接受。如果训练集上的性能很差,说明学习算法没有用好已有的训练数据,此时没有理由收集更多数据;相反,应当尝试增大模型的规模——加入更多层、给每层加入更多隐藏单元——也可以尝试改进学习算法,例如调学习率超参数。如果使用大模型和仔细调好的优化算法效果仍不好,那么问题可能在训练数据的质量上。数据可能噪声太大,或者没有包含预测目标输出所需的正确输入。这种情况下建议推倒重来,收集更干净的数据或更丰富的特征集。

如果训练集上性能可以接受,则在测试集上度量性能。如果测试集上性能也可接受,那就没有什么需要做的。如果测试集性能远差于训练集性能,则收集更多数据是最有效的方案之一。关键考量包括:收集更多数据的代价与可行性、通过其他手段降低测试误差的代价与可行性、要把测试集性能显著提升预计需要多少数据。在拥有百万或亿级用户的大型互联网公司中,收集大数据集是可行的,做这件事的代价可能远低于其他选择,所以几乎总是"收集更多训练数据"。例如,大型标注数据集的开发是攻克目标识别问题最重要的因素之一。在其他场景(如医学应用)中,收集更多数据可能代价高昂或不可行。一种收集更多数据的简单替代是缩减模型规模或改进正则化,如调整 weight decay 系数等超参数,或加入 dropout 等正则化策略。如果发现即便调了正则化超参数,训练与测试性能之间的差距仍不可接受,则建议收集更多数据。

决定是否收集更多数据时,还需要决定收集多少。绘制训练集大小与泛化误差关系的曲线很有帮助(如图 5.4 所示)。通过外推这类曲线,可以预测还需要多少额外的训练数据才能达到某个性能水平。通常,少量增加样本总数对泛化误差不会有显著影响。建议在实验中对训练集大小做对数尺度上的实验,例如相邻实验之间样本数翻倍。

如果收集远多数据不可行,改善泛化误差的唯一途径就是改进学习算法本身——而这就属于研究领域、而非应用实践者的建议范畴了。

11.4 选择超参数(Selecting Hyperparameters)

大多数深度学习算法都带有很多控制算法行为各方面的超参数。部分超参数影响算法运行的时间和内存开销;部分超参数影响训练过程所恢复模型的质量以及模型在部署到新输入时做出正确推断的能力。

选择超参数有两条基本路线:手动选择与自动选择。手动选择要求理解超参数的作用以及机器学习模型如何实现良好泛化。自动超参数选择算法大大降低了对这些概念的理解需求,但通常计算代价昂贵得多。

11.4.1 手动超参数调节(Manual Hyperparameter Tuning)

要手动设定超参数,必须理解超参数、训练误差、泛化误差和计算资源(内存与运行时间)之间的关系。这要求在第 5 章有关学习算法有效容量的基本思想上有扎实基础。

手动超参数搜索的目标通常是在某个运行时间与内存预算下找到最低的泛化误差。本书不讨论各种超参数对运行时间与内存的影响,因为高度依赖具体平台。

手动超参数搜索的主要目标是调整模型的有效容量以匹配任务的复杂度。有效容量受三个因素约束:模型的表示容量、学习算法成功最小化训练代价函数的能力、代价函数与训练过程对模型施加的正则化程度。层数更多、每层隐藏单元更多的模型具有更高的表示容量——它能表示更复杂的函数。但它未必能真正学到所有这些函数——如果训练算法无法发现"某些函数能很好地最小化训练代价",或者 weight decay 等正则化项禁止了其中某些函数。

把泛化误差画成某个超参数的函数时通常呈 U 形曲线(如图 5.3)。一端超参数值对应低容量,泛化误差偏高,因为训练误差就高(这是欠拟合区域)。另一端超参数值对应高容量,泛化误差偏高,因为训练与测试误差之间的差距大。曲线中段是最优模型容量——通过把中等泛化差距加到中等训练误差上,达到最低可能泛化误差。

对某些超参数,过拟合发生在超参数值较大时。每层隐藏单元数就是一个例子,因为增加隐藏单元数会提高模型容量。对另一些超参数,过拟合发生在超参数值较小时。例如 weight decay 系数最小允许值 0 对应学习算法的最大有效容量。

并非每个超参数都能探索完整 U 形曲线。许多超参数是离散的——如每层单元数、maxout 单元的线性段数——只可能沿曲线访问少数点。有些超参数是二值的——通常作为开关,指定是否使用学习算法的某个可选组件(如对输入特征做均值减除再除以标准差的预处理)。这些超参数只能探索曲线上两个点。其他超参数有最小或最大允许值,使其无法探索曲线的某部分。例如 weight decay 系数最小是 0。这意味着如果 weight decay 为 0 时模型在欠拟合,我们也无法仅通过修改 weight decay 系数进入过拟合区。换言之,某些超参数只能减少容量。

学习率或许是最重要的超参数。如果只来得及调一个超参数,就调学习率。它以比其他超参数更复杂的方式控制模型的有效容量——模型有效容量在"学习率对优化问题合适"时最高,而不是在学习率特别大或特别小时。学习率对训练误差呈 U 形曲线(如图 11.1)。学习率过大时,梯度下降可能反而增大训练误差;在理想二次情形下,当学习率至少是最优值的两倍时会发生这种情况(LeCun et al., 1998a)。学习率过小时,训练不仅更慢,还可能永久卡在较高的训练误差处——这种现象至今没有被很好地理解(对凸损失函数不会发生)。

调节学习率以外的参数需要同时监控训练与测试误差,以判断模型过拟合还是欠拟合,再相应调整其容量。

如果训练集上的误差高于目标误差率,那只能提升容量。如果未使用正则化且对优化算法表现有信心,则必须给网络加层或加隐藏单元。不幸的是,这会增加与模型相关的计算成本。

如果测试集上的误差高于目标误差率,那么可以采取两类动作。测试误差是训练误差与训练—测试差距之和。最优测试误差通过对二者做权衡取得。神经网络通常在训练误差非常低(因此容量高)且测试误差主要由训练—测试差距驱动时表现最好。目标是在不使训练误差比差距下降更快的前提下缩小差距。要缩小差距,可以改变正则化超参数以降低模型有效容量,例如加入 dropout 或 weight decay。最佳性能通常来自"大模型 + 强正则化",例如使用 dropout。

大部分超参数都可以通过"它们是增加还是减少模型容量"来推理设定。表 11.1 给出了一些例子。

手动调节超参数时,不要忘记最终目标:测试集上表现良好。加入正则化只是达成目标的一种方式。只要训练误差低,你总可以通过收集更多训练数据来降低泛化误差。从实用上保证成功的"蛮力"做法是不断增大模型容量和训练集大小直到任务被解决。当然这会增加训练和推断的计算代价,因此只有具备相应资源时才可行。原则上这种方法可能因优化困难而失败,但对许多问题来说,只要模型选得合适,优化似乎不构成显著障碍。

11.4.2 自动超参数优化算法(Automatic Hyperparameter Optimization Algorithms)

理想的学习算法只需要接受数据集并输出函数,而不需要手工调超参数。logistic 回归和 SVM 等学习算法的流行部分源于它们在只调一两个超参数的情况下就能表现良好。神经网络有时也能在只调少量超参数的情况下表现良好,但通常会从调四十多个超参数中显著获益。当用户有好的起点时——比如由其他人在相同类型应用和架构上确定,或用户有多年对类似任务调神经网络超参数的经验——手动调超参数可以工作得很好。然而对许多应用来说,这些起点并不存在。在这些情形下,自动算法可以找到有用的超参数值。

如果思考一下用户搜索好的超参数值的方式,就会意识到这里在进行一种优化:我们试图找到一组超参数值使某个目标函数(如验证误差)最优化(有时带有约束,如训练时间、内存或识别时间的预算)。因此原则上完全可以开发"包装"学习算法并为之选择超参数的超参数优化算法,从而对用户隐藏学习算法的超参数。不幸的是,超参数优化算法本身往往也有自己的超参数,例如对每个学习算法超参数应当探索的值域。但这些"二级超参数"通常更易选择——在广泛的任务范围内使用相同的二级超参数都可以取得可接受的性能。

当超参数数量为三个或更少时,通常的做法是网格搜索。对每个超参数,用户选择一小组要探索的值。网格搜索算法为每个超参数取值的笛卡尔积中每一个联合取值训练一个模型。产生最佳验证集误差的实验被选为找到的最优超参数组合。图 11.2 左侧展示了一组超参数值构成的网格示意。

怎样选择要搜索的值列表?数值型(有序)超参数的情况下,根据对类似实验的先验经验,保守地选择最小与最大元素,以确保最优值很可能落在所选范围内。网格搜索通常在对数尺度上选值,例如学习率在集合 \(\{-1, -2, -3, -4, -5\}\)(即 0.1, 0.01, \(10^{-3}\), \(10^{-4}\), \(10^{-5}\))中取,隐藏单元数在集合 \(\{50, 100, 200, 500, 1000, 2000\}\) 中取。

网格搜索通常在重复执行时表现最好。例如假设对超参数 \(\alpha\)\(\{-1, 0, 1\}\) 上做网格搜索。如果发现最优值是 1,则低估了最优 \(\alpha\) 所在的范围,应当移动网格并在 \(\alpha \in \{1, 2, 3\}\) 上再搜索一次。如果发现最优 \(\alpha=0\),则可以缩小估计、在 \(\{-0.1, 0, 0.1\}\) 上再做一次网格搜索以精细化。

网格搜索的明显问题是其计算开销随超参数数量指数增长。如果有 \(m\) 个超参数、每个最多取 \(n\) 个值,则所需训练和评估试验数按 \(O(n^m)\) 增长。试验可以并行运行、可以利用松散的并行性(不同机器做搜索几乎不需要通信)。但由于网格搜索的指数代价,即使并行化也可能达不到令人满意的搜索规模。

幸运的是,网格搜索有一个替代方案,编程同样简单、使用更方便、且更快收敛到好的超参数值:随机搜索(Bergstra and Bengio, 2012)。

随机搜索的流程如下。首先为每个超参数定义一个边缘分布,例如对二元或离散超参数用 Bernoulli 或 multinoulli 分布,或对正实数超参数在对数尺度上用均匀分布。例如

\[ \mathrm{log\_learning\_rate} \sim u(-1, -5) \]
\[ \mathrm{learning\_rate} = 10^{\mathrm{log\_learning\_rate}} \]

其中 \(u(a, b)\) 表示在区间 \((a, b)\) 上的均匀分布采样。类似地 log_number_of_hidden_units 可以从 \(u(\log 50, \log 2000)\) 中采样。

与网格搜索不同,不应将超参数的值离散化或分箱。这允许探索更大的值集,且不增加额外计算代价。事实上如图 11.2 所示,当存在若干对性能度量影响不大的超参数时,随机搜索可以指数级地比网格搜索更高效。这在 Bergstra and Bengio (2012) 中有详细研究,他们发现按"试验次数"度量,随机搜索比网格搜索更快地降低验证集误差。

和网格搜索一样,常常需要对随机搜索做重复运行,以便基于第一次运行的结果精细化搜索。

随机搜索比网格搜索更快找到好的解,其主要原因是没有"浪费"的实验——而在网格搜索中,当一个超参数的两个取值(在固定其他超参数取值下)会产生相同结果时,就出现了浪费。在网格搜索中,其他超参数在这两次运行中取值相同;而在随机搜索中,这两次运行中其他超参数通常有不同的取值。因此如果这两个值之间变化对验证集误差影响不大,网格搜索会不必要地重复两次等价实验,而随机搜索则会对其他超参数做两次独立的探索。

11.4.5 基于模型的超参数优化(Model-Based Hyperparameter Optimization)

对好的超参数的搜索可以形式化为一个优化问题。决策变量是超参数。要优化的代价是使用这些超参数训练后得到的验证集误差。在某些简化设定下,可以计算验证集上某个可微误差度量对超参数的梯度,则可以直接沿该梯度方向优化(Bengio et al., 1999; Bengio, 2000; Maclaurin et al., 2015)。但在实际中这个梯度往往不可得,要么因为计算和内存代价过高,要么因为超参数与验证集误差之间存在本质非可微的交互——例如离散值的超参数。

为弥补梯度的缺失,可以对验证集误差建立一个模型,然后在该模型内做优化来提出新的超参数猜测。大多数基于模型的超参数搜索算法使用贝叶斯回归模型来估计"每个超参数对应的验证集误差期望值"以及"该期望的不确定度"。优化过程因而要在 exploration(提出不确定度大的超参数——可能带来大幅提升、也可能表现很差)和 exploitation(提出模型确信其表现会接近已见过最好值的超参数——通常与已见过的非常相似)之间做权衡。当代超参数优化方法包括 Spearmint (Snoek et al., 2012)、TPE (Bergstra et al., 2011) 和 SMAC (Hutter et al., 2011)。

目前还无法明确地把贝叶斯超参数优化推荐为取得更好深度学习结果或以更少努力获得这些结果的成熟工具。贝叶斯超参数优化有时能与人类专家打平、有时更好,但在另一些问题上一败涂地。值得一试以看它是否对某个特定问题有效,但目前尚不够成熟或可靠。话虽如此,超参数优化是一个重要的研究方向——它虽然通常主要由深度学习的需求推动,但有潜力让整个机器学习领域乃至整个工程学科受益。

大多数比随机搜索更复杂的超参数优化算法都有一个共同缺点:它们要求一次训练实验完整跑完才能从中提取任何信息。这在"实验中能多早获取信息"的意义上比人类实践者的手工搜索效率低得多——因为人类通常很早就能判断某组超参数是不是完全病态的。Swersky et al. (2014) 提出了一种"多实验并行"算法的早期版本。在各个时间点,超参数优化算法可以选择启动一个新实验、"冻结"一个看起来无前景的运行中的实验,或"解冻"并恢复一个之前被冻结、但根据新信息看起来有前景的实验。

11.5 调试策略(Debugging Strategies)

当一个机器学习系统表现不佳时,通常难以判断性能差是算法本身的内禀问题,还是算法实现中的 bug。机器学习系统因多种原因而难调试。

大多数情况下,我们并不能先验地知道算法应有的行为。事实上,使用机器学习的全部意义就在于它会学到我们无法自己显式指定的有用行为。如果我们在一个新的分类任务上训练一个神经网络并达到 5% 测试误差,我们没有直接办法知道这是预期行为还是次优行为。

另一个困难是大多数机器学习模型都有多个自适应部分。如果其中某一部分坏掉,其他部分会自适应并仍取得大致可接受的性能。例如假设我们训练一个由权重 \(W\) 和偏置 \(b\) 参数化的多层神经网络,再假设我们手工实现了对每个参数的梯度下降规则,并在偏置更新中犯了一个错误:

\[ b \leftarrow b - \alpha \]

其中 \(\alpha\) 是学习率。这一错误的更新根本没用梯度。它会让偏置在整个学习过程中持续变负——显然不是任何合理学习算法的正确实现。但仅看模型输出,这个 bug 未必明显:依赖于输入分布,权重或许会自适应以补偿这些为负的偏置。

大多数神经网络的调试策略都是为了绕开以上两个困难中的某一个或两个。要么设计一个简单到正确行为可以被预测的案例,要么设计一个隔离地测试神经网络某一部分的测试。

一些重要的调试测试包括:

可视化模型的行为:在训练检测图像中目标的模型时,查看一些图像并把模型提出的检测结果叠加在图像上。在训练语音的生成模型时,听它产生的一些语音样本。这看起来显然,但很容易只盯着准确率或对数似然等定量性能度量看。直接观察机器学习模型执行其任务有助于判断它达到的定量性能数字是否合理。评估 bug 可能属于最具毁灭性的 bug——它们会误导你相信系统表现良好而实际并非如此。

可视化最严重的错误:大多数模型能输出对所执行任务的一种置信度量。例如基于 softmax 输出层的分类器对每个类分配一个概率。最高概率类的概率值因此给出模型对其分类决策的置信度的估计。最大似然训练通常让这些值是高估值而非"正确预测"的真实概率,但它们在某种意义上仍然有用——实际不太可能被正确标注的样本在模型下得到的概率较小。通过查看"模型最难正确建模的训练样本",通常可以发现数据预处理或标注方式的问题。例如街景转录系统最初有一个问题:门牌号检测系统把图像裁得太紧,漏掉了部分数字。于是转录网络在这些图像上对正确答案分配了非常低的概率。通过排序图像以找出"最自信的错误",发现了这一裁剪的系统性问题。修改检测系统使其裁剪得更宽,整体系统性能获得大幅提升——尽管转录网络因此需要能处理门牌号在位置和尺度上的更大变化。

借助训练和测试误差推断软件是否正确:要判断底层软件是否正确实现并不容易。一些线索可以从训练和测试误差获得。如果训练误差低但测试误差高,那么训练过程很可能是正确工作的,模型因算法层面的根本原因而过拟合。另一种可能是测试误差度量方式有误——例如训练后保存模型再在测试集上重新加载的过程有问题,或者测试数据的准备方式与训练数据不同。如果训练和测试误差都高,则很难判断是软件缺陷还是模型因算法层面根本原因而欠拟合——这一情形需要进一步的测试(接下来描述)。

拟合一个很小的数据集:如果训练集误差很高,要判断是真正的欠拟合还是软件缺陷。通常即便小模型也能保证能拟合足够小的数据集。例如一个只含一个样本的分类数据集只需把输出层的偏置设对就能拟合。通常如果你训练一个分类器无法正确标注单个样本、训练自编码器无法高保真地重建单个样本、或者训练生成模型无法始终产生与单个样本相似的样本,那么存在软件缺陷使训练集上的优化无法成功。该测试可以扩展到只有少量样本的小数据集。

把反向传播梯度与数值梯度比较:如果使用一个要求自己实现梯度计算的软件框架,或者为某个可微库添加新操作并必须定义其 bprop 方法,那么梯度实现错误是常见错误源。验证梯度正确性的一种方式是把自动微分算出的梯度与有限差分算出的梯度作比较。由于

\[ f'(x) = \lim_{\epsilon \to 0} \frac{f(x+\epsilon) - f(x)}{\epsilon} \]

可以用一个小的有限 \(\epsilon\) 来近似:

\[ f'(x) \approx \frac{f(x+\epsilon) - f(x)}{\epsilon} \]

可以通过使用中心差分来提高近似精度:

\[ f'(x) \approx \frac{f(x+\tfrac{1}{2}\epsilon) - f(x-\tfrac{1}{2}\epsilon)}{\epsilon} \]

扰动大小 \(\epsilon\) 必须选得足够大以保证扰动不会在有限精度数值计算中被完全舍入掉。

通常我们会想测试向量值函数 \(g: \mathbb{R}^m \to \mathbb{R}^n\) 的梯度或 Jacobian。有限差分只能一次算一个导数。可以跑 \(mn\) 次有限差分来评估 \(g\) 的所有偏导,或者把测试应用到一个新函数上——对 \(g\) 的输入和输出都做随机投影。例如可以测试 \(f(x) = u^\top g(vx)\) 的导数,其中 \(u\)\(v\) 是随机选取的向量。正确地计算 \(f'(x)\) 需要能正确地反向传播通过 \(g\),但用有限差分来做是高效的,因为 \(f\) 只有一个输入和一个输出。通常对多于一组 \((u, v)\) 重复该测试以减少"漏掉了与随机投影正交的错误"的概率。

如果可以访问复数上的数值计算,那么有一种非常高效的方法——用复数作为函数输入来数值估计梯度(Squire and Trapp, 1998)。该方法基于如下观察

\[ f(x + i\epsilon) = f(x) + i\epsilon f'(x) + O(\epsilon^2) \]
\[ \mathrm{real}(f(x+i\epsilon)) = f(x) + O(\epsilon^2), \quad \mathrm{imag}\left(\frac{f(x+i\epsilon)}{\epsilon}\right) = f'(x) + O(\epsilon^2) \]

其中 \(i = \sqrt{-1}\)。与实数情形不同,这里没有"在不同点函数值之差"造成的相消效应。这允许使用极小的 \(\epsilon\),例如 \(\epsilon = 10^{-150}\),使得 \(O(\epsilon^2)\) 项在实际意义上可以忽略。

监控激活和梯度的直方图:可视化神经网络激活和梯度的统计量通常很有用——这些统计量是在大量(也许一个 epoch)训练迭代上收集的。隐藏单元的预激活值可以告诉我们单元是否饱和、或者饱和的频率。例如对 ReLU 单元,它们多久是关的?是否有永远关闭的单元?对 tanh 单元,预激活绝对值的均值告诉我们单元的饱和程度。在深度网络中,传播梯度如果迅速增大或消失,优化可能受阻。最后,比较参数梯度的量级和参数本身的量级是有用的。按 Bottou (2015) 的建议,希望一个 minibatch 上参数更新的量级大致是参数量级的 1%,而不是 50% 或 0.001%(后者会让参数移动过慢)。可能有些参数组在以合适速度前进而其他参数停滞。当数据稀疏时(如自然语言中),有些参数可能很少被更新,监控其演化时应当考虑这一情况。

最后,许多深度学习算法会对每一步产生的结果给出某种保证。例如在第三部分中,将看到一些近似推断算法通过对优化问题使用代数解来工作。通常可以通过测试其各项保证来调试这些算法。一些优化算法提供的保证包括:算法每一步之后目标函数不会上升;对某子集变量的梯度在每步之后为零;对所有变量的梯度在收敛时为零。由于舍入误差,这些条件在数字计算机上不会完全成立,因此调试测试应包含一个容差参数。

11.6 示例:多位数识别(Example: Multi-Digit Number Recognition)

为了提供如何在实际中应用设计方法论的端到端描述,下面给出街景转录系统的简要叙述——从深度学习组件设计的视角出发。显然完整系统的许多其他组件(如街景车、数据库基础设施等)也是至关重要的。

从机器学习任务的角度,流程始于数据收集。街景车收集原始数据,人类操作员提供标签。转录任务之前有大量的数据集整理工作,包括使用其他机器学习技术在转录之前先检测出门牌号。

转录项目始于选择性能度量和这些度量的目标值。一个重要的通用原则是让度量选择契合项目的业务目标。由于地图只有高准确率时才有价值,因此对项目设定高准确率要求非常重要。具体目标是达到 98% 的人类水平准确率。该准确率水平并非总是能达到。为了达到这一水平,街景转录系统在 coverage 上做出了牺牲。因此 coverage 成为项目期间主要优化的性能度量,准确率则保持在 98%。随着卷积网络改进,可以逐步降低"网络拒绝转录输入"的置信度阈值,最终超过 95% coverage 的目标。

在选定量化目标之后,推荐方法论的下一步是快速搭建一个合理的基线系统。对视觉任务,这意味着一个带 ReLU 的卷积网络。转录项目就从这样一个模型起步。当时卷积网络输出一个预测序列并不常见。为了从最简单的可能基线开始,模型输出层的首个实现由 \(n\) 个独立的 softmax 单元组成,用来预测长度为 \(n\) 的字符序列。这些 softmax 单元的训练方式与分类任务完全一样——每个 softmax 单元被独立训练。

推荐方法论主张反复精细化基线,并测试每次改动是否带来改进。街景转录系统的第一次改动受到对 coverage 度量和数据结构的理论理解的驱动。具体来说,当 \(p(y\mid x) < t\)(其中 \(t\) 是某阈值)时,网络拒绝分类输入 \(x\)。最初 \(p(y\mid x)\) 的定义是临时性的,简单地把所有 softmax 输出相乘。这促使开发一个专门的输出层和代价函数,使其实际计算有原则的对数似然。这种做法让"拒绝样本"的机制工作得更有效。

在这一点上,coverage 仍低于 90%,但方法本身已无明显理论问题。因此方法论建议对训练集和测试集性能做仪表化,以判断问题是欠拟合还是过拟合。在这个案例中,训练集与测试集误差几乎一致。该项目进展得如此顺利主要因为有包含数千万标注样本的数据集。因为训练与测试误差如此接近,提示问题要么来自欠拟合、要么来自训练数据问题。我们推荐的一个调试策略是可视化模型最严重的错误。在此案例中,这意味着可视化"模型给出最高置信度的错误训练集转录"。这些样本大部分是输入图像被裁剪过紧的样本——门牌号的部分数字被裁剪操作去掉了。例如一张"1849"的地址照片可能被裁得只剩"849"可见。该问题本来可以通过花几周改进门牌号检测系统的准确率(该系统决定裁剪区域)来解决。团队做了一个更实际的决定——简单地让裁剪区域系统性地宽于门牌号检测系统所预测的宽度。这一项单独的改动就给转录系统的 coverage 增加了十个百分点。

最后,几个百分点的性能来自调整超参数。这主要包括在保持计算开销约束的前提下增大模型。因为训练与测试误差始终大致相等,可以清楚判断任何性能不足都源于欠拟合,以及数据集本身残余的一些问题。

总体而言,转录项目是巨大成功,让数亿地址的转录比单纯依赖人力更快、成本更低。希望本章所描述的设计原则能带来许多其他类似的成功。

本章个人批注

本章在整本书中处于"工具→应用"过渡点的位置——之前九章(线性代数、概率、优化、前馈、卷积、循环等)建立的是"机器学习算法的理论 + 工程"基础,第 12 章往后是"把工具应用到具体任务"。本章插在中间,主题是"如何在实际问题中选择、评估、调试这些工具"。从结构上看,本章和 Ng 的 Deep Learning 课程影响很大(11.1 节明言"many of the recommendations are adapted from Ng (2015)"),所以读起来有一种"经过大量工程实践沉淀出来的方法论"的味道。

11.1 节把"先确定误差度量,再确定目标值"作为第一步,背后的逻辑是"误差度量会指导之后所有的动作"——这其实是一个比看起来要深的洞察。许多工程团队的失败不是因为模型不够好,而是因为选错了度量(或优化了一个"方便度量"而非"业务目标")。spam detection 的例子("屏蔽合法邮件的代价 > 放过垃圾邮件的代价")看似简单,但实际中很多系统的成功 / 失败都能用"代价函数选错了"解释。precision/recall 和 F-score 那一段对类别不平衡问题的标准处理方式也给了一个干净的形式化。

11.2 节"先建端到端基线,再增量改进"和 11.3 节"训练误差可以接受 vs 测试误差可以接受"的二分法是本章工程价值最高的两节。它们的核心思想是"在不知道问题是过拟合还是欠拟合之前就改算法是浪费时间"——这一观点对新手尤其重要。11.3 节末段关于"如果收集更多数据不可行,那就进入研究领域"这句话是整章最诚实的一句。

11.4.1 节关于"学习率"作为最重要超参数、以及"学习率与有效容量的非单调关系"是本章读起来最有启发的部分。Table 11.1 把各种超参数按"增大 / 减小容量"分两栏列出,并附了"原因"和"注意事项"两列,是非常好的速查表。"某些超参数只能减少容量"(例如 weight decay 最小为 0)这个观察也很有用——它解释了为什么仅靠 weight decay 调不出完美欠拟合点。

11.4.3 / 11.4.4 节关于 grid search 与 random search 的对比(Bergstra and Bengio 2012)是本章的"经典内容"。核心洞察是:当只有少数超参数真正影响性能时,random search 在"对每个有影响力超参数都试不同值"上指数级地比 grid search 更高。Figure 11.2 把这个对比可视化地讲清楚。11.4.5 节关于 Bayesian hyperparameter optimization 的讨论相对克制——作者明确说"目前还不能把它推荐为成熟工具",这与一些推销 Bayesian optimization 的文章形成对比。

11.5 节"调试策略"是本章最实用的一节,对实际工作最有用。其中"可视化最严重的错误"和"在小数据集上过拟合"是两个特别值得记住的测试——前者揭示数据预处理 bug(如街景案例中的裁剪问题),后者揭示代码 bug。"反向传播梯度与数值梯度比较"那段对工程实现者是基本功,作者还专门提了"用复数做有限差分"这种方法(Squire and Trapp, 1998)——它避免了实数差分的相消问题,可以用极小的 \(\epsilon\)(如 \(10^{-150}\)),这个技巧在主流教材中很少被讲到。

11.6 节的街景转录系统案例是本章"把所有内容串起来"的实例。它演示了本章倡导的完整流程:选度量(accuracy 98%, coverage 95%)→ 建基线(n 个独立 softmax)→ 用 coverage 理论驱动第一次改动(专门的输出层和代价函数)→ 仪表化(发现 train ≈ test)→ 调试(可视化最严重的错误 → 发现裁剪问题)→ 调超参数收尾。一个完整的迭代循环在案例中体现得淋漓尽致。

与上下章的衔接(一段话)

本章是全书"理论→应用"过渡的桥梁:第 1-10 章建立机器学习算法与模型的工具箱(线性代数、概率、信息论、机器学习基础、前馈网络、正则化、优化、卷积网络、循环网络),第 12-20 章是"应用到具体任务"(视觉、NLP、生成模型等),本章夹在中间,主题是"如何选择、评估、调试这些工具"。它与上一章(第 10 章 序列建模)共享一个隐含的哲学:从"算法如何工作"转向"算法如何用好"。本章又为下一章(第 12 章 应用)做铺垫——第 12 章给的是具体应用(语音、视觉、NLP)中的特定架构选择,而本章给的是"无论做哪个应用都用得上的通用方法论":性能度量、基线、是否需要更多数据、超参数选择、调试策略、完整案例。读者可以想象成"第 12 章告诉你每个应用有哪些候选工具,本章告诉你怎样在候选工具中做选型"。从更长期的视角看,本章建立的"先建基线→ 仪表化→ 增量改进"循环也是后续所有应用章节隐含的工作流程——尤其第 12 章大尺度应用、第 17 章蒙特卡罗方法、第 20 章深度生成模型等章节都隐含了这一方法论。