跳到正文

主成分分析

PCA 找到高维数据中方差最大的方向。它是量化金融中因子提取、风险分解和数据可视化的主力工具。

第 6 篇 · 共 6 篇 约 3 分钟

目标:找到重要的方向

当你有 500 个资产时,一个 500×500500 \times 500 协方差矩阵包含 500×5012=125,250\frac{500 \times 501}{2} = 125{,}250 个独特的数字(对角线加上上三角或下三角)。其中大部分是噪声——不代表真实关系的结构。主成分分析(PCA) 找到数据中实际包含信号的几个方向,丢弃其余的。

核心思想很简单:任何数据集中,有些方向的变化大于其他方向。最大方差方向是最具信息量的方向——它以最少的维度捕获最多的结构。PCA 找到所有方差递减的正交方向,然后让你只保留前 kk 个。

“正交”意味着这些方向互不相关——它们指向数据特征空间中独立的方向。

PCA 的五个步骤

给定具有 nn 个观测值(行)和 pp 个变量(列)的数据矩阵 X\mathbf{X}:

步骤 1:中心化数据。 减去每列的均值,使每个变量的均值都为零。这确保第一个主成分不仅仅指向数据的平均位置。

Xc=X−Xˉ\mathbf{X}_c = \mathbf{X} - \bar{\mathbf{X}}

步骤 2:计算协方差矩阵。 对于中心化数据,这就是:

Σ=1n−1Xc⊤Xc\mathbf{\Sigma} = \frac{1}{n-1}\mathbf{X}_c^\top\mathbf{X}_c

步骤 3:求 Σ\mathbf{\Sigma} 的特征向量和特征值。 每个特征向量是一个主成分方向,其特征值告诉你沿该方向有多少方差。

步骤 4:按特征值排序(从大到小)。 最大特征值的特征向量指向最大方差方向。第二大特征值给出下一个正交方向,以此类推。

步骤 5:选择 kk 个成分并投影数据。 第 ii 个成分解释的方差比例为:

λi∑j=1pλj\frac{\lambda_i}{\sum_{j=1}^{p} \lambda_j}

用 Python 实现

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


np.random.seed(42)
n_assets = 20
n_days = 500

# 使用 Cholesky 分解生成相关收益率
L = np.random.randn(n_assets, n_assets) * 0.3
Sigma = L @ L.T + np.eye(n_assets) * 0.5  # 正定
returns = np.random.randn(n_days, n_assets) @ np.linalg.cholesky(Sigma).T

# 手动 PCA
returns_centered = returns - returns.mean(axis=0)
cov_matrix = np.cov(returns_centered.T)
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
eigenvalues = eigenvalues[::-1]  # 降序

# 解释方差
total_var = eigenvalues.sum()
explained = eigenvalues / total_var
cumulative = np.cumsum(explained)

# 绘制解释方差图
plt.figure(figsize=(10, 5))
plt.bar(range(1, n_assets + 1), explained * 100, alpha=0.6, label="Individual")
plt.plot(range(1, n_assets + 1), cumulative * 100, "ro-", label="Cumulative")
plt.axhline(y=95, color="g", linestyle="--", label="95% threshold")
plt.xlabel("Principal Component")
plt.ylabel("Variance Explained (%)")
plt.title("PCA: Variance Explained by Component")
plt.legend()
plt.show()

# 多少成分才能达到 95%?
k_95 = np.argmax(cumulative >= 0.95) + 1
print(f"解释 95% 方差所需的成分数: {k_95}")

提示词

用 numpy 和 matplotlib 手动实现 PCA:模拟相关收益,算协方差矩阵,做特征分解,画解释方差柱状图,找出达到 95% 方差需要的成分数。

使用 sklearn 使这更加简洁:

pca = PCA(n_components=0.95)  # 保留 95% 的方差
reduced = pca.fit_transform(returns)

print(f"原始形状: {returns.shape}")
print(f"降维后形状:  {reduced.shape}")
print(f"使用的成分数: {pca.n_components_}")

提示词

用 sklearn.decomposition.PCA(n_components=0.95) 对 20 资产 500 天的模拟收益做降维,打印原始形状、降维后形状和自动选的成分数。

金融应用:降维 500 股票相关矩阵

量化金融中的经典应用是将大规模相关矩阵缩减为其本质因子。PCA 可能会发现,仅 5 个主成分就能解释 80% 的变异——而不是追踪 500 只股票中每只与另外 499 只的相对变动。这些成分对应于可解释的市场因子,如市场贝塔、规模、价值、动量和波动率。

这并非巧合。经济和金融中的因子模型之所以发展,部分是因为同样的直觉——金融数据的大部分变异可以由少数潜在因子解释——这早于 PCA 但在数学上与之等价。

import numpy as np

# 模拟 100 资产月收益率
np.random.seed(0)
returns = np.random.randn(120, 100) * 0.02

# 相关矩阵
corr = np.corrcoef(returns.T)

# 相关矩阵上的 PCA
eigenvalues, _ = np.linalg.eigh(corr)
eigenvalues = eigenvalues[::-1]

# 解释方差
var_explained = eigenvalues / eigenvalues.sum()
cum_var = np.cumsum(var_explained)

# 多少成分解释 50%、70%、90%?
for threshold in [0.50, 0.70, 0.90]:
    k = np.argmax(cum_var >= threshold) + 1
    print(f"{threshold*100:.0f}% 方差: {k} 个成分")

提示词

用 numpy 演示 PCA 在金融中怎么用:模拟 100 资产 120 个月收益,算相关矩阵做 PCA,报告达到 50%、70%、90% 方差需要几个成分。

过拟合:成分过多或过少的风险

PCA 是一个工具,像任何工具一样,它可能被误用。保留太多成分(接近 nn)会同时保留噪声和信号。样本协方差矩阵本身是对真实协方差的有噪声估计,对它进行特征分解并不能消除这种噪声。事实上,样本协方差矩阵的最小的特征值大部分是噪声。

保留太少成分会丢弃真实结构。如果真正的数据生成过程有 20 个有意义的因子,但你只保留 3 个,你就欠拟合了——失去了影响收益的真实关系。

一个实用规则:在解释方差曲线中寻找”肘部”——添加更多成分收益递减的点。或者,保留足够解释 70–90% 方差的成分,视具体情况而定。

参考文献

Jolliffe, Principal Component Analysis, 2nd edition, Springer, Chapters 1–2. ISBN: 978-0387954424.

Hastie, Tibshirani & Friedman, The Elements of Statistical Learning, 2nd edition, Springer, Chapter 3. ISBN: 978-0387848570.