一、PCA原理
1.PCA概念
PCA(Principal Component Analysis),即主成分分析方法,是一种使用最广泛的数据降维算法。PCA的主要思想是将n维特征映射到k维上,这k维是全新的正交特征也被称为主成分,是在原有n维特征的基础上重新构造出来的k维特征。PCA的工作就是从原始的空间中顺序地找一组相互正交的坐标轴,新的坐标轴的选择与数据本身是密切相关的。其中,第一个新坐标轴选择是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。通过这种方式获得的新的坐标轴,我们发现,大部分方差都包含在前面k个坐标轴中,后面的坐标轴所含的方差几乎为0。于是,我们可以忽略余下的坐标轴,只保留前面k个含有绝大部分方差的坐标轴。事实上,这相当于只保留包含绝大部分方差的维度特征,而忽略包含方差几乎为0的特征维度,实现对数据特征的降维处理。
2.PCA基本思路
2. 协方差和散度矩阵
样本均值:
样本方差:
样本X和样本Y的协方差:
由上面的公式,我们可以得到以下结论:
(1) 方差的计算公式是针对一维特征,即针对同一特征不同样本的取值来进行计算得到;而协方差则必须要求至少满足二维特征;方差是协方差的特殊情况。
(2) 方差和协方差的除数是n-1,这是为了得到方差和协方差的无偏估计。
协方差为正时,说明X和Y是正相关关系;协方差为负时,说明X和Y是负相关关系;协方差为0时,说明X和Y是相互独立。Cov(X,X)就是X的方差。当样本是n维数据时,它们的协方差实际上是协方差矩阵(对称方阵)。例如,对于3维数据(x,y,z),计算它的协方差就是:
散度矩阵定义为:
对于数据X的散度矩阵为 XX^T 。其实协方差矩阵和散度矩阵关系密切,散度矩阵就是协方差矩阵乘以(总数据量-1)。因此它们的特征值和特征向量是一样的。这里值得注意的是,散度矩阵是SVD奇异值分解的一步,因此PCA和SVD是有很大联系。
3. 特征值分解矩阵原理
(1) 特征值与特征向量
如果一个向量v是矩阵A的特征向量,将一定可以表示成下面的形式:
其中,λ是特征向量v对应的特征值,一个矩阵的一组特征向量是一组正交向量。
(2) 特征值分解矩阵
对于矩阵A,有一组特征向量v,将这组向量进行正交化单位化,就能得到一组正交单位向量。特征值分解,就是将矩阵A分解为如下式:
其中,Q是矩阵A的特征向量组成的矩阵,则是一个对角阵,对角线上的元素就是特征值。
4. SVD分解矩阵原理
奇异值分解是一个能适用于任意矩阵的一种分解的方法,对于任意矩阵A总是存在一个奇异值分解:
假设A是一个m*n的矩阵,那么得到的U是一个m*m的方阵,U里面的正交向量被称为左奇异向量。Σ是一个m*n的矩阵,Σ除了对角线其它元素都为0,对角线上的元素称为奇异值。 是v的转置矩阵,是一个n*n的矩阵,它里面的正交向量被称为右奇异值向量。而且一般来讲,我们会将Σ上的值按从大到小的顺序排列。
SVD分解矩阵A的步骤:
(1) 求的特征值和特征向量,用单位化的特征向量构成 U。
(2) 求 的特征值和特征向量,用单位化的特征向量构成 V。
(3) 将 或者 A^TA 的特征值求平方根,然后构成 Σ。
二、PCA算法
1.基于特征值分解协方差矩阵实现PCA算法
输入:数据集 ,需要降到k维。
1) 去平均值(即去中心化),即每一位特征减去各自的平均值。
2) 计算协方差矩阵 ,注:这里除或不除样本数量n或n-1,其实对求出的特征向量没有影响。
3) 用特征值分解方法求协方差矩阵 的特征值与特征向量。
4) 对特征值从大到小排序,选择其中最大的k个。然后将其对应的k个特征向量分别作为行向量组成特征向量矩阵P。
5) 将数据转换到k个特征向量构建的新空间中,即Y=PX。
2.基于SVD分解协方差矩阵实现PCA算法
输入:数据集 ,需要降到k维。
1) 去平均值,即每一位特征减去各自的平均值。
2) 计算协方差矩阵。
3) 通过SVD计算协方差矩阵的特征值与特征向量。
4) 对特征值从大到小排序,选择其中最大的k个。然后将其对应的k个特征向量分别作为列向量组成特征向量矩阵。
5) 将数据转换到k个特征向量构建的新空间中。
在PCA降维中,我们需要找到样本协方差矩阵 的最大k个特征向量,然后用这最大的k个特征向量组成的矩阵来做低维投影降维。可以看出,在这个过程中需要先求出协方差矩阵
,当样本数多、样本特征数也多的时候,这个计算还是很大的。当我们用到SVD分解协方差矩阵的时候,SVD有两个好处:
1) 有一些SVD的实现算法可以先不求出协方差矩阵 也能求出我们的右奇异矩阵V。也就是说,我们的PCA算法可以不用做特征分解而是通过SVD来完成,这个方法在样本量很大的时候很有效。实际上,scikit-learn的PCA算法的背后真正的实现就是用的SVD,而不是特征值分解。
2)注意到PCA仅仅使用了我们SVD的左奇异矩阵,没有使用到右奇异值矩阵,那么右奇异值矩阵有什么用呢?
假设我们的样本是m*n的矩阵X,如果我们通过SVD找到了矩阵最大的k个特征向量组成的k*n的矩阵
,则我们可以做如下处理:
可以得到一个m*k的矩阵X',这个矩阵和我们原来m*n的矩阵X相比,列数从n减到了k,可见对列数进行了压缩。也就是说,左奇异矩阵可以用于对行数的压缩;右奇异矩阵可以用于对列(即特征维度)的压缩。这就是我们用SVD分解协方差矩阵实现PCA可以得到两个方向的PCA降维(即行和列两个方向)。
三、PCA用例实现
代码:
# 导入所需模块
import matplotlib.pyplot as plt
import numpy as np
import os
import cv2
# plt显示灰度图片
def plt_show(img):
plt.imshow(img,cmap='gray')
plt.show()
# 读取一个文件夹下的所有图片,输入参数是文件名,返回文件地址列表
def read_directory(directory_name):
faces_addr = []
for filename in os.listdir(directory_name):
faces_addr.append(directory_name + "/" + filename)
return faces_addr
# 读取所有人脸文件夹,保存图像地址在faces列表中
faces = []
for i in range(1,41):
faces_addr = read_directory('C:/Users/86183/Desktop/Face_Dataset/Face/ORL_Faces/s'+str(i))
for addr in faces_addr:
faces.append(addr)
# 读取图片数据,生成列表标签
images = []
labels = []
for index,face in enumerate(faces):
# enumerate函数可以同时获得索引和值
image = cv2.imread(face,0)
images.append(image)
labels.append(int(index/10+1))
print(len(labels))
print(len(images))
print(type(images[0]))
print(labels)
# 画出最后20组人脸图像
# 创建画布和子图对象
fig, axes = plt.subplots(10,20
,figsize=(20,10)
,subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
)
# 图片x行y列,画布x宽y高
# 填充图像
for i, ax in enumerate(axes.flat):
ax.imshow(images[i+200],cmap="gray") #选择色彩的模式
# 图像数据矩阵转换为一维
image_data = []
for image in images:
data = image.flatten()
# a是个矩阵或者数组,a.flatten()就是把a降到一维,默认是按横的方向降
image_data.append(data)
print(image_data[0].shape)
# 转换为numpy数组
X = np.array(image_data)
y = np.array(labels)
print(type(X))
print(X.shape)
# 导入sklearn的pca模块
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
# 画出每个图像数据降到一维后的矩阵
import pandas as pd
data = pd.DataFrame(X)
data.head()
# 划分数据集
x_train,x_test,y_train,y_test = train_test_split(X, y, test_size=0.2) # train训练,test测试
# 训练PCA模型
pca=PCA(n_components=100) # 保留100个纬度
pca.fit(x_train) # 训练过程
# 返回训练集和测试集降维后的数据集
x_train_pca = pca.transform(x_train) # 转换过程
x_test_pca = pca.transform(x_test)
print(x_train_pca.shape) # 320个训练集,保留了100个特征
print(x_test_pca.shape) # 80个测试集,保留了100个特征
V = pca.components_
V.shape
# 100个特征脸
# 创建画布和子图对象
fig, axes = plt.subplots(10,10
,figsize=(15,15)
,subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
)
#填充图像
for i, ax in enumerate(axes.flat):
ax.imshow(V[i,:].reshape(112,92),cmap="gray") #reshape规定图片的大小,选择色彩的模式
# 该选择多少个特征呢?
# 属性explained_variance_ratio,查看降维后每个新特征向量所占的信息量占原始数据总信息量的百分比
# 又叫做可解释方差贡献率
pca.explained_variance_ratio_
# 返回特征所携带的数据是原始数据的多少
pca.explained_variance_ratio_.sum()
# 画出特征个数和所携带信息数的曲线图,总共画了150个特征
explained_variance_ratio = []
for i in range(1,151):
pca=PCA(n_components=i).fit(x_train)
explained_variance_ratio.append(pca.explained_variance_ratio_.sum())
plt.plot(range(1,151),explained_variance_ratio)
plt.show()
# 模型创建与训练
model = cv2.face.EigenFaceRecognizer_create()
model.train(x_train,y_train)
# 预测
res = model.predict(x_test[0])
print(res)
y_test[0]
# 测试数据集的准确率
ress = []
true = 0
for i in range(len(y_test)):
res = model.predict(x_test[i])
if y_test[i] == res[0]:
true = true+1
else:
print(i)
print('测试集识别准确率:%.2f'% (true/len(y_test)))
# 降维
pca=PCA(n_components=100)
pca.fit(X)
X = pca.transform(X)
# 将所有数据都用作训练集
# 模型创建与训练
model = cv2.face.EigenFaceRecognizer_create()
model.train(X,y)
# 输入图片识别,同时进行灰度处理
# 此图片是s1文件夹里的图片画了两笔
img = cv2.imread('C:/Users/86183/Desktop/Face_Dataset/Face/ORL_Faces/test.jpg',0)
plt_show(img)
print(img.shape)
imgs = []
imgs.append(img)
# 矩阵
image_data = []
for img in imgs:
data = img.flatten()
image_data.append(data)
test = np.array(image_data)
test.shape
# 用训练好的pca模型给图片降维
test = pca.transform(test)
test[0].shape
res = model.predict(test)
res
print('人脸识别结果:',res[0])
运行结果:
四、总结
PCA(Principal Component Analysis)是一种常用的数据降维方法,可以将高维数据转换为低维数据并保留原始数据的主要特征。在人脸识别中,PCA可以用于提取人脸图像的主要成分,将人脸图像转换为向量,并通过降维处理减少计算量。具体实现时,首先需要收集一组已知人脸图像,提取图像特征,然后利用PCA算法计算主成分,并得到一个特征空间。对于待识别的人脸图像,同样提取其特征并在特征空间中进行比较,从而判断其归属于哪个已知人脸。该方法在人脸识别领域应用广泛,具有较高的准确性和鲁棒性。