主题切换
8.2 无监督与降维
概念详解
无监督学习在量化金融中有着广泛而独特的应用。与监督学习依赖标签数据不同,无监督学习直接在数据本身中寻找结构、模式和关系,这使其特别适合金融数据中常见的标签稀缺问题(例如:没有明确的"正确答案"告诉我们哪个市场regime正在运行)。
量化金融中无监督学习的主要应用方向包括:
降维(Dimensionality Reduction) 量化因子库通常包含数百甚至上千个候选因子,但其中许多因子高度相关,且真实的驱动维度远小于观测维度。降维技术可以将高维因子矩阵压缩为少数几个"元因子"(meta-factors),减少过拟合风险,增强策略的稳健性。
主要降维技术包括:
- PCA(主成分分析):寻找数据中方差最大的方向,将数据投影到低维空间。在金融中,第一主成分通常对应"市场因子"(market factor),后续主成分解释行业和风格效应。
- ICA(独立成分分析):寻找统计上相互独立的成分,而非仅不相关。对于寻找"纯"Alpha源特别有用。
- t-SNE / UMAP:非线性降维技术,主要用于高维数据的可视化而非特征工程。
- Autoencoder:神经网络降维,可以学习数据中的非线性结构。
聚类(Clustering) 将相似的股票/市场状态/交易模式分组,典型的量化应用包括:
- 市场Regime识别:根据波动率、相关性、趋势强度等指标将市场分为不同的"状态"(牛市、熊市、震荡市、危机模式)
- 股票分组:在行业内进一步细分"同群效应",构建更精准的配对
- 交易模式检测:识别相似的价格形态或订单流模式
异常检测(Anomaly Detection) 识别数据中的异常点,用于:
- 检测市场异常(闪崩、异常波动)
- 发现数据质量问题
- 识别潜在的欺诈或操纵行为
名词解释:PCA
主成分分析(Principal Component Analysis),通过线性变换将高维数据投影到低维空间,保留方差最大的方向,用于降维和去噪。
名词解释:市场Regime
市场的宏观状态,由波动率、相关性、流动性、趋势等特征共同定义。不同的Regime下资产行为模式显著不同。
名词解释:自动编码器(Autoencoder)
一种神经网络架构,通过学习将输入数据压缩成低维表示后再重建,可用于非线性降维和特征学习。
数学原理
PCA的数学推导
给定
PCA的目标是找到一个正交变换
$$\max_{w} \; \text{Var}(Xw) = \max_{w} \; w^T \Sigma w \quad \text{s.t.} \quad \|w\| = 1$$
其中
$$\text{Explained Variance Ratio} = \frac{\sum_{i=1}^{k} \lambda_i}{\sum_{i=1}^{p} \lambda_i}$$
K-Means聚类
K-Means将数据点划分到
$$\min_{C} \sum_{k=1}^{K} \sum_{x_i \in C_k} \|x_i - \mu_k\|^2$$
其中
在金融中,K-Means通常用于将市场日分类为不同的Regime,或者将股票按多维度特征分组。
层次聚类与相关系数距离
对于股票聚类,常用的距离度量是基于相关系数的:
$$d(i, j) = \sqrt{\frac{1}{2}(1 - \rho_{ij})}$$
其中
Python实战
📌 案例1:PCA降维
此处有展示代码展开 ▼
python
from sklearn.decomposition import PCA
pca = PCA(n_components=5)
principal_factors = pca.fit_transform(factor_matrix)点击展开可浏览运行结果
--- 结果 ---
principal_factors = array([[ 3.02966342e-03, 5.98824374e-01, 2.55932976e-01,
1.52290071e+00, 3.64438078e-01],
[ 1.28683249e+00, 9.57901022e-01, -6.75342020e-01,
-8.02287271e-01, 2.20531751e-0📌 案例2:因子去冗余与PCA元因子构建
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
def pca_factor_compression(factor_df, n_components=None, variance_threshold=0.90):
"""
使用PCA压缩因子,构建元因子
factor_df: DataFrame, columns=因子名, index=股票
n_components: 保留的主成分数(None则自动选择)
variance_threshold: 方差解释阈值(自动选择时使用)
"""
# 1. 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(factor_df)
# 2. 拟合PCA
if n_components is None:
pca_full = PCA()
pca_full.fit(X_scaled)
cum_var = np.cumsum(pca_full.explained_variance_ratio_)
n_components = np.searchsorted(cum_var, variance_threshold) + 1
pca = PCA(n_components=n_components)
meta_factors = pca.fit_transform(X_scaled)
# 3. 解释各主成分
loadings = pd.DataFrame(
pca.components_.T,
index=factor_df.columns,
columns=[f'PC{i+1}' for i in range(n_components)]
)
# 分析每个主成分的主要贡献因子
print(f"因子数从 {factor_df.shape[1]} 压缩到 {n_components} 个元因子")
print(f"累计解释方差: {pca.explained_variance_ratio_.sum():.2%}\n")
for i in range(n_components):
pc_loading = loadings[f'PC{i+1}']
top_positive = pc_loading.nlargest(3)
top_negative = pc_loading.nsmallest(3)
print(f"PC{i+1} (解释方差 {pca.explained_variance_ratio_[i]:.1%}):")
print(f" 正向载荷: {dict(top_positive)}")
print(f" 负向载荷: {dict(top_negative)}")
meta_factors_df = pd.DataFrame(
meta_factors,
index=factor_df.index,
columns=[f'PC{i+1}' for i in range(n_components)]
)
return meta_factors_df, loadings, pca
# ===== 示例 =====
np.random.seed(42)
n_stocks = 200
n_factors = 30
# 模拟因子数据(许多因子高度相关)
base_factors = np.random.randn(n_stocks, 3) # 只有3个真实驱动
noise = np.random.randn(n_stocks, n_factors) * 0.3
factor_data = np.zeros((n_stocks, n_factors))
for i in range(n_factors):
w1 = np.random.uniform(-1, 1)
w2 = np.random.uniform(-1, 1)
w3 = np.random.uniform(-1, 1)
factor_data[:, i] = (w1 * base_factors[:, 0] +
w2 * base_factors[:, 1] +
w3 * base_factors[:, 2] + noise[:, i])
factor_df = pd.DataFrame(
factor_data,
index=[f'Stock_{i:04d}' for i in range(n_stocks)],
columns=[f'Factor_{i:02d}' for i in range(n_factors)]
)
meta_factors, loadings, pca_model = pca_factor_compression(factor_df)点击展开可浏览运行结果
因子数从 30 压缩到 3 个元因子
累计解释方差: 90.45%
PC1 (解释方差 42.7%):
正向载荷: {'Factor_26': np.float64(0.26886749225437206), 'Factor_27': np.float64(0.26614549248492647), 'Factor_12': np.float64(0.24393799823520157)}
负向载荷: {'Factor_03': np.float64(-0.2684565972801462), 'Factor_13': np.float64(-0.25979311388443993), 'Factor_25': np.float64(-0.24454605613499852)}
PC2 (解释方差 28.0%):
正向载荷: {'Factor_06': np.float64(0.32633876749531127), 'Factor_10': np.float64(0.2777935675147454), 'Factor_22': np.float64(0.26035084201784975)}
负向载荷: {'Factor_20': np.float64(-0.3171079516877533), 'Factor_21': np.float64(-0.2896797984326876), 'Factor_00': np.float64(-0.27028431173712025)}
PC3 (解释方差 19.8%):
正向载荷: {'Factor_29': np.float64(0.35416247351057945), 'Factor_11': np.float64(0.3356628461175257), 'Factor_08': np.float64(0.33481961826395973)}
负向载荷: {'Factor_19': np.float64(-0.1859381546358359), 'Factor_24': np.float64(-0.18011319505315454), 'Factor_15': np.float64(-0.15630031310965198)}📌 案例3:市场Regime聚类分析
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
def market_regime_clustering(returns_df, n_regimes=4, lookback=20):
"""
使用K-Means对市场状态进行聚类,识别不同Regime
returns_df: DataFrame, index=日期, columns=资产收益率
n_regimes: 聚类数
lookback: 回看窗口(用于计算滚动统计量)
"""
# 构建Regime特征
features = pd.DataFrame(index=returns_df.index)
# 1. 市场平均收益率和波动率
features['mean_ret'] = returns_df.mean(axis=1).rolling(lookback).mean()
features['volatility'] = returns_df.std(axis=1).rolling(lookback).mean()
# 2. 相关性水平(平均成对相关性)
rolling_corr = returns_df.rolling(lookback).corr()
# 简化处理:每个时间点的平均相关性
avg_corr = []
for i in range(lookback, len(returns_df)):
corr_mat = returns_df.iloc[i-lookback:i].corr()
upper_tri = corr_mat.values[np.triu_indices_from(corr_mat.values, k=1)]
avg_corr.append(np.mean(np.abs(upper_tri)))
features.loc[features.index[lookback:], 'avg_corr'] = avg_corr
# 3. 趋势强度(正收益率比例)
features['trend_strength'] = (returns_df > 0).mean(axis=1).rolling(lookback).mean()
# 4. 尾部风险(极值收益率频率)
features['tail_risk'] = (returns_df < -0.02).mean(axis=1).rolling(lookback).mean()
# 去除NaN
features = features.dropna()
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(features)
# K-Means聚类
kmeans = KMeans(n_clusters=n_regimes, random_state=42, n_init=10)
labels = kmeans.fit_predict(X_scaled)
# 分析每个Regime的特征
results = pd.DataFrame(X_scaled, index=features.index,
columns=features.columns)
results['regime'] = labels
print("市场Regime特征(均值):")
print("=" * 60)
for r in range(n_regimes):
regime_data = results[results['regime'] == r]
n_days = len(regime_data)
print(f"\nRegime {r} ({n_days} 个交易日, {n_days/len(results)*100:.1f}%):")
for col in features.columns:
original_val = regime_data[col].mean() * scaler.scale_[features.columns.get_loc(col)] + scaler.mean_[features.columns.get_loc(col)]
print(f" {col}: {original_val:.4f}")
return results, kmeans
# 示例
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=500, freq='B')
sample_ret = pd.DataFrame(
np.random.randn(500, 10) * 0.01,
index=dates,
columns=[f'Asset_{i}' for i in range(10)]
)
regime_results, model = market_regime_clustering(sample_ret, n_regimes=3)点击展开可浏览运行结果
市场Regime特征(均值): ============================================================ Regime 0 (113 个交易日, 23.5%): mean_ret: 0.0004 volatility: 0.0101 avg_corr: 0.1800 trend_strength: 0.5166 tail_risk: 0.0223 Regime 1 (204 个交易日, 42.5%): mean_ret: -0.0006 volatility: 0.0097 avg_corr: 0.1958 trend_strength: 0.4823 tail_risk: 0.0313 Regime 2 (163 个交易日, 34.0%): mean_ret: 0.0006 volatility: 0.0094 avg_corr: 0.1924 trend_strength: 0.5272 tail_risk: 0.0144
常见误区
误区1:PCA降维保留的主成分越多越好。 保留过多的主成分(如设置explained_variance_ratio=0.99)可能导致最后几个成分捕获的是噪声而非信号。金融数据中通常保留90-95%的方差解释即可。
误区2:PCA降维后的新因子一定有经济含义。 PCA是纯数学变换,提取的"元因子"不一定有直观的经济解释。需要结合载荷分析来解释各主成分的经济意义。
误区3:K-Means的聚类数越多越精细。 聚类数的选择需要在"簇内紧密度"和"模型复杂度"间平衡,通常使用肘部法则(Elbow Method)或轮廓系数(Silhouette Score)来辅助决策。
误区4:聚类分析可以完全替代行业分类。 基于统计的聚类可能会把不同行业但同期表现相似的股票分在一起,这在某些场景可能有用,但不能替代传统的行业分类体系(GICS/申万)。
误区5:无监督学习的输出可以直接用于交易。 无监督方法通常作为探索性分析工具使用,其输出往往需要结合经济逻辑和监督学习框架才能转化为可交易的信号。
实战练习
练习1: 使用PCA对包含50个因子的因子库进行降维:
- 画出方差解释比例(Explained Variance Ratio)的横轴图
- 找出解释90%方差所需的最少主成分数
- 分析每个主成分的载荷结构,尝试给每个主成分命名(如"价值主成分"、"动量主成分")
- 用元因子构建选股模型,与原始50因子模型的IC对比
练习2: 实现一个"Adaptive Regime Switching"策略:
- 使用滚动窗口的K-Means将市场分为3个Regime
- 在每个Regime下训练独立的选股模型(XGBoost)
- 实时判断当前Regime,切换到对应的模型进行预测
- 对比Regime自适应策略与单一模型策略的表现差异
练习3: 使用层次聚类分析股票的相关性结构:
- 计算所有股票的收益率相关系数矩阵
- 使用层次聚类(Ward方法或Complete Linkage)将股票分组
- 画出树状图(Dendrogram)
- 分析:哪些板块/行业的股票"出乎意料"地聚在一起?
延伸阅读
- Avellaneda, M., & Lee, J. H. (2010). Statistical Arbitrage in the US Equities Market. Quantitative Finance, 10(7), 761-782. — 使用PCA构建统计套利组合的经典研究。
- Ledoit, O., & Wolf, M. (2004). A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices. Journal of Multivariate Analysis, 88(2), 365-411. — 高维协方差矩阵的收缩估计方法,与PCA密切相关。
- Bai, J., & Ng, S. (2002). Determining the Number of Factors in Approximate Factor Models. Econometrica, 70(1), 191-221. — 因子数量确定的经济计量方法。
- MacQueen, J. (1967). Some Methods for Classification and Analysis of Multivariate Observations. — K-Means算法的原始论文。
- Van der Maaten, L., & Hinton, G. (2008). Visualizing Data using t-SNE. JMLR, 9(11). — t-SNE可视化技术的经典论文。
本章要点
- PCA是最常用的线性降维方法,可有效压缩因子维度,提取正交的元因子
- 金融因子数据的内在维度通常远小于观测维度(许多因子高度相关)
- 聚类分析可以识别市场Regime和股票群体结构,为自适应策略提供基础
- 无监督方法通常作为探索性/辅助性工具,需要结合监督学习使用
- 降维后元因子的可解释性是使用PCA的主要挑战
- Autoencoder等非线性降维方法在某些场景下优于PCA,但增加了过拟合风险