内容总结自花书《Deep Learning》。
要想成功地运用深度学习,仅仅知道存在哪些算法和解释它们为何有效是不够的。我们必须能够针对具体应用挑选一个合适的算法,并根据实验反馈改进深度学习系统。我们在日常开发中,需要自己决定是否需要收集更多的数据、增加或减少模型容量、添加或删除正则化项、使用不同地优化方法等等。这些尝试都需要耗费大量时间,因此盲目猜测绝对是不可取的。
《Deep Learning》给我们地设计流程提供了一些建议:
- 确定目标,决定使用什么样的误差度量,并为此误差度量指定目标值
- 尽快搭建一个端到端的工作流程,包括估计合适的性能度量
- 搭建系统,并确定性能瓶颈。检查哪个部分的性能差于预期,以及是否是因为过拟合、欠拟合,或者数据、软件缺陷造成的。
- 根据具体观察反复地进行增量式的改动,如收集新数据、调整超参数或改进算法。
对于大多数应用而言,我们都不可能实现绝对的零误差,即使我们有无限多的训练数据。当然,通常我们的训练数据数量都有一定限制,进一步增多训练数据可能会进一步减少误差,但也会耗费更多的时间、$$ 等,因此,我们需要在两者之间做出权衡。
在工程应用中,一个合理的性能期望可能意味着一个安全的、具有成本效益的或者能吸引消费者的错误率,一旦确定之后,我们之后的设计就将由这个错误率来指导。当然,错误率并不是唯一的度量选择,我们还可以选择准确率等等。
有些应用可能需要更高级的度量。有时,一种错误可能会比另一种错误更严重。例如,垃圾邮件检测系统会有两种错误:将正常邮件错误的归为垃圾邮件,或者将垃圾邮件错误的归为正常邮件,显然前者的错误要更严重些。因此我们选择的代价函数要使得前者的代价更高。
还有一种情况是我们需要训练检测某些罕见事件的二元分类器,如某种罕见疾病,每百万人中只有一人患病。我们都不需要训练了,直接让分类器一直报告没有患病,就可以到达 99% 以上的准确率,这显然是不恰当的。我们需要其它的性能度量来描述这个系统的性能,如精度(precision)和召回率(recall)。精度是检测是正确的比率,而召回率则是真实事件被检测到的比率。分类器永远报告没有患者,会得到一个完美的精度,但召回率为零。而如果我们每次都报告为患病,那么召回率是完美的,而精度会变为百万分之一。由此可见,精度和召回率是互相影响的,我们可以分别把它们理解为查准率和查全率,查准率高了,查全率就低了。
当使用精度和召回率时,我们通常会画 PR 曲线, y y y 轴表示精度, x x x 轴表示召回率。我们可以用 PR 曲线下方的总面积来概括分类器的性能。如果一个分类器的 PR 曲线被另一个完全包住了,说明后者的性能更优。如果想用具体的数字来衡量,我们可以将精度 p p p 和召回率 r r r 转换为 F 分数(F-score):
F = 2 p r p + r F=\frac{2pr}{p+r} F=p+r2pr
准确来说,上式描述的为 F1 分数,更为一般的 F β F_\beta Fβ 形式可以让我们表达出对精度以及召回率的偏好,例如在上述疾病检测中,我们更希望不漏掉患病的人,因此召回率更为重要:
F β = ( 1 + β 2 ) × p × r β 2 × p + r F_\beta=\frac{(1+\beta^2)\times p \times r}{\beta^2\times p+r} Fβ=β2×p+r(1+β2)×p×r
在一些应用中,我们可能会希望机器学习系统拒绝做出判断,尤其是在错误的判断会导致严重危害的时候。我们考虑街景地址号码转录系统这样一个应用场景,该应用的目标是将建筑物添加到谷歌地图。街景车拍摄建筑物,并记录与每张建筑照片相关的 GPS 坐标。卷积网络识别每张照片上的地址号码,由谷歌地图数据库在正确的位置添加该地址。这个任务是识别照片上的地址号码,将照片拍摄地点对应到地图上的地址。如果地图是不精确的,那么地图的价值会严重下降。因此只在转录正确的情况下添加地址十分重要。在这种情况下,一种自然的性能度量是覆盖(coverage)。覆盖是机器学习系统能够产生响应的样本所占的比率。我们权衡覆盖和精度。一个系统可以通过拒绝处理任意样本的方式来达到 100% 的精度,但是覆盖降到了0%。对于街景任务,该项目的目标是达到人类级别的转录精度,同时保持 95% 的覆盖。在
这项任务中,人类有 98% 的精度。
确定性能度量和目标后,任何实际应用的下一步是尽快建立一个合理的端到端的系统。下面是一些不同情况下使用哪种算法作为第一基准方法的推荐。
根据问题的复杂性,项目开始时可能无需使用深度学习。如果只需正确地选择几个线性权重就可能解决问题,那么我们可以从一个简单的统计模型,如逻辑回归开始。
如果问题完全属于 AI 型的,如对象识别、语音识别等,那么选择一个深度模型自然比较好。
具有衰减学习率以及动量的 SGD 是优化算法一个合理的选择,Adam 也是经常使用的优化算法。批标准化对性能也有着显著的影响,特别是对卷积网络和具有 sigmoid 非线性函数的网络而言。最初可以忽略批标准化,但当网络的训练出现问题的时候,可以考虑使用批标准化来使训练更加顺畅。
一般情况下,我们在一开始就应该做些适当的正则化,如提前终止、Dropout 等。批标准化也能降低泛化误差,此时可以不使用 Dropout,因为用于标准化变量的统计量估计本身就存在噪声。
如果我们的任务和另一个被广泛研究的任务相似,那么通过复制先前研究中已知性能良好的模型和算法,可能会得到很好的效果。甚至可以从该任务中复制一个训练好的模型。例如,通常会使用在 ImageNet 上训练好的卷积网络的特征来解决其他计算机视觉任务。
在建立第一个端到端的系统之后,我们不要急于的去尝试不同的算法,收集更多的数据可能比改进学习算法要有用的多。
如果模型在训练集上的性能就很差,那我们就没有必要收集更多的数据,反之,可以尝试增加更多的层数或每层增加更多的隐藏单元来增加模型容量。
如果大的模型和仔细调试的优化算法效果不佳,那么问题可能源于训练数据的质量,数据可能包含太多噪声,这意味着我们需要重新收集更干净的或者特征更加丰富的数据。
如果训练集上的性能还不错,那么我们开始度量测试集上的性能,如果测试集性能比训练集的差很多,那么收集更多数据是最有效的解决方法之一。通常,加入总数目一小部分的样本不会对泛化误差产生显著的影响,因此我们往往考虑成倍的增加样本数目。如果收集大规模数据的成本很高,一个可行的简单方法是降低模型容量或是改进正则化。
手动搜索超参数的主要目标是调整模型的有效容量以匹配任务的复杂性。有效容量受限于三个因素:模型的表示容量、学习算法成功最小化训练模型代价函数的能力以及代价函数和训练过程正则化的程度。具有更多网络层,每层有更多隐藏单元的模型具有较高的表示能力——能够表示更复杂的函数。然而,如果训练算法不能找到某个合适的函数来最小化训练代价,或是正则化项(如权重衰减)排除了这些合适的函数,那么即使模型的表达能力较高,也不能学习出合适的函数。
学习率可能是最重要的超参数,如果你只有时间调整一个超参数,那就调整学习率。当学习率适合优化问题时,模型的有效容量最高。学习率关于训练误差具有 U 形曲线。学习率过大时,可能会错过最优点,过小时,有可能永久停留在一个很高的训练误差。
调整学习率外的其他参数时,需要同时监测训练误差和测试误差,以判断模型是否过拟合或欠拟合,然后适当调整其容量。大部分超参数可以通过推理其是否增加或减少模型容量来设置,如下表所示:
超参数 | 容量何时增加 | 原因 | 注意事项 |
---|---|---|---|
隐藏单元数量 | 增加 | 增加隐藏单元数量会增加模型的表示能力 | 几乎模型每个操作所需的时间和内存代价都会随隐藏单元数量的增加而增加 |
学习率 | 调制最优 | 太高或太低的学习率都会由于优化失败而导致模型的有效容量降低 | |
卷积核宽度 | 增加 | 增加了模型的参数数量 | 较宽的卷积核会导致较窄的输出尺寸,除非使用填充操作,否则会降低模型容量。且较宽的卷积核也会增加内存存储参数,增加运行时间 |
权重衰减系数 | 降低 | 降低权重衰减系数使得模型参数可以自由变大 | |
Dropout 比率 | 降低 | 丢弃较少的单元可以让更多的单元彼此协作来适应训练集 |
当有三个或者更少的超参数时,常见的超参数搜索方法是网格搜索(grid search)。对于每个超参数,我们都会指定一个较小的值集范围,然后这些超参数组合成一组组不同的超参数,网格搜索使用每组超参数训练模型,挑选验证集误差最小的超参数作为最好的超参数。
通常,网格搜索大约会在对数尺度下挑选合适的值,例如,一个学习率的取值集合是 {0.1, 0.01, 0.001, 0.0001, 0.00001},或者隐藏单元数目的取值集合 {50, 100, 200, 500, 1000, 2000}。
通常重复进行网格搜索时,效果会更好。例如,假设我们在集合 {-1, 0, 1} 上网格搜索超参数 α \alpha α。如果找到的最佳值是 1,那么说明我们可能可以将范围扩大,得到更优的参数值,例如再在集合 {1, 2, 3} 中搜索。而如果最佳值是 0,那么我们可以进一步细化搜索范围,例如在 {-0.1, 0, 0.1} 上继续搜索。
网格搜索的一个明显问题是,计算代价会随着超参数数量呈指数级增长。
我们举一个使用 sklearn 在一个小规模人脸识别任务上运用网格搜索的例子:
from time import time
import logging
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import fetch_lfw_people
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.decomposition import PCA
from sklearn.svm import SVC
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
n_samples, h, w = lfw_people.images.shape
n_samples, h, w
"""
(1288, 50, 37)
"""
X = lfw_people.data
y = lfw_people.target
target_names = lfw_people.target_names
n_classes = target_names.shape[0]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
# Compute a PCA (eigenfaces) on the face dataset (treated as unlabeled dataset):
# unsupervised feature extraction / dimensionality reduction
n_components = 150
t0 = time()
pca = PCA(n_components=n_components, svd_solver='randomized',
whiten=True).fit(X_train)
print("Done in %.3fs" % (time() - t0))
eigenfaces = pca.components_.reshape((n_components, h, w))
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
param_grid = {'C': [1e3, 5e3, 1e4, 5e4, 1e5],
'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1]} # 网格搜索的超参数
clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, cv=5) # 网格搜索
clf = clf.fit(X_train_pca, y_train)
print("Best estimator found by grid search:")
print(clf.best_estimator_)
"""
Done in 1.008s
Best estimator found by grid search:
SVC(C=1000.0, class_weight='balanced', gamma=0.005)
"""
y_pred = clf.predict(X_test_pca)
print(classification_report(y_test, y_pred))
print(confusion_matrix(y_test, y_pred, labels=range(n_classes)))
"""
precision recall f1-score support
0 0.75 0.46 0.57 13
1 0.78 0.88 0.83 60
2 0.94 0.63 0.76 27
3 0.82 0.98 0.89 146
4 0.95 0.80 0.87 25
5 1.00 0.47 0.64 15
6 1.00 0.72 0.84 36
accuracy 0.84 322
macro avg 0.89 0.71 0.77 322
weighted avg 0.86 0.84 0.84 322
[[ 6 2 0 5 0 0 0]
[ 1 53 0 6 0 0 0]
[ 1 2 17 7 0 0 0]
[ 0 3 0 143 0 0 0]
[ 0 1 0 4 20 0 0]
[ 0 4 0 3 1 7 0]
[ 0 3 1 6 0 0 26]]
"""
随机搜索是一个替代网格搜索的方法,实现更为简单,且能更快的收敛到超参数的良好取值。
我们首先会为每个超参数定义一个边缘分布,例如伯努利分布,或者对数尺度上的均匀分布,然后搜索算法从联合的超参数空间中采用,使用该组采样超参数训练模型。与网格搜索不同,我们不需要离散化超参数的值。这允许我们在一个更大的集合上进行搜索。与网格搜索一样,我们也可以重复运行不同版本的随机搜索来改进结果的选择。
某两次的网格搜索中,可能只有两个参数的取值是不同的,其他参数取值都是相同的,而随机搜索通常不会采样到相同的超参数值,因此能更快的找到良好的超参数。
一些重要的调试检测如下所列:
I. J. Goodfellow, Y. Bengio, and A. Courville, Deep Learning. Cambridge, MA, USA: MIT Press, 2016, http://www.deeplearningbook.org.