机器学习——主成分分析(PCA)

一、PCA核心思想

主成分分析(PCA)旨在解决高维数据处理难题,其核心思想是在保留数据关键信息的前提下降低维度 。面对高维数据,直接处理易遭遇维度灾难(如计算量大、噪声干扰多等),PCA 通过寻找 “主成分”,提取数据中最具代表性的特征组合,让数据在低维空间仍能保留原始数据的主要差异,既简化计算,又助力挖掘数据本质结构,广泛用于数据预处理、可视化、降噪等场景。

二、PCA 定义与数学描述

(一)定义

PCA 是一种无监督线性降维方法,它将原始高维特征空间的数据,通过线性变换映射到低维空间,新的维度(主成分)是原始特征的线性组合,且各主成分间正交(互不相关) ,按对数据方差的解释能力排序,第一主成分方差最大,后续主成分方差依次递减 。

(二)数学符号约定

设原始数据为矩阵 X ,维度是m×n(m 为样本数,n 为原始特征数 );标准化后数据为;协方差矩阵为 \(\Sigma\) ;特征值为 \(\lambda_1, \lambda_2, \dots, \lambda_n\) ,对应特征向量 \(\mathbf{v}_1, \mathbf{v}_2, \dots, \mathbf{v}_n\) ;选取 k 个主成分时,投影矩阵为 \(\mathbf{V}_k\) ,降维后数据为 Y 。

三、PCA 原理推导

(一)数据标准化

不同特征量纲差异会影响 PCA 效果,需先标准化。计算每个特征的均值:

和标准差:

再对数据做标准化变换:目的是让每个特征均值为 0、方差为 1,消除量纲和数值范围差异的影响。

(二)协方差矩阵计算

协方差反映特征间线性相关程度,标准化后数据的协方差矩阵:

协方差矩阵是对称矩阵,其对角线元素是各特征自身方差,非对角线元素是特征间协方差,通过它可衡量特征间相关性。

(三)特征值与特征向量分解

对协方差矩阵 \sum 做特征值分解:得到的特征值_{\lambda i}表示对应特征向量方向上数据的方差大小,特征值越大,该方向(主成分)对数据差异的解释能力越强;特征向量v_{i}是主成分的方向向量,且不同特征向量正交

(四)主成分选择

按特征值从大到小排序,选取前 k 个最大特征值对应的特征向量,组成投影矩阵。k的选择需权衡,可参考累积方差解释比(选取能解释大部分方差的最小 k ),公式为:累积方差解释比 =

 

(五)数据投影降维

将标准化后的数据X_{std} 投影到主成分构成的低维空间,得到降维后数据:此时 Y 维度为 m×n ,实现从 n 维到 k 维的转换,保留了数据主要差异信息。

四、基于人脸数据集的 Python 实现

代码实现

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

# 配置路径
DATA_DIR = "E:/QQ/ORL_Faces/ORL_Faces"  # 包含40个文件夹的根目录
OUTPUT_DIR = "output"

# 1. 数据加载与预处理
def load_images():
    images = []
    labels = []
    for person_id, person_dir in enumerate(sorted(os.listdir(DATA_DIR))):
        person_path = os.path.join(DATA_DIR, person_dir)
        if not os.path.isdir(person_path):
            continue
        
        for img_file in os.listdir(person_path):
            img_path = os.path.join(person_path, img_file)
            img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)  # 转为灰度图
            if img is None:
                continue
            img = cv2.resize(img, (100, 100))  # 统一尺寸
            images.append(img.flatten())        # 展平为向量 (10000维)
            labels.append(person_id)
    
    return np.array(images), np.array(labels), (100, 100)

# 2. 执行PCA(使用sklearn)
def apply_pca(X, n_components=50):
    # 数据标准化(中心化)
    mean_face = X.mean(axis=0)
    X_centered = X - mean_face
    
    # PCA降维
    pca = PCA(n_components=n_components)
    X_pca = pca.fit_transform(X_centered)
    
    # 重建图像
    X_reconstructed = pca.inverse_transform(X_pca) + mean_face
    return X_pca, X_reconstructed, mean_face, pca

# 2b. 手动实现PCA
def manual_pca(X, n_components=50):
    # 数据中心化
    mean_face = X.mean(axis=0)
    X_centered = X - mean_face
    
    # 计算协方差矩阵 (注意这里使用高效的计算方式)
    # 原始方式: cov_matrix = np.cov(X_centered, rowvar=False)  # 计算量极大 (10000x10000矩阵)
    # 高效方式: 使用 X_centered^T * X_centered 代替
    cov_matrix = np.dot(X_centered.T, X_centered) / (X_centered.shape[0] - 1)
    
    # 特征值分解
    eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
    
    # 按特征值降序排序
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]
    
    # 选择前k个特征向量
    eigenvectors = eigenvectors[:, :n_components]
    
    # 数据投影到特征空间
    X_pca = np.dot(X_centered, eigenvectors)
    
    # 重建图像
    X_reconstructed = np.dot(X_pca, eigenvectors.T) + mean_face
    
    # 计算解释方差比
    explained_variance_ratio = eigenvalues[:n_components] / np.sum(eigenvalues)
    
    return X_pca, X_reconstructed, mean_face, eigenvalues, eigenvectors, explained_variance_ratio

# 3. 可视化对比
def visualize_comparison(original, reconstructed_sklearn, reconstructed_manual, mean_face, img_shape, num_samples=5):
    # 随机选择样本
    indices = np.random.choice(len(original), num_samples, replace=False)
    
    plt.figure(figsize=(15, 10))
    plt.subplot(4, 1, 1)
    plt.imshow(mean_face.reshape(img_shape), cmap='gray')
    plt.title("Mean Face"), plt.axis('off')
    
    for i, idx in enumerate(indices):
        # 原始图像
        plt.subplot(4, num_samples, num_samples + i + 1)
        plt.imshow(original[idx].reshape(img_shape), cmap='gray')
        plt.title(f"Original {idx}"), plt.axis('off')
        
        # sklearn重建图像
        plt.subplot(4, num_samples, 2*num_samples + i + 1)
        plt.imshow(reconstructed_sklearn[idx].reshape(img_shape), cmap='gray')
        plt.title(f"Sklearn Recon {idx}"), plt.axis('off')
        
        # 手动PCA重建图像
        plt.subplot(4, num_samples, 3*num_samples + i + 1)
        plt.imshow(reconstructed_manual[idx].reshape(img_shape), cmap='gray')
        plt.title(f"Manual Recon {idx}"), plt.axis('off')
    
    plt.tight_layout()
    plt.savefig(os.path.join(OUTPUT_DIR, "comparison_with_manual.png"))
    plt.show()

# 4. 可视化特征脸(主成分)
def visualize_eigenfaces(pca_sklearn, manual_eigenvectors, img_shape, n_components=16):
    eigenfaces_sklearn = pca_sklearn.components_.reshape((n_components, *img_shape))
    eigenfaces_manual = manual_eigenvectors.T.reshape((n_components, *img_shape))
    
    plt.figure(figsize=(15, 10))
    plt.suptitle("特征脸对比: Sklearn vs 手动PCA")
    
    for i in range(min(n_components, 16)):
        # Sklearn特征脸
        plt.subplot(4, 8, 2*i + 1)
        plt.imshow(eigenfaces_sklearn[i], cmap='gray')
        plt.title(f"Sklearn PC {i+1}"), plt.axis('off')
        
        # 手动PCA特征脸
        plt.subplot(4, 8, 2*i + 2)
        plt.imshow(eigenfaces_manual[i], cmap='gray')
        plt.title(f"Manual PC {i+1}"), plt.axis('off')
    
    plt.tight_layout()
    plt.subplots_adjust(top=0.92)
    plt.savefig(os.path.join(OUTPUT_DIR, "eigenfaces_comparison.png"))
    plt.show()

# 5. 可视化解释方差比对比
def visualize_variance_ratio(pca_sklearn, manual_explained_variance):
    plt.figure(figsize=(12, 6))
    
    plt.subplot(1, 2, 1)
    plt.plot(range(1, len(pca_sklearn.explained_variance_ratio_) + 1), 
             np.cumsum(pca_sklearn.explained_variance_ratio_), 'o-', label='Sklearn')
    plt.xlabel('主成分数量')
    plt.ylabel('累积方差解释比')
    plt.title('Sklearn PCA累积方差解释比')
    plt.grid(True)
    plt.legend()
    
    plt.subplot(1, 2, 2)
    plt.plot(range(1, len(manual_explained_variance) + 1), 
             np.cumsum(manual_explained_variance), 's-', label='Manual')
    plt.xlabel('主成分数量')
    plt.ylabel('累积方差解释比')
    plt.title('手动PCA累积方差解释比')
    plt.grid(True)
    plt.legend()
    
    plt.tight_layout()
    plt.savefig(os.path.join(OUTPUT_DIR, "variance_ratio_comparison.png"))
    plt.show()

# 主流程
if __name__ == "__main__":
    os.makedirs(OUTPUT_DIR, exist_ok=True)
    
    # 加载数据
    X, y, img_shape = load_images()
    print(f"Loaded {len(X)} images from {len(np.unique(y))} persons")
    
    # PCA处理 (sklearn)
    n_components = 50  # 主成分数量(可调整)
    X_pca_sklearn, X_recon_sklearn, mean_face, pca = apply_pca(X, n_components)
    
    # 手动PCA处理
    X_pca_manual, X_recon_manual, mean_face_manual, eigenvalues, eigenvectors, explained_variance = manual_pca(X, n_components)
    
    # 可视化结果对比
    visualize_comparison(X, X_recon_sklearn, X_recon_manual, mean_face, img_shape)
    visualize_eigenfaces(pca, eigenvectors, img_shape)
    visualize_variance_ratio(pca, explained_variance)
    
    # 打印解释方差比
    print(f"Sklearn解释方差比: {np.sum(pca.explained_variance_ratio_):.4f}")
    print(f"手动PCA解释方差比: {np.sum(explained_variance):.4f}")
    
    # 验证两种方法的相似性(通过重构误差)
    reconstruction_error_sklearn = np.mean((X - X_recon_sklearn) ** 2)
    reconstruction_error_manual = np.mean((X - X_recon_manual) ** 2)
    print(f"Sklearn重构误差: {reconstruction_error_sklearn:.4f}")
    print(f"手动PCA重构误差: {reconstruction_error_manual:.4f}")

 重建与原图对比

散点图

五、结果与原理关联分析

(一)可视化结果对应原理

从图像对比看,50 个主成分重构的人脸图像保留主要面部特征,符合 PCA “保留关键差异” 思想 —— 主成分提取了人脸灰度变化、轮廓等关键信息。累积方差解释比曲线体现特征值作用,前若干主成分快速累积方差,验证了 “主成分按方差贡献排序” 的原理,说明少量主成分可承载数据大部分差异。

(二)实际场景应用思考

在人脸识别等任务中,PCA 降维能减少计算量、抗噪声。但主成分数量需适配场景,若追求高精度识别,可提高 k 保留更多细节;若侧重效率,选解释大部分方差的小 k 。面对复杂人脸数据(如姿态、光照多样),需结合数据预处理(如光照归一化)增强 PCA 效果,这也呼应了 PCA 对数据分布、特征相关性的依赖,体现其 “利用数据内在结构降维” 的核心逻辑。

六、总结

PCA 以 “提取主成分、降维保留关键信息” 为核心,通过标准化、协方差矩阵分解、投影等步骤,实现高维数据的高效处理。结合人脸数据集的实践,从原理推导到代码实现,清晰展现其在简化数据、保留关键特征上的作用。理解 PCA 原理与操作,能为高维数据预处理、分析提供有力工具,助力在数据科学、机器学习等领域更高效挖掘数据价值,适配多样实际场景需求 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值