吴恩达机器学习课程(PyTorch 适配)学习笔记:3.2 降维技术详解(PCA)
特征降维概述
什么是降维?
降维是指将高维数据转换为低维表示的过程,同时尽可能保留原始数据的关键信息。在机器学习中,当特征数量过多时,会出现"维度灾难"问题,降维技术能有效解决这一问题。
为什么需要降维?
- 缓解维度灾难:高维空间中数据稀疏,模型难以学习有效模式
- 减少计算成本:降低特征数量能显著减少训练时间和存储需求
- 数据可视化:将高维数据降至2D或3D便于可视化分析
- 去除噪声和冗余:识别并保留重要特征,提高模型泛化能力
降维方法分类
- 线性降维:PCA、LDA、因子分析
- 非线性降维:t-SNE、UMAP、自编码器
- 基于特征选择:过滤法、包裹法、嵌入法
PCA 算法详解
基本思想
主成分分析(PCA)通过正交变换将可能存在相关性的变量转换为一组线性不相关的变量,这组变量称为主成分。第一个主成分具有最大方差,后续每个主成分都与前一个正交且方差递减。
数学原理
1. 数据标准化
首先对原始数据进行标准化处理:
import torch
import numpy as npdef standardize_data(X):"""标准化数据"""mean = torch.mean(X, dim=0)std = torch.std(X, dim=0)return (X - mean) / std
2. 协方差矩阵计算
对于标准化后的数据矩阵 XXX,计算协方差矩阵:
Σ=1n−1XTX \Sigma = \frac{1}{n-1} X^T X Σ=n−11XTX
3. 特征分解
对协方差矩阵进行特征分解:
Σ=QΛQT \Sigma = Q \Lambda Q^T Σ=QΛQT
其中 QQQ 是特征向量矩阵,Λ\LambdaΛ 是对角特征值矩阵。
4. 选择主成分
按特征值从大到小排序,选择前 kkk 个特征向量组成投影矩阵。
PCA 算法流程
PCA 的 PyTorch 实现
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
import numpy as npclass PCA:"""PCA 实现类"""def __init__(self, n_components):self.n_components = n_componentsself.components = Noneself.mean = Noneself.explained_variance_ratio = Nonedef fit(self, X):"""训练PCA模型"""# 计算均值self.mean = torch.mean(X, dim=0)# 中心化数据X_centered = X - self.mean# 计算协方差矩阵covariance_matrix = torch.mm(X_centered.T, X_centered) / (X.shape[0] - 1)# 特征值分解eigenvalues, eigenvectors = torch.linalg.eigh(covariance_matrix)# 按特征值降序排序sorted_indices = torch.argsort(eigenvalues, descending=True)eigenvalues = eigenvalues[sorted_indices]eigenvectors = eigenvectors[:, sorted_indices]# 选择前n_components个主成分self.components = eigenvectors[:, :self.n_components]# 计算解释方差比total_variance = torch.sum(eigenvalues)self.explained_variance_ratio = eigenvalues[:self.n_components] / total_variancereturn selfdef transform(self, X):"""将数据投影到主成分空间"""X_centered = X - self.meanreturn torch.mm(X_centered, self.components)def fit_transform(self, X):"""训练并转换数据"""self.fit(X)return self.transform(X)# 示例:在Iris数据集上应用PCA
def pca_demo():# 加载数据iris = load_iris()X = torch.tensor(iris.data, dtype=torch.float32)y = iris.target# 应用PCApca = PCA(n_components=2)X_pca = pca.fit_transform(X)print(f"原始数据形状: {X.shape}")print(f"降维后数据形状: {X_pca.shape}")print(f"解释方差比: {pca.explained_variance_ratio}")print(f"累计解释方差: {torch.sum(pca.explained_variance_ratio):.4f}")# 可视化结果plt.figure(figsize=(12, 5))plt.subplot(1, 2, 1)for i, target_name in enumerate(iris.target_names):plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1], label=target_name, alpha=0.7)plt.xlabel('Principal Component 1')plt.ylabel('Principal Component 2')plt.title('PCA: Iris Dataset')plt.legend()plt.grid(True, alpha=0.3)plt.subplot(1, 2, 2)# 绘制解释方差比pca_full = PCA(n_components=4)pca_full.fit(X)explained_variance = pca_full.explained_variance_ratiocumulative_variance = torch.cumsum(explained_variance, dim=0)plt.bar(range(1, 5), explained_variance, alpha=0.6, label='Individual explained variance')plt.step(range(1, 5), cumulative_variance, where='mid',label='Cumulative explained variance')plt.ylabel('Explained variance ratio')plt.xlabel('Principal components')plt.title('Explained Variance by Components')plt.legend()plt.tight_layout()plt.show()# 运行示例
pca_demo()
基于PyTorch的神经网络PCA实现
class NeuralPCA(nn.Module):"""使用自编码器实现PCA的神经网络版本"""def __init__(self, input_dim, encoding_dim):super(NeuralPCA, self).__init__()self.encoder = nn.Sequential(nn.Linear(input_dim, encoding_dim, bias=False),)self.decoder = nn.Sequential(nn.Linear(encoding_dim, input_dim, bias=False),)def forward(self, x):encoded = self.encoder(x)decoded = self.decoder(encoded)return decoded, encodeddef train_neural_pca(X, encoding_dim=2, epochs=1000, lr=0.01):"""训练神经PCA"""model = NeuralPCA(X.shape[1], encoding_dim)criterion = nn.MSELoss()optimizer = torch.optim.Adam(model.parameters(), lr=lr)losses = []for epoch in range(epochs):optimizer.zero_grad()reconstructed, encoded = model(X)loss = criterion(reconstructed, X)loss.backward()optimizer.step()losses.append(loss.item())if epoch % 200 == 0:print(f'Epoch [{epoch}/{epochs}], Loss: {loss.item():.6f}')return model, encoded, losses# 使用神经PCA
def neural_pca_demo():iris = load_iris()X = torch.tensor(iris.data, dtype=torch.float32)y = iris.target# 标准化数据X_normalized = (X - torch.mean(X, dim=0)) / torch.std(X, dim=0)# 训练神经PCAmodel, encoded, losses = train_neural_pca(X_normalized)# 可视化训练过程和结果plt.figure(figsize=(15, 5))plt.subplot(1, 3, 1)plt.plot(losses)plt.title('Training Loss')plt.xlabel('Epoch')plt.ylabel('MSE Loss')plt.grid(True, alpha=0.3)plt.subplot(1, 3, 2)for i, target_name in enumerate(iris.target_names):plt.scatter(encoded.detach().numpy()[y == i, 0], encoded.detach().numpy()[y == i, 1], label=target_name, alpha=0.7)plt.xlabel('Encoded Dimension 1')plt.ylabel('Encoded Dimension 2')plt.title('Neural PCA: Iris Dataset')plt.legend()plt.grid(True, alpha=0.3)plt.subplot(1, 3, 3)# 比较传统PCA和神经PCAtraditional_pca = PCA(n_components=2)X_traditional = traditional_pca.fit_transform(X_normalized)plt.scatter(X_traditional[:, 0].numpy(), X_traditional[:, 1].numpy(), c='red', alpha=0.5, label='Traditional PCA')plt.scatter(encoded.detach().numpy()[:, 0], encoded.detach().numpy()[:, 1],c='blue', alpha=0.5, label='Neural PCA')plt.xlabel('Component 1')plt.ylabel('Component 2')plt.title('Comparison: Traditional vs Neural PCA')plt.legend()plt.grid(True, alpha=0.3)plt.tight_layout()plt.show()neural_pca_demo()
注意事项和易错点
1. 数据预处理
# 错误做法:未标准化数据
# pca.fit(raw_data) # 可能导致某些特征主导PCA# 正确做法:标准化数据
def proper_preprocessing(X):# 方法1: 标准化 (推荐)X_standardized = (X - torch.mean(X, dim=0)) / torch.std(X, dim=0)# 方法2: 归一化X_normalized = (X - torch.min(X, dim=0)[0]) / (torch.max(X, dim=0)[0] - torch.min(X, dim=0)[0])return X_standardized
2. 主成分数量选择
def select_optimal_components(X, variance_threshold=0.95):"""自动选择主成分数量"""pca_full = PCA(n_components=X.shape[1])pca_full.fit(X)cumulative_variance = torch.cumsum(pca_full.explained_variance_ratio, dim=0)# 找到达到方差阈值的第一个成分n_components = torch.argmax(cumulative_variance >= variance_threshold) + 1print(f"推荐的主成分数量: {n_components}")print(f"累计解释方差: {cumulative_variance[n_components-1]:.4f}")return n_components# 使用示例
iris = load_iris()
X = torch.tensor(iris.data, dtype=torch.float32)
X_standardized = (X - torch.mean(X, dim=0)) / torch.std(X, dim=0)optimal_k = select_optimal_components(X_standardized)
3. PCA的局限性
- 线性假设:PCA只能捕捉线性关系
- 方差不代表重要性:高方差特征不一定是最重要的
- 类别信息丢失:无监督方法,可能丢失与类别相关的信息
高级应用:增量PCA
class IncrementalPCA:"""增量PCA,适用于大数据集"""def __init__(self, n_components, batch_size=50):self.n_components = n_componentsself.batch_size = batch_sizeself.components = Noneself.mean = Noneself.var = Noneself.n_samples_seen = 0def partial_fit(self, X):"""增量学习"""if self.mean is None:# 初始化self.mean = torch.zeros(X.shape[1])self.var = torch.zeros(X.shape[1])self.components = torch.randn(X.shape[1], self.n_components)# 更新统计量batch_mean = torch.mean(X, dim=0)batch_var = torch.var(X, dim=0)# 更新均值和方差n_total = self.n_samples_seen + X.shape[0]self.mean = (self.n_samples_seen * self.mean + X.shape[0] * batch_mean) / n_totalself.var = (self.n_samples_seen * self.var + X.shape[0] * batch_var) / n_totalself.n_samples_seen += X.shape[0]return selfdef transform(self, X):"""转换数据"""X_centered = X - self.meanreturn torch.mm(X_centered, self.components)# 使用增量PCA处理大数据
def incremental_pca_demo():# 生成示例大数据torch.manual_seed(42)n_samples, n_features = 1000, 20large_data = torch.randn(n_samples, n_features)ipca = IncrementalPCA(n_components=5, batch_size=100)# 分批处理数据for i in range(0, n_samples, 100):batch = large_data[i:i+100]ipca.partial_fit(batch)transformed = ipca.transform(large_data)print(f"增量PCA结果形状: {transformed.shape}")incremental_pca_demo()
总结
PCA是降维技术中最基础且重要的方法,应该掌握:
- PCA的数学原理和算法流程
- 使用PyTorch实现PCA的多种方法
- 主成分数量选择的策略
- PCA的注意事项和局限性
- 高级PCA变体的实现
在实际应用中,要根据数据特性和任务需求选择合适的降维方法,并注意数据预处理和参数调优。PCA作为线性降维的基准方法,为理解更复杂的降维技术奠定了重要基础。