【机器学习实战7】主成分分析(PCA)——人脸识别
目录
一、算法概述
1.1 算法简介
主成分分析(Principal Component Analysis,简称PCA)是一种常用的线性降维技术,其目标是将高维数据投影到低维空间中,同时尽可能保留原始数据中的主要信息。它通过将原始数据变换到新的坐标系统中,使得新的坐标轴(主成分)方向上的方差最大化,从而提取出数据的主要结构。
1.2 数学原理
1.2.1 数据预处理
在进行PCA之前,通常需要对数据进行标准化处理。将每个特征的均值调整为0,标准差调整为1。这一步骤可以消除不同特征之间的量纲差异和均值差异,使PCA的结果更加合理。
假设原始数据集为 X,其中每一行表示一个样本,每一列表示一个特征。对于每个特征 xi,计算其均值 μi 和标准差 σi:

对每个数据点 xji 进行标准化:

标准化后的数据记为 X′。
1.2.2 协方差矩阵
PCA的核心是计算数据的协方差矩阵。协方差矩阵是一个方阵,其对角线元素表示各个特征的方差,非对角线元素表示不同特征之间的协方差。协方差矩阵反映了数据中各个特征之间的相关性。
对于标准化后的数据集 X′,协方差矩阵 C 的计算公式为:

其中,n 是数据点的数量,
是 X′ 的转置。
1.2.3 特征值和特征向量
计算协方差矩阵 C 的特征值 λ 和特征向量 v。特征值表示数据在对应特征向量方向上的方差大小,特征值越大,说明该方向上的数据变化越显著。特征向量则表示数据的主成分方向。通常选择特征值最大的若干个特征向量作为主成分。
协方差矩阵 C 的特征值和特征向量的计算公式为:

其中,λ 是特征值,v 是对应的特征向量。
1.2.4 选择主成分
选择前 k 个特征值最大的特征向量,构成投影矩阵 P。这 k 个特征向量对应的方向就是数据的主要变化方向,即主成分方向。
设 v1,v2,…,vk 是特征值最大的 k 个特征向量,则投影矩阵 P 为:

1.2.5 数据投影
将标准化后的数据 X′ 投影到这 k 个主成分方向上,得到降维后的数据 Y:

其中,Y 是降维后的数据集,每一行表示一个样本在低维空间中的表示。
1.3 PCA步骤
-
数据标准化:对数据进行均值为0、标准差为1的标准化处理。
-
计算协方差矩阵:根据标准化后的数据计算协方差矩阵。
-
求解特征值和特征向量:计算协方差矩阵的特征值和特征向量,并按照特征值从大到小排序。
-
选择主成分:选择前 k 个特征值最大的特征向量,构成投影矩阵。
-
数据投影:将原始数据投影到这 k 个主成分方向上,得到降维后的数据。
二、算法实现
2.1 导入数据
数据集共包含40个不同人的400张图像,此数据集包含40个类,每个类含10张图片。
对于每个类,选择前7张图片用于训练,后3张图片用于测试。将图像缩放至原来的0.5倍,最后选择100个特征向量进行降维。
import os
import cv2
import numpy as np
from typing import Union
import matplotlib.pyplot as plt
# 1. 准备
scaled = 0.5 # 图像缩放至原来的scaled倍
train_num = 7 # 7张用于训练
img_num = 10 # 总共10张图片
num_k = 100 # 选择100个特征
save_path = './ORL_eigenfaces' # 保存特征脸的路径
dataset_path = r"C:\Users\wu\Desktop\ORL_Faces" # 数据集路径
cov_matrix_path = './PCA_ORL.npy' # 保存PCA特征向量的路径
2.2 数据集处理
将每个类的前7张图片使用img2np转为一维向量,作为训练集。计算每个类前7张图片的平均特征向量,用于后面的对比。
class_names = [name for name in os.listdir(dataset_path) if os.path.isdir(os.path.join(dataset_path, name))]
print(f'数据集总共有 {len(class_names)} 个类')
# 列表示特征值,行表示图像
samples_data = []
avg_feature = [] # 每个类的平均特征
for class_name in class_names:
class_path = os.path.join(dataset_path, class_name)
if not os.path.isdir(class_path): # 如果不是目录,则跳过
continue
img_names = os.listdir(class_path)
class_feature = [] # 计算每个类前train_num的平均特征
for img_name in img_names[:train_num]: # 取前七张参与训练
img_data = img2np(os.path.join(class_path, img_name), scaled=scaled)
samples_data.append(img_data)
class_feature.append(img_data)
avg_feature.append(np.array(class_feature).mean(axis=0))
samples_data = np.array(samples_data)
avg_feature = np.array(avg_feature)
print(f'数据集特征矩阵形状: {samples_data.shape},平均特征矩阵形状:{avg_feature.shape}')
数据集共有 40 个类
数据集特征矩阵形状: (280, 2576),平均特征矩阵形状:(40, 2576)
2.3 PCA降维
PCA计算时间较长,因此先对图像进行缩放,并保存降维后的特征矩阵,方便后续调用。
# 3. PCA降维
# PCA算法保留前k个特征向量
def pca(X: np.ndarray, k: int) -> np.ndarray:
# 计算每个特征均值
mean = np.mean(X, axis=0)
# 数据标准化
norm_X = X - mean
# 计算协方差矩阵
cov_matrix = np.cov(norm_X, rowvar=False)
# 计算特征值与特征向量
eig_val, eig_vec = np.linalg.eig(cov_matrix)
# 按照特征值的绝对值大小降序排序
sorted_index = np.argsort(np.abs(eig_val))[::-1]
# 选择前k个特征值对应的特征向量
selected_vectors = eig_vec[:, sorted_index[:k]]
return selected_vectors
if os.path.exists(cov_matrix_path):
pca_vectors = np.load(cov_matrix_path)
else:
pca_vectors = pca(samples_data, num_k) # 选择100个特征向量
np.save(cov_matrix_path, pca_vectors) # 训练时间较长,可以保存方便调用
print(f'PCA降维后的特征矩阵形状: {pca_vectors.shape}')
2.4 结果可视化
将特征向量保存为特征脸并输出。
# 5. 结果展示
# 保存前k个特征向量的特征脸
def save_eigenfaces(eigenvectors: np.ndarray,
k: int,
img_size: tuple[int, int], # (h, w)
path: Union[str]) -> bool:
if not os.path.exists(path):
os.mkdir(path) # 创建保存路径文件夹
for i in range(k):
eigenfaces = np.real(eigenvectors[:, i])
norm_eigenfaces = ((eigenfaces - np.min(eigenfaces)) /
(np.max(eigenfaces) - np.min(eigenfaces)) * 255)
norm_eigenface_uint8 = norm_eigenfaces.astype(np.uint8)
shaped_eigenface = norm_eigenface_uint8.reshape(img_size)
cv2.imwrite(os.path.join(path, f'eigenfaces{i}.png'), shaped_eigenface)
return True
save_eigenfaces(pca_vectors, 100, (46, 56), save_path) # 原大小为112 x 92,缩放后为56 x 46
# 显示前16个特征脸
plt.figure(figsize=(8, 8))
for i in range(16): # 遍历前16个特征脸
plt.subplot(4, 4, i + 1) # 创建子图
eigenface_path = os.path.join(save_path, f'eigenfaces{i}.png')
eigenface_img = cv2.imread(eigenface_path, cv2.IMREAD_GRAYSCALE)
plt.imshow(eigenface_img, cmap='gray') # 显示灰度图像
plt.title(f'eigenfaces{i}') # 添加标题
plt.xticks([]) # 去掉x轴刻度
plt.yticks([]) # 去掉y轴刻度
plt.show() # 显示图像
三、完整代码
import os
import cv2
import numpy as np
from typing import Union
import matplotlib.pyplot as plt
# 1. 准备
scaled = 0.5 # 图像缩放至原来的scaled倍
train_num = 7 # 7张用于训练
img_num = 10 # 总共10张图片
num_k = 100 # 选择100个特征
save_path = './ORL_eigenfaces' # 保存特征脸的路径
dataset_path = r"C:\Users\wu\Desktop\ORL_Faces" # 数据集路径
cov_matrix_path = './PCA_ORL.npy' # 保存PCA特征向量的路径
# 图像转为一维向量
def img2np(img_path: Union[str], scaled: float = 1.) -> np.ndarray:
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # 直接读取为灰度图
h, w = img.shape[0], img.shape[1]
img = cv2.resize(img, (int(w * scaled), int(h * scaled))) # 注意宽高顺序
return img.flatten()
# 2. 数据集处理
class_names = [name for name in os.listdir(dataset_path) if os.path.isdir(os.path.join(dataset_path, name))]
print(f'数据集总共有 {len(class_names)} 个类')
# 列表示特征值,行表示图像
samples_data = []
avg_feature = [] # 每个类的平均特征
for class_name in class_names:
class_path = os.path.join(dataset_path, class_name)
if not os.path.isdir(class_path): # 如果不是目录,则跳过
continue
img_names = os.listdir(class_path)
class_feature = [] # 计算每个类前train_num的平均特征
for img_name in img_names[:train_num]: # 取前七张参与训练
img_data = img2np(os.path.join(class_path, img_name), scaled=scaled)
samples_data.append(img_data)
class_feature.append(img_data)
avg_feature.append(np.array(class_feature).mean(axis=0))
samples_data = np.array(samples_data)
avg_feature = np.array(avg_feature)
print(f'数据集特征矩阵形状: {samples_data.shape},平均特征矩阵形状:{avg_feature.shape}')
# 3. PCA降维
# PCA算法保留前k个特征向量
def pca(X: np.ndarray, k: int) -> np.ndarray:
# 计算每个特征均值
mean = np.mean(X, axis=0)
# 数据标准化
norm_X = X - mean
# 计算协方差矩阵
cov_matrix = np.cov(norm_X, rowvar=False)
# 计算特征值与特征向量
eig_val, eig_vec = np.linalg.eig(cov_matrix)
# 按照特征值的绝对值大小降序排序
sorted_index = np.argsort(np.abs(eig_val))[::-1]
# 选择前k个特征值对应的特征向量
selected_vectors = eig_vec[:, sorted_index[:k]]
return selected_vectors
if os.path.exists(cov_matrix_path):
pca_vectors = np.load(cov_matrix_path)
else:
pca_vectors = pca(samples_data, num_k) # 选择100个特征向量
np.save(cov_matrix_path, pca_vectors) # 训练时间较长,可以保存方便调用
print(f'PCA降维后的特征矩阵形状: {pca_vectors.shape}')
# 4. 结果展示
# 保存前k个特征向量的特征脸
def save_eigenfaces(eigenvectors: np.ndarray,
k: int,
img_size: tuple[int, int], # (h, w)
path: Union[str]) -> bool:
if not os.path.exists(path):
os.mkdir(path) # 创建保存路径文件夹
for i in range(k):
eigenfaces = np.real(eigenvectors[:, i])
norm_eigenfaces = ((eigenfaces - np.min(eigenfaces)) /
(np.max(eigenfaces) - np.min(eigenfaces)) * 255)
norm_eigenface_uint8 = norm_eigenfaces.astype(np.uint8)
shaped_eigenface = norm_eigenface_uint8.reshape(img_size)
cv2.imwrite(os.path.join(path, f'eigenfaces{i}.png'), shaped_eigenface)
return True
save_eigenfaces(pca_vectors, 100, (46, 56), save_path) # 原大小为112 x 92,缩放后为56 x 46
# 显示前16个特征脸
plt.figure(figsize=(8, 8))
for i in range(16): # 遍历前16个特征脸
plt.subplot(4, 4, i + 1) # 创建子图
eigenface_path = os.path.join(save_path, f'eigenfaces{i}.png')
eigenface_img = cv2.imread(eigenface_path, cv2.IMREAD_GRAYSCALE)
plt.imshow(eigenface_img, cmap='gray') # 显示灰度图像
plt.title(f'eigenfaces{i}') # 添加标题
plt.xticks([]) # 去掉x轴刻度
plt.yticks([]) # 去掉y轴刻度
plt.show() # 显示图像
四、实验总结
4.1 PCA人脸识别的优势
-
降维效果显著:PCA可以将高维的人脸图像数据降维到低维空间,大大减少了计算量和存储需求。
-
特征提取直观:PCA提取的特征脸是基于人脸图像的全局信息,能够较好地反映人脸的主要特征。
-
算法实现简单:PCA算法的实现相对简单,计算过程较为直观,易于理解和实现。
4.2 PCA人脸识别的局限
-
对光照和表情变化敏感:PCA主要基于图像的灰度信息,对于光照条件的变化和表情的差异较为敏感。
-
特征提取不够精细:PCA提取的特征是全局的,对于局部特征的表达能力较弱。
-
数据量要求较高:PCA需要足够多的训练样本才能准确地估计协方差矩阵,否则可能会导致特征提取不准确。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)