基于PCA–LDA的人脸识别
ORC数据集
提取码: cggh
本次的实验是在模式识别与机器学习(作业4),PCA降维的基础上加入了线性判别分析,对维度进行了进一步的降低。
1. 导入数据
一共有40个人脸,每个人脸10张图片,8张用于训练,2张用于测试。所以训练集有320张图片,测试集有80张图片。图像的分辨率为112*92,我们将其reshape为(1,10304)。由此得到训练集(320,10304),测试集(180,10304)
#%%导入数据
import numpy as np
import cv2
import os.path as osp
import os
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
#训练集路径
osp.abspath(".")
#导入训练数据
train_x,train_y = [],[]
for i in os.listdir(osp.join(osp.realpath("."),"train_1")):
img = cv2.imread(osp.join(osp.realpath("."),"train_1",i),0)
train_x.append(np.reshape(img,(1,-1)))
a,_ = i.split("_")
train_y.append(a)
test_x,test_y = [],[]
for i in os.listdir(osp.join(osp.realpath("."),"test")):
img = cv2.imread(osp.join(osp.realpath("."),"test",i),0)
test_x.append(np.reshape(img,(1,-1)))
a,_ = i.split("_")
test_y.append(a)
train_x = np.array(train_x,dtype = np.float32).squeeze(1)
train_y = np.array(train_y)
test_x = np.array(test_x,dtype = np.float32).squeeze(1)
test_y = np.array(test_y)
2. PCA降维
在训练时,最初采用的是特征值分解的方法,但是存在的问题是,因为维度较高,特征分解时间较长,并且分解后的特征向量存在虚数,不利于识别。因此,我采用了第二种方法,SVD分解的方法**(保留前80个维度)**,具体的步骤请参照李航博士《统计学习原理》第二版一书(page336),这里不再一一赘述。
class PCA:
#定义K近邻的值必须大于1
def __init__(self):
self.x_train_fit = None
self.y_train_fit = None
self.u = None
self.s = None
self.vh = None
def fit(self,x_train,y_train):
self.x_train_fit = x_train
self.y_train_fit = y_train
return self
#中心化,建立模型
def Centralization(self,X):
Centra = X - np.mean(self.x_train_fit,axis = 0)
return Centra
def model(self):
#定义一个新矩阵
X_ = (1/np.sqrt(len(self.Centralization(self.x_train_fit))))*self.Centralization(self.x_train_fit)
#进行奇异值分解
self.u, self.s, self.vh = np.linalg.svd(X_)
#降维
def transform(self,X,K):
X = self.Centralization(X)
X_dunction = (((self.vh).T[:,:K]).T).dot(X.T).T
return X_dunction
#%%
pca = PCA()
pca.fit(train_x,train_y)
pca.model()
k=80
X_test_dunction_1 = pca.transform(test_x,k)
X_train_dunction_1 = pca.transform(train_x,k)
3. LDA降维
LDA也可以推广到了多分类的任务当中。假定存在 N N N个类,且第 i i i 类示例数为 m i m_i mi我们先定义"全局散度矩阵"。
S t = S b + S w = ∑ i = 1 m ( x i − μ ) ( x i − μ ) T \begin{aligned} &\mathbf{S}_{t}=\mathbf{S}_{b}+\mathbf{S}_{w}\\ &=\sum_{i=1}^{m}\left(x_{i}-\mu\right)\left(x_{i}-\mu\right)^{\mathrm{T}} \end{aligned} St=Sb+Sw=i=1∑m(xi−μ)(xi−μ)T
其中 u u u是所有示例样本的均值, x i x_i xi为第 i i i类样本的示例。
我们还要重新定义多分类任务的类内散度 S w S_w Sw,即每个类别的散度矩阵之和:
S w = ∑ i = 1 N S w i \mathbf{S}_{w}=\sum_{i=1}^{N} \mathbf{S}_{w_{i}} Sw=i=1∑NSwi
其中 S w i = ∑ x ∈ X i ( x − μ i ) ( x − μ i ) T \mathbf{S}_{w_{i}}=\sum_{\boldsymbol{x} \in X_{i}}\left(\boldsymbol{x}-\boldsymbol{\mu}_{i}\right)\left(\boldsymbol{x}-\boldsymbol{\mu}_{i}\right)^{\mathrm{T}} Swi=x∈Xi∑(x−μi)(x−μi)T
由以上公式可以知道
S b = S t − S w = ∑ i = 1 N m i ( μ i − μ ) ( μ i − μ ) T \begin{aligned} \mathbf{S}_{b} &=\mathbf{S}_{t}-\mathbf{S}_{w} \\ &=\sum_{i=1}^{N} m_{i}\left(\boldsymbol{\mu}_{i}-\boldsymbol{\mu}\right)\left(\boldsymbol{\mu}_{i}-\boldsymbol{\mu}\right)^{\mathrm{T}} \end{aligned} Sb=St−Sw=i=1∑Nmi(μi−μ)(μi−μ)T
其中 S b S_b Sb为类间散度, m i m_i mi表示的为类别,在本题目中有40个类别。 u i u_i ui是同一类别下的特征均值。
一般的我们可以利用如下公式,进行求解,其中 W W W就是一个投影矩阵,我们可以选取特征值最大的前K个,用于特征的降维。
S b W = λ S w W \mathbf{S}_{b} \mathbf{W}=\lambda \mathbf{S}_{w} \mathbf{W} SbW=λSwW
#%%导入数据
#%%算法的判别
#需要计算肋间方差和类内方差
import collections
#求取所用示例的均值向量(k,),k表示降维后的数据
mean_u = np.mean(X_train_dunction_1,axis = 0)
#求取类间散度矩阵,和类内散度矩阵
S_b = np.zeros((X_train_dunction_1.shape[1],X_train_dunction_1.shape[1]))
S_w = np.zeros((X_train_dunction_1.shape[1],X_train_dunction_1.shape[1]))
get_class = collections.Counter(train_y).keys()
for classes in get_class:
x_i = np.array([x_i for x_i,i in zip(X_train_dunction_1,train_y) if i == classes])
mean_u_i = np.mean(x_i,axis = 0)
#类间散度矩阵
S_b = S_b + (mean_u_i - mean_u).reshape(-1,1).dot((mean_u_i - mean_u).reshape(1,-1))
#类内散度矩阵,西瓜书公式3.41,3.42
S_w = S_w + (x_i - mean_u_i).T.dot(x_i - mean_u_i)
#类间散度矩阵,西瓜书公式3.43
S_b = S_b * len(get_class)
#进行特征值分解
eig_vals, eig_vecs = np.linalg.eig(np.linalg.inv(S_w).dot(S_b))
#对特征值进行由高到低的排序
eig_vecs = np.array([eig_vecs[:,i] for i in np.argsort(-eig_vals)])
#%%求取准确度
for k in range(1,40):
X_train_dunction_2 = X_train_dunction_1.dot(eig_vecs[:,:k])
X_test_dunction_2 = X_test_dunction_1.dot(eig_vecs[:,:k])
accu = 0
for i in range(len(test_y)):
a = [np.linalg.norm(X_test_dunction_2[i,:] - X_train_dunction_2[j,:]) for j in range(len(train_y))]
min_dix = np.argmin(a)
if train_y[min_dix] == test_y[i]:
accu += 1
print("保留前{}维的准确度为{}".format(k,accu/80))
结果
保留前1维的准确度为0.0875
保留前2维的准确度为0.425
保留前3维的准确度为0.575
保留前4维的准确度为0.7125
保留前5维的准确度为0.8
保留前6维的准确度为0.85
保留前7维的准确度为0.875
保留前8维的准确度为0.8875
保留前9维的准确度为0.875
保留前10维的准确度为0.8625
保留前11维的准确度为0.875
保留前12维的准确度为0.9125
保留前13维的准确度为0.9375
保留前14维的准确度为0.9125
保留前15维的准确度为0.9375
保留前16维的准确度为0.95
保留前17维的准确度为0.9375
保留前18维的准确度为0.9125
保留前19维的准确度为0.9625
保留前20维的准确度为0.9625
保留前21维的准确度为0.9625
保留前22维的准确度为0.9625
保留前23维的准确度为0.9625
保留前24维的准确度为0.975
保留前25维的准确度为0.975
保留前26维的准确度为0.975
保留前27维的准确度为0.9625
保留前28维的准确度为0.9625
保留前29维的准确度为0.9625
保留前30维的准确度为0.95
保留前31维的准确度为0.9625
保留前32维的准确度为0.95
保留前33维的准确度为0.9625
保留前34维的准确度为0.9625
保留前35维的准确度为0.9625
保留前36维的准确度为0.9625
保留前37维的准确度为0.95
保留前38维的准确度为0.9625
保留前39维的准确度为0.975
有测试结果得出,在本实验中保留前24维,准确度可以达到最佳。