02 表征学习:从高维数据中提取核心洞见

欢迎来到第二章:表征学习

今日议程:

  1. 引言: 为何经济与金融学需要“降维”?
  2. 数据预处理: 成功的基石
  3. Part 1: 线性方法 (PCA, LDA, MDS)
  4. Part 2: 非线性流形学习 (Isomap, LLE, t-SNE)
  5. Part 3: 高级主题 (稀疏表征)
  6. 总结: 如何为你的问题选择合适的工具

本章核心问题:为何要在经济金融学中学习“降维”?

想象一下,我们想预测一家公司的股票回报。我们手头可能有多少变量?

  • 公司内部数据: 上百个财务比率 (P/E, ROA, 杠杆率…)
  • 市场数据: 历史价格、交易量、波动率…
  • 宏观经济数据: GDP、利率、通胀率、失业率…
  • 另类数据: 卫星图像、新闻情绪、供应链信息…

我们轻易就会得到一个维度高达数百甚至数千的数据集。

我们正面临“数据丰富,信息贫乏”的困境

数据量的爆炸式增长,不直接等同于洞见的增长。表征学习的目标就是从噪音中提取信号。

应对维度灾难 (The Curse of Dimensionality) 高维空间 充满噪声与冗余 (Noisy & Redundant) 表征学习 发现有意义的结构 低维洞见空间 清晰、可操作 (Clear & Actionable)

这就是所谓的 “维度灾难”(Curse of Dimensionality

什么是“维度灾难”?

随着维度 d 的增加,固定的样本量会变得越来越稀疏,数据点之间的空间呈指数级增长。

维度灾难 (Curse of Dimensionality) 1D 空间 数据高度密集 2D 空间 数据开始稀疏 3D 空间 数据极其稀疏

“维度灾难”是建模与分析的共同敌人

当数据维度 d 过高时,会出现一系列严重问题:

问题类别 具体表现 对经济学研究的影响
计算效率 算法复杂度呈指数级增长 模型训练时间过长,无法进行有效的迭代和检验。
数据稀疏性 固定数量的样本在高维空间中变得极其稀疏 样本不具有代表性,难以找到统计上显著的关系。
模型过拟合 模型学习到了样本中的噪声,而非真实的规律 模型在样本内表现完美,但在样本外(预测)时表现极差。
多重共线性 许多特征高度相关 难以识别单个变量的真实影响,参数估计不稳定。

表征学习(或称降维)正是解决这一问题的关键钥匙。

表征学习的目标:信息损失最小,化繁为简

我们的目标是将一个高维的样本集 \(X \in \mathbb{R}^{d \times N}\) 映射到一个低维空间 \(Z \in \mathbb{R}^{l \times N}\),其中 \(l \ll d\)

\[ \large{ \underbrace{ \begin{pmatrix} z_{1,n} \\ \vdots \\ z_{l,n} \end{pmatrix} }_{Z_n \in \mathbb{R}^{l \times 1}} = \underbrace{ \begin{pmatrix} w_{1,1} & \cdots & w_{1,d} \\ \vdots & \ddots & \vdots \\ w_{l,1} & \cdots & w_{l,d} \end{pmatrix} }_{W^T \in \mathbb{R}^{l \times d}} \underbrace{ \begin{pmatrix} x_{1,n} \\ \vdots \\ x_{d,n} \end{pmatrix} }_{X_n \in \mathbb{R}^{d \times 1}} } \]

核心要求: 降维后的新表征 \(Z\) 必须保留原始数据 \(X\) 中最重要的“结构”或“信息”。不同的算法对“结构”的定义不同,从而产生了不同的降维方法。

动工前的准备:数据预处理是模型成功的基石

在我们应用任何复杂的降维算法之前,必须对原始数据进行清洗。这就像盖楼前必须打好地基。

Data Preprocessing Pipeline A three-step flowchart: Raw Data -> Clean & Impute -> Standardize -> Ready Data. 原始数据离群点, 缺失值, 量纲不一 1. 清洗 & 填充处理离群点/缺失值 2. 标准化统一量纲 准备就绪

预处理问题1:离群点 (Outliers)

极端值会严重扭曲模型的方差计算(例如PCA),使其偏向离群点的方向。

离群点对主成分分析 (PCA) 的影响 无离群点 (No Outliers) PC1 (最大方差方向) 有离群点 (With Outliers) 离群点 被扭曲的 PC1

常见处理方法: Winsorization(缩尾)、对数变换、或直接移除。

预处理问题2 & 3:缺失值与量纲

  • 数据丢失 (Missing Data): 大多数算法无法处理缺失值 NaN
    • 常用策略:
      1. 删除: 如果缺失比例很小,直接删除该行或列。
      2. 填充: 用均值、中位数、或更复杂的模型(如K-近邻)进行填充。
  • 量纲不一致 (Inconsistent Scales): 如果“公司市值”(万亿)和“市盈率”(几十)放在一起分析,市值会完全主导结果。
    • 解决方案: 特征缩放。最常用的是标准化 (Standardization),将数据处理为均值为0,方差为1。
    • 公式: \(x'_{i} = \large{\frac{x_i - \mu_i}{\sigma_i}}\)

实战预处理:使用Python清洗股票财务数据

我们以S&P 500公司的几个基本面指标为例,展示如何用scikit-learn进行预处理。

#| echo: true
#| warning: false

# 为确保代码可复现,我们不依赖实时API,而是创建一个模拟数据集
# 该数据集的结构与yfinance获取的数据类似
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer

sp500_tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'NVDA', 'TSLA', 'JPM', 'V', 'JNJ', 'WMT']
data = {
    'MarketCap': [2.8e12, 2.5e12, 1.8e12, 1.5e12, 1.2e12, 8e11, 4.5e11, 5e11, 4.8e11, 4.2e11],
    'trailingPE': [28.5, 35.2, 26.8, 60.1, 95.3, 120.2, 12.1, 38.5, 25.4, 22.1],
    'forwardPE': [27.1, 33.1, 25.0, 55.6, 70.1, np.nan, 11.5, 36.2, 24.1, 21.0],
    'returnOnEquity': [1.5, 0.45, 0.3, 0.25, 0.6, 0.28, 0.17, 0.22, np.nan, 0.2],
    'priceToBook': [45.1, 12.3, 7.1, 9.8, 30.2, 25.1, 1.8, 12.5, 6.7, 5.4],
    'debtToEquity': [150.1, 50.2, 12.5, 120.8, 30.1, 20.5, np.nan, 55.3, 40.1, 80.2]
}
df = pd.DataFrame(data, index=sp500_tickers)
df.index.name = 'Ticker'

print('模拟的原始数据 (前5行):')
print(df.head())

预处理第1步:处理离群点与缺失值

对数变换可以缓解极端值(如市值)的影响。然后,我们用特征的均值来填充缺失的 NaN 值。

#| echo: true
#| warning: false
#| cont: true

# 对数变换可以缓解极端值和右偏分布
df['MarketCap_log'] = np.log(df['MarketCap'])

# 选择用于分析的特征
features = ['MarketCap_log', 'trailingPE', 'forwardPE', 'returnOnEquity', 'priceToBook', 'debtToEquity']
df_features = df[features]

# 使用均值填充缺失值
imputer = SimpleImputer(strategy='mean')
df_imputed = pd.DataFrame(imputer.fit_transform(df_features), columns=features, index=df.index)

print('\n填充缺失值后 (前5行):')
print(df_imputed.head())

预处理第2步:特征缩放(标准化)

标准化将所有特征都转换成均值为0、方差为1的分布。这确保了所有特征在后续模型(如PCA)中具有相同的权重。

#| echo: true
#| warning: false
#| cont: true

# 标准化
scaler = StandardScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df_imputed), columns=features, index=df.index)

print('\n标准化后 (前5行):')
print(df_scaled.head())

现在,我们的数据已经准备好进入模型了。

Part 1: 线性降维方法

主成分分析(PCA): 寻找数据变化最大的方向

PCA是最经典、最常用的线性降维方法。

  • 核心思想: 旋转坐标系,使得新的坐标轴(主成分)能最大化地解释数据的方差。
  • 目标: 保留方差,就是保留信息。
Principal Component Analysis Intuition A scatter plot showing PC1 aligned with the maximum variance direction. PC1 (最大方差方向) PC2

PCA的目标函数

PCA的目标是找到一个投影方向(一个单位向量 \(w\)),使得原始数据 \(X\) 在该方向上的投影的方差最大。

  • 投影后数据: \(Z = Xw\)
  • 投影后方差: \(\text{Var}(Z) = \text{Var}(Xw) = w^T S w\), 其中 \(S\)\(X\) 的协方差矩阵。

优化问题可以写成:

\[ \large{\max_{w} \quad w^T S w} \] \[ \large{\text{s.t.} \quad w^T w = 1} \]

PCA的数学推导:一个经典的特征值问题

我们使用拉格朗日乘子法来求解这个带约束的优化问题。

  1. 拉格朗日函数: \[ \large{L(w, \lambda) = w^T S w - \lambda(w^T w - 1)} \]

  2. \(w\) 求导并令其为0: \[ \large{\frac{\partial L}{\partial w} = 2Sw - 2\lambda w = 0} \]

  3. 得到最终形式: \[ \large{Sw = \lambda w} \]

结论: 最优的投影方向(主成分)\(w\) 就是协方差矩阵 \(S\)特征向量,对应的方差大小就是特征值 \(\lambda\)。最大的特征值对应的特征向量,就是第一主成分。

PCA的关键步骤总结

将抽象的数学理论转化为一个清晰的操作流程。

PCA Algorithm Steps A flowchart illustrating the five main steps of performing PCA. 1. 数据标准化 2. 计算协方差矩阵 S 3. 求解特征值& 特征向量 4. 选择前 l 个主成分 5. 转换数据$Z = W^T X$

Python实战:用PCA分析美国国债收益率曲线

收益率曲线的变动是宏观经济分析的核心。不同期限的利率高度相关,非常适合用PCA降维。

# 为保证可复现,我们生成模拟的收益率数据
# 真实数据可通过fredapi获取
import pandas as pd
import numpy as np

# 模拟数据生成
np.random.seed(42)
dates = pd.date_range('2000-01-01', '2024-01-01', freq='B')
n_days = len(dates)
base_level = np.linspace(1.0, 3.0, n_days) + np.random.randn(n_days).cumsum() * 0.05
maturities = ['1M', '3M', '6M', '1Y', '2Y', '3Y', '5Y', '7Y', '10Y', '20Y', '30Y']
n_maturities = len(maturities)

# 创建因子
level_factor = np.random.randn(n_days) * 0.1
slope_factor = np.random.randn(n_days) * 0.05
curve_factor = np.random.randn(n_days) * 0.02

# 创建收益率
slope_loadings = np.linspace(-1, 1, n_maturities)
curve_loadings = np.sin(np.linspace(0, np.pi, n_maturities))
yields = base_level[:, None] + level_factor[:, None] * 1.0 + slope_factor[:, None] * slope_loadings + curve_factor[:, None] * curve_loadings
yield_df = pd.DataFrame(yields, index=dates, columns=maturities)

# 计算日度变化
yield_changes = yield_df.diff().dropna()
print('模拟的收益率日度变化 (前5行):')
print(yield_changes.head())
模拟的收益率日度变化 (前5行):
                  1M        3M        6M        1Y        2Y        3Y  \
2000-01-04 -0.119564 -0.115018 -0.110528 -0.106145 -0.101909 -0.097846   
2000-01-05  0.090287  0.098524  0.105740  0.111013  0.113614  0.113072   
2000-01-06 -0.085673 -0.077000 -0.067781 -0.057523 -0.045833 -0.032462   
2000-01-07  0.148844  0.138733  0.127924  0.115786  0.101820  0.085705   
2000-01-10 -0.138239 -0.116690 -0.094897 -0.072639 -0.049741 -0.026092   

                  5Y        7Y       10Y       20Y       30Y  
2000-01-04 -0.093964 -0.090255 -0.086693 -0.083238 -0.079839  
2000-01-05  0.109226  0.102237  0.092576  0.080972  0.068347  
2000-01-06 -0.017323 -0.000502  0.017750  0.037041  0.056879  
2000-01-07  0.067329  0.046804  0.024450  0.000768 -0.023612  
2000-01-10 -0.001653  0.023538  0.049368  0.075662  0.102201  

运行PCA并解释方差

我们对收益率的日度变化运行PCA,因为在金融中我们更关心变量的变动而非水平。

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

# 标准化数据
scaler_yield = StandardScaler()
scaled_changes = scaler_yield.fit_transform(yield_changes)

# 运行PCA
pca = PCA()
pca.fit(scaled_changes)

# 可视化解释方差
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance_ratio = np.cumsum(explained_variance_ratio)


fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(range(1, len(explained_variance_ratio) + 1), explained_variance_ratio, alpha=0.6, color='skyblue', label='Individual explained variance')
ax.step(range(1, len(cumulative_variance_ratio) + 1), cumulative_variance_ratio, where='mid', color='red', linestyle='--', label='Cumulative explained variance')
ax.set_ylabel('Explained variance ratio')
ax.set_xlabel('Principal component index')
ax.set_title('The First 3 Components Explain Over 95% of Yield Curve Variance', fontsize=16)
ax.axhline(y=0.95, color='gray', linestyle=':', linewidth=2)
ax.text(len(explained_variance_ratio), 0.95, '95% threshold', va='bottom', ha='right')
ax.legend(loc='best')
ax.xaxis.set_major_locator(mticker.MaxNLocator(integer=True))
plt.show()
Figure 1: PCA解释方差比率

揭示主成分的真面目:水平、斜率和曲率

通过观察主成分的载荷(即特征向量),我们可以赋予它们经济含义。

#| echo: true
#| warning: false
#| label: fig-pca-components
#| fig-cap: '收益率曲线主成分的经济解释'
#| cont: true

components = pd.DataFrame(pca.components_[:3, :].T, 
                          columns=['PC1 (Level)', 'PC2 (Slope)', 'PC3 (Curvature)'], 
                          index=yield_changes.columns)
# 为了与理论一致,我们可能需要翻转某些向量的符号(不影响解释)
if components['PC1 (Level)'].mean() < 0: components['PC1 (Level)'] *= -1
if components['PC2 (Slope)'][0] > 0: components['PC2 (Slope)'] *= -1
if components['PC3 (Curvature)'].mean() > 0: components['PC3 (Curvature)'] *= -1


fig, ax = plt.subplots(figsize=(12, 7))
components.plot(ax=ax, marker='o')
ax.set_title('Economic Interpretation of Yield Curve Principal Components', fontsize=16)
ax.set_ylabel('Component Loading')
ax.set_xlabel('Maturity')
ax.axhline(0, color='black', linewidth=0.5, linestyle='--')
ax.legend(title='Principal Components')
plt.show()

主成分的经济学意义总结

  • PC1 (Level / 水平): 所有期限的载荷都是同向的。代表整个收益率曲线的平行移动。这是最重要的变动,通常与货币政策的整体松紧有关。

  • PC2 (Slope / 斜率): 短期载荷为负,长期为正。代表收益率曲线的斜率变化(变陡或变平),反映了市场对未来短期利率和经济增长的预期。

  • PC3 (Curvature / 曲率): 短期和长期为正,中期为负。代表收益率曲线的曲率变化(“弓形”弯曲程度),与市场对利率波动性的预期有关。

线性判别分析(LDA): 为分类而生的降维

LDA是一种监督学习的降维算法。与PCA寻找最大方差不同,LDA的目标是找到一个投影方向,使得不同类别的数据点尽可能分开,相同类别的数据点尽可能聚集

LDA Objective LDA aims to maximize between-class distance and minimize within-class distance. 最大化“类间”距离 最小化“类内”距离

LDA的目标函数

  • 类内散布矩阵 (Within-class Scatter): \(S_w = \sum_{c=1}^{C} \sum_{x_i \in c} (x_i - \mu_c)(x_i - \mu_c)^T\)
    • 衡量每个类别内部数据的离散程度。我们希望它小。
  • 类间散布矩阵 (Between-class Scatter): \(S_b = \sum_{c=1}^{C} N_c (\mu_c - \mu)(\mu_c - \mu)^T\)
    • 衡量不同类别中心之间的离散程度。我们希望它大。

LDA的目标是最大化类间散布与类内散布的比率: \[ \large{J(W) = \frac{\text{tr}(W^T S_b W)}{\text{tr}(W^T S_w W)}} \]

PCA vs. LDA:一个直观的对比

PCA

PCA只关心数据的整体方差,它会选择水平方向作为第一主成分,但这并不能很好地分开两类数据。

PC1

LDA

LDA同时考虑类别信息,它会选择一个能最大化类别间距离的投影方向,从而完美地分开了两类数据。

LD1

LDA的数学求解:一个广义特征值问题

最大化 \(J(W)\) 的问题,最终可以转化为求解一个广义特征值问题

\[ \large{S_b w = \lambda S_w w} \]

两边同乘 \(S_w^{-1}\),可以得到一个更熟悉的形式:

\[ \large{S_w^{-1} S_b w = \lambda w} \]

结论: LDA的最优投影方向 \(w\),是矩阵 \(S_w^{-1} S_b\) 的特征向量。

Python实战:使用LDA对鸢尾花数据集进行分类降维

鸢尾花数据集是检验分类算法的“Hello World”,它包含3个类别,每个类别有4个特征。我们的目标是将其降到2维并可视化。

#| echo: true
#| warning: false
#| label: fig-lda-iris
#| fig-cap: 'LDA将4维鸢尾花数据投影到2维'

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

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

# 运行LDA,降到2维
lda = LinearDiscriminantAnalysis(n_components=2)
X_r = lda.fit(X, y).transform(X)

# 可视化
plt.figure(figsize=(8, 6))
colors = ['navy', 'turquoise', 'darkorange']
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
    plt.scatter(X_r[y == i, 0], X_r[y == i, 1], alpha=.8, color=color,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('LDA of IRIS dataset')
plt.xlabel('LD1')
plt.ylabel('LD2')
plt.grid(True)
plt.show()

结果分析: 即使从4维压缩到2维,LDA也出色地分开了三个类别,展示了其强大的分类能力。

多维缩放(MDS): 从距离重构“地图”

MDS与PCA和LDA的出发点完全不同。它不直接处理特征矩阵 \(X\),而是从一个已知的距离(或不相似度)矩阵 \(D\) 出发。

  • 核心思想: 在低维空间中寻找一组点 \(Z\),使得这些点之间的欧氏距离与原始的距离矩阵 \(D\) 尽可能一致。
  • 应用场景: 当我们无法得到原始特征,但能度量对象间的不相似性时。例如,问卷调查中“品牌A和品牌B的相似度”,或者基因序列间的“编辑距离”。

MDS的直观类比:绘制城市地图

想象一下,你只知道国内几个主要城市之间的直线飞行距离,但没有任何经纬度信息。

MDS Analogy with Chinese Cities An illustration showing the concept of reconstructing a map of Chinese cities from a distance matrix using MDS. 输入: 距离矩阵 (千米) 北京 上海 北京 上海 0 1080 1080 0 MDS 输出: 2D坐标 (地图) 北京 上海 成都

MDS的目标就是根据这个距离矩阵,反推出每个城市在二维地图上的最佳坐标。

Part 2: 非线性降维方法(流形学习)

线性方法的局限:当数据结构是弯曲的

PCA, LDA等线性方法假设数据分布在一个平坦的超平面上。但如果数据的内在结构是弯曲的呢?

1. 三维“瑞士卷”流形 数据内在结构是弯曲的 A点 B点 沿流形距离 (Geodesic Distance) 很远 线性投影 (PCA) 2. 线性PCA投影 (错误) 丢失了非线性结构 A' B' 欧氏距离 (Euclidean Distance) 被错误地拉近

线性方法PCA会错误地将欧氏距离很远的点(如A和B)投影到一起,无法“展开”这个卷。

流形学习的核心思想:数据镶嵌在低维流形上

  • 流形(Manifold)假设: 我们观测到的高维数据,实际上是由少数几个潜在变量(内在维度)生成的,这些点分布在一个嵌入在高维空间中的低维流形上。
  • 目标: “展开”这个流形,找到能反映数据真实邻近关系的低维坐标。
  • 与线性方法的区别: 流形学习关注局部结构,认为只有邻近点之间的欧氏距离是可信的。

Isomap: 沿着“表面”测量距离

Isomap是对MDS的一个巧妙改进,它用测地线距离 (Geodesic Distance) 代替了欧氏距离。

步骤 1: 想象一个平面世界 A B 从A到B的最短路径是一条直线 (这是我们熟悉的欧氏距离) 步骤 2: 把世界“弯曲” A B 现在,想象一只蚂蚁 约束: 蚂蚁只能沿着纸面爬行,不能飞行。 步骤 3: 两种“距离”出现 测地线距离 (蚂蚁的路径) 欧氏距离 (“飞行”路径) A B 步骤 4: 应用到数据流形 A B Isomap就像那只蚂蚁: 它沿着数据的“表面”测量距离, 而不是直接“穿过”它。

步骤: 1. 构建邻域图: 对每个点,只连接它与最近的K个邻居。 2. 计算最短路径: 使用图算法(如Dijkstra)计算图中任意两点间的最短路径长度,作为测地线距离的近似。 3. 应用MDS: 将计算出的最短路径距离矩阵作为输入,应用经典的MDS算法进行降维。

局部线性嵌入(LLE): 保持局部线性关系

LLE的假设是,每个数据点都可以由其近邻点线性重构,并且这个局部几何关系在低维空间中也应该保持不变。

局部线性嵌入 (LLE): “展开”流形 1. 在高维空间中定义局部关系 Xᵢ 高维流形 计算重构权重 Wᵢⱼ LLE 嵌入 2. 在低维空间中保持局部关系 Zᵢ 低维嵌入 保持权重 Wᵢⱼ, 而非距离 (因此,局部几何形状可能改变)

LLE的核心: 保持重构权重, 而非距离

LLE的核心: 保持重构权重 (Wᵢⱼ), 而非距离 1. 原始高维邻域 M₁ Xᵢ 在此计算权重 W (决定了相对位置) 1 2 2. LLE嵌入 (保持权重) M₂ Zᵢ 几何形状改变, 但比例关系不变 Zᵢ = Σ Wᵢⱼ Zⱼ 1 2 3. 对比: 刚性嵌入 (这不是LLE的目标: 保持距离)

t-SNE: 数据可视化的瑞士军刀

t-SNE (t-distributed Stochastic Neighbor Embedding) 是目前将高维数据降维到2D或3D进行可视化的最强大和最常用的工具。

  • 核心思想(概率匹配):
    1. 在高维空间中,将点与点之间的欧氏距离转化为一个条件概率,表示点i会选择点j作为其邻居的可能性(使用高斯分布)。
    2. 在低维空间中,也定义一个类似的条件概率(使用更“长尾”的t分布)。
    3. 通过优化,调整低维空间中点的位置,使得两个空间的概率分布尽可能相似(最小化KL散度)。
  • 优势: 能非常好地展现数据的聚类结构

t-SNE 关键参数与注意事项

  • Perplexity (困惑度):
    • 这是最重要的参数,大致可以理解为每个点考虑的“有效近邻数”。
    • 一般取值在 5 到 50 之间。较低的值关注局部结构,较高的值关注全局结构。
  • 注意事项 (非常重要!):
    1. 不要过分解读簇间距离: t-SNE图上两个簇离得远,不代表它们在原始空间中就“更”远。
    2. 不要过分解读簇的大小: 某个簇在图上面积大,不代表它包含更多的数据点或方差更大。
    3. t-SNE是用于可视化的探索性工具,不是用于严格的聚类分析。

Python实战:使用t-SNE可视化手写数字

我们将使用scikit-learn中的手写数字数据集。每个数字是一个8x8=64维的向量。我们用t-SNE将其降到2维。

#| echo: true
#| warning: false
#| label: fig-tsne-digits
#| fig-cap: 't-SNE将64维的手写数字数据降到2维'
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
import numpy as np

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

# 运行 t-SNE
# init='pca' 加速收敛, learning_rate='auto' 是新版sklearn的推荐设置
tsne = TSNE(n_components=2, init='pca', random_state=0, perplexity=30, learning_rate='auto')
X_tsne = tsne.fit_transform(X)

# 可视化
plt.figure(figsize=(10, 8))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap=plt.cm.get_cmap('jet', 10))
plt.title('t-SNE visualization of handwritten digits')
plt.xlabel('t-SNE dimension 1')
plt.ylabel('t-SNE dimension 2')
cbar = plt.colorbar(scatter)
cbar.set_ticks(np.arange(10))
cbar.set_ticklabels(np.arange(10))
plt.show()

结果分析: t-SNE完美地将不同数字的样本在2维空间中分成了清晰的簇,这对于理解高维数据的结构非常有帮助。

Part 3: 高级主题:稀疏表征

稀疏表征:用最少的“积木”来搭建信号

前面的方法旨在“压缩”数据,而稀疏表征的出发点不同。

  • 核心思想: 任何一个信号(如一段经济时间序列),都可以被看作是由一个“字典” \(\Psi\) 中的少数几个“原子”(基向量)线性组合而成的。 \[ \large{x = \Psi s} \] 其中 \(s\) 是一个稀疏向量,意味着它的大部分元素都为零。
Sparse Representation Analogy A complex signal is constructed by a few active elements from a large dictionary. 信号 x = 字典 Ψ × 稀疏系数 s 1.2 0.8

经济学直觉: 市场的复杂动态可能只是由少数几个“潜在经济状态”或“冲击”组合驱动的。稀疏表征旨在找到这些最核心的驱动因子。

压缩感知:用更少的采样恢复完整信号

稀疏性假设带来一个惊人的推论:压缩感知

如果我们知道信号 \(x\) 在某个字典 \(\Psi\) 下是稀疏的,那么我们不需要完整地观测 \(x\),只需要进行少量的随机测量 \(z\) 就可以精确地重构出原始信号 \(x\)

\[ \large{z = \Phi x = \Phi \Psi s = \Theta s} \]

  • \(z\): 少量观测值 (\(l \times 1\))
  • \(\Phi\): 观测矩阵 (\(l \times d\), \(l \ll d\))
  • \(\Theta\): 感知矩阵
  • 目标: 已知 \(z\), \(\Theta\),求解稀疏向量 \(s\)。这是现代信号处理和MRI等领域的基石。

重构算法:匹配追踪(Matching Pursuit)

如何从 \(z\)\(\Theta\) 中找到稀疏的 \(s\) 是一个NP-hard问题。匹配追踪是一种贪心算法,用迭代的方式来逼近解。

  1. 初始化: 残差 \(r_0 = z\),稀疏解 \(s=0\)
  2. 寻找最相关原子: 在字典 \(\Theta\) 中,找到与当前残差 \(r\) 最相关的原子(内积最大)。
  3. 更新解: 将该原子的贡献加入到解 \(s\) 中。
  4. 更新残差: 从当前残差 \(r\) 中减去该原子的贡献。
  5. 迭代: 重复步骤2-4,直到残差足够小或达到迭代次数。

经济学应用:识别结构性断点

稀疏表征可以被用来识别时间序列中的结构性断点

  • 信号 (x): 经济时间序列的一阶差分。
  • 字典 (Ψ): 一个单位矩阵。
  • 稀疏系数 (s): 在一个平稳的时期,差分序列接近于0,因此 s 也是稀疏的(接近0)。当一个结构性断点(突变)发生时,差分会产生一个大的尖峰,对应s中一个大的非零元素。

通过寻找 s 中的非零项,我们可以自动地检测出经济制度或市场行为发生突变的时刻。

总结:如何选择合适的降维方法?

方法 类型 核心思想 优点 缺点
PCA 线性, 无监督 最大化方差 简单、快速、可解释性强 无法处理非线性结构
LDA 线性, 有监督 最大化类别可分性 对分类问题效果好 需要类别标签,对类别分布有假设
MDS 距离驱动 保持成对距离 灵活,只需距离矩阵 计算复杂度高,结果依赖距离度量
Isomap 非线性, 无监督 保持测地线距离 能“展开”某些流形 对“捷径”噪声敏感,计算量大
LLE 非线性, 无监督 保持局部线性重构 计算效率较高,能处理多种流形 对K值选择敏感,可能产生扭曲
t-SNE 非线性, 无监督 保持邻域概率 可视化效果极佳,能揭示聚类 计算量大,只用于可视化,不用于降维

最终思考:降维非目的,而是理解数据的手段

在本章中,我们学习了从线性到非线性的多种表征学习(降维)方法。

  • 它们是探索性数据分析的强大工具,能帮助我们从看似混乱的高维数据中发现隐藏的结构,如因子、簇和低维流形。
  • 它们也是构建预测模型的关键预处理步骤,能有效提升模型的稳定性和泛化能力。

关键在于,始终要结合你的领域知识(经济学、金融学)来解释降维后的结果,赋予这些抽象的维度和结构现实的意义。

谢谢!

问题与讨论