机器学习:使用OpenCV、Python和scikit-learn进行智能图像处理(原书第2版)
上QQ阅读APP看书,第一时间看更新

3.2 理解监督学习

我们之前已经确定了监督学习的目标总是预测数据的标签(或目标值)。然而,根据这些标签的性质,监督学习可以有两种不同形式:

  • 分类:当我们使用数据预测类别时,就将监督学习称为分类。分类的一个很好的例子是我们试图预测一张图像包含猫还是狗。这里,数据的标签是类别,一个类别或另一个类别,但从来不是混合类别。例如,一个图片包含一只猫或一只狗,从不会是50%的猫和50%的狗(这里我们从不考虑卡通角色CatDog),而我们的工作只是分辨出它是哪一种动物。当只有两种选择时,它就称为二分类二值分类。当有两个以上的类别时,如预测鸢尾花的种类(回顾一下我们在第1章中使用的鸢尾花数据集),它称为多分类
  • 回归:当我们使用数据预测真实值时,就将监督学习称为回归。回归的一个很好的例子是预测股票价格。回归的目标不是预测股票的类别,而是尽可能准确地预测一个目标值,例如以最小误差预测股票价格。

有许多不同的方法来进行分类和回归,但我们只探讨其中的几种方法。判断我们正在处理的是分类问题还是回归问题,最简单的方法也许是问我们自己下面这个问题:我们到底要预测什么?图3-1给出了答案。

图3-1 分类问题和回归问题的区别

3.2.1 看看OpenCV中的监督学习

如果我们不能将其付诸实践,仅知道监督学习的原理是没有任何用处的。值得庆幸的是,OpenCV为所有统计学习模型(包括所有的监督学习模型)提供了一个非常简单的接口。

在OpenCV中,每个机器学习模型都派生于cv::ml::StatModel基类。如果我们想要使用OpenCV中的一个机器学习模型,那么我们必须提供StatModel允许我们实现的所有功能。这包括训练模型的一种方法(名为train)以及度量模型性能的一种方法(名为calcError)。

注意

面向对象编程(Object-Oriented Programming,OOP)中,我们主要处理对象或者类。对象是由称为方法的函数和称为成员属性的变量组成。你可以在https://docs.python.org/3/tutorial/classes.html学习更多有关Python中的面向对象编程的知识。

因为软件的这种组织方式,用OpenCV构建一个机器学习模型始终遵循相同的逻辑,如下:

  • 初始化:我们通过名称调用模型,创建模型的一个空实例。
  • 设置参数:如果模型需要一些参数,我们可以通过setter方法设置这些参数,每个模型的setter方法都可以不同。例如,为了让一个k-NN算法工作,我们需要指定它的开放参数k(我们稍后会学到)。
  • 训练模型:每个模型都必须提供一个名为train的方法,用于将模型与某些数据进行拟合。
  • 预测新标签:每个模型都必须提供一个名为predict的方法,用于预测新数据的标签。
  • 给模型评分:每个模型都必须提供一个名为calcError的方法,用于度量性能。对于每个模型,这种计算可能是不同的。

提示

因为OpenCV是一个庞大的、社区驱动的项目,所以并不是所有的算法都像用户期望的那样遵循这些规则。例如,k-NN算法的大部分工作是在findNearest方法中完成的,但是predict仍然有效。我们将通过不同的例子来说明这些差异。

因为我们会偶尔使用scikit-learn来实现OpenCV没有提供的一些机器学习算法,所以有必要指出,scikit-learn中的算法遵循几乎相同的逻辑。最显著的区别是scikit-learn在初始化步骤中设置了所有需要的模型参数。此外,scikit-learn调用训练函数fit,而不调用train;调用评分函数score,而不调用calcError。

3.2.2 用评分函数度量模型性能

构建机器学习系统最重要的一个部分是找到一种方法来度量模型预测质量。在真实场景中,模型很少能成功地预测所有的内容。从前几章的学习中,我们知道应该使用测试集的数据来评估我们的模型。但是这到底是如何工作的呢?

简短但不是很有用的答案是,这取决于模型。人们已经提出了各种评分函数,它可用于在所有可能的场景中评估训练模型。好消息是,很多评分函数实际上是scikit-learn的metrics模块的一部分。

让我们快速了解一些最重要的评分函数。

1. 使用准确率、精度和召回率评分分类器

在二值分类任务中只有两个不同的类标签,有许多不同的方法来度量分类性能。一些常见的评估指标如下所示:

  • accuracy_score:准确率(accuracy)计算测试集中预测正确的数据点数,并返回正确预测的数据点的比例。以将图片分类为猫或狗为例,准确率表示正确分类为包含猫或狗的图片比例。该函数是最基本的分类器评分函数。
  • precision_score:精度(precision)描述了一个分类器不把包含狗的图片标记为猫的能力。或者说,在分类器认为测试集所有包含猫的图片中,精度是实际包含一只猫的图片比例。
  • recall_score:召回率(recall,或者敏感度)描述了一个分类器检索包含猫的所有图片的能力。或者说,测试集所有包含猫的图片中,召回率是正确识别为猫的图片比例。

假设我们有一些ground truth(正确与否取决于我们的数据集)类标签,不是0就是1。我们使用NumPy的随机数生成器随机生成数据点。显然,这意味着只要我们重新运行代码,就会随机生成新数据点。可是,对于本书而言,这并没有多大的帮助,因为我们希望你能够运行代码,并总是得到和书中相同的结果。实现此目的的一个很好的技巧是固定随机数生成器的种子。这会保证你在每次运行脚本时,都以相同的方式初始化生成器:

1)我们使用下列代码可以固定随机数生成器的种子:

2)然后,选取(0, 2)范围内的随机整数,我们可以生成0或1的5个随机标签:

注意

在文献中,这两类有时也被称为正样例(类标签是1的所有数据点)和负样例(其他所有数据点)。

假设我们有一个分类器试图预测之前提到的类标签。为方便讨论,假设分类器不是很聪明,总是预测标签为1。通过硬编码预测标签,我们可以模拟这种行为:

我们预测的准确率是多少?

如前所述,准确率计算测试集中预测正确的数据点数,并返回测试集大小的比例。我们只是正确地预测了第二个数据点(实际标签是1)。除此之外,实际标签是0,而我们预测为1。因此,我们的准确率应该是1/5或者0.2。

准确率指标的一个简单实现可总结为:预测的类标签与实际类标签相符的所有情况。

scikit-learn的metrics模块提供了一个更智能、更便捷的实现:

这并不难,不是吗?但是,要理解精度和召回率,我们需要对I型错误和II型错误有大致的了解。让我们来回忆一下,通常把类标签为1的数据点称为正样例,把类标签为0(或–1)的数据点称为负样例。然后,对特定数据点进行分类,可能会产生以下4种结果之一,如表3-1的混淆矩阵所示。

表3-1 4种可能的分类结果

047-04

让我们进行一下分析。如果一个数据点实际是正样例,并且我们也将其预测为正样例,那么我们就预测对了!在这种情况下,将结果称为真阳性。如果我们认为数据点是正样例,但是该数据点实际是一个负样例,那么我们错误地预测了一个正样例(因此就有了假阳性这个术语)。类似地,如果我们认为数据点是负样例,但是该数据点实际是一个正样例,那么我们就错误地预测了一个负样例(假阴性)。最后,如果我们预测了一个负样例,而且该数据点确实是一个负样例,那么我们就找到了一个真阴性。

提示

在统计学假设检验中,假阳性也称为I型错误,而假阴性也称为II型错误

让我们在模拟数据上快速计算一下这4个评估指标。我们有一个真阳性,实际标签是1,并且我们预测为1:

类似地,一个假阳性是我们预测为1,但ground truth却是0:

现在,我相信你已经掌握了窍门。但是我们必须做数学运算才能知道预测的负样例吗?我们的并不是很聪明的分类器从不会预测为0,因此(y_pred==0)应该不会是真的:

让我们再来绘制一个混淆矩阵,如表3-2所示。

表3-2 混淆矩阵

048-03

要保证我们做的都是正确的,让我们再计算一下准确率。准确率应该是真阳性数据点数量加上真阴性数据点数量(即所有正确预测的数据点数)除以数据点总数:

成功了!接着给出精度,为真阳性数据点数除以所有正确预测的数据点数:

在我们的例子中,精度并不比准确率好。让我们用scikit-learn查看一下我们的数学运算:

最后,召回率是我们正确分类为正样例占所有正样例的比例:

召回率太棒了!但是,回到我们的模拟数据,很明显,这个优秀的召回率得分仅仅是运气好而已。因为在我们的模拟数据集中只有一个标签为1,而我们碰巧正确地对其进行了分类,所以我们得到了一个完美的召回率得分。这是否就意味着我们的分类器是完美的呢?未必如此!但是我们却发现了3个有用的评估指标,似乎从互补的方面度量了我们分类器性能。

2. 使用均方差、可释方差和R平方评分回归

在涉及回归模型时上述评估指标就不再有效了。毕竟,我们现在预测的是连续输出值,而不是区分分类标签。幸运的是,scikit-learn还提供了一些其他有用的评分函数:

  • mean_squared_error:对于回归问题,最常用的误差评估指标是对训练集中每个数据点的预测值和真实目标值之间的平方误差(所有数据点的平均值)进行度量。
  • explained_variance_score:一个更复杂的评估指标是度量一个模型对测试数据的变化或分配的可解释程度。通常使用相关系数度量可释方差的数量。
  • r2_score:R2得分(R平方)与可释方差得分密切相关,但使用一个无偏方差估计。它也被称为决定系数(coefficient of determination)。

让我们创建另一个模拟数据集。假设我们的观测数据看起来像是x值的一个sin函数。我们从生成0到10之间等间距的100个x值开始。

可是,真实数据总是有噪声的。为了尊重这一事实,我们希望目标值y_true也是有噪声的。我们通过在sin函数中加入噪声来实现:

这里,我们使用NumPy的rand函数在[0,1]范围内加入均匀分布的噪声,然后通过减去0.5将噪声集中在0周围。因此,我们有效地将每个数据点上下抖动最大0.5。

假设我们的模型足够聪明,能够计算出sin(x)的关系。因此,预测的y值如下所示:

这些数据是什么样子的呢?我们可以使用matplotlib对其进行可视化:

生成的线图如图3-2所示。

图3-2 使用matplotlib生成的可视化结果

确定我们的模型预测性能最直接的评估指标是均方误差。对于每个数据点,我们看预测值和实际y值之间的差异,然后对其进行平方。再计算所有数据点的平方误差的平均值:

为了方便计算,scikit-learn提供了自有的均方误差实现:

另一个常见的评估指标是测量数据的分散或变化:如果每个数据点都等于所有数据点的均值,那么数据中就没有分散或变化,我们就可以用一个数据值来预测所有未来的数据点。这将是世上最无聊的机器学习问题。但我们发现这些数据点通常会遵循一些我们想要揭示的未知的、隐藏的关系。在前面的例子中,这就是导致数据分散的y=sin(x)关系。

我们可以测量能够解释的数据(或方差)的分散程度。这通过计算预测标签和实际标签之间的方差来实现;这是我们的预测无法解释的所有方差。如果用数据的总方差对这个值进行归一化,我们就得到未知方差的分数(fraction of variance unexplained):

因为这个评估指标是一个分数,其值在0到1之间。我们可以从1中减去这个分数,得到可释方差的分数:

让我们用scikit-learn验证我们的数学运算:

完全正确!最后,我们可以计算出所谓的决定系数或者R2。R2与可释方差分数密切相关,并将先前计算的均方误差和数据中的实际方差进行比较:

通过scikit-learn也可以获得同样的值:

我们的预测与数据拟合得越好,与简单的平均数相比,R2得分的值越接近1。R2得分可以取负值,因为模型预测可以是小于1的任意值。一个常量模型总是预测y的期望值,独立于输入x,得到的R2得分为0: