计算机视觉之PCA和特征脸
主成分分析(PCA)是一种统计/非监督机器学习方法,它使用一个正交变换将一组可能相关的变量的观测值转化为一组线性不相关的变量的值(称为主成分),从而在数据集中发现最大方向的方差(沿着主成分)。这可以用于(线性)降维(只有几个突出的主成分在大多数情况下捕获数据集中的几乎所有方差)和具有多个维度的数据集的可视化(在二维空间中)。PCA 的一个应用是特征脸,找到一组可以(从理论上)表示任意脸(作为这些特征脸的线性组合)的特征脸。
1.用 PCA 降维及可视化
我们将使用 scikit-learn 的数字数据集,其中包含 1797 张手写数字图像(每张图像大小为 8×8)。每一行表示数据矩阵中的一幅图像。用下面的代码加载并显示数据集中的前 25 位数字:
import numpy as np
import matplotlib.pylab as plt
from matplotlib import pylab
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
digits = load_digits()
#print(digits.keys())
print(digits.data.shape)
j = 1
np.random.seed(2)
fig = plt.figure(figsize=(3,3))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05,wspace=0.05)
#从数组中随机抽取元素
for i in np.random.choice(digits.data.shape[0], 25):
plt.subplot(5,5,j), plt.imshow(np.reshape(digits.data[i,:], (8,8)),cmap='binary'), plt.axis('off')
j += 1
plt.show()
运行上述代码,输出数据集中的前 25 位手写数字,如图:
2.二维投影和可视化
从加载的数据集可以看出,它是一个 64 维的数据集。现在,首先利用scikit-learn 的 PCA()函数来找到这个数据集的两个主成分并将数据集沿着两个维度进行投影;其次利用 matplotlib 和表示图像(数字)的每个数据点,对投影数据进行散点绘图,数字标签用一种独特的颜色表示,如下面的代码所示:
plt.show()
pca_digits=PCA(2)
digits.data_proj = pca_digits.fit_transform(digits.data)
print(np.sum(pca_digits.explained_variance_ratio_))
# 0.28509364823696987
plt.figure(figsize=(15,10))
plt.scatter(digits.data_proj[:, 0], digits.data_proj[:, 1], lw=0.25, c=digits.target, edgecolor='k', s=100, cmap=plt.cm.get_cmap('cubehelix',10))
plt.xlabel('PC1', size=20), plt.ylabel('PC2', size=20), plt.title('2D Projection of handwritten digits with PCA', size=25)
plt.colorbar(ticks=range(10), label='digit value')
plt.clim(-0.5, 9.5)
3.基于 PCA 的特征脸
加载 scikit-learn 包的 olivetti 人脸数据集,其中包含 400 张人脸图像,每
张图像大小为 64×64。如下代码显示了数据集中的一些随机人脸:
#人脸数据
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().data
print(faces.shape) # there are 400 faces each of them is of 64x64=4096 pixels
fig = plt.figure(figsize=(5,5))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
# plot 25 random faces
j = 1
np.random.seed(0)
for i in np.random.choice(range(faces.shape[0]), 25):
ax = fig.add_subplot(5, 5, j, xticks=[], yticks=[])
ax.imshow(np.reshape(faces[i,:],(64,64)), cmap=plt.cm.bone,interpolation='nearest')
j += 1
plt.show()
运行上述代码,输出从数据集中随机选取的 25 张人脸图像接下来,对数据集进行预处理,在对图像应用 PCA 之前先执行 z-score 归一化(从所有人脸中减去均值人脸,然后除以标准差),这是必要的步骤;然后,使用 PCA()计算主成分,只选取 64 个(而不是 4096 个)主成分,并将数据集投影到 PC 方向上,如下面的代码所示,而且通过选择越来越多的主成分来可视化图像数据集的方差。
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
n_comp =64
pipeline = Pipeline([('scaling', StandardScaler()), ('pca',PCA(n_components=n_comp))])
faces_proj = pipeline.fit_transform(faces)
print(faces_proj.shape)
# (400, 64)
mean_face = np.reshape(pipeline.named_steps['scaling'].mean_, (64,64))
sd_face = np.reshape(np.sqrt(pipeline.named_steps['scaling'].var_),(64,64))
pylab.figure(figsize=(8, 6))
pylab.plot(np.cumsum(pipeline.named_steps['pca'].explained_variance_ratio_) , linewidth=2)
pylab.grid(), pylab.axis('tight'), pylab.xlabel('n_components'),
pylab.ylabel('cumulative explained_variance_ratio_')
pylab.show()
pylab.figure(figsize=(10,5))
pylab.subplot(121), pylab.imshow(mean_face, cmap=pylab.cm.bone),
pylab.axis('off'), pylab.title('Mean face')
pylab.subplot(122), pylab.imshow(sd_face, cmap=pylab.cm.bone),
pylab.axis('off'), pylab.title('SD face')
pylab.show()
运行上述代码,输出结果如图所示。可以看到,大约 90%的方差仅由前 64 个主成分所解释。从数据集中计算得到的人脸图像的均值和标准差.
(1)特征脸
在主成分分析的基础上,计算得到的两 PC 方向相互正交,每个 PC 包
含 4096 个像素,并且可以重建成 64×64 像素的图像。称这些主成分为特征脸(因为它们也是特征向量)。可以看出,特征脸代表了人脸的某些属性。如下代码用于显示一些计算出来的特征脸:
fig = plt.figure(figsize=(5,2))
fig.subplots_adjust(left=0, right=2, bottom=0, top=5, hspace=0.07,wspace=0.005)
# plot the first 10 eigenfaces
for i in range(25):
ax = fig.add_subplot(5, 5, i+1, xticks=[], yticks=[])
ax.imshow(np.reshape(pipeline.named_steps['pca'].components_[i,:],(64,64)), cmap=plt.cm.bone, interpolation='nearest')
plt.savefig("C:/Users/zhuyupeng/Desktop/新建文件夹/hsdh.png",bbox_inches = 'tight')
运行上述代码,输出前 25 张特征脸,如图所示
(2)重建。
如下代码演示了如何将每张人脸近似地表示成这 64 张主要特征脸的线性
组合。使用 scikit-learn 中的 inverse_transform()函数变换回到原空间,但是只基于这 64 张主要特征脸,而抛弃所有其他特征脸。
#重建
# face reconstruction
faces_inv_proj = pipeline.named_steps['pca'].inverse_transform(faces_proj)
#reshaping as 400 images of 64x64 dimension
fig = plt.figure(figsize=(5,5))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05,wspace=0.05)
# plot the faces, each image is 64 by 64 dimension but 8x8 pixels
j = 1
np.random.seed(0)
for i in np.random.choice(range(faces.shape[0]), 25):
ax = fig.add_subplot(5, 5, j, xticks=[], yticks=[])
ax.imshow(mean_face + sd_face*np.reshape(faces_inv_proj,(400,64,64))[i,:], cmap=plt.cm.bone, interpolation='nearest')
j += 1
运行上述代码,从 64 张特征脸中随机选择 25 张重建的人脸图像,如图 上 所示。可以看到,它们看起来很像原始的人脸(没有很多明显的错误)。如下代码有助于更近距离地观察原始人脸,并将其与重建后的人脸进行对比,代码的输出结果如下图所示。可以看到,重建后的人脸与原始人脸近似,但存在某种程度的失真。
orig_face = np.reshape(faces[0,:], (64,64))
reconst_face =np.reshape(faces_proj[0,:]@pipeline.named_steps['pca'].components_, (64,64))
reconst_face = mean_face + sd_face*reconst_face
plt.figure(figsize=(10,5))
plt.subplot(121), plt.imshow(orig_face, cmap=plt.cm.bone, interpolation='nearest'), plt.axis('off'), plt.title('original', size=20)
plt.subplot(122), plt.imshow(reconst_face, cmap=plt.cm.bone, interpolation='nearest'), plt.axis('off'), plt.title('reconstructed', size=20)
plt.show()