跳到主要内容

降维方法

降维是机器学习中处理高维数据的重要技术。高维数据不仅增加了计算成本,还可能导致"维度灾难",使得模型难以学到有效的模式。降维可以在保留关键信息的同时减少特征数量,提高计算效率并改善模型性能。

为什么需要降维?

维度灾难

当特征维度增加时,数据在空间中变得更加稀疏。在高维空间中,几乎所有点对之间的距离都差不多远,这使得基于距离的方法(如 KNN)效果下降。

具体影响

  • 数据稀疏:训练样本在高维空间中分布稀疏,难以学习到有效模式
  • 计算成本:维度增加带来计算量指数级增长
  • 过拟合风险:高维数据容易导致模型过拟合
  • 可视化困难:无法直接观察高维数据的分布

降维的目标

降维的核心目标是找到一个低维表示,在尽可能保留原始数据信息的同时减少维度。具体包括:

  • 数据压缩:减少存储空间和计算成本
  • 特征提取:发现数据中的潜在结构
  • 可视化:将高维数据投影到 2D 或 3D 进行可视化
  • 去噪:去除数据中的噪声成分

主成分分析(PCA)

主成分分析是最经典的线性降维方法,通过找到数据方差最大的方向来实现降维。

原理详解

PCA 的核心思想是将数据投影到一组正交的方向上,使得投影后的数据方差最大化。

数学推导

假设数据矩阵 XX 已经中心化(每列均值为 0),PCA 要找到一个投影矩阵 WW,使得 Z=XWZ = XW 的方差最大化。

  1. 计算协方差矩阵:C=1n1XTXC = \frac{1}{n-1}X^TX
  2. 对协方差矩阵进行特征值分解
  3. 选择前 k 个最大特征值对应的特征向量
  4. 将数据投影到这 k 个特征向量上

为什么最大化方差?

方差表示数据的离散程度。投影后方差越大,说明在这个方向上数据的信息量越大。选择方差最大的方向,相当于保留了最多的信息。

基本用法

from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

# 加载数据
iris = load_iris()
X = iris.data
y = iris.target

# PCA 降维到 2 维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

print(f"原始维度: {X.shape[1]}")
print(f"降维后: {X_pca.shape[1]}")
print(f"解释方差比: {pca.explained_variance_ratio_}")
print(f"累计解释方差: {sum(pca.explained_variance_ratio_):.2%}")

# 可视化
plt.figure(figsize=(10, 4))

plt.subplot(1, 2, 1)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', s=50)
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('PCA 降维结果')

# 解释方差比
plt.subplot(1, 2, 2)
plt.bar(range(len(pca.explained_variance_ratio_)), pca.explained_variance_ratio_)
plt.xlabel('主成分')
plt.ylabel('解释方差比')
plt.title('各主成分解释方差比')

plt.tight_layout()
plt.show()

重要参数

参数说明默认值
n_components保留的主成分数量,可以是整数、浮点数或 'mle'None
svd_solverSVD 求解方法:'auto''full''arpack''randomized''auto'
whiten是否白化数据False

选择主成分数量

import numpy as np

# 方法 1:指定保留的方差比例
pca = PCA(n_components=0.95) # 保留 95% 的方差
X_pca = pca.fit_transform(X)
print(f"保留 95% 方差需要 {pca.n_components_} 个主成分")

# 方法 2:绘制解释方差累计曲线
pca = PCA().fit(X)
cumsum = np.cumsum(pca.explained_variance_ratio_)

plt.figure(figsize=(8, 5))
plt.plot(range(1, len(cumsum) + 1), cumsum, 'bo-')
plt.axhline(y=0.95, color='r', linestyle='--', label='95% 方差')
plt.xlabel('主成分数量')
plt.ylabel('累计解释方差比')
plt.title('选择主成分数量')
plt.legend()
plt.grid(True)
plt.show()

白化(Whitening)

白化将主成分缩放到单位方差,去除特征之间的相关性。这对于后续使用假设特征独立的算法(如 KNN、SVM)有帮助。

pca_whiten = PCA(n_components=2, whiten=True)
X_whitened = pca_whiten.fit_transform(X)

print(f"白化后各特征方差: {X_whitened.var(axis=0)}")

增量 PCA(Incremental PCA)

对于大规模数据集,无法一次性加载到内存,可以使用增量 PCA 分批处理。

from sklearn.decomposition import IncrementalPCA

# 分批处理
batch_size = 50
ipca = IncrementalPCA(n_components=2, batch_size=batch_size)

# 模拟大数据集分批处理
for i in range(0, len(X), batch_size):
batch = X[i:i + batch_size]
ipca.partial_fit(batch)

X_ipca = ipca.transform(X)

PCA 的优缺点

优点

  • 无参数,不需要调优
  • 计算效率高
  • 数学原理清晰,结果可解释
  • 去除特征相关性

缺点

  • 只能捕获线性结构
  • 主成分可能难以解释
  • 对异常值敏感
  • 假设高方差即高信息量,不一定总是成立

核 PCA(Kernel PCA)

核 PCA 使用核技巧将 PCA 扩展到非线性场景。

原理

核 PCA 首先将数据映射到高维特征空间,然后在特征空间中执行 PCA。通过核函数,可以隐式地进行高维映射,而不需要显式计算映射后的数据。

基本用法

from sklearn.decomposition import KernelPCA
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt

# 生成月牙形数据(非线性结构)
X, y = make_moons(n_samples=200, noise=0.05, random_state=42)

# 对比线性 PCA 和核 PCA
kpca = KernelPCA(n_components=2, kernel='rbf', gamma=15)
X_kpca = kpca.fit_transform(X)

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

plt.figure(figsize=(12, 4))

plt.subplot(1, 3, 1)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', s=30)
plt.title('原始数据')

plt.subplot(1, 3, 2)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', s=30)
plt.title('线性 PCA')

plt.subplot(1, 3, 3)
plt.scatter(X_kpca[:, 0], X_kpca[:, 1], c=y, cmap='viridis', s=30)
plt.title('核 PCA (RBF)')

plt.tight_layout()
plt.show()

常用核函数

核函数参数适用场景
linear等同于线性 PCA
polydegree, gamma, coef0多项式映射
rbfgamma高斯径向基,最常用
sigmoidgamma, coef0神经网络激活函数
# 不同核函数对比
kernels = ['linear', 'poly', 'rbf', 'sigmoid']

plt.figure(figsize=(14, 3))

for i, kernel in enumerate(kernels, 1):
kpca = KernelPCA(n_components=2, kernel=kernel, gamma=10)
X_kpca = kpca.fit_transform(X)

plt.subplot(1, 4, i)
plt.scatter(X_kpca[:, 0], X_kpca[:, 1], c=y, cmap='viridis', s=30)
plt.title(f'kernel = {kernel}')

plt.tight_layout()
plt.show()

t-SNE

t-SNE(t-Distributed Stochastic Neighbor Embedding)是一种非线性降维方法,特别适合将高维数据可视化到 2D 或 3D 空间。

原理简介

t-SNE 的核心思想是保持高维空间中的局部邻域关系。它在高维空间中使用高斯分布计算点对之间的相似度,在低维空间中使用 t 分布计算相似度,然后最小化两个分布之间的 KL 散度。

为什么 t-SNE 可视化效果好?

  • t 分布比高斯分布有更重的尾部,可以缓解"拥挤问题"
  • 局部结构保持得好,相似的点在低维空间中聚集
  • 不同类别的数据通常能明显分开

基本用法

from sklearn.manifold import TSNE
from sklearn.datasets import load_digits

# 加载手写数字数据集
digits = load_digits()
X = digits.data
y = digits.target

print(f"数据维度: {X.shape}")

# t-SNE 降维
tsne = TSNE(
n_components=2,
perplexity=30,
learning_rate=200,
n_iter=1000,
random_state=42
)
X_tsne = tsne.fit_transform(X)

# 可视化
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=20)
plt.colorbar(scatter)
plt.title('t-SNE: 手写数字数据集')
plt.xlabel('t-SNE 维度 1')
plt.ylabel('t-SNE 维度 2')
plt.show()

重要参数

参数说明典型值
n_components目标维度2 或 3
perplexity困惑度,影响局部/全局结构平衡5-50,通常 30
learning_rate学习率10-1000,通常 200
n_iter迭代次数250-1000
metric距离度量'euclidean'

困惑度的选择

  • 低困惑度(5-10):关注局部结构,可能丢失全局结构
  • 高困惑度(40-50):关注全局结构,可能模糊局部细节
  • 通常从 30 开始尝试
perplexities = [5, 30, 50, 100]

plt.figure(figsize=(14, 3))

for i, perp in enumerate(perplexities, 1):
tsne = TSNE(n_components=2, perplexity=perp, random_state=42)
X_tsne = tsne.fit_transform(X)

plt.subplot(1, 4, i)
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=10)
plt.title(f'perplexity = {perp}')
plt.axis('off')

plt.tight_layout()
plt.show()

t-SNE 的注意事项

计算成本:t-SNE 计算复杂度为 O(n2)O(n^2),对于大数据集很慢。可以先使用 PCA 降维到合理维度(如 50),再使用 t-SNE。

from sklearn.pipeline import Pipeline

# PCA 预处理加速 t-SNE
pipeline = Pipeline([
('pca', PCA(n_components=50)),
('tsne', TSNE(n_components=2, random_state=42))
])
X_tsne = pipeline.fit_transform(X)

结果不可复现:t-SNE 是随机算法,不同运行可能得到不同结果。设置 random_state 保证可复现。

不适合用于特征工程:t-SNE 不能转换新数据,只适合可视化。

截断 SVD(TruncatedSVD)

截断 SVD 适用于稀疏数据(如文本的 TF-IDF 矩阵),不需要计算协方差矩阵。

原理

截断 SVD 直接对数据矩阵进行奇异值分解:

XUkΣkVkTX \approx U_k \Sigma_k V_k^T

其中 kk 是保留的奇异值数量。

基本用法

from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer

# 文本数据
documents = [
"机器学习是人工智能的一个分支",
"深度学习使用神经网络",
"自然语言处理处理文本数据",
"计算机视觉处理图像数据",
"机器学习包括监督学习和无监督学习"
]

# TF-IDF 向量化
vectorizer = TfidfVectorizer()
X_tfidf = vectorizer.fit_transform(documents)

print(f"TF-IDF 矩阵形状: {X_tfidf.shape}")

# 截断 SVD(也称为 LSA)
svd = TruncatedSVD(n_components=2, random_state=42)
X_svd = svd.fit_transform(X_tfidf)

print(f"降维后形状: {X_svd.shape}")

截断 SVD vs PCA

方面PCA截断 SVD
数据要求需要中心化不需要中心化
稀疏数据不适合(中心化破坏稀疏性)适合
计算方式协方差矩阵特征值分解直接 SVD

因子分析(Factor Analysis)

因子分析假设观测变量由潜在因子生成,是一种统计学方法。

原理

因子分析模型假设:

X=WH+μ+ϵX = W H + \mu + \epsilon

其中 WW 是因子载荷矩阵,HH 是潜在因子,ϵ\epsilon 是噪声。

基本用法

from sklearn.decomposition import FactorAnalysis

# 因子分析
fa = FactorAnalysis(n_components=2, random_state=42)
X_fa = fa.fit_transform(X)

plt.figure(figsize=(8, 6))
plt.scatter(X_fa[:, 0], X_fa[:, 1], c=y, cmap='viridis', s=30)
plt.title('因子分析')
plt.xlabel('因子 1')
plt.ylabel('因子 2')
plt.show()

因子分析 vs PCA

  • PCA:寻找最大方差方向,是数据压缩技术
  • 因子分析:假设数据由潜在因子生成,是统计模型

因子分析可以处理异方差噪声,对某些数据效果更好。

非负矩阵分解(NMF)

非负矩阵分解要求分解后的矩阵元素非负,适用于计数数据(如词频、图像像素)。

原理

NMF 将非负矩阵 XX 分解为两个非负矩阵 WWHH

XWHX \approx W H

其中 XRm×nX \in \mathbb{R}^{m \times n}WRm×kW \in \mathbb{R}^{m \times k}HRk×nH \in \mathbb{R}^{k \times n}

基本用法

from sklearn.decomposition import NMF

# NMF 要求输入非负
# 对图像数据进行 NMF
from sklearn.datasets import fetch_olivetti_faces

faces = fetch_olivetti_faces(shuffle=True, random_state=42)
X_faces = faces.data

print(f"人脸数据形状: {X_faces.shape}")

# NMF 分解
nmf = NMF(n_components=15, random_state=42, max_iter=200)
W = nmf.fit_transform(X_faces)
H = nmf.components_

print(f"W 形状: {W.shape}") # 样本在潜在空间中的表示
print(f"H 形状: {H.shape}") # 基向量(特征脸)

# 可视化基向量
fig, axes = plt.subplots(3, 5, figsize=(12, 8))
for i, ax in enumerate(axes.flat):
ax.imshow(H[i].reshape(64, 64), cmap='gray')
ax.axis('off')
ax.set_title(f'分量 {i+1}')

plt.suptitle('NMF 学习到的基向量', fontsize=14)
plt.tight_layout()
plt.show()

NMF 的特点

  • 可解释性:分解结果是加性组合,更易解释
  • 局部性:学习到的是局部特征,而非全局特征
  • 稀疏性:分解结果通常更稀疏

降维方法对比

方法类型适用场景优点缺点
PCA线性通用降维快速、可解释、可转换新数据只能捕获线性结构
核 PCA非线性非线性结构可捕获非线性模式参数调优复杂
t-SNE非线性可视化可视化效果好慢、不能转换新数据
截断 SVD线性稀疏数据适合稀疏矩阵无中心化
因子分析线性统计建模可处理异方差噪声假设较强
NMF线性非负数据可解释性好要求非负输入

如何选择降维方法

根据任务选择

  • 特征工程/数据压缩:PCA
  • 可视化:t-SNE、PCA
  • 稀疏数据:截断 SVD
  • 非负数据(图像、文本):NMF
  • 非线性结构:核 PCA、t-SNE

根据数据量选择

  • 小数据集:可以尝试各种方法
  • 中等数据集:PCA、截断 SVD
  • 大数据集:PCA(用 randomized solver)、增量 PCA

根据是否需要转换新数据选择

  • 需要转换新数据:PCA、核 PCA、截断 SVD、NMF
  • 仅用于可视化:t-SNE

完整示例

下面是一个完整的降维示例,展示不同方法的效果对比:

from sklearn.datasets import load_digits
from sklearn.decomposition import PCA, KernelPCA, NMF
from sklearn.manifold import TSNE
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

# 加载数据
digits = load_digits()
X = digits.data
y = digits.target

# 归一化(NMF 需要非负输入)
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

# 不同降维方法
methods = {
'PCA': PCA(n_components=2, random_state=42),
'核PCA (RBF)': KernelPCA(n_components=2, kernel='rbf', gamma=0.01, random_state=42),
'NMF': NMF(n_components=2, random_state=42, max_iter=200),
't-SNE': TSNE(n_components=2, perplexity=30, random_state=42)
}

# 应用降维
results = {}
for name, method in methods.items():
if name == '核PCA (RBF)':
results[name] = method.fit_transform(X)
else:
results[name] = method.fit_transform(X_scaled)

# 可视化对比
fig, axes = plt.subplots(2, 2, figsize=(14, 12))

for ax, (name, X_reduced) in zip(axes.flat, results.items()):
scatter = ax.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap='tab10', s=15, alpha=0.7)
ax.set_title(name, fontsize=14)
ax.set_xlabel('维度 1')
ax.set_ylabel('维度 2')

plt.colorbar(scatter, ax=axes, label='数字类别')
plt.suptitle('不同降维方法对比:手写数字数据集', fontsize=16)
plt.tight_layout()
plt.show()

小结

降维是处理高维数据的重要技术:

  1. PCA 是首选:大多数情况下,PCA 是最简单有效的选择
  2. t-SNE 用于可视化:当需要将高维数据可视化时,t-SNE 通常能产生更清晰的分离
  3. 考虑数据特点:稀疏数据用截断 SVD,非负数据用 NMF
  4. 注意计算成本:t-SNE 和核 PCA 计算成本较高
  5. 预处理很重要:大多数降维方法对数据尺度敏感,需要先标准化
  6. 不是万能的:降维会丢失信息,需要权衡维度和信息保留

选择合适的降维方法需要考虑数据特点、任务需求和计算资源。在实际应用中,建议先尝试 PCA,再根据需要尝试其他方法。