3.4.8.14. 特征脸示例:PCA 和 SVM 的链式组合

本例的目的是展示如何将无监督方法和监督方法进行链式组合以获得更好的预测效果。它从一种具有教学意义但冗长的做法开始,最后以 scikit-learn 中管道化的惯用方法结束。

这里我们将看一个简单的面部识别示例。理想情况下,我们将使用一个数据集,该数据集包含 野外标记人脸 数据的一个子集,该数据集可通过 sklearn.datasets.fetch_lfw_people() 获取。但是,这是一个相对较大的下载文件(约 200MB),因此我们将使用一个更简单、信息量较少的数据集进行教程。欢迎您探索 LFW 数据集。

from sklearn import datasets
faces = datasets.fetch_olivetti_faces()
faces.data.shape
downloading Olivetti faces from https://ndownloader.figshare.com/files/5976027 to /home/runner/scikit_learn_data
(400, 4096)

让我们可视化这些面孔,看看我们正在处理什么。

import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 6))
# plot several images
for i in range(15):
ax = fig.add_subplot(3, 5, i + 1, xticks=[], yticks=[])
ax.imshow(faces.images[i], cmap="bone")
plot eigenfaces

提示

请注意,这些面孔已经过定位并缩放到一个共同的大小。这是面部识别的一个重要的预处理步骤,并且是一个可能需要大量训练数据的过程。这可以在 scikit-learn 中完成,但挑战在于收集足够数量的训练数据以使算法能够工作。幸运的是,这个步骤非常常见,因此已经完成了。一个很好的资源是 OpenCV,即开放式计算机视觉库

我们将对图像执行支持向量分类。我们将对图像进行典型的训练-测试分割。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
faces.data, faces.target, random_state=0
)
print(X_train.shape, X_test.shape)
(300, 4096) (100, 4096)

预处理:主成分分析

1850 个维度对于 SVM 来说太多了。我们可以使用 PCA 将这 1850 个特征降至可管理的大小,同时保留数据集中大部分信息。

from sklearn import decomposition
pca = decomposition.PCA(n_components=150, whiten=True)
pca.fit(X_train)
PCA(n_components=150, whiten=True)
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任笔记本。
在 GitHub 上,HTML 表示形式无法呈现,请尝试使用 nbviewer.org 加载此页面。


PCA 的一个有趣的部分是它计算“平均”面孔,这可能很有趣。

plt.imshow(pca.mean_.reshape(faces.images[0].shape), cmap="bone")
plot eigenfaces
<matplotlib.image.AxesImage object at 0x7f78e577ff20>

主成分测量沿正交轴的此平均值的偏差。

(150, 4096)

可视化这些主成分也很有趣。

fig = plt.figure(figsize=(16, 6))
for i in range(30):
ax = fig.add_subplot(3, 10, i + 1, xticks=[], yticks=[])
ax.imshow(pca.components_[i].reshape(faces.images[0].shape), cmap="bone")
plot eigenfaces

这些成分(“特征脸”)按其重要性从左上到右下排序。我们看到前几个成分似乎主要处理照明条件;其余的成分提取某些识别特征:鼻子、眼睛、眉毛等。

计算完此投影后,我们现在可以将原始训练和测试数据投影到 PCA 基上。

(300, 150)
(100, 150)

这些投影的成分对应于成分图像线性组合中的因子,使得组合接近原始面孔。

进行学习:支持向量机

现在,我们将对此减少的数据集执行支持向量机分类。

from sklearn import svm
clf = svm.SVC(C=5.0, gamma=0.001)
clf.fit(X_train_pca, y_train)
SVC(C=5.0, gamma=0.001)
在 Jupyter 环境中,请重新运行此单元格以显示 HTML 表示形式或信任笔记本。
在 GitHub 上,HTML 表示形式无法呈现,请尝试使用 nbviewer.org 加载此页面。


最后,我们可以评估这种分类的效果如何。首先,我们可以绘制一些测试用例,以及从训练集中学习到的标签。

import numpy as np
fig = plt.figure(figsize=(8, 6))
for i in range(15):
ax = fig.add_subplot(3, 5, i + 1, xticks=[], yticks=[])
ax.imshow(X_test[i].reshape(faces.images[0].shape), cmap="bone")
y_pred = clf.predict(X_test_pca[i, np.newaxis])[0]
color = "black" if y_pred == y_test[i] else "red"
ax.set_title(y_pred, fontsize="small", color=color)
13, 30, 34, 19, 24, 6, 15, 26, 14, 21, 3, 13, 11, 34, 1

鉴于其学习模型的简单性,分类器在大量图像上都是正确的!使用线性分类器对从像素级数据导出的 150 个特征进行分类,该算法能够正确识别图像中大量的人。

同样,我们可以使用 sklearn.metrics 中的几种度量之一来量化这种有效性。首先,我们可以进行分类报告,该报告显示了分类“优劣”的精度、召回率和其他度量。

from sklearn import metrics
y_pred = clf.predict(X_test_pca)
print(metrics.classification_report(y_test, y_pred))
              precision    recall  f1-score   support
0 1.00 0.50 0.67 6
1 1.00 1.00 1.00 4
2 0.50 1.00 0.67 2
3 1.00 1.00 1.00 1
4 0.33 1.00 0.50 1
5 1.00 1.00 1.00 5
6 1.00 1.00 1.00 4
7 1.00 0.67 0.80 3
9 1.00 1.00 1.00 1
10 1.00 1.00 1.00 4
11 1.00 1.00 1.00 1
12 0.67 1.00 0.80 2
13 1.00 1.00 1.00 3
14 1.00 1.00 1.00 5
15 1.00 1.00 1.00 3
17 1.00 1.00 1.00 6
19 1.00 1.00 1.00 4
20 1.00 1.00 1.00 1
21 1.00 1.00 1.00 1
22 1.00 1.00 1.00 2
23 1.00 1.00 1.00 1
24 1.00 1.00 1.00 2
25 1.00 0.50 0.67 2
26 1.00 0.75 0.86 4
27 1.00 1.00 1.00 1
28 0.67 1.00 0.80 2
29 1.00 1.00 1.00 3
30 1.00 1.00 1.00 4
31 1.00 1.00 1.00 3
32 1.00 1.00 1.00 3
33 1.00 1.00 1.00 2
34 1.00 1.00 1.00 3
35 1.00 1.00 1.00 1
36 1.00 1.00 1.00 3
37 1.00 1.00 1.00 3
38 1.00 1.00 1.00 1
39 1.00 1.00 1.00 3
accuracy 0.94 100
macro avg 0.95 0.96 0.94 100
weighted avg 0.97 0.94 0.94 100

另一个有趣的指标是混淆矩阵,它指示任何两个项目被混淆的频率。完美分类器的混淆矩阵在对角线上只有非零项,在非对角线上为零。

[[3 0 0 ... 0 0 0]
[0 4 0 ... 0 0 0]
[0 0 2 ... 0 0 0]
...
[0 0 0 ... 3 0 0]
[0 0 0 ... 0 1 0]
[0 0 0 ... 0 0 3]]

管道化

在上面,我们在应用支持向量机分类器之前使用 PCA 作为预处理步骤。将一个估计器的输出直接插入第二个估计器的输入是一个常用的模式;出于这个原因,scikit-learn 提供了一个 Pipeline 对象来自动执行此过程。上述问题可以表示为以下管道。

from sklearn.pipeline import Pipeline
clf = Pipeline(
[
("pca", decomposition.PCA(n_components=150, whiten=True)),
("svm", svm.LinearSVC(C=1.0)),
]
)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print(metrics.confusion_matrix(y_pred, y_test))
plt.show()
[[4 0 0 ... 0 0 0]
[0 4 0 ... 0 0 0]
[0 0 1 ... 0 0 0]
...
[1 0 0 ... 3 0 0]
[0 0 0 ... 0 1 0]
[0 0 0 ... 0 0 3]]

关于面部识别的说明

在这里,我们使用了 PCA “特征脸”作为面部识别的预处理步骤。我们之所以选择它,是因为 PCA 是一种广泛适用的技术,可用于各种数据类型。然而,面部识别领域的具体研究表明,其他更具体的特征提取方法可以更有效。

脚本的总运行时间:(0 分钟 4.174 秒)

由 Sphinx-Gallery 生成的图库