性能度量

对于大多数应用而言,我们都不可能实现绝对的零误差,即使我们有无限多的训练数据。当然,通常我们的训练数据数量都有一定限制,进一步增多训练数据可能会进一步减少误差,但也会耗费更多的时间、$$ 等,因此,我们需要在两者之间做出权衡。

在工程应用中,一个合理的性能期望可能意味着一个安全的、具有成本效益的或者能吸引消费者的错误率,一旦确定之后,我们之后的设计就将由这个错误率来指导。当然,错误率并不是唯一的度量选择,我们还可以选择准确率等等。

有些应用可能需要更高级的度量。有时,一种错误可能会比另一种错误更严重。例如,垃圾邮件检测系统会有两种错误:将正常邮件错误的归为垃圾邮件,或者将垃圾邮件错误的归为正常邮件,显然前者的错误要更严重些。因此我们选择的代价函数要使得前者的代价更高。

还有一种情况是我们需要训练检测某些罕见事件的二元分类器,如某种罕见疾病,每百万人中只有一人患病。我们都不需要训练了,直接让分类器一直报告没有患病,就可以到达 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

  • β > 1 \beta>1 β>1 时表示偏好召回率
  • β < 1 \beta<1 β<1 时表示偏好精度

在一些应用中,我们可能会希望机器学习系统拒绝做出判断,尤其是在错误的判断会导致严重危害的时候。我们考虑街景地址号码转录系统这样一个应用场景,该应用的目标是将建筑物添加到谷歌地图。街景车拍摄建筑物,并记录与每张建筑照片相关的 GPS 坐标。卷积网络识别每张照片上的地址号码,由谷歌地图数据库在正确的位置添加该地址。这个任务是识别照片上的地址号码,将照片拍摄地点对应到地图上的地址。如果地图是不精确的,那么地图的价值会严重下降。因此只在转录正确的情况下添加地址十分重要。在这种情况下,一种自然的性能度量是覆盖(coverage)。覆盖是机器学习系统能够产生响应的样本所占的比率。我们权衡覆盖和精度。一个系统可以通过拒绝处理任意样本的方式来达到 100% 的精度,但是覆盖降到了0%。对于街景任务,该项目的目标是达到人类级别的转录精度,同时保持 95% 的覆盖。在
这项任务中,人类有 98% 的精度。


默认的基准模型

确定性能度量和目标后,任何实际应用的下一步是尽快建立一个合理的端到端的系统。下面是一些不同情况下使用哪种算法作为第一基准方法的推荐。

根据问题的复杂性,项目开始时可能无需使用深度学习。如果只需正确地选择几个线性权重就可能解决问题,那么我们可以从一个简单的统计模型,如逻辑回归开始。

如果问题完全属于 AI 型的,如对象识别、语音识别等,那么选择一个深度模型自然比较好。

具有衰减学习率以及动量的 SGD 是优化算法一个合理的选择,Adam 也是经常使用的优化算法。批标准化对性能也有着显著的影响,特别是对卷积网络和具有 sigmoid 非线性函数的网络而言。最初可以忽略批标准化,但当网络的训练出现问题的时候,可以考虑使用批标准化来使训练更加顺畅。

一般情况下,我们在一开始就应该做些适当的正则化,如提前终止、Dropout 等。批标准化也能降低泛化误差,此时可以不使用 Dropout,因为用于标准化变量的统计量估计本身就存在噪声。

如果我们的任务和另一个被广泛研究的任务相似,那么通过复制先前研究中已知性能良好的模型和算法,可能会得到很好的效果。甚至可以从该任务中复制一个训练好的模型。例如,通常会使用在 ImageNet 上训练好的卷积网络的特征来解决其他计算机视觉任务。


决定是否收集更多数据

在建立第一个端到端的系统之后,我们不要急于的去尝试不同的算法,收集更多的数据可能比改进学习算法要有用的多。

如果模型在训练集上的性能就很差,那我们就没有必要收集更多的数据,反之,可以尝试增加更多的层数或每层增加更多的隐藏单元来增加模型容量。

如果大的模型和仔细调试的优化算法效果不佳,那么问题可能源于训练数据的质量,数据可能包含太多噪声,这意味着我们需要重新收集更干净的或者特征更加丰富的数据。

如果训练集上的性能还不错,那么我们开始度量测试集上的性能,如果测试集性能比训练集的差很多,那么收集更多数据是最有效的解决方法之一。通常,加入总数目一小部分的样本不会对泛化误差产生显著的影响,因此我们往往考虑成倍的增加样本数目。如果收集大规模数据的成本很高,一个可行的简单方法是降低模型容量或是改进正则化。


选择超参数

手动调整

手动搜索超参数的主要目标是调整模型的有效容量以匹配任务的复杂性。有效容量受限于三个因素:模型的表示容量、学习算法成功最小化训练模型代价函数的能力以及代价函数和训练过程正则化的程度。具有更多网络层,每层有更多隐藏单元的模型具有较高的表示能力——能够表示更复杂的函数。然而,如果训练算法不能找到某个合适的函数来最小化训练代价,或是正则化项(如权重衰减)排除了这些合适的函数,那么即使模型的表达能力较高,也不能学习出合适的函数。

学习率可能是最重要的超参数,如果你只有时间调整一个超参数,那就调整学习率。当学习率适合优化问题时,模型的有效容量最高。学习率关于训练误差具有 U 形曲线。学习率过大时,可能会错过最优点,过小时,有可能永久停留在一个很高的训练误差。

调整学习率外的其他参数时,需要同时监测训练误差和测试误差,以判断模型是否过拟合或欠拟合,然后适当调整其容量。大部分超参数可以通过推理其是否增加或减少模型容量来设置,如下表所示:

半自动搜索

网格搜索

当有三个或者更少的超参数时,常见的超参数搜索方法是网格搜索(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]]
"""

随机搜索

随机搜索是一个替代网格搜索的方法,实现更为简单,且能更快的收敛到超参数的良好取值。

我们首先会为每个超参数定义一个边缘分布,例如伯努利分布,或者对数尺度上的均匀分布,然后搜索算法从联合的超参数空间中采用,使用该组采样超参数训练模型。与网格搜索不同,我们不需要离散化超参数的值。这允许我们在一个更大的集合上进行搜索。与网格搜索一样,我们也可以重复运行不同版本的随机搜索来改进结果的选择。

某两次的网格搜索中,可能只有两个参数的取值是不同的,其他参数取值都是相同的,而随机搜索通常不会采样到相同的超参数值,因此能更快的找到良好的超参数。


调试策略

一些重要的调试检测如下所列:

  • 可视化计算中模型的行为:当训练模型检测图像中的对象时,查看一些模型检测到部分重叠的图像。在训练语音生成模型时,试听一些生成的语音样本。这似乎是显而易见的,但在实际中很容易只注意量化性能度量,如准确率或对数似然。直接观察机器学习模型运行其任务,有助于确定其达到的量化性能数据是否看上去合理。例如,我们的模型精度很高,但可视化却发现其分类的依据在于背景噪声,这显然是不合理的。关于如何代码实现可视化,可以看博主可视化卷积神经网络的三篇文章。
  • 可视化最严重的错误:通过查看训练集中很难正确建模的样本,通常可以发现该数据预处理或者标记方式的问题
  • 根据训练和测试误差检测软件:我们往往很难确定底层软件是否有缺陷。训练和测试误差能够提供一些线索。如果训练误差较低,但是测试误差较高,那么很有可能训练过程是在正常运行,但模型由于算法原因过拟合了。另一种可能是,测试误差没有被正确地度量,可能是由于训练后保存模型再重载去度量测试集时出现问题,或者是因为测试数据和训练数据预处理的方式不同。如果训练和测试误差都很高,那么很难确定是软件错误,还是由于算法原因模型欠拟合。这种情况需要进一步的测试。
    • 拟合极小的数据集:当训练集上有很大的误差时,我们需要确定问题是真正的欠拟合,还是软件错误。通常,即使是小模型也可以保证很好地拟合一个足够小的数据集。例如,只有一个样本的分类数据可以通过正确设置输出层的偏置来拟合。通常,如果不能训练一个分类器来正确预测一个单独的样本,或不能训练一个自编码器来成功地精准再现一个单独的样本,或不能训练一个生成模型来一致地生成一个单独的样本,那么很有可能是由于软件错误阻止训练集上的成功优化。此测试可以扩展到只有少量样本的小数据集上。
  • 监控激活函数值和梯度的直方图: 可视化神经网络在大量训练迭代后收集到的激活函数值和梯度的统计量往往是有用的。例如在卷积神经网络中,可视化中间激活有助于理解卷积神经网络连续的层如何对输入进行变换,也有助于初步了解卷积神经网络每个过滤器的含义。某个过滤器没有被激活说明在图像中找不到这些过滤器所编码的模式。监测梯度也是十分有意义的,在深度网络中,传播梯度的快速增长或快速消失(梯度爆炸,梯度消失),可能会阻碍优化过程。

References

I. J. Goodfellow, Y. Bengio, and A. Courville, Deep Learning. Cambridge, MA, USA: MIT Press, 2016, http://www.deeplearningbook.org.

11-28 07:20