一、PCA 的基本概念

主成分分析(Principal Component Analysis,PCA)是一种常用的无监督学习降维技术,其核心目标是通过线性变换将高维数据映射到低维空间,同时尽可能保留原始数据的关键信息(方差),从而简化数据结构、降低计算复杂度,并揭示数据的潜在结构。

二、PCA 的核心思想

1.方差最大化

  • 原始数据在高维空间中可能存在冗余或相关性(例如多个特征高度相关)。PCA 通过寻找一组新的正交坐标轴(主成分),使得数据在这些轴上的投影方差最大。
  • 主成分是原始特征的线性组合,第一个主成分对应数据方差最大的方向,第二个主成分与第一个正交且方差次大,依此类推。

2.降维本质

  • 选择前 k 个主成分(k < 原始维度),将数据从 n 维降至 k 维,使得降维后的数据尽可能接近原始数据的分布。

三、PCA 的主要步骤

  • 数据预处理:对原始数据进行标准化(缩放特征至相同尺度,避免量纲影响)。
  • 计算协方差矩阵:协方差矩阵用于衡量特征之间的相关性,对角线元素为各特征的方差,非对角线元素为特征间的协方差。

方差指的是数据映射到新坐标轴上的数据分布方差,方差越大,映射即降维后的数据,保存的原始数据信息最大

  • 求解特征值与特征向量:对协方差矩阵进行特征分解,得到特征值(衡量对应主成分的方差大小)和特征向量(主成分的方向)。
  • 选择主成分:按特征值从大到小排序,选择前 k 个特征值对应的特征向量,组成变换矩阵。
  • 数据映射:将原始数据投影到选定的主成分上,得到降维后的数据。

四.具体应用

1.通过PCA算法实现人脸识别,代码实现

import os
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

def load_orl_faces(data_path='./ORL_Faces'):
    """加载ORL人脸数据库"""
    X, y = [], []
    for person_id in range(1, 41):
        person_dir = os.path.join(data_path, f's{person_id}')
        for img_id in range(1, 11):
            img_path = os.path.join(person_dir, f'{img_id}.pgm')
            if os.path.exists(img_path):
                img = Image.open(img_path).convert('L')
                img = img.resize((92, 112))
                X.append(np.array(img).flatten())
                y.append(person_id)
    return np.array(X), np.array(y)

def train_test_split(X, y, test_size=0.5, random_state=42):
    """自定义训练集测试集划分"""
    np.random.seed(random_state)
    indices = np.random.permutation(len(X))
    test_size = int(len(X) * test_size)
    return X[indices[test_size:]], X[indices[:test_size]], y[indices[test_size:]], y[indices[:test_size]]

class PCA:
    """自定义PCA实现"""
    def __init__(self, n_components):
        self.n_components = n_components
        
    def fit(self, X):
        self.mean_ = np.mean(X, axis=0)
        X_centered = X - self.mean_
        
        if X.shape[0] < X.shape[1]:
            cov = np.dot(X_centered, X_centered.T) / (X.shape[0] - 1)
            eigenvalues, eigenvectors = np.linalg.eigh(cov)
            idx = np.argsort(eigenvalues)[::-1]
            eigenvalues, eigenvectors = eigenvalues[idx], eigenvectors[:, idx]
            eigenvectors = np.dot(X_centered.T, eigenvectors)
            for i in range(eigenvectors.shape[1]):
                eigenvectors[:, i] /= np.linalg.norm(eigenvectors[:, i])
        else:
            cov = np.dot(X_centered.T, X_centered) / (X.shape[0] - 1)
            eigenvalues, eigenvectors = np.linalg.eigh(cov)
            idx = np.argsort(eigenvalues)[::-1]
            eigenvalues, eigenvectors = eigenvalues[idx], eigenvectors[:, idx]
            
        self.components_ = eigenvectors[:, :self.n_components].T
        self.explained_variance_ = eigenvalues[:self.n_components]
        return self
    
    def transform(self, X):
        return np.dot(X - self.mean_, self.components_.T)

class KNNClassifier:
    """自定义KNN分类器"""
    def __init__(self, n_neighbors=1):
        self.n_neighbors = n_neighbors
        
    def fit(self, X, y):
        self.X_train = X
        self.y_train = y
        return self
    
    def predict(self, X):
        y_pred = np.zeros(len(X), dtype=self.y_train.dtype)
        for i, x in enumerate(X):
            distances = np.sqrt(np.sum((self.X_train - x) ** 2, axis=1))
            nearest_indices = np.argsort(distances)[:self.n_neighbors]
            nearest_classes = self.y_train[nearest_indices]
            y_pred[i] = np.bincount(nearest_classes).argmax()
        return y_pred

def visualize_eigenfaces(eigenfaces, n=10):
    """可视化特征脸"""
    fig, axes = plt.subplots(1, n, figsize=(20, 3))
    for i in range(n):
        axes[i].imshow(eigenfaces[i].reshape(112, 92), cmap='gray')
        axes[i].set_title(f'特征脸 {i+1}')
        axes[i].axis('off')
    plt.tight_layout()
    plt.show()

def visualize_recognition_results(X_test, y_test, y_pred, n=9):
    """可视化人脸识别结果"""
    fig, axes = plt.subplots(3, 3, figsize=(12, 10))
    for i, ax in enumerate(axes.flat):
        if i < len(X_test):
            ax.imshow(X_test[i].reshape(112, 92), cmap='gray')
            ax.set_title(f'真实: {y_test[i]}, 预测: {y_pred[i]}', fontsize=10)
            ax.axis('off')
    plt.tight_layout()
    plt.show()

def main():
    # 加载数据
    print("加载人脸数据...")
    X, y = load_orl_faces()
    
    # 划分数据集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
    
    # PCA降维
    print("执行PCA降维...")
    pca = PCA(n_components=100)  # 可调整主成分数量
    X_train_pca = pca.fit(X_train).transform(X_train)
    X_test_pca = pca.transform(X_test)
    
    # 可视化特征脸
    print("可视化特征脸...")
    visualize_eigenfaces(pca.components_)
    
    # 训练KNN分类器
    print("训练人脸识别模型...")
    knn = KNNClassifier(n_neighbors=1)
    knn.fit(X_train_pca, y_train)
    y_pred = knn.predict(X_test_pca)
    
    # 计算准确率
    accuracy = np.mean(y_pred == y_test)
    print(f"人脸识别准确率: {accuracy:.4f}")
    
    # 可视化识别结果
    print("可视化识别结果...")
    visualize_recognition_results(X_test, y_test, y_pred)

if __name__ == "__main__":
    main()    

(1)加载 ORL 人脸数据集:
(2)自定义 PCA 实现特征脸提取:
核心步骤:
数据中心化:减去平均脸
协方差矩阵计算:X^T·X
特征分解:获取特征值和特征向量
主成分选择:按特征值排序,保留前n_components个
特征脸:协方差矩阵的特征向量重塑回图像形状(112×92)
(3)KNN 分类器进行人脸识别
(4)特征脸可视化(显示前 10 个特征脸)
(5)人脸识别结果可视化(9 个样本)

2.显示结果

特征脸

人脸识别可视化展示

3.PCA算法优缺点

优点 缺点
1. 无监督学习,无需标签数据
2. 计算效率高,适用于大规模数据
3. 可解释性较强(主成分的方差含义明确)
1. 可能损失有意义的细节(尤其当数据非线性相关时)
2. 主成分的物理含义可能不明确(依赖原始特征的线性组合)
3. 对异常值敏感(标准化后仍可能受影响)
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐