在数据分析、机器学习和图像处理等领域,PCA(主成分分析,Principal Component Analysis) 是一种非常经典又高效的降维技术。虽然它在应用层面只需要几行代码,但它背后的数学思想却非常优雅且值得学习。
一、为什么需要降维?
在现实场景中,我们常常面对的是高维数据,比如:
图像(一个28×28像素的灰度图像就是784维)
文本向量(使用 TF-IDF 或 word embedding 后)
基因组数据(每个样本包含成千上万个基因表达量)
高维问题会带来什么?
1.计算复杂度高:维度越高,模型训练越慢。
2.过拟合风险增加:高维数据容易拟合噪声,泛化能力变差。
3.数据冗余:很多特征是高度相关的,信息重复。
PCA 的目标正是:
在信息尽量保留的前提下,用更少的维度来表示数据。
二、PCA 的直观理解
假设采集了一批人的“身高”和“体重”,发现这两个特征高度相关。
可以画出所有数据点在二维坐标系中的分布图,通常会呈现一种“斜着的椭圆形”,也就是说数据沿着某个方向变化更大。
PCA 的工作就是:
1.找出这个变化最大的方向(也就是“第一主成分”)。
2.在与第一主成分垂直的方向上找出次大的变化方向(第二主成分)。
3.根据需要保留前k个主成分,压缩数据维度。
这其实就是从原始坐标系,变换到一个新的“旋转后的坐标系”。
三、PCA 的数学原理
Step 1:中心化数据
先将每个特征减去其均值(中心化),使得每一维特征的平均值为0:

Step 2:计算协方差矩阵
协方差矩阵衡量的是各个特征之间的线性相关性:

Step 3:特征值分解
我们对协方差矩阵做特征值分解:

v:特征向量(主成分方向)
λ:特征值(表示每个方向上的方差)
按特征值从大到小排序,选前 k 个特征向量组成变换矩阵 W。
Step 4:降维转换
将原始数据投影到新的主成分空间:

其中 Z 就是降维后的数据。
四、利用PCA处理人像
代码展示:
import numpy as np
from PIL import Image
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import os
def load_pgm_image(image_path):
try:
img = Image.open(image_path)
# 确保图像是灰度模式 (L代表亮度)
if img.mode != 'L':
img = img.convert('L')
return np.array(img)
except FileNotFoundError:
print(f"Error: Image file not found at {image_path}")
return None
except Exception as e:
print(f"Error loading image {image_path}: {e}")
return None
def load_images_from_nested_folders(root_folder_path):
images = []
# 用于检查所有图像是否具有相同的尺寸
first_image_shape = None
print(f"Loading images from root folder: {root_folder_path} and its subfolders...")
# os.walk 遍历目录树,生成 (root, dirs, files) 元组
for dirpath, dirnames, filenames in os.walk(root_folder_path):
for filename in filenames:
if filename.lower().endswith(('.pgm', '.pnm')):
image_path = os.path.join(dirpath, filename)
img_data = load_pgm_image(image_path)
if img_data is not None:
# 检查图像尺寸是否一致
if first_image_shape is None:
first_image_shape = img_data.shape
elif img_data.shape != first_image_shape:
print(f"Warning: Image {image_path} has a different shape {img_data.shape} than others {first_image_shape}. Skipping.")
continue # 跳过尺寸不匹配的图像
images.append(img_data.flatten()) # 展平图像
print(f"Loaded: {image_path}, Shape: {img_data.shape}")
if not images:
print(f"No PGM images found in {root_folder_path} or its subfolders.")
return None, None
# 将所有展平的图像堆叠成一个NumPy矩阵
image_matrix = np.array(images)
print(f"Combined image matrix shape: {image_matrix.shape}")
return image_matrix, first_image_shape
def pgm_pca_processing_dataset(image_root_folder_path, n_components=0.95, output_dir="pca_dataset_output"):
# 创建输出目录
if not os.path.exists(output_dir):
os.makedirs(output_dir)
image_matrix, image_original_shape = load_images_from_nested_folders(image_root_folder_path)
if image_matrix is None or image_original_shape is None:
print("No images loaded or dimensions are inconsistent. Exiting PCA processing.")
return
# 初始化PCA模型
pca = PCA(n_components=n_components, whiten=True)
# 拟合PCA模型并转换数据
transformed_data = pca.fit_transform(image_matrix)
print(f"\nNumber of samples: {image_matrix.shape[0]}")
print(f"Original feature (pixel) count per image: {image_matrix.shape[1]}")
print(f"Number of components retained: {pca.n_components_}")
print(f"Explained variance ratio (cumulative): {np.sum(pca.explained_variance_ratio_):.4f}")
print(f"Transformed data shape: {transformed_data.shape}")
# --- 可视化特征脸 (Eigenfaces) ---
eigenfaces = pca.components_
print(f"Shape of Eigenfaces (Principal Components): {eigenfaces.shape}")
num_eigenfaces_to_show = min(10, pca.n_components_)
plt.figure(figsize=(10, 2 * (num_eigenfaces_to_show // 5 + (1 if num_eigenfaces_to_show % 5 > 0 else 0)) + 2))
plt.suptitle('Top Principal Components (Eigenfaces)')
for i in range(num_eigenfaces_to_show):
plt.subplot(num_eigenfaces_to_show // 5 + (1 if num_eigenfaces_to_show % 5 > 0 else 0), 5, i + 1)
eigenface_image = eigenfaces[i].reshape(image_original_shape)
# 归一化到0-255以便显示
eigenface_image_scaled = ((eigenface_image - eigenface_image.min()) /
(eigenface_image.max() - eigenface_image.min()) * 255).astype(np.uint8)
plt.imshow(eigenface_image_scaled, cmap='gray')
plt.title(f'PC {i+1}')
plt.axis('off')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
eigenfaces_path = os.path.join(output_dir, "eigenfaces.png")
plt.savefig(eigenfaces_path)
print(f"Eigenfaces visualization saved to: {eigenfaces_path}")
plt.show()
# --- 选择一个图像进行重建和可视化 ---
if image_matrix.shape[0] > 0:
sample_index = 0 # 选择数据集中第一张图像进行演示
original_sample_image = image_matrix[sample_index].reshape(image_original_shape)
sample_transformed_data = transformed_data[sample_index:sample_index+1]
reconstructed_sample_image_flattened = pca.inverse_transform(sample_transformed_data)
reconstructed_sample_image = reconstructed_sample_image_flattened.reshape(image_original_shape)
reconstructed_sample_image_scaled = ((reconstructed_sample_image - reconstructed_sample_image.min()) /
(reconstructed_sample_image.max() - reconstructed_sample_image.min()) * 255).astype(np.uint8)
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(original_sample_image, cmap='gray')
plt.title('Original PGM Image (Sample)')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(reconstructed_sample_image_scaled, cmap='gray')
plt.title(f'Reconstructed Image ({pca.n_components_} components)')
plt.axis('off')
plt.suptitle(f'PCA Reconstruction of a Sample Image (Explained Variance: {np.sum(pca.explained_variance_ratio_)*100:.2f}%)')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
sample_reconstruction_path = os.path.join(output_dir, f"sample_{sample_index}_reconstruction.png")
plt.savefig(sample_reconstruction_path)
print(f"Sample image reconstruction saved to: {sample_reconstruction_path}")
plt.show()
else:
print("No images available for sample reconstruction.")
if __name__ == "__main__":
image_dataset_root_path = r"C:\Users\26687\Desktop\test\deep learning\e7\ORL_Faces"
if not os.path.exists(image_dataset_root_path) or not any(os.listdir(image_dataset_root_path)):
print(f"'{image_dataset_root_path}' not found or empty. Creating simulated ORL-like dataset for demonstration...")
simulated_root = "simulated_orl_faces_dataset"
os.makedirs(simulated_root, exist_ok=True)
for i in range(1, 5): # 模拟 s1, s2, s3, s4 四个 subjects
subject_folder = os.path.join(simulated_root, f"s{i}")
os.makedirs(subject_folder, exist_ok=True)
for j in range(1, 6): # 每个 subject 5张图像 (为了快速演示)
dummy_img_data = np.random.randint(0, 256, (92, 112), dtype=np.uint8) # ORL_Faces标准尺寸
# 稍微加点特征,让图像有点区别
if i == 1:
dummy_img_data[20:40, 20:40] = 200
elif i == 2:
dummy_img_data[50:70, 50:70] = 100
elif i == 3:
dummy_img_data[30:60, 10:30] = 50
else:
dummy_img_data[40:50, 80:90] = 255
dummy_img = Image.fromarray(dummy_img_data, 'L')
dummy_img.save(os.path.join(subject_folder, f"{j}.pgm"))
print(f"Simulated dataset created at: {simulated_root}")
# 如果创建了模拟数据,就使用模拟数据的路径
image_dataset_root_path = simulated_root
else:
print(f"Using actual dataset folder: {image_dataset_root_path}")
# --- 运行PCA处理 ---
# n_components=0.95 表示保留95%的方差
pgm_pca_processing_dataset(image_dataset_root_path, n_components=0.95)
效果展示:

该图片为通过主成分分析(PCA)从一个面部图像数据集中提取出的前10个“特征脸”。每个特征脸(PC 1到PC 10)代表了数据集中面部变化的一个主要方向或模式。PC 1通常捕捉到所有面部的平均或最普遍特征,而后续的主成分则逐渐捕捉到越来越细微、正交于前面成分的变化,例如光照、表情、头部姿态或面部细节差异。这些特征脸是构建和识别人脸的基础“维度”,任何一张原始面部图像都可以通过这些特征脸的加权组合来近似表示,从而实现数据压缩和特征提取。

该图为PCA(主成分分析)对人脸图像的重建效果。左侧是原始的PGM灰度人脸图像,包含所有细节。右侧是使用PCA的190个主成分重建后的图像,该重建图像解释了原始图像95.02%的方差。尽管重建图像与原始图像高度相似,但由于PCA是一种有损压缩技术,它在保留了人脸主要特征和结构的同时,牺牲了部分精细的纹理和细节,因此重建图像显得略微模糊,这是PCA在降维过程中丢弃次要信息的结果。
声明:本博客仅为个人理解,如有错误,敬请谅解并指出
3874

被折叠的 条评论
为什么被折叠?



