2025-12-17
本讲座目标:帮助你掌握一种强大的数据分析工具——高斯图模型(Gaussian Graphical Model),使你能够从股票价格数据中”看见”公司之间的真实联系,而非被表面的”同涨同跌”所迷惑。
假设你是一名量化分析师,需要为客户构建一个分散化的股票投资组合。你观察到:
问题:这三只股票之间真的都有直接联系吗?还是其中某些相关性只是”假象”?
核心问题:使用本讲座介绍的高斯图模型,我们能够检验:招商银行与中国平安之间是否只是因为都与平安银行相关,而彼此之间没有直接联系?
📌 预告:在本讲座的最后(第 8.5 节),我们将使用真实的 A 股数据来验证这个假设,看看条件独立性分析能否揭示这些金融股之间的”真实关系”。
概率图模型(Probabilistic Graphical Models, PGM)结合了概率论与图论,为处理复杂系统中的不确定性和多变量依赖关系提供了强有力的框架。
💡 直觉理解:把每只股票想象成一个”节点”,如果两只股票之间有”边”相连,说明它们有直接的统计联系;如果没有边,说明它们只是通过其他股票”间接”相关。图模型就是帮助我们画出这张”真正的关系图”的数学工具。
PGM 的思想可以追溯到多个学科领域:
本讲座重点介绍高斯图模型(Gaussian Graphical Model, GGM),属于连续变量的无向图模型。其数学基础由 Arthur Dempster (1972) 在其经典论文”Covariance Selection”中奠定,
核心思想是:通过研究精度矩阵(Precision Matrix,即协方差矩阵的逆)的稀疏结构来推断变量之间的条件独立性关系。
在现代高维统计学中,GGM 因其简洁的理论性质和与 Lasso 正则化的自然结合而成为研究热点。
理解 GGM 的关键在于区分两种相关性概念:
| 概念 | 衡量什么 | 矩阵对应 | 例子 |
|---|---|---|---|
| 边际相关(Marginal Correlation) | 两变量之间的总体相关性 | 协方差矩阵 \(\Sigma\) 或相关矩阵 | 股票 A 和股票 B 同涨同跌 |
| 条件相关(Conditional Correlation) | 控制其他所有变量后的相关性 | 精度矩阵 \(\Theta = \Sigma^{-1}\) | 排除市场因素后,A 和 B 是否仍相关 |
考虑一个简单的例子:假设有三只股票 \(A\)、\(B\) 和 \(M\)(市场指数)。
股票 A ←——→ 市场 M ←——→ 股票 B
这正是 GGM 能够揭示的:精度矩阵中 \(\Theta_{AB} = 0\),表示 \(A\) 和 \(B\) 在条件独立意义下没有边相连。
这是统计学中最著名的虚假相关(Spurious Correlation)案例之一:
现象:研究发现,冰淇淋销量与游泳溺水事故数量呈强正相关(相关系数约 0.8)。
错误结论:吃冰淇淋会导致溺水?或者溺水会刺激冰淇淋消费?
真相:存在一个隐藏的混杂变量——夏季气温!
冰淇淋销量 ← 炎热天气 → 游泳人数增加 → 溺水事故
一旦我们控制住天气因素(条件于气温),冰淇淋销量与溺水事故之间的相关性就消失了!
数学语言:\(\text{冰淇淋} \perp \text{溺水} \mid \text{天气}\)
这正是高斯图模型所做的:在网络中,只有真正有直接联系的变量之间才会有边。
另一种理解条件独立的方式是通过社交网络:
想象你和小明都是小红的朋友,但你和小明之间从未直接交流过。
高斯图模型就是帮我们找出:谁是真朋友,谁只是朋友的朋友?
| 传统相关分析 | GGM 分析 |
|---|---|
| 发现”谁和谁一起涨跌” | 发现”谁直接影响谁” |
| 受市场因子混杂 | 剔除第三方因素干扰 |
| 网络稠密、难以解读 | 网络稀疏、结构清晰 |
| 侧重描述 | 更接近因果机制 |
目标:理解为什么精度矩阵(协方差矩阵的逆)能揭示变量之间的”直接联系”。这是整个高斯图模型的数学核心。
在深入理论之前,我们先回顾几个关键的线性代数概念:
协方差矩阵 \(\Sigma\)
对于 \(p\) 个随机变量 \(X_1, \dots, X_p\),协方差矩阵 \(\Sigma\) 是一个 \(p \times p\) 矩阵: \[ \Sigma = \begin{pmatrix} \text{Var}(X_1) & \text{Cov}(X_1,X_2) & \cdots & \text{Cov}(X_1,X_p) \\ \text{Cov}(X_2,X_1) & \text{Var}(X_2) & \cdots & \text{Cov}(X_2,X_p) \\ \vdots & \vdots & \ddots & \vdots \\ \text{Cov}(X_p,X_1) & \text{Cov}(X_p,X_2) & \cdots & \text{Var}(X_p) \end{pmatrix} \]
矩阵的逆:若 \(\Sigma\) 可逆,则存在唯一矩阵 \(\Theta = \Sigma^{-1}\) 使得 \(\Sigma \Theta = I\)。
Schur 补(Schur Complement)
将矩阵分块为: \[ M = \begin{pmatrix} A & B \\ C & D \end{pmatrix} \] 若 \(D\) 可逆,则 \(A\) 关于 \(D\) 的 Schur 补为 \(A - BD^{-1}C\)。
正定矩阵
若对所有非零向量 \(v\),都有 \(v^T \Sigma v > 0\),则 \(\Sigma\) 是正定的。协方差矩阵总是半正定的。
这是理解高斯图模型的核心数学工具。
设定:设 \(X = (X_1, X_2)^T \sim \mathcal{N}(\mu, \Sigma)\),将均值和协方差分块:
\[ \mu = \begin{pmatrix} \mu_1 \\ \mu_2 \end{pmatrix}, \quad \Sigma = \begin{pmatrix} \Sigma_{11} & \Sigma_{12} \\ \Sigma_{21} & \Sigma_{22} \end{pmatrix} \]
目标:求 \(X_1 | X_2 = x_2\) 的条件分布。
第一步:联合密度函数 \[ f(x) = \frac{1}{(2\pi)^{p/2} |\Sigma|^{1/2}} \exp\left( -\frac{1}{2} (x - \mu)^T \Sigma^{-1} (x - \mu) \right) \]
第二步:分块逆矩阵
设 \(\Theta = \Sigma^{-1}\),分块为 \(\begin{pmatrix} \Theta_{11} & \Theta_{12} \\ \Theta_{21} & \Theta_{22} \end{pmatrix}\),则:
\[ \Theta_{11} = (\Sigma_{11} - \Sigma_{12}\Sigma_{22}^{-1}\Sigma_{21})^{-1}, \quad \Theta_{12} = -\Theta_{11} \Sigma_{12} \Sigma_{22}^{-1} \]
第三步:展开二次型 \[ (x-\mu)^T \Theta (x-\mu) = (x_1 - \mu_1)^T \Theta_{11} (x_1 - \mu_1) + 2(x_1 - \mu_1)^T \Theta_{12} (x_2 - \mu_2) + \cdots \]
第四步:配方(Completing the Square)
对 \(x_1\) 配方,令 \(\tilde{\mu}_1 = \mu_1 - \Theta_{11}^{-1}\Theta_{12}(x_2 - \mu_2)\): \[ (x-\mu)^T \Theta (x-\mu) = (x_1 - \tilde{\mu}_1)^T \Theta_{11} (x_1 - \tilde{\mu}_1) + \text{(与 } x_1 \text{ 无关的项)} \]
\[ \boxed{X_1 | X_2 = x_2 \sim \mathcal{N}\left( \mu_1 + \Sigma_{12}\Sigma_{22}^{-1}(x_2 - \mu_2), \; \Sigma_{11} - \Sigma_{12}\Sigma_{22}^{-1}\Sigma_{21} \right)} \]
关键观察: \[ \boxed{\text{条件协方差} = \Sigma_{11} - \Sigma_{12}\Sigma_{22}^{-1}\Sigma_{21} = \Theta_{11}^{-1}} \]
💡 直观理解:
假设我们有一组随机变量 \(X = (X_1, \dots, X_p)^T\),服从多元正态分布 \(\mathcal{N}(\mu, \Sigma)\),其中 \(\Sigma\) 是 \(p \times p\) 的协方差矩阵。
我们定义精度矩阵(Precision Matrix) \(\Theta\) 为协方差矩阵的逆: \[ \Theta = \Sigma^{-1} \]
精度矩阵也称为浓度矩阵(Concentration Matrix)或信息矩阵(Information Matrix)。
💡 为什么要研究逆矩阵? 这看起来像是”多此一举”——我们明明有协方差矩阵,为什么还要费力求逆?
答案是:协方差矩阵 \(\Sigma\) 衡量的是边际关系(不考虑其他变量),而精度矩阵 \(\Theta\) 衡量的是条件关系(控制住其他变量后)。这两种关系可能完全不同!
高斯图模型的理论基础是以下定理:
定理(Precision Matrix 与条件独立性):设 \(X \sim \mathcal{N}(\mu, \Sigma)\),\(\Theta = \Sigma^{-1}\)。则: \[X_i \perp X_j \mid X_{\setminus \{i,j\}} \iff \Theta_{ij} = 0\]
即变量 \(X_i\) 和 \(X_j\) 在给定所有其他变量的条件下相互独立,当且仅当精度矩阵的 \((i,j)\) 元素为零。
第一步:条件分布的显式形式
设将 \(X\) 分块为 \((X_i, X_j, X_{-(i,j)})^T\)。给定 \(X_{-(i,j)}\) 时:
\[ \begin{pmatrix} X_i \\ X_j \end{pmatrix} \Bigg| X_{-(i,j)} \sim \mathcal{N}\left( \mu^*, \Sigma^* \right) \]
其中条件协方差矩阵 \(\Sigma^*\) 可以通过 Schur 补计算。
第二步:精度矩阵与条件协方差的关系
设 \(\Theta_{12}\) 为精度矩阵对应 \((i,j)\) 的 \(2 \times 2\) 子矩阵: \[ \Theta_{12} = \begin{pmatrix} \Theta_{ii} & \Theta_{ij} \\ \Theta_{ji} & \Theta_{jj} \end{pmatrix} \]
则条件协方差为: \[ \text{Cov}(X_i, X_j \mid X_{-(i,j)}) = \frac{-\Theta_{ij}}{\Theta_{ii}\Theta_{jj} - \Theta_{ij}^2} \]
结论:
另一种理解精度矩阵的直观方式是通过线性回归:
\[ X_i = \sum_{k \neq i} \beta_{ik} X_k + \varepsilon_i \]
可以证明: \[ \beta_{ij} = -\frac{\Theta_{ij}}{\Theta_{ii}}, \quad \text{Var}(\varepsilon_i) = \frac{1}{\Theta_{ii}} \]
证明 \(\beta_{ij} = -\Theta_{ij}/\Theta_{ii}\):
在多元正态分布中,条件期望就是最优线性预测: \[ \mathbb{E}[X_i \mid X_{-i}] = \mu_i + \Sigma_{i,-i} \Sigma_{-i,-i}^{-1} (X_{-i} - \mu_{-i}) \]
利用分块矩阵逆的性质:\(\Theta_{i,-i} = -\Theta_{ii} \cdot \Sigma_{i,-i} \Sigma_{-i,-i}^{-1}\)
因此:\(\beta_{ij} = -\Theta_{ij}/\Theta_{ii}\) \(\square\)
| 精度矩阵元素 | 回归解释 | 图模型含义 |
|---|---|---|
| \(\Theta_{ii}\) | 残差精度(\(1/\text{Var}(\varepsilon_i)\)) | 节点 \(i\) 的”固有不确定性” |
| \(\Theta_{ij}\) | 与回归系数成比例 | 边\((i,j)\)的”强度” |
| \(\Theta_{ij} = 0\) | \(X_j\) 不在 \(X_i\) 的回归模型中 | 节点 \(i\) 和 \(j\) 之间无边 |
💡 关键洞察:精度矩阵实际上编码了一系列”节点对节点”的回归关系!
这解释了为什么 Graphical Lasso 的核心子问题是 Lasso 回归——我们实际上在做的是:对每个节点 \(i\),用正则化回归找出哪些其他节点能”直接预测”它。
精度矩阵的元素 \(\Theta_{ij}\) 虽然携带了条件依赖的信息,但其数值大小不直观(受变量尺度影响)。为此,我们将其标准化为偏相关系数(Partial Correlation Coefficient):
\[ \rho_{ij \mid \text{rest}} = - \frac{\Theta_{ij}}{\sqrt{\Theta_{ii} \Theta_{jj}}} \]
解释:
条件协方差:\(\text{Cov}(X_i, X_j \mid X_{-(i,j)}) = -\Theta_{ij}/\det(\Theta_{12})\)
条件方差:\(\text{Var}(X_i \mid X_{-(i,j)}) = \Theta_{jj}/\det(\Theta_{12})\)
偏相关系数 = 条件协方差 / √(条件方差乘积),化简后得:
\[\rho_{ij \mid \text{rest}} = -\frac{\Theta_{ij}}{\sqrt{\Theta_{ii} \Theta_{jj}}} \quad \square\]
协方差矩阵与精度矩阵: \[ \Sigma = \begin{pmatrix} 1 & 0.8 & 0.6 \\ 0.8 & 1 & 0.7 \\ 0.6 & 0.7 & 1 \end{pmatrix}, \quad \Theta = \Sigma^{-1} \approx \begin{pmatrix} 2.80 & -2.09 & -0.22 \\ -2.09 & 3.52 & -1.21 \\ -0.22 & -1.21 & 1.98 \end{pmatrix} \]
| 偏相关 | 计算式 | 结果 |
|---|---|---|
| \(\rho_{12 \mid 3}\) | \(-\frac{-2.09}{\sqrt{2.80 \times 3.52}}\) | ≈ 0.67 |
| \(\rho_{13 \mid 2}\) | \(-\frac{-0.22}{\sqrt{2.80 \times 1.98}}\) | ≈ 0.09 |
| \(\rho_{23 \mid 1}\) | \(-\frac{-1.21}{\sqrt{3.52 \times 1.98}}\) | ≈ 0.46 |
import numpy as np
# 定义协方差矩阵
Sigma = np.array([
[1.0, 0.8, 0.6],
[0.8, 1.0, 0.7],
[0.6, 0.7, 1.0]
])
# 计算精度矩阵
Theta = np.linalg.inv(Sigma)
print("精度矩阵 Θ:")
print(np.round(Theta, 2))
# 计算偏相关系数:这里展示公式 -Θ_ij / sqrt(Θ_ii * Θ_jj)
def compute_partial_corr(Theta):
"""从精度矩阵计算偏相关系数矩阵"""
p = Theta.shape[0]
d = np.sqrt(np.diag(Theta))
partial_corr = -Theta / np.outer(d, d)
np.fill_diagonal(partial_corr, 1)
return partial_corr
partial_corr = compute_partial_corr(Theta)
print("\n偏相关系数矩阵:")
print(np.round(partial_corr, 3))
# 对比:边际相关系数
print("\n边际相关系数(协方差矩阵本身,因为是标准化的):")
print(Sigma)精度矩阵 Θ:
[[ 2.8 -2.09 -0.22]
[-2.09 3.52 -1.21]
[-0.22 -1.21 1.98]]
偏相关系数矩阵:
[[1. 0.665 0.093]
[0.665 1. 0.458]
[0.093 0.458 1. ]]
边际相关系数(协方差矩阵本身,因为是标准化的):
[[1. 0.8 0.6]
[0.8 1. 0.7]
[0.6 0.7 1. ]]
Important
🔑 关键发现
| 变量对 | 边际相关系数 | 偏相关系数 | 解释 |
|---|---|---|---|
| \((X_1, X_2)\) | 0.80 | 0.67 | 仍有较强直接联系 |
| \((X_1, X_3)\) | 0.60 | 0.09 | 大部分是通过 \(X_2\) 的间接联系! |
| \((X_2, X_3)\) | 0.70 | 0.46 | 仍有较强直接联系 |
结论:\(X_1\) 和 \(X_3\) 的高边际相关(0.60)大部分是虚假的——它们主要是因为都与 \(X_2\) 相关。控制住 \(X_2\) 后,偏相关仅为 0.09。
这正是高斯图模型的价值:看透表象,找到真正的”直接联系”。
在实际金融数据中,我们面临高维问题:
| 情形 | 样本协方差矩阵 \(S\) | 直接求逆 \(S^{-1}\) |
|---|---|---|
| \(n > p\) | 满秩,可逆 | 可行,但不稳定 |
| \(n \approx p\) | 接近奇异 | 高度不稳定 |
| \(n < p\) | 秩亏,不可逆 | 不可行 |
即使 \(n > p\),直接用 \(S^{-1}\) 作为 \(\Theta\) 的估计也会过拟合。
我们假设真实的金融网络是稀疏的(Sparse):
Tip
💡 Lasso(Least Absolute Shrinkage and Selection Operator)是一种正则化技术,通过对参数的绝对值施加惩罚,能够自动将不重要的参数”压缩”为精确的零。
直观理解:想象在二维空间中最小化目标函数:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Polygon
from matplotlib.lines import Line2D
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# === 左图:L2 约束(Ridge)===
ax1 = axes[0]
ax1.set_xlim(-2.5, 2.5)
ax1.set_ylim(-2.5, 2.5)
ax1.set_aspect('equal')
ax1.axhline(y=0, color='gray', linewidth=0.5, linestyle='--')
ax1.axvline(x=0, color='gray', linewidth=0.5, linestyle='--')
# 绘制椭圆等高线(目标函数)
theta1 = np.linspace(0, 2*np.pi, 100)
for r in [0.5, 1.0, 1.5, 2.0]:
x_ellipse = 1.5 + r * 0.8 * np.cos(theta1)
y_ellipse = 1.2 + r * 0.5 * np.sin(theta1 - 0.3)
ax1.plot(x_ellipse, y_ellipse, 'b-', alpha=0.3, linewidth=1)
# L2 约束圆
circle = Circle((0, 0), 1.2, fill=False, edgecolor='red', linewidth=2.5)
ax1.add_patch(circle)
ax1.fill_between(1.2*np.cos(theta1), 1.2*np.sin(theta1), alpha=0.15, color='red')
# 最优解点(在圆上但不在坐标轴)
opt_x_l2, opt_y_l2 = 0.85, 0.85
ax1.plot(opt_x_l2, opt_y_l2, 'ko', markersize=12, markerfacecolor='gold', markeredgewidth=2)
ax1.annotate('Optimal\n(non-sparse)', (opt_x_l2, opt_y_l2),
xytext=(opt_x_l2+0.5, opt_y_l2+0.6), fontsize=11,
arrowprops=dict(arrowstyle='->', color='black'))
# 无约束最优解
ax1.plot(1.5, 1.2, 'b*', markersize=15)
ax1.annotate('Unconstrained\nOptimum', (1.5, 1.2), xytext=(1.8, 1.8), fontsize=10,
arrowprops=dict(arrowstyle='->', color='blue'))
ax1.set_xlabel(r'$\theta_1$', fontsize=12)
ax1.set_ylabel(r'$\theta_2$', fontsize=12)
ax1.set_title(r'L2 Constraint (Ridge): $\|\theta\|_2^2 \leq t$', fontsize=13, fontweight='bold')
# === 右图:L1 约束(Lasso)===
ax2 = axes[1]
ax2.set_xlim(-2.5, 2.5)
ax2.set_ylim(-2.5, 2.5)
ax2.set_aspect('equal')
ax2.axhline(y=0, color='gray', linewidth=0.5, linestyle='--')
ax2.axvline(x=0, color='gray', linewidth=0.5, linestyle='--')
# 绘制椭圆等高线(目标函数)
for r in [0.5, 1.0, 1.5, 2.0]:
x_ellipse = 1.5 + r * 0.8 * np.cos(theta1)
y_ellipse = 1.2 + r * 0.5 * np.sin(theta1 - 0.3)
ax2.plot(x_ellipse, y_ellipse, 'b-', alpha=0.3, linewidth=1)
# L1 约束菱形
t = 1.2
diamond = Polygon([(t, 0), (0, t), (-t, 0), (0, -t)],
fill=True, facecolor='lightgreen', edgecolor='green',
linewidth=2.5, alpha=0.4)
ax2.add_patch(diamond)
# 最优解点(在菱形角上 = 稀疏!)
opt_x_l1, opt_y_l1 = 1.2, 0.0 # 在坐标轴上
ax2.plot(opt_x_l1, opt_y_l1, 'ko', markersize=12, markerfacecolor='gold', markeredgewidth=2)
ax2.annotate(r'Optimal ($\theta_2 = 0$)' + '\nSPARSE!', (opt_x_l1, opt_y_l1),
xytext=(opt_x_l1+0.3, opt_y_l1-0.8), fontsize=11, color='darkgreen',
fontweight='bold', arrowprops=dict(arrowstyle='->', color='darkgreen'))
# 无约束最优解
ax2.plot(1.5, 1.2, 'b*', markersize=15)
ax2.annotate('Unconstrained\nOptimum', (1.5, 1.2), xytext=(1.8, 1.8), fontsize=10,
arrowprops=dict(arrowstyle='->', color='blue'))
# 高亮菱形顶点
for corner in [(t, 0), (0, t), (-t, 0), (0, -t)]:
ax2.plot(corner[0], corner[1], 'g^', markersize=8)
ax2.set_xlabel(r'$\theta_1$', fontsize=12)
ax2.set_ylabel(r'$\theta_2$', fontsize=12)
ax2.set_title(r'L1 Constraint (Lasso): $\|\theta\|_1 \leq t$', fontsize=13, fontweight='bold')
# 添加图例
legend_elements = [
Line2D([0], [0], color='blue', alpha=0.3, linewidth=2, label='Objective contours'),
Line2D([0], [0], marker='*', color='w', markerfacecolor='blue', markersize=12, label='Unconstrained optimum'),
Line2D([0], [0], marker='o', color='w', markerfacecolor='gold', markersize=10,
markeredgecolor='black', markeredgewidth=1.5, label='Constrained optimum'),
]
fig.legend(handles=legend_elements, loc='lower center', ncol=3, fontsize=10, frameon=True)
plt.tight_layout()
plt.subplots_adjust(bottom=0.18)
plt.show()从几何上看:
关键观察:
对于一维 Lasso 问题 \(\min_\theta \frac{1}{2}(y - \theta)^2 + \lambda|\theta|\),最优解有解析形式:
\[ \hat{\theta} = \text{sign}(y) \cdot \max(|y| - \lambda, 0) = S_\lambda(y) \]
这称为软阈值函数(Soft Thresholding)。
import numpy as np
import matplotlib.pyplot as plt
y = np.linspace(-3, 3, 500)
lambdas = [0.5, 1.0, 1.5]
colors = ['#2ecc71', '#3498db', '#9b59b6']
fig, ax = plt.subplots(figsize=(8, 5))
# 无惩罚的恒等线
ax.plot(y, y, 'k--', alpha=0.4, linewidth=1.5, label=r'No penalty ($\lambda=0$)')
for lam, color in zip(lambdas, colors):
soft_thresh = np.sign(y) * np.maximum(np.abs(y) - lam, 0)
ax.plot(y, soft_thresh, color=color, linewidth=2.5, label=rf'$\lambda = {lam}$')
# 标注零区域
ax.axvspan(-lam, lam, alpha=0.05, color=color)
ax.axhline(y=0, color='gray', linewidth=0.5)
ax.axvline(x=0, color='gray', linewidth=0.5)
ax.set_xlabel(r'Input $y$', fontsize=12)
ax.set_ylabel(r'Output $\hat{\theta} = S_\lambda(y)$', fontsize=12)
ax.set_title('Soft Thresholding Function', fontsize=13, fontweight='bold')
ax.legend(loc='lower right', fontsize=10)
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)
ax.grid(True, alpha=0.3)
# 添加注释
ax.annotate('Zero region\n(sparse!)', xy=(0, 0), xytext=(1.5, -1.5),
fontsize=10, color='darkgreen',
arrowprops=dict(arrowstyle='->', color='darkgreen'))
plt.tight_layout()
plt.show()直观解释:当输入信号 \(|y|\) 小于阈值 \(\lambda\) 时,Lasso 直接将其”压缩”为零——这就是为什么 Lasso 能自动进行变量选择!
在 Graphical Lasso 中,这意味着:如果两只股票之间的条件相关性较弱(信号小于阈值),算法会直接认为它们没有直接联系。
这就是为什么 Graphical Lasso 能够自动发现哪些股票之间真的有直接联系,哪些只是”假相关”。
设观测数据为 \(X^{(1)}, \dots, X^{(n)} \stackrel{i.i.d.}{\sim} \mathcal{N}(0, \Sigma)\)。
对数似然函数(忽略常数)为: \[\ell(\Theta) = \frac{n}{2} \log \det \Theta - \frac{n}{2} \text{tr}(\Theta S)\]
其中 \(S = \frac{1}{n}\sum_{i=1}^n X^{(i)}(X^{(i)})^T\) 是样本协方差矩阵。
添加 \(\ell_1\) 惩罚后,优化问题为: \[\hat{\Theta} = \arg \max_{\Theta \succ 0} \left\{ \log \det \Theta - \text{tr}(S \Theta) - \lambda \|\Theta\|_{1,\text{off}} \right\}\]
其中 \(\|\Theta\|_{1,\text{off}} = \sum_{i \neq j} |\Theta_{ij}|\) 是非对角元素的 \(\ell_1\) 范数。
| \(\lambda\) 值 | 估计的 \(\hat{\Theta}\) | 图结构 |
|---|---|---|
| \(\lambda \to 0\) | 接近 \(S^{-1}\) | 稠密 |
| \(\lambda\) 适中 | 稀疏但保留关键边 | 较稀疏 |
| \(\lambda \to \infty\) | 对角矩阵 | 无边(孤立节点) |
Friedman 等人(2008)提出的 Graphical Lasso 算法采用块坐标下降策略:
输入: \(S\), \(\lambda\), 收敛阈值 \(\varepsilon\)
初始化: \(W = S + \lambda I\)
循环 对每一列 \(j = 1, \dots, p\):
直到 \(W\) 收敛
输出: \(\Theta = W^{-1}\)
| 方法 | 核心思想 | 优点 | 缺点 |
|---|---|---|---|
| Graphical Lasso | 直接优化带惩罚似然 | 高效 | 需调参 |
| 邻域选择 (MB) | 对每个节点做 Lasso | 可并行 | 不保证正定 |
| CLIME | 线性规划 | 理论优雅 | 计算慢 |
| QUIC | 二阶近似加速 | 更快收敛 | 实现复杂 |
\(\lambda\) 的选择对结果影响巨大。常用方法:交叉验证、BIC / EBIC 准则。
将数据分为训练集和验证集,选择使验证集对数似然最大的 \(\lambda\): \[ \lambda^* = \arg\max_\lambda \left\{ \log \det \hat{\Theta}_\lambda^{\text{train}} - \text{tr}(S^{\text{valid}} \hat{\Theta}_\lambda^{\text{train}}) \right\} \]
优点:直接优化预测性能
缺点:计算量大;可能选择过于稠密的模型
BIC(贝叶斯信息准则): \[ \text{BIC}(\lambda) = -2 \ell(\hat{\Theta}_\lambda) + \log(n) \cdot |\mathcal{E}_\lambda| \]
其中 \(|\mathcal{E}_\lambda|\) 是估计图中的边数。
Foygel & Drton (2010) 提出的 EBIC: \[ \text{EBIC}_\gamma(\lambda) = -2 \ell(\hat{\Theta}_\lambda) + \log(n) \cdot |\mathcal{E}_\lambda| + 4 \gamma \log(p) \cdot |\mathcal{E}_\lambda| \]
Meinshausen & Bühlmann (2010) 提出:
优点:结果稳健,不依赖单一的 \(\lambda\) 选择
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 探索性分析 | 交叉验证 | 平衡拟合与泛化 |
| 学术研究 | EBIC (\(\gamma = 0.5\)) | 理论保证,偏向稀疏 |
| 稳健推断 | 稳定性选择 | 减少假阳性边 |
首先,我们需要导入必要的库并设置 Tushare API。
为了演示,我们将选取上证50 (SSE 50) 成分股中权重最大的 30 只股票来进行分析,以观察它们之间的关联结构。
这里我们获取过去三年的日线数据,以确保样本量足够。
# 获取上证50成分股列表
df_index = pro.index_weight(index_code='000016.SH', start_date='20230101', end_date='20230131')
# 选取权重最大的 30 只股票
tickers = df_index.head(30)['con_code'].tolist()
ticker_names = df_index.head(30)['con_code'].tolist() # 暂时用代码,后续可以换成名称
print(f"Selected {len(tickers)} stocks for analysis.")
# 获取日线数据(3年)
data_dict = {}
start_date = '20210101' # 扩展为3年
end_date = '20231231'
for ticker in tickers:
try:
df = pro.daily(ts_code=ticker, start_date=start_date, end_date=end_date)
df = df.set_index('trade_date').sort_index()
data_dict[ticker] = df['close']
except Exception as e:
print(f"Error fetching {ticker}: {e}")
# 合并为一个 DataFrame
prices = pd.DataFrame(data_dict)
prices.index = pd.to_datetime(prices.index)
# 查看数据概况
print(prices.shape)
prices.head()Selected 30 stocks for analysis.
(727, 30)
| 600519.SH | 601318.SH | 600036.SH | 601012.SH | 601166.SH | 600900.SH | 600030.SH | 600887.SH | 601888.SH | 603259.SH | ... | 600690.SH | 600436.SH | 601088.SH | 601288.SH | 601601.SH | 600406.SH | 603799.SH | 688599.SH | 603986.SH | 601225.SH | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trade_date | |||||||||||||||||||||
| 2021-01-04 | 1997.00 | 85.18 | 43.17 | 100.10 | 19.65 | 18.90 | 29.18 | 47.36 | 292.25 | 137.11 | ... | 30.01 | 279.98 | 17.99 | 3.13 | 38.35 | 26.69 | 87.1 | 24.17 | 198.62 | 9.59 |
| 2021-01-05 | 2059.45 | 84.27 | 42.18 | 104.87 | 18.86 | 18.89 | 29.14 | 50.93 | 293.02 | 141.99 | ... | 31.78 | 296.38 | 17.65 | 3.13 | 37.90 | 27.12 | 88.3 | 23.18 | 208.90 | 9.51 |
| 2021-01-06 | 2100.00 | 85.74 | 44.15 | 107.00 | 19.73 | 19.21 | 29.62 | 50.56 | 288.00 | 143.74 | ... | 31.65 | 294.45 | 17.95 | 3.13 | 38.17 | 26.29 | 89.0 | 22.73 | 203.72 | 9.59 |
| 2021-01-07 | 2140.00 | 86.26 | 45.90 | 113.55 | 20.10 | 19.23 | 29.75 | 51.69 | 311.00 | 145.95 | ... | 32.68 | 299.91 | 18.26 | 3.14 | 39.27 | 27.20 | 95.6 | 23.60 | 200.55 | 10.05 |
| 2021-01-08 | 2090.00 | 86.10 | 46.60 | 110.40 | 20.32 | 19.34 | 30.06 | 49.27 | 291.00 | 146.21 | ... | 33.00 | 296.59 | 18.63 | 3.14 | 39.00 | 27.82 | 94.9 | 23.50 | 202.56 | 10.24 |
5 rows × 30 columns
我们需要计算股票的对数收益率,并进行标准化处理。
我们使用交叉验证(Cross-Validation)来自动选择正则化参数 \(\alpha\),以获得最佳的稀疏结构。
我们将精度矩阵转换为网络图。非零的偏相关系数(Partial Correlation)表示节点之间的边。
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
# 获取股票名称(通过tushare查询或使用预设映射)
# 尝试获取股票名称
try:
stock_name_map = {}
for code in returns.columns:
stock_info = pro.stock_basic(ts_code=code, fields='ts_code,name')
if len(stock_info) > 0:
stock_name_map[code] = stock_info.iloc[0]['name']
else:
stock_name_map[code] = code
except:
# 如果API调用失败,使用代码作为名称
stock_name_map = {code: code for code in returns.columns}
# 从精度矩阵计算偏相关系数矩阵
# 正确公式: ρ_ij|rest = -Θ_ij / √(Θ_ii × Θ_jj)
d = np.sqrt(np.diag(precision))
partial_corr = -precision / np.outer(d, d) # 注意负号!
np.fill_diagonal(partial_corr, 1) # 对角线为1(自相关)
# 创建图结构
G = nx.Graph()
# 使用股票名称作为节点标签
stock_codes = returns.columns.tolist()
stock_names_display = [stock_name_map.get(code, code) for code in stock_codes]
G.add_nodes_from(stock_names_display)
# 添加边(设定阈值,只显示较强的关联)
threshold = 0.15
edges = []
weights = []
edge_colors_list = []
n_features = len(stock_codes)
for i in range(n_features):
for j in range(i + 1, n_features):
coef = partial_corr[i, j]
if abs(coef) > threshold:
name_i = stock_names_display[i]
name_j = stock_names_display[j]
G.add_edge(name_i, name_j, weight=coef)
edges.append((name_i, name_j))
weights.append(coef)
# 根据偏相关系数正负确定颜色
if coef > 0:
edge_colors_list.append('#e74c3c') # 红色:正相关
else:
edge_colors_list.append('#27ae60') # 绿色:负相关
# 绘图 - 使用更紧凑的尺寸
fig, ax = plt.subplots(figsize=(10, 7))
# 布局算法 - 减小k值使节点更紧凑
pos = nx.spring_layout(G, k=0.15, seed=42, iterations=100)
# 绘制节点
nx.draw_networkx_nodes(G, pos, node_size=400, node_color='#3498db', alpha=0.85, ax=ax)
# 绘制边(使用正确的颜色列表)
if edges: # 只有当有边时才绘制
edge_widths = [abs(w) * 5 for w in weights]
nx.draw_networkx_edges(G, pos, edgelist=edges, width=edge_widths,
edge_color=edge_colors_list, alpha=0.7, ax=ax)
# 绘制标签(使用较小字体以适应中文名称)
nx.draw_networkx_labels(G, pos, font_size=5, font_family='sans-serif', ax=ax)
# 添加图例
pos_patch = mpatches.Patch(color='#e74c3c', label='正偏相关(协同波动)')
neg_patch = mpatches.Patch(color='#27ae60', label='负偏相关(反向波动)')
ax.legend(handles=[pos_patch, neg_patch], loc='upper left', fontsize=10, frameon=True)
# 统计边的数量
n_pos_edges = sum(1 for w in weights if w > 0)
n_neg_edges = sum(1 for w in weights if w <= 0)
ax.set_title(f'上证50成分股条件依赖网络\n(阈值={threshold}, 正边数={n_pos_edges}, 负边数={n_neg_edges})',
fontsize=14, fontweight='bold')
ax.axis('off')
plt.tight_layout()
plt.show()
# 打印边统计信息
print(f"\n=== 网络统计 ===")
print(f"节点数: {G.number_of_nodes()}")
print(f"边数: {G.number_of_edges()}")
print(f" - 正偏相关边: {n_pos_edges}")
print(f" - 负偏相关边: {n_neg_edges}")
=== 网络统计 ===
节点数: 30
边数: 18
- 正偏相关边: 18
- 负偏相关边: 0
上述上证50成分股主要是大盘蓝筹股,它们之间几乎都是正偏相关(红色边)。为了让同学们直观地看到红色边和绿色边的对比,我们下面使用一组跨行业的股票进行分析。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import networkx as nx
from sklearn.covariance import GraphicalLassoCV
from sklearn.preprocessing import StandardScaler
# 选取跨行业代表性股票(去除金融和地产,加入农业和消费)
# 样本区间扩展为3年,增加观察样本量
cross_industry_stocks = {
# 科技成长
'海康威视': '002415.SZ',
'立讯精密': '002475.SZ',
'中兴通讯': '000063.SZ',
# 医药(防御性)
'恒瑞医药': '600276.SH',
'片仔癀': '600436.SH',
'云南白药': '000538.SZ',
# 白酒消费(防御+成长)
'贵州茅台': '600519.SH',
'五粮液': '000858.SZ',
'泸州老窖': '000568.SZ',
# 日常消费
'伊利股份': '600887.SH',
'海天味业': '603288.SH',
'双汇发展': '000895.SZ',
# 农业
'牧原股份': '002714.SZ',
'温氏股份': '300498.SZ',
'新希望': '000876.SZ',
# 新能源(高成长)
'宁德时代': '300750.SZ',
'隆基绿能': '601012.SH',
# 传统制造
'三一重工': '600031.SH',
'美的集团': '000333.SZ',
}
# 获取数据(3年期间)
data_cross = {}
for name, code in cross_industry_stocks.items():
try:
df = pro.daily(ts_code=code, start_date='20210101', end_date='20231231') # 3年数据
df = df.set_index('trade_date').sort_index()
data_cross[name] = df['close']
except Exception as e:
print(f"获取 {name} 失败: {e}")
# 合并数据
prices_cross = pd.DataFrame(data_cross)
prices_cross.index = pd.to_datetime(prices_cross.index)
# 处理缺失值
prices_cross = prices_cross.dropna(axis=1, how='any') # 删除有缺失的股票
prices_cross = prices_cross.dropna() # 删除有缺失的日期
print(f"成功获取 {len(prices_cross.columns)} 只股票,{len(prices_cross)} 个交易日的数据")
# 计算对数收益率
returns_cross = np.log(prices_cross / prices_cross.shift(1)).dropna()
# 标准化
scaler_cross = StandardScaler()
X_cross = scaler_cross.fit_transform(returns_cross)
# 训练 Graphical Lasso
model_cross = GraphicalLassoCV(cv=5)
model_cross.fit(X_cross)
precision_cross = model_cross.precision_
print(f"Graphical Lasso 最优 alpha: {model_cross.alpha_:.4f}")
# 计算偏相关系数(注意负号!)
d_cross = np.sqrt(np.diag(precision_cross))
partial_corr_cross = -precision_cross / np.outer(d_cross, d_cross)
np.fill_diagonal(partial_corr_cross, 1)
# 创建网络
G_cross = nx.Graph()
stock_names_cross = returns_cross.columns.tolist()
G_cross.add_nodes_from(stock_names_cross)
# 添加边(使用较低阈值以显示更多边)
threshold_cross = 0.15 # 与上证50样本使用相同阈值,便于对比
edges_cross = []
weights_cross = []
colors_cross = []
n_cross = len(stock_names_cross)
for i in range(n_cross):
for j in range(i + 1, n_cross):
coef = partial_corr_cross[i, j]
if abs(coef) > threshold_cross:
G_cross.add_edge(stock_names_cross[i], stock_names_cross[j], weight=coef)
edges_cross.append((stock_names_cross[i], stock_names_cross[j]))
weights_cross.append(coef)
if coef > 0:
colors_cross.append('#e74c3c') # 红色:正偏相关
else:
colors_cross.append('#27ae60') # 绿色:负偏相关
# 统计
n_pos_cross = sum(1 for w in weights_cross if w > 0)
n_neg_cross = sum(1 for w in weights_cross if w <= 0)
# 按行业分配颜色(与stock list对应)
industry_colors = {
# 科技:青色
'海康威视': '#1abc9c', '立讯精密': '#1abc9c', '中兴通讯': '#1abc9c',
# 医药:红色
'恒瑞医药': '#e74c3c', '片仔癀': '#e74c3c', '云南白药': '#e74c3c',
# 白酒:金色
'贵州茅台': '#f1c40f', '五粮液': '#f1c40f', '泸州老窖': '#f1c40f',
# 日常消费:橙色
'伊利股份': '#e67e22', '海天味业': '#e67e22', '双汇发展': '#e67e22',
# 农业:棕色
'牧原股份': '#8B4513', '温氏股份': '#8B4513', '新希望': '#8B4513',
# 新能源:绿色
'宁德时代': '#2ecc71', '隆基绿能': '#2ecc71',
# 传统制造:蓝色
'三一重工': '#3498db', '美的集团': '#3498db',
}
node_colors = [industry_colors.get(n, '#3498db') for n in G_cross.nodes()]
# 绘图 - 使用更紧凑的尺寸
fig, ax = plt.subplots(figsize=(10, 7))
# 布局 - 减小k值使节点更紧凑
pos_cross = nx.spring_layout(G_cross, k=0.25, seed=42, iterations=100)
# 绘制节点
nx.draw_networkx_nodes(G_cross, pos_cross, node_size=500, node_color=node_colors, alpha=0.9, ax=ax)
# 绘制边
if edges_cross:
edge_widths = [abs(w) * 6 for w in weights_cross]
nx.draw_networkx_edges(G_cross, pos_cross, edgelist=edges_cross, width=edge_widths,
edge_color=colors_cross, alpha=0.7, ax=ax)
# 绘制标签
nx.draw_networkx_labels(G_cross, pos_cross, font_size=7, font_family='sans-serif', ax=ax)
# 图例
pos_patch = mpatches.Patch(color='#e74c3c', label=f'正偏相关(协同波动): {n_pos_cross} 条')
neg_patch = mpatches.Patch(color='#27ae60', label=f'负偏相关(反向波动): {n_neg_cross} 条')
ax.legend(handles=[pos_patch, neg_patch], loc='upper left', fontsize=11, frameon=True)
ax.set_title(f'跨行业股票条件依赖网络\n(阈值={threshold_cross}, 正边={n_pos_cross}, 负边={n_neg_cross})',
fontsize=14, fontweight='bold')
ax.axis('off')
plt.tight_layout()
plt.show()
# 打印统计
print(f"\n=== 跨行业网络统计 ===")
print(f"节点数: {G_cross.number_of_nodes()}")
print(f"边数: {G_cross.number_of_edges()}")
print(f" - 正偏相关边(红色): {n_pos_cross}")
print(f" - 负偏相关边(绿色): {n_neg_cross}")
# 展示部分偏相关系数
partial_corr_df_cross = pd.DataFrame(partial_corr_cross,
index=stock_names_cross,
columns=stock_names_cross)
print("\n=== 偏相关系数矩阵(部分展示)===")
print(partial_corr_df_cross.round(2))成功获取 18 只股票,727 个交易日的数据
Graphical Lasso 最优 alpha: 0.0799
=== 跨行业网络统计 ===
节点数: 18
边数: 13
- 正偏相关边(红色): 13
- 负偏相关边(绿色): 0
=== 偏相关系数矩阵(部分展示)===
海康威视 立讯精密 恒瑞医药 片仔癀 云南白药 贵州茅台 五粮液 泸州老窖 伊利股份 海天味业 双汇发展 牧原股份 \
海康威视 1.00 0.18 0.04 0.07 0.16 0.00 0.07 0.00 0.05 0.00 0.00 0.00
立讯精密 0.18 1.00 0.06 0.07 0.00 0.03 0.00 0.01 0.01 0.00 0.00 0.00
恒瑞医药 0.04 0.06 1.00 0.07 0.10 0.00 0.06 0.00 0.04 0.05 0.05 0.00
片仔癀 0.07 0.07 0.07 1.00 0.15 0.12 0.09 0.11 0.04 0.06 0.00 0.00
云南白药 0.16 0.00 0.10 0.15 1.00 0.00 0.02 0.01 0.05 0.00 0.00 0.00
贵州茅台 0.00 0.03 0.00 0.12 0.00 1.00 0.33 0.29 0.06 0.04 0.05 0.03
五粮液 0.07 0.00 0.06 0.09 0.02 0.33 1.00 0.45 0.12 0.07 0.01 0.00
泸州老窖 0.00 0.01 0.00 0.11 0.01 0.29 0.45 1.00 0.02 0.08 0.00 0.00
伊利股份 0.05 0.01 0.04 0.04 0.05 0.06 0.12 0.02 1.00 0.08 0.14 0.04
海天味业 0.00 0.00 0.05 0.06 0.00 0.04 0.07 0.08 0.08 1.00 0.17 0.00
双汇发展 0.00 0.00 0.05 0.00 0.00 0.05 0.01 0.00 0.14 0.17 1.00 0.06
牧原股份 0.00 0.00 0.00 0.00 0.00 0.03 0.00 0.00 0.04 0.00 0.06 1.00
温氏股份 -0.00 0.00 0.00 -0.00 0.00 -0.00 -0.00 -0.00 0.00 0.00 0.13 0.24
新希望 0.00 0.00 0.04 0.00 0.00 0.00 0.00 0.00 0.04 0.00 0.13 0.32
宁德时代 0.09 0.07 0.00 0.09 0.02 0.01 0.02 0.00 0.01 0.00 -0.00 0.01
隆基绿能 0.08 0.02 0.00 0.06 0.00 0.00 0.05 0.00 0.00 0.00 -0.00 0.00
三一重工 0.09 0.08 0.03 0.01 0.06 0.00 0.06 0.00 0.07 0.00 0.04 0.03
美的集团 0.05 0.05 0.11 0.00 0.00 0.09 0.01 0.02 0.20 0.04 0.02 0.03
温氏股份 新希望 宁德时代 隆基绿能 三一重工 美的集团
海康威视 -0.00 0.00 0.09 0.08 0.09 0.05
立讯精密 0.00 0.00 0.07 0.02 0.08 0.05
恒瑞医药 0.00 0.04 0.00 0.00 0.03 0.11
片仔癀 -0.00 0.00 0.09 0.06 0.01 0.00
云南白药 0.00 0.00 0.02 0.00 0.06 0.00
贵州茅台 -0.00 0.00 0.01 0.00 0.00 0.09
五粮液 -0.00 0.00 0.02 0.05 0.06 0.01
泸州老窖 -0.00 0.00 0.00 0.00 0.00 0.02
伊利股份 0.00 0.04 0.01 0.00 0.07 0.20
海天味业 0.00 0.00 0.00 0.00 0.00 0.04
双汇发展 0.13 0.13 -0.00 -0.00 0.04 0.02
牧原股份 0.24 0.32 0.01 0.00 0.03 0.03
温氏股份 1.00 0.41 -0.00 -0.00 0.00 0.00
新希望 0.41 1.00 0.00 -0.00 0.04 0.00
宁德时代 -0.00 0.00 1.00 0.17 0.05 0.00
隆基绿能 -0.00 -0.00 0.17 1.00 0.04 0.00
三一重工 0.00 0.04 0.05 0.04 1.00 0.25
美的集团 0.00 0.00 0.00 0.00 0.25 1.00
观察结果:即使选取了跨行业的股票,网络中几乎全部是红色边(正偏相关)。
这反映了中国股票市场的重要特征:整体联动性强。
| 边颜色 | 含义 | 投资启示 |
|---|---|---|
| 红色(正) | 同涨同跌 | 不能用于分散风险 |
| 绿色(负) | 反向波动 | 潜在的对冲机会 |
A 股全是红边说明:中国股市难以通过”选股”完全规避系统性风险。
通过 Graphical Lasso 模型,网络图具有明确的金融含义。
| 边类型 | 含义 | 经济学解释 |
|---|---|---|
| 红色边 | 正偏相关 | 同行业、供应链或政策驱动 |
| 绿色边 | 负偏相关 | 可能存在对冲关系 |
| 无边 | 条件独立 | 仅通过中心节点间接相关 |
大多数股票之间没有连边,验证了我们的假设:
| 分析方法 | 衡量内容 | 优缺点 |
|---|---|---|
| 皮尔逊相关 | 边际相关 | 简单但受混杂影响 |
| 偏相关(无正则化) | 条件相关 | 高维不稳定 |
| Graphical Lasso | 稀疏条件相关 | 高维稳健、可解释 |
| 滑动窗口 Glasso | 时变稀疏条件相关 | 捕捉动态变化 |
| 方法 | 核心思想 | 优缺点 |
|---|---|---|
| 相关性网络 | 阈值化相关矩阵 | 简单但阈值选择任意 |
| 最小生成树 (MST) | 保留最强连接 | 结构唯一但丢失信息 |
| GGM (本方法) | 条件依赖建模 | 理论基础强 |
| 格兰杰因果网络 | 时序因果关系 | 有方向性 |
优势 (Advantages):
局限性 (Limitations):
在本讲座开篇,我们提出了一个问题:招商银行与中国平安之间的高相关性是”真实的直接联系”还是”通过平安银行的间接联系”?
现在,让我们用实际数据来验证这个假设:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.covariance import GraphicalLassoCV
from sklearn.preprocessing import StandardScaler
# 获取三只股票的数据
# 招商银行: 600036.SH, 平安银行: 000001.SZ, 中国平安: 601318.SH
three_stocks = {
'招商银行': '600036.SH',
'平安银行': '000001.SZ',
'中国平安': '601318.SH'
}
# 获取数据
data_3stocks = {}
for name, code in three_stocks.items():
try:
df = pro.daily(ts_code=code, start_date='20210101', end_date='20231231') # 3年数据
df = df.set_index('trade_date').sort_index()
data_3stocks[name] = df['close']
except Exception as e:
print(f"Error fetching {name}: {e}")
# 合并数据
prices_3 = pd.DataFrame(data_3stocks)
prices_3.index = pd.to_datetime(prices_3.index)
# 计算对数收益率
returns_3 = np.log(prices_3 / prices_3.shift(1)).dropna()
# 标准化
scaler_3 = StandardScaler()
X_3 = scaler_3.fit_transform(returns_3)
# ====== 1. 计算边际相关系数(皮尔逊相关) ======
corr_matrix = returns_3.corr()
print("=" * 60)
print("【边际相关系数矩阵(皮尔逊相关)】")
print("=" * 60)
print(corr_matrix.round(3))============================================================
【边际相关系数矩阵(皮尔逊相关)】
============================================================
招商银行 平安银行 中国平安
招商银行 1.000 0.783 0.675
平安银行 0.783 1.000 0.660
中国平安 0.675 0.660 1.000
# ====== 2. 计算条件相关(偏相关系数) ======
# 使用精度矩阵计算
cov_matrix = returns_3.cov().values
precision_3 = np.linalg.inv(cov_matrix)
# 计算偏相关系数
d_3 = np.sqrt(np.diag(precision_3))
partial_corr_3 = -precision_3 / np.outer(d_3, d_3)
np.fill_diagonal(partial_corr_3, 1)
partial_corr_df = pd.DataFrame(partial_corr_3,
index=returns_3.columns,
columns=returns_3.columns)
print("=" * 60)
print("【偏相关系数矩阵(条件相关)】")
print("=" * 60)
print(partial_corr_df.round(3))============================================================
【偏相关系数矩阵(条件相关)】
============================================================
招商银行 平安银行 中国平安
招商银行 1.000 0.609 0.337
平安银行 0.609 1.000 0.287
中国平安 0.337 0.287 1.000
# ====== 3. 使用 Graphical Lasso 验证 ======
model_3 = GraphicalLassoCV(cv=5)
model_3.fit(X_3)
precision_glasso = model_3.precision_
d_glasso = np.sqrt(np.diag(precision_glasso))
partial_corr_glasso = -precision_glasso / np.outer(d_glasso, d_glasso)
np.fill_diagonal(partial_corr_glasso, 1)
partial_corr_glasso_df = pd.DataFrame(partial_corr_glasso,
index=returns_3.columns,
columns=returns_3.columns)
print("=" * 60)
print("【Graphical Lasso 偏相关系数矩阵】")
print(f"(最优正则化参数 α = {model_3.alpha_:.4f})")
print("=" * 60)
print(partial_corr_glasso_df.round(3))============================================================
【Graphical Lasso 偏相关系数矩阵】
(最优正则化参数 α = 0.0488)
============================================================
招商银行 平安银行 中国平安
招商银行 1.000 0.570 0.329
平安银行 0.570 1.000 0.287
中国平安 0.329 0.287 1.000
# ====== 4. 可视化对比 ======
# 使用 constrained_layout 自动调整布局,防止重叠
fig, axes = plt.subplots(1, 3, figsize=(15, 5), constrained_layout=True)
# 热力图函数
def plot_heatmap(ax, matrix, title, cmap='RdBu_r'):
im = ax.imshow(matrix, cmap=cmap, vmin=-1, vmax=1)
ax.set_xticks(range(len(matrix.columns)))
ax.set_yticks(range(len(matrix.index)))
ax.set_xticklabels(matrix.columns, rotation=45, ha='right', fontsize=10)
ax.set_yticklabels(matrix.index, fontsize=10)
ax.set_title(title, fontsize=12, fontweight='bold')
# 添加数值标签
for i in range(len(matrix.index)):
for j in range(len(matrix.columns)):
val = matrix.iloc[i, j]
color = 'white' if abs(val) > 0.5 else 'black'
ax.text(j, i, f'{val:.2f}', ha='center', va='center',
fontsize=11, color=color, fontweight='bold')
return im
plot_heatmap(axes[0], corr_matrix, '边际相关系数\n(皮尔逊相关)')
plot_heatmap(axes[1], partial_corr_df, '偏相关系数\n(精度矩阵)')
im = plot_heatmap(axes[2], partial_corr_glasso_df, '偏相关系数\n(Graphical Lasso)')
# 添加 colorbar
fig.colorbar(im, ax=axes, shrink=0.8, label='相关系数', location='right')
# plt.tight_layout() # constrained_layout 已启用,不需要 tight_layout
plt.show()
# ====== 5. 结论分析 ======
print("=" * 60)
print("【结论分析】")
print("=" * 60)
# 提取关键数值
marginal_zs_zp = corr_matrix.loc['招商银行', '中国平安']
partial_zs_zp = partial_corr_df.loc['招商银行', '中国平安']
glasso_zs_zp = partial_corr_glasso_df.loc['招商银行', '中国平安']
print(f"招商银行 与 中国平安:")
print(f" - 边际相关系数: {marginal_zs_zp:.3f}")
print(f" - 偏相关系数: {partial_zs_zp:.3f}")
print(f" - Glasso偏相关: {glasso_zs_zp:.3f}")
print()
if abs(partial_zs_zp) < 0.3 and abs(marginal_zs_zp) > 0.5:
print("✓ 验证成功!招商银行与中国平安的高边际相关主要是间接关联,")
print(" 控制平安银行后,两者的条件相关性大幅下降。")
verification_result = "consistent"
elif abs(partial_zs_zp) >= 0.3:
print("⚠ 结果与预期不完全一致:招商银行与中国平安之间存在一定的直接联系,")
print(" 但偏相关系数仍低于边际相关系数,说明部分相关来自间接联系。")
verification_result = "partial"
else:
print("ℹ 边际相关本身不高,条件独立性分析意义有限。")
verification_result = "low_marginal"============================================================
【结论分析】
============================================================
招商银行 与 中国平安:
- 边际相关系数: 0.675
- 偏相关系数: 0.337
- Glasso偏相关: 0.329
⚠ 结果与预期不完全一致:招商银行与中国平安之间存在一定的直接联系,
但偏相关系数仍低于边际相关系数,说明部分相关来自间接联系。
根据上述实际数据分析,我们观察到具体的数值变化:
结论解读:
这正是高斯图模型的价值:它不只是告诉你”相关”,还能量化这其中有多少是”直接”的,有多少是”间接”的。
本讲座介绍了概率图模型(PGM)在金融市场分析中的应用,重点聚焦于高斯图模型(GGM)。
| 概念 | 核心内容 |
|---|---|
| 精度矩阵 \(\Theta\) | 协方差矩阵的逆,编码条件依赖结构 |
| 条件独立性 | \(\Theta_{ij} = 0 \Leftrightarrow X_i \perp X_j \mid X_{\text{rest}}\) |
| 偏相关系数 | \(\rho_{ij \mid \text{rest}} = -\Theta_{ij}/\sqrt{\Theta_{ii}\Theta_{jj}}\) |
| Graphical Lasso | \(\ell_1\) 正则化精度矩阵估计,实现稀疏网络 |
| 模型选择 | 交叉验证、EBIC、稳定性选择 |
Tip
✅ 自我检测:你学会了吗?
完成本讲座后,你应该能够回答以下问题:
1. 概念理解
2. 方法应用
partial_corr = -precision / np.outer(np.sqrt(np.diag(precision)), np.sqrt(np.diag(precision))),其中 precision = np.linalg.inv(cov_matrix) 或使用 GraphicalLassoCV 估计。
3. 金融解读
Note
🎯 下一步学习建议
如果你想进一步深入学习:
| 阶段 | 建议内容 |
|---|---|
| 巩固基础 | 复习本讲座代码,自己下载数据运行一遍 |
| 理论深化 | 阅读 Hastie 等人的免费电子书 Chapter 7 |
| 实践拓展 | 选取不同行业的股票,比较网络结构差异 |
| 高级进阶 | 学习时变图模型或 Copula 方法 |
相关课程推荐:
以下是理解本讲座内容的核心参考资料,按重要性排序:
以下文献适合有兴趣深入研究的同学:
经典理论
金融网络应用
最新综合参考