面向 OpenCV 的机器学习(三)

原文:Machine Learning for OpenCV

协议:CC BY-NC-SA 4.0

十、分类的集成方法

到目前为止,我们已经研究了许多有趣的机器学习算法,从线性回归等经典方法到深度神经网络等更先进的技术。在不同的地方,我们指出每种算法都有自己的优点和缺点——我们注意到了如何发现和克服这些缺点。

然而,如果我们可以简单地将一堆平均分类器堆叠在一起,形成一个更强大的分类器集成,那不是很棒吗?

在本章中,我们将这样做。集成方法是将多个不同的模型绑定在一起以解决一个共享问题的技术。它们的使用已经成为竞争性机器学习的一种常见做法…

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 10

以下是软件和硬件要求的简短总结:

  • OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以正常工作)。
  • Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • Anaconda Python 3,用于安装 Python 和所需的模块。
  • 这本书可以使用任何操作系统——苹果操作系统、视窗操作系统或基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行书中提供的代码。

理解集成方法

集成方法的目标是结合用给定学习算法构建的几个独立估计器的预测,以解决一个共享问题。通常,一个合奏由两个主要部分组成:

  • 一套模型
  • 一组决策规则,用于控制如何将这些模型的结果组合成单个输出

The idea behind ensemble methods has much to do with the wisdom of the crowd concept. Rather than the opinion of a single expert, we consider the collective opinion of a group of individuals. In the context of machine learning, these individuals would be classifiers or regressors. The idea is that if we just ask a large enough number of classifiers, one of them ought to get …

理解平均系综

平均方法在机器学习中有很长的历史,通常应用于分子动力学和音频信号处理等领域。这种集合通常被视为给定系统的精确复制品。

平均集合本质上是在同一数据集上训练的模型的集合。然后以多种方式汇总他们的结果。

一种常见的方法是创建多个模型配置,这些配置采用不同的参数子集作为输入。采用这种方法的技术统称为装袋方法。

装袋方法有许多不同的口味。但是,它们的不同之处通常在于它们绘制训练集随机子集的方式:

  • 粘贴方法绘制样本的随机子集,而不替换数据样本。
  • Bagging 方法通过替换数据样本来抽取样本的随机子集。
  • 随机子空间方法绘制特征的随机子集,但在所有数据样本上进行训练。
  • 随机面片方法绘制样本和特征的随机子集。

Averaging ensembles can be used to reduce the variability of a model’s performance.

在 scikit-learn 中,打包方法可以使用BaggingClassifierBaggingRegressor元估计器来实现。这些是元估计器,因为它们允许我们从任何其他基础估计器构建一个集合。

实现打包分类器

例如,我们可以从 10 个 k -NN 分类器的集合中构建一个集成,如下所示:

In [1]: from sklearn.ensemble import BaggingClassifier...     from sklearn.neighbors import KNeighborsClassifier...     bag_knn = BaggingClassifier(KNeighborsClassifier(),...                                 n_estimators=10)

BaggingClassifier类提供了许多选项来定制集合:

  • n_estimators:如前面的代码所示,这指定了集合中基本估计量的数量。
  • max_samples:这表示从数据集中抽取样本的数量(或分数),以训练每个基本估计量。我们可以将bootstrap=True设置为替换取样(有效实施装袋),也可以将bootstrap=False设置为实施…

实现打包回归器

同样,我们可以使用BaggingRegressor类来形成回归器的集合。

例如,我们可以构建一个决策树集合,从第三章、的波士顿数据集预测房价,这是监督学习的第一步

在以下步骤中,您将学习如何使用 bagging 回归器来形成回归器的集合:

  1. 语法几乎与设置 bagging 分类器相同:
In [7]: from sklearn.ensemble import BaggingRegressor
...     from sklearn.tree import DecisionTreeRegressor
...     bag_tree = BaggingRegressor(DecisionTreeRegressor(),
...                                 max_features=0.5, n_estimators=10, 
...                                 random_state=3)
  1. 当然,我们需要像对乳腺癌数据集那样加载和拆分数据集:
In [8]: from sklearn.datasets import load_boston
...     dataset = load_boston()
...     X = dataset.data
...     y = dataset.target
In [9]: from sklearn.model_selection import train_test_split
...     X_train, X_test, y_train, y_test = train_test_split(
...         X, y, random_state=3
...     )
  1. 然后,我们可以在X_train上拟合装袋回归器,并在X_test上进行评分:
In [10]: bag_tree.fit(X_train, y_train)
...      bag_tree.score(X_test, y_test)
Out[10]: 0.82704756225081688

在前面的示例中,我们发现性能提升了大约 5%,从单个决策树的 77.3%准确率提升到 82.7%。

当然,我们不会就此打住。没有人说集合需要由 10 个独立的估计器组成,所以我们可以自由探索不同大小的集合。除此之外,max_samplesmax_features参数允许大量定制。

A more sophisticated version of bagged decision trees is called random forests, which we will talk about later in this chapter.

理解增强合奏

另一种构建合奏的方法是通过增强。增强模型按顺序使用多个个体学习者来迭代地增强集成的性能。

通常,用于增强的学习者相对简单。一个很好的例子是只有一个节点的决策树——一个决策树桩。另一个例子可以是简单的线性回归模型。我们的想法不是拥有最强的个人学习者,而是相反的——我们希望个人成为弱学习者,这样我们只有在考虑大量个人时才能获得优异的表现。

在该过程的每次迭代中,训练集被调整,使得下一个分类器被应用于…

弱学习者

弱学习者是与实际分类只有轻微关联的分类器;它们可能比随机预测要好一些。相反,强有力的学习者与正确的分类任意相关。

这里的想法是,你不能只使用一个,而是使用一组广泛的弱学习者,每一个都比随机的稍好。弱学习者的许多实例可以使用 boosting、bagging 等集合在一起,以创建强集成分类器。好处是最终的分类器不会导致在你的训练数据上过度拟合

例如,AdaBoost 在不同的加权训练数据上适合一系列弱学习者。它从预测训练数据集开始,并给予每个观察/样本同等的权重。如果第一个学习者的预测是不正确的,那么它会给予预测错误的观察/样本更高的权重。由于这是一个迭代过程,它会继续添加学习者,直到模型数量或准确性达到极限。

实现增强分类器

例如,我们可以从 10 棵决策树的集合中构建一个增强分类器,如下所示:

In [11]: from sklearn.ensemble import GradientBoostingClassifier...      boost_class = GradientBoostingClassifier(n_estimators=10,...                                               random_state=3)

这些分类器支持二进制和多类分类。

类似于BaggingClassifier类,GradientBoostingClassifier类提供了许多选项来定制集合:

  • n_estimators:这表示集合中基础估计量的数量。大量的估计器通常会带来更好的性能。
  • loss:表示需要优化的损失函数(或成本函数)。设置loss='deviance'实现逻辑回归…

实现增强回归器

实现增强回归器遵循与增强分类器相同的语法:

In [15]: from sklearn.ensemble import GradientBoostingRegressor
...      boost_reg = GradientBoostingRegressor(n_estimators=10,
...                                            random_state=3)

我们之前已经看到,在波士顿数据集上,单个决策树可以达到 79.3%的准确率。由 10 棵回归树组成的袋装决策树分类器达到 82.7%的准确率。但是一个被提升的回归者如何比较呢?

让我们重新加载波士顿数据集,并将其分成训练集和测试集。我们希望确保对random_state使用相同的值,以便最终在相同的数据子集上进行训练和测试:

In [16]: dataset = load_boston()
...      X = dataset.data
...      y = dataset.target
In [17]: X_train, X_test, y_train, y_test = train_test_split(
...          X, y, random_state=3
...     )

事实证明,增强的决策树集成实际上比之前的代码表现更差:

In [18]: boost_reg.fit(X_train, y_train)
...      boost_reg.score(X_test, y_test)
Out[18]: 0.71991199075668488

这个结果一开始可能会令人困惑。毕竟,我们使用的分类器比单一决策树多 10 倍。为什么我们的数字会变得更糟?

你可以看到这是一个专家分类器比一群弱学习者更聪明的好例子。一个可能的解决方案是让整体变大。事实上,在强化合奏中,习惯上使用大约 100 个弱学习者:

In [19]: boost_reg = GradientBoostingRegressor(n_estimators=100)

然后,当我们在波士顿数据集上重新训练集成时,我们得到了 89.8%的测试分数:

In [20]: boost_reg.fit(X_train, y_train)
...      boost_reg.score(X_test, y_test)
Out[20]: 0.89984081091774459

当你把数字增加到n_estimators=500时会发生什么?通过使用可选参数,我们可以做更多的事情。

如您所见,boosting 是一个强大的过程,它允许您通过组合大量相对简单的学习者来获得巨大的性能提升。

A specific implementation of boosted decision trees is the AdaBoost algorithm, which we will talk about later in this chapter.

理解堆叠集合

到目前为止,我们看到的所有集成方法都有一个共同的设计理念:将多个单独的分类器与数据相匹配,并借助一些简单的决策规则(如平均或提升)将其预测合并到最终预测中。

另一方面,堆叠系综构建了具有层次的系综。这里,个体学习者被组织成多个层,其中一层学习者的输出被用作下一层模型的训练数据。这样,就有可能成功地融合数百种不同的模式。

不幸的是,详细讨论堆叠集合超出了本书的范围。

然而,正如所见,这些模型可能非常强大,…

将决策树组合成随机森林

袋装决策树的一种流行变体是所谓的随机森林。这些本质上是决策树的集合,其中每个树与其他树略有不同。与袋装决策树不同,随机森林中的每棵树都是在稍微不同的数据特征子集上训练的。

尽管单个无限深度的树可能在预测数据方面做得相对较好,但它也容易过度拟合。随机森林背后的想法是建立大量的树,每个树都在数据样本和特征的随机子集上训练。由于过程的随机性,森林中的每棵树都会以稍微不同的方式对数据进行过度填充。过度拟合的影响可以通过对单棵树的预测取平均值来降低。

理解决策树的缺点

决策树经常遭受数据集过拟合的影响,通过一个简单的例子可以最好地证明这一点。

为此,我们将从 scikit-learn 的datasets模块返回到make_moons功能,我们之前在第八章、使用无监督学习发现隐藏结构将数据组织成两个交错的半圆。这里,我们选择生成属于两个半圆的 100 个数据样本,结合一些标准偏差为0.25的高斯噪声:

In [1]: from sklearn.datasets import make_moons...     X, y = make_moons(n_samples=100, noise=0.25,...                       random_state=100)

我们可以使用 matplotlib 和scatter可视化这些数据

实现我们的第一个随机森林

在 OpenCV 中,可以使用ml模块中的RTrees_create功能构建随机森林:

In [7]: import cv2
...     rtree = cv2.ml.RTrees_create()

树对象提供了许多选项,其中最重要的是:

  • setMaxDepth:这将设置集合中每棵树的最大可能深度。如果首先满足其他终止标准,实际获得的深度可能会更小。
  • setMinSampleCount:这设置了一个节点可以包含的最小样本数,以便进行拆分。
  • setMaxCategories:设置允许的最大类别数。将类别数设置为比数据中实际类别数小的值会导致子集估计。
  • setTermCriteria:设置算法的终止条件。这也是您设置森林中树木数量的地方。

Although we might have hoped for a setNumTrees method to set the number of trees in the forest (kind of the most important parameter of them all, no?), we instead need to rely on the setTermCriteria method. Confusingly, the number of trees is conflated with cv2.TERM_CRITERA_MAX_ITER, which is usually reserved for the number of iterations that an algorithm is run for, not for the number of estimators in an ensemble.

我们可以通过向setTermCriteria方法传递一个整数n_trees来指定森林中的树木数量。在这里,我们还想告诉算法,一旦分数从一次迭代到下一次迭代至少没有增加eps就退出:

In [8]: n_trees = 10
...     eps = 0.01
...     criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
...                 n_trees, eps)
...     rtree.setTermCriteria(criteria)

然后,我们准备在前面代码的数据上训练分类器:

In [9]: rtree.train(X_train.astype(np.float32), cv2.ml.ROW_SAMPLE,
                    y_train);

测试标签可以用predict方法预测:

In [10]: _, y_hat = rtree.predict(X_test.astype(np.float32))

使用 scikit-learn 的accuracy_score,我们可以在测试集上评估模型:

In [11]: from sklearn.metrics import accuracy_score
...      accuracy_score(y_test, y_hat)
Out[11]: 0.83999999999999997

经过训练,我们可以将预测的标签传递给plot_decision_boundary功能:

In [12]: plot_decision_boundary(rtree, X_test, y_test)

这将产生以下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了随机森林分类器的决策场景。

用 scikit-learn 实现随机森林

或者,我们可以使用 scikit-learn 实现随机森林:

In [13]: from sklearn.ensemble import RandomForestClassifier...      forest = RandomForestClassifier(n_estimators=10, random_state=200)

在这里,我们有许多选项来定制集合:

  • n_estimators:指定森林中的树木数量。
  • criterion:指定节点拆分的标准。设置criterion='gini'实现基尼杂质,设置criterion='entropy'实现信息增益。
  • max_features:指定每个节点分割时要考虑的特征数量(或分数)。
  • max_depth:指定每棵树的最大深度。
  • min_samples:指定最小数量…

实现极度随机化的树

随机森林已经相当随意了。但是如果我们想把随机性发挥到极致呢?

在极其随机的树中(见ExtraTreesClassifierExtraTreesRegressor类),随机性甚至比随机森林更进一步。还记得决策树通常如何为每个特征选择一个阈值,以使节点分裂的纯度最大化吗?另一方面,极度随机化的树会随机选择这些阈值。然后,这些随机生成的阈值中的最佳阈值被用作拆分规则。

我们可以构建一个极其随机的树,如下所示:

In [16]: from sklearn.ensemble import ExtraTreesClassifier
...      extra_tree = ExtraTreesClassifier(n_estimators=10, random_state=100)

为了说明单个决策树、随机森林和极随机树之间的区别,让我们考虑一个简单的数据集,例如 Iris 数据集:

In [17]: from sklearn.datasets import load_iris
...      iris = load_iris()
...      X = iris.data[:, [0, 2]]
...      y = iris.target
In [18]: X_train, X_test, y_train, y_test = train_test_split(
...          X, y, random_state=100
...      )

然后,我们可以像以前一样对树对象进行拟合和评分:

In [19]: extra_tree.fit(X_train, y_train)
...      extra_tree.score(X_test, y_test)
Out[19]: 0.92105263157894735

相比之下,使用随机林会产生相同的性能:

In [20]: forest = RandomForestClassifier(n_estimators=10,
                                        random_state=100)
...      forest.fit(X_train, y_train)
...      forest.score(X_test, y_test)
Out[20]: 0.92105263157894735

事实上,对于单棵树也是如此:

In [21]: tree = DecisionTreeClassifier()
...      tree.fit(X_train, y_train)
...      tree.score(X_test, y_test)
Out[21]: 0.92105263157894735

那么,它们之间有什么区别呢?要回答这个问题,我们必须看决策边界。幸运的是,我们已经在前面的部分中导入了我们的plot_decision_boundary助手函数,所以我们所需要做的就是将不同的分类器对象传递给它。

我们将构建一个分类器列表,其中列表中的每个条目都是一个元组,包含一个索引、分类器的名称和分类器对象:

In [22]: classifiers = [
...          (1, 'decision tree', tree),
...          (2, 'random forest', forest),
...          (3, 'extremely randomized trees', extra_tree)
...      ]

然后,很容易将分类器列表传递给我们的助手函数,这样每个分类器的决策场景都绘制在自己的子场景中:

In [23]: for sp, name, model in classifiers:
...      plt.subplot(1, 3, sp)
...      plot_decision_boundary(model, X_test, y_test)
...      plt.title(name)
...      plt.axis('off')

结果是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在三个分类器之间的区别变得更加清晰。我们看到单棵树绘制了迄今为止最简单的决策边界,使用水平决策边界分割景观。随机森林能够更清楚地将决策图左下角的数据点云分开。然而,只有极其随机的树能够从四面八方将数据点云转向景观中心。

现在我们已经知道了树集合的所有不同变体,让我们继续看真实世界的数据集。

使用随机森林进行人脸识别

我们还没怎么讨论的一个流行数据集是奥利韦蒂人脸数据集。

奥利维蒂人脸数据集是剑桥美国电话电报公司实验室在 1990 年收集的。该数据集包括 40 个不同受试者在不同时间和不同光照条件下拍摄的面部图像。此外,受试者的面部表情(睁开/闭上眼睛、微笑/不微笑)和面部细节(戴眼镜/不戴眼镜)各不相同。

然后,图像被量化为 256 个灰度级,并存储为无符号 8 位整数。因为有 40 个不同的主题,所以数据集带有 40 个不同的目标标签。因此,识别人脸构成了多类分类任务的一个例子。

正在加载数据集

像许多其他经典数据集一样,可以使用 scikit-learn 加载 Olivetti 人脸数据集:

In [1]: from sklearn.datasets import fetch_olivetti_faces
...     dataset = fetch_olivetti_faces()
In [2]: X = dataset.data
...     y = dataset.target

虽然原始图像由 92 x 112 像素的图像组成,但通过 scikit-learn 提供的版本包含缩小到 64 x 64 像素的图像。

为了了解数据集,我们可以绘制一些示例图像。让我们从数据集中随机选取八个索引:

In [3]: import numpy as np
...     np.random.seed(21)
...     idx_rand = np.random.randint(len(X), size=8)

我们可以使用 matplotlib 绘制这些示例图像,但是在绘制之前,我们需要确保将列向量重塑为 64 x 64 像素的图像:

In [4]: import matplotlib.pyplot as plt
...     %matplotlib inline
...     for p, i in enumerate(idx_rand):
...         plt.subplot(2, 4, p + 1)
... plt.imshow(X[i, :].reshape((64, 64)), cmap='gray')
...         plt.axis('off')

上述代码产生以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你可以看到所有的脸是如何在黑暗的背景下拍摄的,并且是肖像。不同图像的面部表情差异很大,这使得分类成为一个有趣的问题。尽量不要嘲笑他们中的一些人!

预处理数据集

在我们将数据集传递给分类器之前,我们需要按照第四章、中表示数据和工程特征的最佳实践对其进行预处理。

具体来说,我们希望确保所有示例图像具有相同的平均灰度级别:

In [5]: n_samples, n_features = X.shape[:2]...     X -= X.mean(axis=0)

我们对每个图像重复此过程,以确保每个数据点(即X中的一行)的特征值都以零为中心:

In [6]: X -= X.mean(axis=1).reshape(n_samples, -1)

预处理数据可以使用以下代码可视化:

In [7]: for p, i in enumerate(idx_rand):...         plt.subplot(2, 4, p + 1)...         plt.imshow(X[i, :].reshape((64, 64)), cmap='gray')... plt.axis('off') ...

随机森林的训练和测试

我们继续遵循最佳实践,将数据分为训练集和测试集:

In [8]: from sklearn.model_selection import train_test_split
...     X_train, X_test, y_train, y_test = train_test_split(
...         X, y, random_state=21
...     )

然后,我们准备对数据应用随机森林:

In [9]: import cv2
...     rtree = cv2.ml.RTrees_create()

在这里,我们想要创建一个包含 50 棵决策树的集合:

In [10]: n_trees = 50
...      eps = 0.01
...      criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
...                  n_trees, eps)
...      rtree.setTermCriteria(criteria)

因为我们有大量的类别(即 40 个),所以我们希望确保随机森林被设置为相应地处理它们:

In [10]: rtree.setMaxCategories(len(np.unique(y)))

我们可以使用其他可选参数,例如节点在拆分前所需的数据点数量:

In [11]: rtree.setMinSampleCount(2)

然而,我们可能不想限制每棵树的深度。这也是我们最终必须试验的一个参数。但是现在,让我们将其设置为一个大的整数值,使深度有效地不受约束:

In [12]: rtree.setMaxDepth(1000)

然后,我们可以将分类器与训练数据进行匹配:

In [13]: rtree.train(X_train, cv2.ml.ROW_SAMPLE, y_train);

我们可以使用以下函数来检查生成的树的深度:

In [13]: rtree.getMaxDepth()
Out[13]: 25

这意味着,虽然我们允许树上升到深度 1000,但最终只需要 25 层。

分类器的评估再次通过首先预测标签(y_hat)然后将它们传递给accuracy_score功能来完成:

In [14]: _, y_hat = tree.predict(X_test)
In [15]: from sklearn.metrics import accuracy_score
...      accuracy_score(y_test, y_hat)
Out[15]: 0.87

我们发现 87%的准确率,这比使用单一决策树要好得多:

In [16]: from sklearn.tree import DecisionTreeClassifier
...      tree = DecisionTreeClassifier(random_state=21, max_depth=25)
...      tree.fit(X_train, y_train)
...      tree.score(X_test, y_test)
Out[16]: 0.46999999999999997

还不错!我们可以玩可选的参数,看看我们是否会变得更好。最重要的似乎是森林中的树木数量。我们可以用一个由 1000 棵树而不是 50 棵树组成的森林重复这个实验:

In [18]: num_trees = 1000
... eps = 0.01
... criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
... num_trees, eps)
... rtree.setTermCriteria(criteria)
... rtree.train(X_train, cv2.ml.ROW_SAMPLE, y_train);
... _, y_hat = rtree.predict(X_test)
... accuracy_score(y_test, y_hat)
Out[18]: 0.94

通过这种配置,我们获得了 94%的准确率!

Here, we tried to improve the performance of our model through creative trial and error: we varied some of the parameters we deemed important and observed the resulting change in performance until we found a configuration that satisfied our expectations. We will learn more sophisticated techniques for improving a model in Chapter 11, Selecting the Right Model with Hyperparameter Tuning.

决策树集成的另一个有趣的用例是 AdaBoost。

实现 AdaBoost

当森林中的树是深度为 1 的树(也称为决策树桩)并且我们执行助推而不是装袋时,得到的算法被称为 AdaBoost

AdaBoost 通过执行以下操作在每次迭代时调整数据集:

  • 选择决策树桩
  • 增加决策树桩标注不正确的案例的权重,同时减少标注正确的案例的权重

这种迭代权重调整使得集成中的每个新分类器优先训练错误标记的案例。因此,该模型通过瞄准高度加权的数据点进行调整。

最终,树桩被组合成最终的分类器。

在 OpenCV 中实现 AdaBoost

虽然 OpenCV 提供了非常高效的 AdaBoost 实现,但它隐藏在哈尔级联分类器下。哈尔级联分类器是非常流行的人脸检测工具,我们可以通过 Lena 图像的例子来说明:

In [1]: img_bgr = cv2.imread('data/lena.jpg', cv2.IMREAD_COLOR)
...     img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

在加载彩色和灰度图像后,我们加载一个预处理的哈尔级联:

In [2]: import cv2
...     filename = 'data/haarcascade_frontalface_default.xml'
...     face_cascade = cv2.CascadeClassifier(filename)

然后,分类器将使用以下函数调用检测图像中出现的人脸:

In [3]: faces = face_cascade.detectMultiScale(img_gray, 1.1, 5)

请注意,该算法仅适用于灰度图像。这就是为什么我们保存了两张 Lena 的图片,一张我们可以应用分类器(img_gray),另一张我们可以在上面绘制结果包围盒(img_bgr):

In [4]: color = (255, 0, 0)
...     thickness = 2
...     for (x, y, w, h) in faces:
...         cv2.rectangle(img_bgr, (x, y), (x + w, y + h),
...                       color, thickness)

然后,我们可以使用以下代码绘制图像:

In [5]: import matplotlib.pyplot as plt
...     %matplotlib inline
...     plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB));

这将产生以下输出,面的位置由蓝色边界框指示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

显然,这张截图只包含了一张脸。然而,前面的代码即使在可以检测到多个人脸的图像上也能工作。试试看!

在 scikit-learn 中实现 AdaBoost

在 scikit-learn 中,AdaBoost 只是另一个集成估计器。我们可以用 50 个决策树桩创建一个集合,如下所示:

In [6]: from sklearn.ensemble import AdaBoostClassifier...     ada = AdaBoostClassifier(n_estimators=50,...                              random_state=456)

我们可以再次加载乳腺癌集,并将其拆分为 75-25:

In [7]: from sklearn.datasets import load_breast_cancer...     cancer = load_breast_cancer()...     X = cancer.data...     y = cancer.targetIn [8]: from sklearn.model_selection import train_test_split...     X_train, X_test, y_train, y_test = train_test_split(...         X, y, random_state=456...     )

然后,使用熟悉的程序fitscore AdaBoost:

In [9]: ada.fit(X_train, y_train)...     ada.score(X_test, y_test)

将不同的模型组合成投票分类器

到目前为止,我们已经看到了如何将同一个分类器或回归器的不同实例组合成一个集合。在这一章中,我们将把这个想法更进一步,将概念上不同的分类器组合成一个被称为投票分类器

投票分类器背后的想法是,集成中的单个学习者不一定需要属于同一类型。毕竟,不管单个分类器如何得出它们的预测,最终,我们将应用一个决策规则,该规则集成了单个分类器的所有投票。这也被称为投票方案

了解不同的投票方案

两种不同的投票方案在投票分类器中很常见:

  • 硬投票(也称多数投票)中,每个个体分类器为一个类投票,多数获胜。在统计学术语中,集合的预测目标标签是单独预测标签的分布模式。
  • 软投票中,每个单独的分类器提供特定数据点属于特定目标类的概率值。预测通过分类器的重要性进行加权,并进行汇总。然后,加权概率之和最大的目标标签赢得投票。

例如,让我们假设集合中有三个不同的分类器执行…

实现投票分类器

让我们看一个投票分类器的简单例子,它结合了三种不同的算法:

  • 来自第三章的逻辑回归分类器:监督学习的第一步
  • 来自第七章、的高斯朴素贝叶斯分类器利用贝叶斯学习实现垃圾邮件过滤器
  • 本章中的随机森林分类器

我们可以将这三种算法组合成投票分类器,并通过以下步骤将其应用于乳腺癌数据集:

  1. 加载数据集,并将其拆分为训练集和测试集:
In [1]: from sklearn.datasets import load_breast_cancer
...     cancer = load_breast_cancer()
...     X = cancer.data
...     y = cancer.target
In [2]: from sklearn.model_selection import train_test_split
...     X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=13)
  1. 实例化各个分类器:
 In [3]: from sklearn.linear_model import LogisticRegression
...     model1 = LogisticRegression(random_state=13)
 In [4]: from sklearn.naive_bayes import GaussianNB
...     model2 = GaussianNB()
In [5]: from sklearn.ensemble import RandomForestClassifier
...     model3 = RandomForestClassifier(random_state=13)
  1. 将单个分类器分配给投票集合。这里,我们需要传递一个元组列表(estimators),其中每个元组由分类器的名称(描述每个分类器的简称的一串字母)和模型对象组成。投票方案可以是voting='hard'也可以是voting='soft'。目前,我们将选择**??😗*
In [6]: from sklearn.ensemble import VotingClassifier
...     vote = VotingClassifier(estimators=[('lr', model1),
...                                ('gnb', model2),('rfc', model3)],voting='hard')
  1. 将集合与训练数据相匹配,并在测试数据上进行评分:
In [7]: vote.fit(X_train, y_train)
...     vote.score(X_test, y_test)
Out[7]: 0.95104895104895104

为了让我们相信 95.1%是一个很好的准确率,我们可以将集成的性能与每个单独分类器的理论性能进行比较。我们通过将单个分类器与数据进行拟合来实现这一点。然后,我们将看到逻辑回归模型自身达到 94.4%的精度:

In [8]: model1.fit(X_train, y_train)
...     model1.score(X_test, y_test)
Out[8]: 0.94405594405594406

类似地,朴素贝叶斯分类器达到 93.0%的准确率:

In [9]:  model2.fit(X_train, y_train)
...      model2.score(X_test, y_test)
Out[9]:  0.93006993006993011

最后但同样重要的是,随机森林分类器也达到了 94.4%的准确率:

In [10]: model3.fit(X_train, y_train)
... model3.score(X_test, y_test)
Out[10]: 0.94405594405594406

总之,通过将三个不相关的分类器组合成一个集成,我们能够获得很好的性能百分比。这些分类器中的每一个都可能在训练集上犯了不同的错误,但这没关系,因为平均来说,我们只需要三个分类器中的两个就能正确。

复数

在前几节中,我们讨论了集成方法。我们之前没有提到的是,结果是如何在集成技术准备的单个模型中聚合的。用于此的概念叫做复数无非就是投票。一个班级获得的票数越高,成为最终班级的机会就越大。想象一下,如果我们在集成技术中准备了三个模型和 10 个可能的类(把它们想象成从 0 到 9 的数字)。每个模型将根据它获得的最高概率选择一个类。最后,得票最多的班级将被选中。这就是多元的概念。在实践中,多元化试图给 ?? 和天真带来好处…

摘要

在本章中,我们讨论了如何通过将各种分类器组合成一个集成来改进它们。我们讨论了如何使用 bagging 对不同分类器的预测进行平均,以及如何使用 boosting 让不同的分类器纠正彼此的错误。我们花了很多时间来讨论组合决策树的所有可能方法,无论是决策树桩(AdaBoost)、随机森林还是极随机树。最后,我们学习了如何通过构建投票分类器来组合集成中不同类型的分类器。

在下一章中,我们将更多地讨论如何通过深入模型选择和超参数调整的世界来比较不同分类器的结果。

十一、使用超参数调整选择正确的模型

既然我们已经探索了各种各样的机器学习算法,我相信你已经意识到它们中的大多数都有大量的设置可供选择。这些设置或调谐旋钮,即所谓的超参数,帮助我们在试图最大化性能时控制算法的行为。

例如,我们可能想要选择决策树中的深度或分裂标准,或者调整神经网络中的神经元数量。寻找模型的重要参数值是一项棘手的任务,但对于几乎所有模型和数据集都是必要的。

在本章中,我们将深入探讨模型评估超参数调整。假设我们有两个不同的…

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 11

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 除了这本书,你可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行本书提供的代码。

评估模型

模型评估策略有许多不同的形式和形状。因此,在接下来的章节中,我们将重点介绍三种最常用的相互比较模型的技术:

  • k 倍交叉验证
  • 拔靴带
  • 麦克内尔的测试

原则上,模型评估很简单:在一些数据上训练模型后,我们可以通过将模型预测与一些地面真实值进行比较来估计其有效性。我们很早就知道,我们应该将数据分成训练集和测试集,并且我们尽可能地遵循这个指导。但是我们到底为什么要再做一次呢?

以错误的方式评估模型

我们从不在训练集上评估模型的原因是,原则上,如果我们向任何数据集扔一个足够强的模型,它就可以被学习。

借助 Iris 数据集可以快速演示这一点,我们在第三章、监督学习的第一步中详细讨论了这个数据集。在那里,目标是根据鸢尾花的物理尺寸对它们进行分类。我们可以使用 scikit-learn 加载 Iris 数据集:

In [1]: from sklearn.datasets import load_iris
...     iris = load_iris()

解决这个问题的一个简单方法是将所有数据点存储在矩阵X中,并将所有类标签存储在向量y中:

In [2]: import numpy as np
...     X = iris.data.astype(np.float32)
...     y = iris.target

接下来,我们选择一个模型及其超参数。例如,让我们使用来自第三章、监督学习的第一步的算法,它只提供一个超参数:邻居的数量, k 。借助 k=1 ,我们得到一个非常简单的模型,将未知点的标签分类为与其最近邻居属于同一类。

在以下步骤中,您将学习如何构建 k 最近邻 ( k-NN )并计算其精度:

  1. 在 OpenCV 中,kNN 实例化如下:
In [3]: import cv2
...     knn = cv2.ml.KNearest_create()
...     knn.setDefaultK(1)
  1. 然后,我们训练模型,并使用它来预测我们已经知道的数据的标签:
In [4]: knn.train(X, cv2.ml.ROW_SAMPLE, y)
...     _, y_hat = knn.predict(X)
  1. 最后,我们计算正确标记点的分数:
In [5]: from sklearn.metrics import accuracy_score
...     accuracy_score(y, y_hat)
Out[5]: 1.0

我们可以看到,准确率得分为1.0,说明我们的模型 100%正确标注了点。

If a model gets 100% accuracy on the training set, we say the model memorized the data.

但是预期的准确性真的被测量了吗?我们是否已经提出了一个我们期望 100%正确的模型?

正如你可能已经收集到的,答案是否定的。这个例子表明,即使是一个简单的算法也能够记忆真实世界的数据集。想象一下,对于一个深度神经网络来说,这个任务是多么容易!通常,模型的参数越多,它就越强大。我们将很快回到这个问题。

以正确的方式评估模型

使用所谓的测试集可以更好地了解模型的性能,但是你已经知道这一点了。当展示了从训练过程中获得的数据时,我们可以检查一个模型是否已经学习了数据中的一些相关性,或者它是否已经记住了训练集。

我们可以使用 scikit-learn 的model_selection模块中熟悉的train_test_split将数据分成训练集和测试集:

In [6]: from sklearn.model_selection import train_test_split

但是我们如何选择正确的列车测试比率呢?甚至有正确比率这种东西吗?还是认为这是模型的另一个超参数?

这里有两个相互矛盾的问题:

  • 如果我们的…

选择最佳型号

当一个模型表现不佳时,通常不清楚如何让它变得更好。在这本书里,我宣布了一个拇指规则,例如,如何选择神经网络的层数。更糟糕的是,答案往往是反直觉的!例如,向网络中添加另一层可能会使结果更糟,而添加更多的训练数据可能根本不会改变性能。

你可以看到为什么这些问题是机器学习最重要的方面。归根结底,确定哪些步骤将改进或不改进我们的模型的能力是成功的机器学习实践者与众不同的地方。

我们来看一个具体的例子。还记得第五章、使用决策树进行医疗诊断吗,我们在回归任务中使用决策树?我们将两个不同的树拟合到一个 sin 函数中——一个深度为 2,另一个深度为 5。提醒一下,回归结果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

应该清楚的是,这两种配合都不是特别好。然而,这两个决策树以两种不同的方式失败!

深度为 2 的决策树(上一张截图中的粗线)试图拟合数据中的四条直线。因为数据本质上比几条直线更复杂,这个模型失败了。我们可以尽可能多地训练它,尽可能多地生成训练样本——它永远无法很好地描述这个数据集。这种模型被称为数据不足。换句话说,模型没有足够的复杂性来考虑数据中的所有特征。因此,该模型具有很高的偏差。

另一个决策树(细线,深度 5)犯了不同的错误。该模型具有足够的灵活性,几乎可以完美地解释数据中的精细结构。然而,在某些点上,模型似乎遵循噪声的特定模式;我们增加了原罪函数而不是原罪函数本身。你可以在图的右边看到,蓝色曲线(细线)会抖动很多。据说这样的模型会使数据过度膨胀。换句话说,这个模型太复杂了,以至于最终要考虑数据中的随机误差。因此,该模型具有较高的方差。

长话短说——秘密就在这里:从根本上说,选择正确的模型归结为在偏见和差异之间找到一个平衡点。

The amount of flexibility a model has (also known as the model complexity) is mostly dictated by its hyperparameters. That is why it is so important to tune them!

让我们回到 kNN 算法和 Iris 数据集。如果我们针对所有可能的值 k 重复将模型拟合到 Iris 数据的过程,并计算训练和测试分数,我们将期望结果如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了作为模型复杂性函数的模型得分。如果这一章有什么我想让你记住的,那就是这个图表。让我们打开它。

该图将模型得分(训练或测试得分)描述为模型复杂性的函数。如前图所述,神经网络的模型复杂性大致随着网络中神经元的数量而增长。在 kNN 的情况下,适用相反的逻辑——值 k 越大,决策边界越平滑,因此复杂度越低。换句话说, k=1 的 kNN 将在前面的图中一直向右,此时训练分数是完美的。难怪我们在训练集上获得了 100%的准确率!

从上图中,我们可以看出在模型复杂性领域有三种状态:

  • 训练数据下的模型复杂度非常低(高偏差模型)。在这种情况下,无论我们训练了多长时间,模型在训练集和测试集上都只能获得很低的分数。
  • 一个具有很高复杂度(或高方差)的模型会过度训练数据,这表明该模型可以很好地预测训练数据,但不能预测未知数据。在这种情况下,模型开始学习仅出现在训练数据中的复杂性或特殊性。因为这些特性不适用于看不见的数据,所以训练分数越来越低。
  • 对于某些中间值,测试分数最大。我们试图找到的正是这种中间状态,即考试分数最高的状态。这是偏见和差异之间权衡的最佳点!

这意味着我们可以通过绘制模型复杂性图来为手头的任务找到最佳算法。具体来说,我们可以使用以下指标来了解我们目前所处的状态:

  • 如果训练和测试分数都低于我们的预期,我们可能处于上图中最左边的状态,此时模型对数据的拟合不足。在这种情况下,一个好主意可能是增加模型的复杂性,然后再试一次。
  • 如果训练分数比测试分数高得多,那么我们可能处于上图中最右边的状态,此时模型正在过度拟合数据。在这种情况下,一个好主意可能是降低模型的复杂性,然后再试一次。

虽然这个过程在总体上是可行的,但是有更复杂的模型评估策略,比简单的训练-测试分割更彻底,这一点我们将在后面的章节中讨论。

理解交叉验证

交叉验证是一种评估模型泛化性能的方法,通常比将数据集拆分为训练集和测试集更稳定和彻底。

交叉验证最常用的版本是 k 倍交叉验证,其中 k 是用户指定的数字(通常是 5 或 10)。这里,数据集被分割成大小大致相等的 k 部分,称为折叠。对于包含 N 数据点的数据集,每个折叠应该具有大约 N / k 个样本。然后,在数据上训练一系列模型,使用 k - 1 折叠进行训练,剩余一个折叠进行测试。对 k 迭代重复该过程,每次选择不同的折叠…

在 OpenCV 中手动实现交叉验证

在 OpenCV 中执行交叉验证最简单的方法是手工进行数据拆分。

例如,为了实现双重交叉验证,我们将执行以下过程:

  1. 加载数据集:
      In [1]: from sklearn.datasets import load_iris
      ...     import numpy as np
      ...     iris = load_iris()
      ...     X = iris.data.astype(np.float32)
      ...     y = iris.target
  1. 将数据分成大小相等的两部分:
      In [2]: from sklearn.model_selection import model_selection
      ...     X_fold1, X_fold2, y_fold1, y_fold2 = train_test_split(
      ...         X, y, random_state=37, train_size=0.5
      ...     )
  1. 实例化分类器:
      In [3]: import cv2
      ...     knn = cv2.ml.KNearest_create()
      ...     knn.setDefaultK(1)
  1. 在第一个折叠上训练分类器,然后预测第二个折叠的标签:
      In [4]: knn.train(X_fold1, cv2.ml.ROW_SAMPLE, y_fold1)
      ...     _, y_hat_fold2 = knn.predict(X_fold2)
  1. 在第二个折叠上训练分类器,然后预测第一个折叠的标签:
      In [5]: knn.train(X_fold2, cv2.ml.ROW_SAMPLE, y_fold2)
      ...     _, y_hat_fold1 = knn.predict(X_fold1)
  1. 计算两次折叠的准确度分数:
      In [6]: from sklearn.metrics import accuracy_score
      ...     accuracy_score(y_fold1, y_hat_fold1)
      Out[6]: 0.92000000000000004
      In [7]: accuracy_score(y_fold2, y_hat_fold2)
      Out[7]: 0.88

该程序将产生两个准确度分数,一个用于第一次折叠(准确度 92%),一个用于第二次折叠(准确度 88%)。平均来说,我们的分类器在看不见的数据上达到了 90%的准确率。

使用 scikit-learn 进行 k 倍交叉验证

在 scikit-learn 中,交叉验证可以分三步进行:

  1. 加载数据集。既然我们之前已经这么做了,就不用再做了。
  2. 实例化分类器:
      In [8]: from sklearn.neighbors import KNeighborsClassifier      ...     model = KNeighborsClassifier(n_neighbors=1)
  1. 使用cross_val_score功能进行交叉验证。该函数将模型、完整数据集(X)、目标标签(y)和折叠数的整数值(cv)作为输入。不需要手动拆分数据,该功能会根据折叠次数自动拆分数据。交叉验证完成后,该函数返回测试分数:
 In [9]: from sklearn.model_selection ...

实施遗漏交叉验证

实现交叉验证的另一种流行方法是选择与数据集中数据点数量相等的折叠数量。换句话说,如果有 N 个数据点,我们设置 k=N 。这意味着我们最终将不得不进行交叉验证的 N 次迭代,但是在每次迭代中,训练集将仅由单个数据点组成。这个过程的优点是我们可以使用除一个以外的所有数据点进行训练。因此,该程序也被称为省去交叉验证。

在 scikit-learn 中,该功能由model_selection模块中的LeaveOneOut方法提供:

In [11]: from sklearn.model_selection import LeaveOneOut

该对象可以通过以下方式直接传递给cross_val_score功能:

In [12]: scores = cross_val_score(model, X, y, cv=LeaveOneOut())

因为每个测试集现在都包含一个数据点,所以我们期望计分器返回 150 个值——数据集中每个数据点一个值。我们得到的每一点可能是对的,也可能是错的。因此,我们期望scores是 1(1)和 0(0)的列表,它们分别对应于正确和不正确的分类:

In [13]: scores
Out[13]: array([ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 0., 1., 0., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                 1., 1., 1., 1., 1., 1., 1.])

如果我们想知道分类器的平均性能,我们仍然会计算分数的平均值和标准偏差:

In [14]: scores.mean(), scores.std()
Out[14]: (0.95999999999999996, 0.19595917942265423)

我们可以看到这个评分方案返回了非常类似于五重交叉验证的结果。

You can learn more about other useful cross-validation procedures at scikit-learn.org/stable/modules/cross_validation.html.

使用自举估计鲁棒性

k 倍交叉验证的另一个程序是引导

引导不是将数据拆分成折叠,而是通过从数据集中随机抽取样本来构建训练集。通常,通过抽取替换样本来形成引导。想象一下,把所有的数据点放进一个袋子里,然后从袋子里随机抽取。抽取样本后,我们会把它放回袋子里。这允许一些样本在训练集中出现多次,这是交叉验证所不允许的。

然后在不属于引导程序的所有样本上测试分类器(所谓的袋外例子),并且该过程被重复大量次…

在 OpenCV 中手动实现引导

引导可以通过以下过程实现:

  1. 加载数据集。既然我们之前已经这么做了,就不用再做了。
  2. 实例化分类器:
      In [15]: knn = cv2.ml.KNearest_create()
      ...      knn.setDefaultK(1)
  1. 从我们的带有 N 样本的数据集中,随机选择带有替换的 N 样本,形成一个自举。这可以通过 NumPy 的random模块中的choice功能最容易地完成。我们告诉函数用替换(replace=True)在[0, len(X)-1]范围内抽取len(X)样本。然后,该函数返回一个索引列表,我们从该列表中构建引导数据库:
      In [16]: idx_boot = np.random.choice(len(X), size=len(X),
      ...                                  replace=True)
      ...      X_boot = X[idx_boot, :]
      ...      y_boot = y[idx_boot]
  1. 将自举中未显示的所有样本放入袋外样品组:
      In [17]: idx_oob = np.array([x not in idx_boot
      ...      for x in np.arange(len(X))],dtype=np.bool)
      ...      X_oob = X[idx_oob, :]
      ...      y_oob = y[idx_oob]
  1. 在引导样本上训练分类器:
      In [18]: knn.train(X_train, cv2.ml.ROW_SAMPLE, y_boot)
      Out[18]: True
  1. 在袋外样品上测试分类器:
      In [19]: _, y_hat = knn.predict(X_oob)
      ...      accuracy_score(y_oob, y_hat)
      Out[19]: 0.9285714285714286
  1. 重复步骤 3-6 特定的迭代次数。
  2. 自举的迭代。重复这些步骤多达 10,000 次,以获得 10,000 个准确度分数,然后对分数求平均值,以了解分类器的平均性能。

为了方便起见,我们可以从第 3 步 - 第 6 步中构建一个函数,这样就可以很容易地将程序运行一些n_iter次。我们还传递一个模型(我们的 kNN 分类器,model)、特征矩阵(X)和带有所有类标签的向量(y):

In [20]: def yield_bootstrap(model, X, y, n_iter=10000):
...          for _ in range(n_iter):

for循环中的步骤本质上是前面提到的代码中的步骤 3 - 6 。这包括在引导程序上训练分类器,并在现成的例子中测试它:

...              # train the classifier on bootstrap
...              idx_boot = np.random.choice(len(X), size=len(X),
...                                          replace=True)
...              X_boot = X[idx_boot, :]
...              y_boot = y[idx_boot]
...              knn.train(X_boot, cv2.ml.ROW_SAMPLE, y_boot)
... 
...              # test classifier on out-of-bag examples
...              idx_oob = np.array([x not in idx_boot
...                                  for x in np.arange(len(X))],
...                                 dtype=np.bool)
...              X_oob = X[idx_oob, :]
...              y_oob = y[idx_oob]
...              _, y_hat = knn.predict(X_oob)

然后,我们需要返回准确度分数。你可能会在这里期待一个return声明。但是,更优雅的方法是使用yield语句,它会自动将功能转换为生成器。这意味着我们不必初始化一个空列表(acc = [])然后在每次迭代时追加新的准确度分数(acc.append(accuracy_score(...)))。簿记是自动完成的:

...              yield accuracy_score(y_oob, y_hat)

为了确保我们都得到相同的结果,让我们修复随机数生成器的种子:

In [21]: np.random.seed(42)

现在,让我们通过将函数输出转换为列表来运行n_iter=10次的过程:

In [22]: list(yield_bootstrap(knn, X, y, n_iter=10))
Out[22]: [0.98333333333333328,
          0.93650793650793651,
          0.92452830188679247,
          0.92307692307692313,
          0.94545454545454544,
          0.94736842105263153,
          0.98148148148148151,
          0.96078431372549022,
          0.93220338983050843,
          0.96610169491525422]

如您所见,对于这个小样本,我们得到的准确率在 92%到 98%之间。为了更可靠地估计模型的性能,我们重复该过程 1000 次,并计算所得分数的平均值和标准偏差:

In [23]: acc = list(yield_bootstrap(knn, X, y, n_iter=1000))
...      np.mean(acc), np.std(acc)
Out[23]: (0.95524155136419198, 0.022040380995646654)

随时欢迎你增加重复次数。但是一旦n_iter足够大,该过程应该对采样过程的随机性具有鲁棒性。在这种情况下,当我们不断增加n_iter到例如 10,000 次迭代时,我们不期望看到分值分布的任何更多变化:

In [24]: acc = list(yield_bootstrap(knn, X, y, n_iter=10000))
...      np.mean(acc), np.std(acc)
Out[24]: (0.95501528733009422, 0.021778543317079499)

典型地,通过自举获得的分数将用于统计测试以评估我们结果的显著性。让我们看看这是如何做到的。

评估我们结果的重要性

假设我们为两个版本的 kNN 分类器实现了交叉验证过程。最终的测试分数是——A 型 92.34%,b 型 92.73%,我们怎么知道哪个模型更好?

按照我们这里介绍的逻辑,我们可能会支持模型 B,因为它有更好的测试分数。但是如果这两种模式没有明显的不同呢?这可能有两个潜在的原因,都是我们测试程序随机性的结果:

  • 据我们所知,B 型车只是运气好。也许我们为交叉验证程序选择了一个非常低的 k 值。也许模型 B 最终得到了一个有益的火车测试分割,这样模型在分类时就没有问题了…

实施学生测验

最著名的统计测试之一是学生 t-test 。你可能以前听说过它:它允许我们确定两组数据是否有明显的不同。这对威廉·希利·戈塞来说是一个非常重要的测试,他是这项测试的发明者,在吉尼斯啤酒厂工作,想知道两批黑啤的质量是否不同。

Note that “Student” here is capitalized. Although Gosset wasn’t allowed to publish his test due to company policy, he did so anyway under his pen name, Student.

在实践中,t 检验允许我们确定两个数据样本是否来自具有相同均值或期望值的基础分布。

出于我们的目的,这意味着我们可以使用 t 检验来确定两个独立分类器的测试分数是否具有相同的平均值。我们从假设两组考试成绩相同开始。我们称之为零假设,因为这是我们想要废掉的假设,也就是说,我们正在寻找证据拒绝假设,因为我们想要确保一个分类器明显优于另一个。

我们接受或拒绝基于 t 检验返回的参数 p 值的无效假设。p 值取01之间的值。0.05的 p 值意味着零假设 100 次中只有 5 次是正确的。因此,一个小的 p 值表明有力的证据表明该假设可以被安全地拒绝。习惯上使用 p=0.05 作为截止值,低于该值我们拒绝零假设。

如果这太令人困惑,可以这样想:当我们运行 t-test 来比较分类器测试分数时,我们希望获得一个小的 p 值,因为这意味着两个分类器给出的结果明显不同。

我们可以从stats模块用 SciPy 的ttest_ind功能实现学生的 t-test:

In [25]: from scipy.stats import ttest_ind

让我们从一个简单的例子开始。假设我们对两个分类器进行了五次交叉验证,并获得了以下分数:

In [26]: scores_a = [1, 1, 1, 1, 1]
...      scores_b = [0, 0, 0, 0, 0]

这意味着模型 A 在所有五次折叠中实现了 100%的精度,而模型 B 获得了 0%的精度。在这种情况下,很明显这两个结果是显著不同的。如果我们对这些数据进行 t 检验,我们会发现一个非常小的 p 值:

In [27]: ttest_ind(scores_a, scores_b)
Out[27]: Ttest_indResult(statistic=inf, pvalue=0.0)

我们有!我们实际上得到最小可能的 p 值, p=0.0

另一方面,如果两个分类器得到完全相同的数字,除了在不同的折叠过程中?在这种情况下,我们希望这两个分类器是等价的,这可以通过一个非常大的 p 值来表示:

In [28]: scores_a = [0.9, 0.9, 0.9, 0.8, 0.8]
...      scores_b = [0.8, 0.8, 0.9, 0.9, 0.9]
...      ttest_ind(scores_a, scores_b)
Out[28]: Ttest_indResult(statistic=0.0, pvalue=1.0)

与前述类似,我们得到最大可能的 p 值, p=1.0

为了看看在一个更现实的例子中会发生什么,让我们回到前面例子中的 kNN 分类器。使用从十倍交叉验证过程中获得的测试分数,我们可以用以下过程比较两个不同的 kNN 分类器:

  1. 获取模型 A 的一组测试分数,我们选择模型 A 作为之前的 kNN 分类器( k=1 ):
      In [29]: k1 = KNeighborsClassifier(n_neighbors=1)
      ...      scores_k1 = cross_val_score(k1, X, y, cv=10)
      ...      np.mean(scores_k1), np.std(scores_k1)
      Out[29]: (0.95999999999999996, 0.053333333333333323)
  1. 获取模型 B 的一组测试分数,让我们选择模型 B 作为 k=3 的 kNN 分类器:
      In [30]: k3 = KNeighborsClassifier(n_neighbors=3)
      ...      scores_k3 = cross_val_score(k3, X, y, cv=10)
      ...      np.mean(scores_k3), np.std(scores_k3)
      Out[30]: (0.96666666666666656, 0.044721359549995787)
  1. 将 t 检验应用于两组分数:
      In [31]: ttest_ind(scores_k1, scores_k3)
      Out[31]: Ttest_indResult(statistic=-0.2873478855663425,
               pvalue=0.77712784875052965)

如您所见,这是一个很好的例子,两个分类器给出了不同的交叉验证分数(96.0%和 96.7%),结果没有显著差异!因为我们得到了一个很大的 p 值( p=0.777 ,所以我们期望这两个分类器 100 次中有 77 次是等价的。

实施麦克内尔的测试

更先进的统计技术是麦克内尔的测试。该测试可用于配对数据,以确定两个样本之间是否有任何差异。与 t 检验的情况一样,我们可以使用 McNemar 的检验来确定两个模型是否给出了显著不同的分类结果。

McNemar 的测试对成对的数据点进行操作。这意味着我们需要知道,对于两个分类器,它们是如何对每个数据点进行分类的。根据第一个分类器正确但第二个分类器错误的数据点数量,以及反之,我们可以确定两个分类器是否等价。

让我们假设前面的模型 A 和模型 B 应用于相同的五个数据点。而模型…

用网格搜索调整超参数

超参数调整最常用的工具是网格搜索,这基本上是一个花哨的术语,表示我们将使用for循环尝试所有可能的参数组合。

让我们看看这是如何在实践中做到的。

实现简单的网格搜索

回到我们的 kNN 分类器,我们发现我们只有一个超参数可以调整: k 。通常,您会有大量的开放参数需要处理,但是 kNN 算法对于我们来说足够简单,可以手动实现网格搜索。

在开始之前,我们需要像以前一样将数据集分割成训练集和测试集:

  1. 这里我们选择 75-25 的分割:
In [1]: from sklearn.datasets import load_iris...     import numpy as np...     iris = load_iris()...     X = iris.data.astype(np.float32)...     y = iris.targetIn [2]: X_train, X_test, y_train, y_test = train_test_split(...          X, y, random_state=37...      )
  1. 然后,目标是循环所有可能的 k 值。当我们这样做的时候,我们希望保持…

理解验证集的价值

按照我们将数据分成训练集和测试集的最佳实践,我们可能会告诉人们,我们已经找到了一个在数据集上以 97.4%的准确率运行的模型。然而,我们的结果不一定能推广到新的数据。这个论点与本书前面我们保证训练-测试分离时的论点相同,即我们需要一个独立的数据集进行评估。

然而,当我们在最后一节实现网格搜索时,我们使用测试集来评估网格搜索的结果,并更新超参数 k 。这意味着我们不能再使用测试集来评估最终数据了!基于测试集准确性做出的任何模型选择都会将信息从测试集泄露到模型中。

解决该数据的一种方法是再次分割数据,并引入所谓的验证集。验证集不同于训练集和测试集,专门用于选择模型的最佳参数。对这个验证集进行所有的探索性分析和模型选择,并保留一个单独的测试集,只用于最终评估,这是一个很好的做法。

换句话说,我们最终应该将数据分成三个不同的集合:

  • 一个训练集,用来建立模型
  • 一个验证集,用于选择模型的参数
  • 一个测试集,用于评估最终模型的性能

下图说明了这种三向分割:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了如何将数据集拆分为训练集、验证集和测试集的示例。实际上,三向分割分两步实现:

  1. 将数据分成两部分:一部分包含训练和验证集,另一部分包含测试集:
      In [6]: X_trainval, X_test, y_trainval, y_test =
      ...        train_test_split(X, y, random_state=37)
      In [7]: X_trainval.shape
      Out[7]: (112, 4)
  1. 再次将X_trainval分成适当的训练和验证集:
      In [8]: X_train, X_valid, y_train, y_valid = train_test_split(
      ...         X_trainval, y_trainval, random_state=37
      ...     )
      In [9]: X_train.shape
      Out[9]: (84, 4)

然后,我们重复前面代码中的手动网格搜索,但这一次,我们将使用验证集来找到最佳的 k (参见代码亮点):

In [10]: best_acc = 0.0
...      best_k = 0
...      for k in range(1, 20):
...          knn = cv2.ml.KNearest_create()
...          knn.setDefaultK(k)
...          knn.train(X_train, cv2.ml.ROW_SAMPLE, y_train)
...          _, y_valid_hat = knn.predict(X_valid)
...          acc = accuracy_score(y_valid, y_valid_hat)
...          if acc >= best_acc:
...              best_acc = acc
...              best_k = k
...      best_acc, best_k
Out[10]: (1.0, 7)

我们现在发现用 k=7 ( best_k)就可以达到 100%的验证分数(best_acc)!然而,回想一下,这个分数可能过于乐观。为了找出模型的实际表现,我们需要在测试集中的数据上测试它。

为了得到最终的模型,我们可以使用在网格搜索中找到的 k 的值,并在训练和验证数据上重新训练模型。这样,我们使用了尽可能多的数据来构建模型,同时仍然遵守列车测试分离原则。

这意味着我们应该在X_trainval上重新训练模型,该模型包含训练集和验证集,并在测试集上对其进行评分:

In [25]: knn = cv2.ml.KNearest_create()
...      knn.setDefaultK(best_k)
...      knn.train(X_trainval, cv2.ml.ROW_SAMPLE, y_trainval)
...      _, y_test_hat = knn.predict(X_test)
...      accuracy_score(y_test, y_test_hat), best_k
Out[25]: (0.94736842105263153, 7)

通过这个过程,我们在测试集上找到了 94.7%的令人生畏的准确率。因为我们遵守了训练-测试分离原则,所以我们现在可以确定,当应用于新数据时,这是我们可以从分类器中预期的性能。虽然没有验证时报告的 100%准确率高,但还是很不错的分数!

将网格搜索与交叉验证相结合

我们刚刚实现的网格搜索的一个潜在危险是,结果可能对我们如何精确地分割数据相对敏感。毕竟,我们可能无意中选择了一个分割,将大部分易于分类的数据点放入测试集中,导致得分过于乐观。虽然一开始我们会很高兴,但当我们在一些新的数据上尝试该模型时,我们会发现分类器的实际性能远低于预期。

相反,我们可以将网格搜索和交叉验证结合起来。这样,数据被多次分割成训练集和验证集,并在网格搜索的每一步执行交叉验证以进行评估…

将网格搜索与嵌套交叉验证相结合

尽管带有交叉验证的网格搜索使得模型选择过程更加健壮,但是您可能已经注意到,我们仍然只执行了一次分割成训练集和验证集的操作。因此,我们的结果可能仍然过于依赖于数据的精确训练-验证分割。

我们可以更进一步,使用多个分割进行交叉验证,而不是将数据分割成训练集和验证集。这将导致所谓的嵌套交叉验证,过程如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在嵌套交叉验证中,网格搜索框上有一个外部循环,重复地将数据分割成训练集和验证集。对于这些拆分中的每一个,将运行网格搜索,这将报告一组最佳参数值。然后,对于每个外部分割,我们使用最佳设置获得测试分数。

Running a grid search over many parameters and on large datasets can be computationally intensive. A particular parameter setting on a particular cross-validation split can be done completely independently from the other parameter settings and models. Hence, parallelization over multiple CPU cores or a cluster is very important for grid search and cross-validation.

现在我们知道了如何找到模型的最佳参数,让我们更仔细地看看我们可以用来给模型评分的不同评估指标。

使用不同评估指标的评分模型

到目前为止,我们已经使用准确性(正确分类样本的比例)评估了分类性能,使用 R 2 评估了回归性能。然而,这只是总结监督模型在给定数据集上表现如何的众多可能方法中的两种。实际上,这些评估指标可能不适合我们的应用,在模型之间进行选择和调整参数时,选择正确的指标非常重要。

选择指标时,我们应该始终牢记机器学习应用程序的最终目标。在实践中,我们通常不仅对做出准确的预测感兴趣,而且对将这些预测作为更大的…

选择正确的分类标准

我们在第三章、监督学习的第一步中讨论了几个基本的评分函数。最基本的分类指标如下:

  • 准确性:这将计算测试集中已被正确预测的数据点的数量,并将该数量作为测试集大小的一部分(sklearn.metrics.accuracy_score)返回。这是分类器最基本的评分函数,我们在整本书中都广泛使用了它。
  • 精度:这描述了分类器不将阳性样本标记为阴性(sklearn.metrics.precision_score)的能力。
  • 回忆 (或灵敏度):这描述了分类器检索所有阳性样本的能力(sklearn.metrics.recall_score)。

虽然精度和召回率是重要的衡量标准,但只看其中一项并不能让我们对全局有一个很好的了解。总结这两种度量的一种方法是 f-scoref-measure ( sklearn.metrics.f1_score),它将精度和召回率的调和平均值计算为 2(精度 x 召回率)/(精度+召回率)。

有时我们需要做的不仅仅是最大限度地提高准确性。例如,如果我们在商业应用中使用机器学习,那么决策应该由业务目标驱动。这些目标之一可能是保证至少 90%的召回率。接下来的挑战是开发一个在满足所有次要需求的同时仍然具有合理精度的模型。像这样设定目标,通常叫做设定操作点

然而,在开发新系统时,通常不清楚操作点应该是什么。为了更好地理解这个问题,重要的是调查所有可能的精度权衡,并立即召回它们。这可以通过一种叫做精确-回忆曲线 ( sklearn.metrics.precision_recall_curve)的工具来实现。

Another commonly used tool to analyze the behavior of classifiers is the Receiver Operating Characteristic (ROC) curve. The ROC curve considers all possible thresholds for a given classifier similar to the precision-recall curve, but it shows the false positive rate against the true positive rate instead of reporting precision and recall.

选择正确的回归度量

回归的评估可以像分类一样详细地完成。在第三章、监督学习的第一步中,我们也谈到了回归的一些基本度量:

  • 均方误差:回归问题最常用的误差度量是测量训练集中每个数据点的预测值和真实目标值之间的平方误差,在所有数据点上取平均值(sklearn.metrics.mean_squared_error)。
  • 解释方差:一个更复杂的度量是衡量一个模型可以在多大程度上解释测试数据的变化或分散(sklearn.metrics.explained_variance_score)。通常,解释的差异量是通过使用…

将算法链接在一起以形成流水线

到目前为止,我们讨论的大多数机器学习问题至少包括一个预处理步骤和一个分类步骤。问题越复杂,这个加工链可能会越长。将多个处理步骤粘合在一起,甚至在网格搜索中使用它们的一种方便方法是使用 scikit-learn 中的Pipeline类。

在 scikit-learn 中实现管道

Pipeline类本身有一个fitpredict和一个score方法,它们的行为就像 scikit-learn 中的任何其他估计器一样。Pipeline类最常见的用例是将不同的预处理步骤与一个监督模型(如分类器)链接在一起。

让我们从第五章回到乳腺癌数据集,使用决策树进行医学诊断。使用 scikit-learn,我们导入数据集,并将其分成训练集和测试集:

In [1]: from sklearn.datasets import load_breast_cancer...     import numpy as np...     cancer = load_breast_cancer()...     X = cancer.data.astype(np.float32)...     y = cancer.targetIn [2]: X_train, X_test, y_train, y_test = train_test_split(... X, y, random_state=37 ...

在网格搜索中使用管道

在网格搜索中使用管道的工作方式与使用任何其他估计器相同。

我们定义一个参数网格,从管道和参数网格中搜索并构建GridSearchCV。但是,在指定参数网格时,会有细微的变化。我们需要为每个参数指定它属于管道的哪个步骤。我们要调整的两个参数Cgamma,都是SVC的参数。在前一节中,我们给这个步骤命名为"svm"。为管道定义参数网格的语法是为每个参数指定步骤名称,后跟__(双下划线),后跟参数名称。

因此,我们将如下构建参数网格:

In [8]: param_grid = {'svm__C': [0.001, 0.01, 0.1, 1, 10, 100],
...                   'svm__gamma': [0.001, 0.01, 0.1, 1, 10, 100]}

有了这个参数网格,我们可以像往常一样使用GridSearchCV:

In [9]: grid = GridSearchCV(pipe, param_grid=param_grid, cv=10)
...     grid.fit(X_train, y_train);

网格中的最佳分数存储在best_score_中:

In [10]: grid.best_score_
Out[10]: 0.97652582159624413

同样,最佳参数存储在best_params_中:

In [11]: grid.best_params_
Out[11]: {'svm__C': 1, 'svm__gamma': 1}

但是回想一下,交叉验证分数可能过于乐观。为了了解分类器的真实性能,我们需要在测试集上对其进行评分:

In [12]: grid.score(X_test, y_test)
Out[12]: 0.965034965034965

与我们之前进行的网格搜索相反,现在,对于交叉验证中的每个分割,MinMaxScaler仅使用训练分割进行重新调整,并且没有信息从测试分割泄露到参数搜索中。

这使得构建一个管道来链接各种步骤变得很容易!你可以在管道中随意混合匹配估计器,你只需要保证管道中的每一步都提供一个transform方法(除了最后一步)。这允许流水线中的估计器产生数据的新表示,这又可以用作下一步的输入。

The Pipeline class is not restricted to preprocessing and classification but can, in fact, join any number of estimators together. For example, we could build a pipeline containing feature extraction, feature selection, scaling, and classification, for a total of four steps. Similarly, the last step could be regression or clustering instead of classification.

摘要

在本章中,我们试图通过讨论模型选择和超参数调整的最佳实践来补充我们现有的机器学习技能。您已经学习了如何在 OpenCV 和 scikit-learn 中使用网格搜索和交叉验证来调整模型的超参数。我们还讨论了各种各样的评估指标,以及如何将算法链接到管道中。现在,你几乎准备好开始自己解决一些现实世界的问题了。

在下一章中,将向您介绍一个激动人心的新主题,即 OpenVINO toolkit,它是 OpenCV 4.0 的关键版本之一。

十二、将 OpenVINO 与 OpenCV 一起使用

在第一章中,我们讨论了 OpenCV 4.0 版本中的各种新增功能。需要注意的关键版本之一是 OpenVINO 工具包。有趣的是,OpenVINO 工具包还被嵌入式视觉联盟选为 2019 年度开发者工具。

在本章中,我们将只关注如何在 OpenCV 中使用 OpenVINO 工具包。我们将从安装 OpenVINO 工具包开始,然后使用它进行交互式人脸检测演示。我们还将学习使用带有 OpenCV 的 OpenVINO Model Zoo 和带有 OpenCV 的 OpenVINO 推理机 ( IE )。在本章的最后,我们还将学习如何使用 OpenCV 和 OpenVINO IE 进行图像分类。

在本章中,我们将涵盖以下主题:

  • OpenVINO 工具包安装
  • 交互式人脸检测演示
  • 用 OpenCV 使用 OpenVINO 模型动物园
  • 使用 OpenVINO IE 搭配 OpenCV
  • 基于 OpenCV 和 OpenVINO IE 的图像分类

技术要求

您可以在以下链接查阅本章的代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 12

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 这本书可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 运行本书提供的代码不需要 GPU。

OpenVINO 简介

OpenVINO (简称 Open 视觉推理和神经网络优化)。它旨在优化各种神经网络,以加快推理阶段。正如我们在前面几章中所讨论的,推理是一个过程,在这个过程中,一个训练好的神经网络被用来用看不见的输入数据产生结果。例如,如果一个网络被训练为对狗或猫进行分类,那么如果我们喂养泰菲(我们邻居的狗)的图像,它应该能够推断图像是狗。

考虑到图像和视频在当今世界已经变得如此普遍,有许多深度神经网络被训练来执行各种操作,例如多标签分类和运动跟踪。世界上执行的大多数推理都发生在中央处理器上,因为中央处理器非常昂贵,通常不适合单个人工智能工程师的预算。在这些情况下,OpenVINO 工具包提供的加速非常关键。

The speedup provided by OpenVINO toolkit consists of two steps. The first step focuses on the hardware specifications; it optimizes the network in a hardware-agnostic way using a Model Optimizer, which ships along with OpenVINO toolkit. The next step involves hardware-specific acceleration using OpenVINO IE.

OpenVINO 工具包由英特尔开发,英特尔以其优化的工具和硬件而闻名,专注于深度学习和人工智能。知道 vpu、GPU 和 FPGAs 也是英特尔制造的也就不足为奇了。

OpenVINO 还为 OpenCV 和 OpenVX 库提供了优化调用——这是两个最著名的计算机视觉库。

OpenVINO 工具包安装

在本节中,我们将使用英特尔的官方说明来安装 OpenVINO 工具包:

  1. 首先,访问 OpenVINO 工具包下载页面(software . Intel . com/en-us/OpenVINO-toolkit/choose-download),根据您的系统规格,选择并下载安装程序。您必须首先注册工具包的副本。
  2. 使用安装说明(docs.openvinotoolkit.org/latest/index.html)在您的系统上安装 OpenVINO 工具包。

OpenVINO toolkit will also install its own Intel-optimized version of OpenCV. If you already have OpenCV installed on your system, the installer will show that another version of OpenCV is already installed. It’s better to install the …

OpenVINO 组件

OpenVINO 工具包由以下主要组件组成:

  • 深度学习部署工具包 ( DLDT )由模型优化器、IE、预训练模型和一些工具组成,帮助您测量模型的准确性。
  • 有一个为英特尔库编译的优化版本的 OpenCV(也进行了优化)。
  • 有 OpenCL 库。
  • 您可以获得英特尔的媒体软件开发工具包来加快视频处理速度。
  • OpenVX 有一个优化版本。

交互式人脸检测演示

OpenVINO 工具包安装还提供了各种演示和示例应用程序。只是为了测试安装,让我们看看是否可以运行交互式人脸检测演示。

首先,我们将移动到deployment_tools/inference_engine文件夹中的samples目录。您将在这里找到各种演示应用程序,例如图像分类和推理管道。

交互式人脸检测演示将视频作为输入,并结合年龄、性别、头部姿势、情绪和面部标志检测来执行人脸检测。根据您要执行的检测类型,您可以使用以下预训练模型列表中的模型:

  • 您只能使用执行人脸检测

用 OpenCV 使用 OpenVINO 推理机

在前一节中,我们讨论了如何运行交互式人脸检测演示。这一切都很好,但仍然存在的问题是如何利用 OpenVINO 的力量与您已经存在的 OpenCV 代码。请注意,在这里,我们强调的是利用 OpenVINO 的优势,只需对代码进行最少的更改。这一点非常重要,因为 OpenVINO 并没有出现在 OpenCV 的早期版本中,包括更常用的 3.4.3 版本。作为一名优秀的开发人员,你的工作是确保你的程序支持最大数量的系统和库。

对我们来说幸运的是,只需要一行代码就可以开始为您的 OpenCV 模型的推理代码使用 OpenVINO 推理引擎,如下面的代码片段所示:

cv::dnn::setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE); // C++
setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE) # Python

就这样!在一个完整的工作示例中,您将如何使用它:

net = cv2.dnn.readNetFromCaffe(prototxt,model)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)

在这里,你可以使用任何其他方法来读取你的神经网络。在这种情况下,我们从.prototxt.caffemodel文件中读取一个 Caffe 模型。

同样,在 C++的情况下,我们可以这样使用它:

Net net = readNetFromCaffe(prototxt, model);
net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE);

用 OpenCV 使用 OpenVINO 模型动物园

在前几节中,我们简要讨论了 OpenVINO Model Zoo 以及如何将 OpenVINO IE 与 OpenCV 一起使用。在本节中,我们将了解更多关于模型动物园及其提供的内容。

OpenVINO Model Zoo 是一个优化的预训练模型的集合,可以直接导入 OpenVINO 进行推理。这个特性的重要性在于,OpenVINO 加速背后的一个主要原因是它用于推理的优化模型文件。底层的推理原理还是和大多数深度学习推理工具包和语言一样,比如 OpenCV。OpenCV 的dnn模块利用 OpenVINO 的这种加速原理,将其作为所有推理任务的默认后端。…

基于 OpenCV 和 OpenVINO 推理机的图像分类

本章我们要讨论的最后一个主题是如何使用 OpenCV 和 OpenVINO 推理机进行图像分类。

在我们切入细节之前,让我们先简单看一下一个图像分类问题。图像分类,也称为图像识别,是深度学习任务集的一部分,也可能是最常见的任务之一。在该任务中,提供一组图像作为模型的输入,模型输出输入图像的类别或标签。

这方面的一个常见例子是狗和猫的分类问题,其中在大量的猫和狗的图像上训练模型,然后在测试阶段,模型预测输入图像是猫的图像还是狗的图像。

虽然这看起来可能是一个非常幼稚的问题,但图像分类在工业应用中有很大的重要性。例如,如果你的相机吹嘘拥有人工智能能力,这意味着它可以识别图像中存在的物体,并相应地更改图像设置——无论是自然风景的图像还是一些食物的 Instagram 级快照。下图显示了人工智能手机摄像头的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

想想我之前拍的我家屋顶的照片。请注意,当切换到人工智能模式时,相机能够检测到我正在拍摄植物照片,并自动更改设置以匹配它。所有这些都是可能的,只是因为图像分类。现在,考虑一下你,作为一个计算机视觉工程师,正在尝试训练一个模型,它可以识别图像是植物、瀑布还是人。

如果您的模型不能在几毫秒内推断出图像的类别或标签,那么您在训练模型时付出的所有努力都将付诸东流。没有人愿意等待哪怕几秒钟,让摄像头检测到物体并更改设置。

这让我们回到 OpenVINO 的推理机的重要性。OpenVINO 有自己版本的图像分类工具包,可以如下使用。

使用 OpenVINO 的图像分类

让我们看看如何使用 OpenVINO 安装目录中的图像分类演示:

  1. 首先,移动到 OpenVINO 安装目录中的deployment_tools/demo目录。
  2. 接下来,让我们在目录中已经存在的演示图像上运行图像分类:
./demo_squeezenet_download_convert_run.sh

这是我得到的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们也运行另一个使用相同图像的演示,推理管道演示,它很好地展示了 OpenVINO 的推理引擎的速度:

./demo_security_barrier_camera.sh

以下是输出图像:

因为我们在…

基于 OpenCV 和 OpenVINO 的图像分类

让我们首先使用 OpenCV 创建一个图像分类推断代码。由于我们只关心推理,我们将使用一个预先训练好的模型:

  1. 首先我们下载一下 Caffe 模型文件,deploy.prototxtbvlc_reference_caffenet.caffemodel,可以从 Berkley Visions 的资源库(github . com/BVLC/Caffe/tree/master/models/bvlc _ reference _ caffenet)中获取。确保在当前工作目录下下载这两个文件。我们还需要一个带有所提到的类标签的文本文件。可以从github . com/torch/tutors/blob/master/7 _ imagenet _ classification/synset _ words . txt获取。
  2. 让我们也使用长颈鹿的样本图像进行图像分类:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,让我们开始用 OpenCV 和 OpenVINO 编写一些图像分类的代码。

  1. 让我们从导入一些模块开始:
import numpy as np
import cv2
  1. 接下来,让我们指定模型文件:
image = cv2.imread("animal-barbaric-brown-1319515.jpg")
labels_file = "synset_words.txt"
prototxt = "deploy.prototxt"
caffemodel = "bvlc_reference_caffenet.caffemodel"

  1. 现在,让我们从标签文本文件中读取标签:
rows = open(labels_file).read().strip().split("\n")
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
  1. 让我们指定用于推断的后端:
net = cv2.dnn.readNetFromCaffe(prototxt,caffemodel)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
  1. 让我们对输入图像进行一些基本的图像处理:
blob = cv2.dnn.blobFromImage(image,1,(224,224),(104,117,123))
  1. 最后,让我们将此图像传递给模型并获得输出:
net.setInput(blob)
predictions = net.forward()
  1. 让我们获得传递给模型的长颈鹿图像的前 10 个预测:
indices = np.argsort(predictions[0])[::-1][:5]
  1. 最后,让我们显示十大预测:
for index in indices:
 print("label: {}, prob.: {:.5}".format(classes[index], predictions[0][index]))

令人惊讶的是,我们得到的结果是:

label: cheetah, prob.: 0.98357
label: leopard, prob.: 0.016108
label: snow leopard, prob.: 7.2455e-05
label: jaguar, prob.: 4.5286e-05
label: prairie chicken, prob.: 3.8205e-05

请注意,我们的模型认为我们作为输入传递的giraffe图像实际上是一个cheetah图像。你认为为什么会这样?那是因为giraffe不在我们的班级名单中。因此,模型得出了最接近的匹配,这是因为猎豹和长颈鹿身上有相似的色斑。因此,下次执行图像分类时,请确保该类实际上出现在标签列表中。

我们还可以在各种后端之间进行比较,以查看使用 OpenVINO 的推理引擎作为后端获得的加速。这是如何做到的。我们只需要更改前面代码中的一行:

net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)

我们可以在以下后端中进行选择:

  • cv2.dnn.DNN_BACKEND_DEFAULT:这是如果你已经安装了 OpenVINO,并将使用它作为默认后端。
  • cv2.dnn.DNN_BACKEND_HALIDE:这需要 OpenCV 使用卤化物构建。你可以在docs . opencv . org/4 . 1 . 0/de/d37/tutorial _ dnn _ halide . html找到这方面的详细文档。
  • cv2.dnn.DNN_BACKEND_OPENCV:这是在两个后端之间进行比较的最佳选择。

因此,您所需要做的就是运行相同的代码,但是用下面的代码替换前面的代码行:

net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)

就这样!现在,您可以通过使用 OpenVINO 的推理引擎作为后端来进行比较,以查看您获得的加速。

You won’t be able to see much difference in speed. To get a noticeable difference, use a for loop to carry out the inference 100 times, add up the total time taken during each step, and then divide it by 100 to obtain an average.

摘要

在这一章中,我们简要地看了 OpenVINO 工具包——它是什么,它用于什么,以及我们如何安装它。我们还研究了如何运行工具包提供的演示和示例,以了解和见证 OpenVINO 的强大功能。最后,我们看到了如何在我们预先存在的 OpenCV 代码中利用这种能力,只需添加一行来指定用于模型推理的后端。

您可能还注意到,我们在本章中没有介绍太多的实践内容。这是因为 OpenVINO 更适合深度学习应用程序,这不在本书的讨论范围内。如果你是一个深度学习爱好者,你绝对应该浏览英特尔在 OpenVINO 工具包上提供的文档并开始学习。…

十三、总结

恭喜你!你刚刚朝着成为机器学习实践者迈出了一大步。您不仅熟悉各种各样的基本机器学习算法,还知道如何将它们应用于监督和非监督学习问题。此外,还向您介绍了一个新的令人兴奋的主题,OpenVINO Toolkit。在前一章中,我们学习了如何安装 OpenVINO 并运行交互式人脸检测和图像分类演示等。我相信你很喜欢了解这些话题。

在我们分道扬镳之前,我想给你一些最后的建议,给你一些额外的资源,并给你一些如何进一步提高你的机器学习和数据科学技能的建议。在本章中,我们将学习如何处理机器学习问题并构建我们自己的估计器。我们将学习如何用 C++编写自己的基于 OpenCV 的分类器,以及用 Python 编写基于 scikit-learn 的分类器。

在本章中,我们将涵盖以下主题:

  • 探讨机器学习问题
  • 用 C++编写自己的基于 OpenCV 的分类器
  • 用 Python 编写自己的基于 scikit-learn 的分类器
  • 从这里去哪里

技术要求

可以从以下链接查阅本章代码:github . com/PacktPublishing/Machine-Learning-for-OpenCV-Second-Edition/tree/master/chapter 13

以下是软件和硬件要求的总结:

  • 您将需要 OpenCV 版本 4.1.x (4.1.0 或 4.1.1 都可以)。
  • 您将需要 Python 3.6 版本(任何 Python 3 . x 版本都可以)。
  • 您将需要 Anaconda Python 3 来安装 Python 和所需的模块。
  • 除了这本书,你可以使用任何操作系统——苹果操作系统、视窗操作系统和基于 Linux 的操作系统。我们建议您的系统中至少有 4 GB 内存。
  • 你不需要一个图形处理器来运行本书提供的代码。

探讨机器学习问题

当你在野外看到一个新的机器学习问题时,你可能会忍不住跳到前面,把你最喜欢的算法扔向这个问题——也许是你最理解的或者最有趣的算法。但是事先知道哪种算法在你的特定问题上表现最好通常是不可能的。

相反,你需要退一步,看看更大的图景。在你陷入太深之前,你会想要定义你试图解决的实际问题。例如,你是否已经有了一个明确的目标,或者你只是想做一些探索性的分析,并在数据中找到一些有趣的东西?通常,你会从一个总体目标开始,比如检测垃圾邮件,制作电影推荐,或者在上传到社交媒体平台的图片中自动标记你的朋友。然而,正如我们在本书中看到的,解决问题通常有几种方法。例如,我们使用逻辑回归、k-均值聚类和深度学习来识别手写数字。定义问题将帮助你提出正确的问题,并在这个过程中做出正确的选择。

根据经验,您可以使用以下五个步骤来处理野外的机器学习问题:

  1. 将问题分类:这是一个两步走的过程:
    • 按输入分类:简单来说,如果你有标注数据,那就是监督学习的问题。如果你有未标记的数据,想要找到结构,这是一个无监督的学习问题。如果你想通过与环境交互来优化目标函数,这是一个强化学习问题。
    • 按输出分类:如果你模型的输出是一个数字,那就是回归问题。如果模型的输出是一个类(或类别),这就是一个分类问题。如果模型的输出是一组输入组,这就是一个聚类问题。
  2. 找到可用的算法:现在您已经对问题进行了分类,您可以使用我们掌握的工具来识别适用且实用的算法。微软已经创建了一个方便的算法备忘单,显示哪些算法可以用于哪类问题。尽管备忘单是为微软 Azure 量身定制的,但你可能会发现它通常很有帮助。

The machine learning algorithm cheat sheet PDF (by Microsoft Azure) can be downloaded from aka.ms/MLCheatSheet.

  1. 实现所有适用的算法 ( 原型):对于任何给定的问题,通常有少数候选算法可以完成这项工作。那么,你怎么知道选哪一个呢?通常,这个问题的答案并不简单,所以你不得不求助于反复试验。原型制作最好分两步完成:
    1. 你的目标应该是用最少的特征工程快速而肮脏地实现几个算法。在这个阶段,您应该主要感兴趣的是看哪种算法在粗略的尺度下表现得更好。这一步有点像招聘:你在寻找任何理由来缩短候选算法的列表。一旦你把列表减少到几个候选算法,真正的原型就开始了。
    2. 理想情况下,您可能希望建立一个机器学习管道,使用一组精心选择的评估标准来比较数据集上每个算法的性能(参见第十一章、使用超参数调整选择正确的模型)。在这个阶段,你应该只处理少数几个算法,这样你就可以把注意力转向真正神奇的地方:特征工程。
  2. 特征工程:也许比选择正确的算法更重要的是选择正确的特征来表示数据。您可以在第四章、中阅读所有关于特征工程的内容,代表数据和工程特征
  3. 优化超参数:最后,你还要优化一个算法的超参数。例子可能包括主成分分析的主成分数,参数,k-最近邻算法中的 k ,或者神经网络中的层数和学习率。你可以看看第十一章、用超参数调谐选择合适的型号,获取灵感。

构建自己的评估器

在这本书里,我们参观了 OpenCV 提供的各种机器学习工具和算法。而且,如果出于某种原因,OpenCV 没有提供我们想要的东西,我们总是可以依靠 scikit-learn。

然而,在处理更高级的问题时,您可能会发现自己想要执行一些 OpenCV 或 scikit-learn 都不提供的非常具体的数据处理,或者您可能想要对现有的算法进行细微的调整。在这种情况下,您可能想要创建自己的估算器。

用 C++编写自己的基于 OpenCV 的分类器

由于 OpenCV 是那些引擎盖下不包含任何一行 Python 代码的 Python 库之一(我开玩笑的,但已经很接近了),您将不得不在 C++中实现您的定制估计器。这可以通过四个步骤来完成:

  1. 实现一个包含主要源代码的 C++源文件。您需要包含两个头文件,一个包含 OpenCV 的所有核心功能(opencv.hpp),另一个包含机器学习模块(ml.hpp):
#include <opencv2/opencv.hpp>
#include <opencv2/ml/ml.hpp>
#include <stdio.h>

然后,可以通过继承StatModel类来创建估计器类:

class MyClass : public cv::ml::StatModel
{
    public:

接下来,定义类的constructordestructor:

MyClass()
{
    print("MyClass constructor\n");
}
~MyClass() {}

然后,你还得定义一些方法。这些是您要填充的内容,以使分类器实际上做一些工作:

int getVarCount() const
{
    // returns the number of variables in training samples
    return 0;
}

bool empty() const
{
    return true;
}

bool isTrained() const
{
    // returns true if the model is trained
    return false;
}

bool isClassifier() const
{
    // returns true if the model is a classifier
    return true;
}

主要工作在train方法中完成,该方法有两种风格(接受cv::ml::TrainDatacv::InputArray作为输入):

bool train(const cv::Ptr<cv::ml::TrainData>& trainData,
          int flags=0) const
{
    // trains the model
    return false;
}

bool train(cv::InputArray samples, int layout, 
          cv::InputArray responses)
{
    // trains the model
    return false;
}

您还需要提供一个predict方法和一个scoring功能:

        float predict(cv::InputArray samples,
                     cv::OutputArray results=cv::noArray(),
                     int flags=0) const
        {
            // predicts responses for the provided samples
            return 0.0f;
        }

        float calcError(const cv::Ptr<cv::ml::TrainData>& data,
                       bool test, cv::OutputArray resp)
        {
            // calculates the error on the training or test dataset
            return 0.0f;
        }
   };

最后要做的是包含一个实例化类的main函数:

   int main()
   {
       MyClass myclass;
       return 0;
   }
  1. 写一个名为CMakeLists.txt的 CMake 文件:
cmake_minimum_required(VERSION 2.8)
project(MyClass)
find_package(OpenCV REQUIRED)
add_executable(MyClass MyClass.cpp)
target_link_libraries(MyClass ${OpenCV_LIBS})
  1. 通过键入以下命令,在命令行上编译文件:
$ cmake
$ make
  1. 运行可执行的MyClass方法,该方法是由最后一个命令生成的,应该会导致以下输出:
$ ./MyClass
MyClass constructor

用 Python 编写自己的基于 scikit-learn 的分类器

或者,您可以使用 scikit-learn 库编写自己的分类器。

可以通过导入BaseEstimatorClassifierMixin来实现。后者将提供相应的score方法,适用于所有分类器:

  1. 或者,首先,您可以覆盖score方法以提供您自己的度量score方法:
In [1]: import numpy as np...     from sklearn.base import BaseEstimator, ClassifierMixin
  1. 然后,可以定义一个继承自BaseEstimatorClassifierMixin的类:
In [2]: class MyClassifier(BaseEstimator, ClassifierMixin):...         """An example classifier"""
  1. 你需要提供一个构造函数,fitpredict方法。构造函数定义了所有的参数…

从这里去哪里

这本书的目标是向你介绍机器学习的世界,并让你做好成为机器学习实践者的准备。现在,您已经了解了基本算法的所有知识,您可能想要更深入地研究一些主题。

虽然没有必要了解我们在本书中实现的所有算法的所有细节,但了解它们背后的一些理论可能会让你成为一名更好的数据科学家。

If you are looking for more advanced material, then you might want to consider some of the following classics:

  • 斯蒂芬·马斯兰,机器学习:算法视角, 第二版,查普曼和霍尔/Crc,ISBN 978-146658328-3,2014
  • 模式识别和机器学习。斯普林格,ISBN 978-038731073-2,2007
  • 特雷弗·哈斯蒂、罗伯特·蒂布希拉尼和杰罗姆·弗里德曼,《统计学习的要素:数据挖掘、推理和预测》。第二版,斯普林格,ISBN 978-038784857-0,2016

说到软件库,我们已经了解了两个基本库——Opencv 和 scikit-learn。通常,使用 Python 对于尝试和评估模型非常有用,但是更大的 web 服务和应用程序通常是用 Java 或 C++编写的。

例如,C++包是 Vowpal Wabbit (大众),自带命令行界面。为了在集群上运行机器学习算法,人们经常使用mllib,一个建立在 Spark 之上的 Scala 库。如果你没有和 Python 结婚,你也可以考虑使用另一种数据科学家的通用语言——R。r 是一种专门为统计分析设计的语言,以其可视化能力和许多(通常是高度专业化的)统计建模包的可用性而闻名。

不管你选择哪种软件,我想最重要的建议是继续练习你的技能。但你已经知道了。有许多优秀的数据集等着你去分析:

  • 在本书中,我们充分利用了 scikit-learn 内置的示例数据集。此外,scikit-learn 提供了一种从外部服务加载数据集的方法,例如mldata.org。更多信息请参考scikit-learn.org/stable/datasets/index.html

  • Kaggle 是一家在其网站 http://www.kaggle.com 举办各种数据集和竞赛的公司。比赛通常由各种公司、非营利组织和大学主办,获胜者可以获得一些严肃的奖金。竞争的一个缺点是,它们已经提供了一个特定的优化指标,通常是一个固定的预处理数据集。

  • OpenML 平台(www.openml.org)拥有超过 20,000 个数据集和超过 50,000 个相关的机器学习任务。

  • 另一个受欢迎的选择是加州大学欧文分校机器学习资源库(archive.ics.uci.edu/ml/index.php),通过一个可搜索的界面托管超过 370 个受欢迎且维护良好的数据集。

Finally, if you are looking for more example code in Python, a number of excellent books nowadays come with their own GitHub repository:

摘要

在本章中,我们学习了如何处理机器学习问题,并构建了自己的估计器。我们学习了如何用 C++编写自己的基于 OpenCV 的分类器,用 Python 编写基于 scikit-learn 的分类器。

在这本书里,我们涵盖了大量的理论和实践。我们讨论了各种各样的基本机器学习算法,既有监督的也有无监督的,并举例说明了最佳实践以及避免常见陷阱的方法,我们还涉及了各种用于数据分析、机器学习和可视化的命令和包。

如果你做到了这一步,你已经朝着机器学习的掌握迈出了一大步。从现在开始,我相信你自己会做得很好。

剩下要说的就是再见了!…

第一部分:机器学习和 OpenCV 的基础

在这本书的第一部分,我们将复习机器学习和 OpenCV 的基础知识,首先是安装所需的库,然后是基本的 OpenCV 函数、监督学习的基础知识及其应用,最后是使用 OpenCV 进行特征检测和识别。

本节包括以下章节:

  • 第一章机器学习的一种尝试
  • 第二章,在 OpenCV 中处理数据
  • 第三章监督学习的第一步
  • 第四章,代表数据和工程特性

第二部分:使用 OpenCV 的操作

本节重点介绍高级机器学习概念,以及如何使用 OpenCV 和 scikit-learn 实现这些概念。我们将介绍一些先进的机器学习概念,如决策树、支持向量机和贝叶斯学习,然后最后讨论第二类机器学习问题——无监督学习。

本节包括以下章节:

  • 第五章利用决策树进行医疗诊断
  • 第六章、支持向量机检测行人
  • 第七章,使用贝叶斯学习实现垃圾邮件过滤器
  • 第八章,用无监督学习发现隐藏结构

第三部分:使用 OpenCV 的高级机器学习

本书的最后一部分将涵盖重要和高级的主题,如深度学习、集成机器学习方法和超参数调整。我们还将介绍 OpenCV 的最新成员——英特尔的 OpenVINO 工具包。我们将简要介绍 OpenVINO,如何安装它,以及它的各种组件是什么,然后最后看看它如何与 OpenCV 一起用于图像分类问题。

本节包括以下章节:

  • 第九章,利用深度学习对手写数字进行分类
  • 第十章、集成分类方法
  • 第十一章、用超参数调谐选择正确的模型
  • 第十二章,使用 OpenVINO 搭配 OpenCV
  • 第十三章结论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值