halo 的技术博客

返回

多因子模型最恼火的事:你精心造了”价值""盈利""动量""低波”四个因子,结果一算相关矩阵,价值跟盈利相关系数 0.6、动量和反转 0.7——它们根本没那么”正交”。后果很具体:截面回归里系数互相污染。一个因子显著,可能只是因为它和另一个真正显著的因子共线,把对方的功劳抢过来;标准误(SE)被共线膨胀,你分不清谁真有用;更糟的是,你想做”因子重要性排序”或”逐个加因子看增量信息”,但加进去的顺序不同,结论就不同。

Gram-Schmidt(施密特)正交化是解决这个的经典线性代数工具:它把一组相关的因子,依次投影、去相关,拆成一串互相正交、互不重叠的纯净分量。本文用 numpy 从零实现,把 4 个被随机混合矩阵搅到最大相关 0.95 的因子,拆成完全正交(离对角相关 ≈0)的纯净信号,并量化它给截面回归带来的改变:系数 SE 从 [0.16, 0.07, 0.54, 0.54] 收缩到四个相等且最小的 0.98,被前驱解释 R² 从 0.98 降到 0。附完整 Python 与四张真实计算图。

原始因子相关热图:4 个因子被搅在一起,离对角相关最高 0.95

一、相关因子的代价:回归系数被污染#

经典截面回归 y=Fβ+ϵy = F\beta + \epsilon,系数 β^=(FF)1Fy\hat\beta = (F'F)^{-1}F'y。当 FF 的列相关高,(FF)1(F'F)^{-1} 的对角线被共线放大,于是:

  • 单个因子显著,可能只是”蹭”了另一个因子的信息;
  • 加因子的顺序不同,β\beta 的解释不同;
  • 你没法干净地说”这个因子贡献了多少增量 R²”。

而正交化后的因子 UU 满足 UU=IU'U = I(列互相正交),于是 β^U=Uy\hat\beta_U = U'y——每个系数只对应自己那一个因子,零污染、SE 最小且全等。这正是我们想要的”纯净信号”。

二、从零实现 Gram-Schmidt#

先造一组”被搅在一起”的相关因子:用随机混合矩阵把 4 个真正正交的 latent 因子搅乱。

然后按公式逐个正交化:第 kk 个分量 = 原始第 kk 个因子,减去它在前面所有已正交分量上的投影,再归一化。

结果干脆:正交后因子间最大相关 = 0.0000。每个 U:,kU_{:,k} 都和前面的 U:,1..k1U_{:,1..k-1} 完全垂直。

正交后相关热图:对角=1、离对角≈0

三、量化”纯净度”:被前驱解释了多少#

怎么证明正交后的因子真的互不重叠?把每个因子对前面所有因子做回归,看 R²:

诚实细节:最后一个原始因子被前 3 个解释了 98.4%——它基本是冗余的,放进回归纯属凑数。正交化之后,每个因子被前驱解释的 R² 都是 0,意味着它们携带的是互不重叠的纯净信息

再看每个正交因子的”增量方差解释”——它占总信息量的比例:

inc_var = (U**2).sum(0) / (U**2).sum()
print([round(x,3) for x in inc_var])
# [0.25, 0.25, 0.25, 0.25]
python

每个正交因子的增量方差解释均为 25%:信息被均匀切分

4 个正交分量各占 25% 方差——因为 Gram-Schmidt 在 NN 维空间里依次切出互相正交的子空间,信息被均匀分配。这让你能逐个评估”加这一个纯净因子,增量解释了多少”,而不受加因子顺序的干扰(因为正交后顺序无关)。

四、截面回归:正交后系数干净、SE 最小#

造一个只由真实潜在因子 g1g_1 驱动的收益 yy(SNR=1),分别用原始因子和正交因子做回归:

截面回归系数:正交后系数干净且 SE 最小(四个 SE 全等)

两个诚实且重要的结论:

  1. R² 一致(0.066)——正交化不改变”因子空间能解释多少”,只改变”谁来解释”。它不创造信息,只是重新分配。
  2. SE 天差地别——原始因子系数 SE 是 [0.163, 0.072, 0.537, 0.542](后两个被共线撑到 0.54,几乎不可信);正交后 SE 是四个相等且最小0.979。因为正交后 (UU)1=I(U'U)^{-1}=I,信息矩阵对角线恒定,每个因子得到公平的估计精度。

更妙的是系数的可加性:正交后 βU,1+βU,2\beta_{U,1}+\beta_{U,2} 精确等于”前两个正交因子合起来”对 yy 的解释贡献,而原始因子系数因为互相耦合,加起来没有这种干净的语义。当 yy 的真实驱动是 g1g_1 时,正交因子与 g1g_1 的相关是 [0.331, -0.494, -0.298, -0.744]——注意不是第一个最高,说明原始因子顺序本身不代表信息重要性,正交化只是把重叠剥掉、不替你排序”哪个因子更真”。

落地坑:Gram-Schmidt 的结果依赖因子输入顺序。先放哪个因子,哪个就”占住”了它在因子空间里的那一维,后面的只能填剩余子空间。本文顺序是随机的,只演示算法。实盘里应按”先验重要性”或”Information Ratio”排序输入,让靠前的因子拿到更”干净”的那一维。这是它和对称正交化(如对称 QR、对称 SVD)的关键区别。

五、已知偏差与适用边界#

  • 顺序敏感:如上,非对称。要顺序无关得用对称化变体(如对称 Gram-Schmidt / 对 FFF'F 做 eigendecomposition 后再旋转)。
  • 正交 ≠ 真因子:正交化只是代数去相关,不保证每个分量有经济学含义。可能把一个有解释力的因子拆得”看起来干净但没人懂”。
  • 样本内才完美:用估计的因子做 Gram-Schmidt,正交性只在样本内成立;样本外因子相关性会漂移,正交性会被打破。建议滚动重估或用样本外固定旋转矩阵。
  • 和 PCA 的区别:PCA 也去相关,但 PCA 按方差大小切主成分,会把”小方差但经济上重要的因子”压到最后;Gram-Schmidt 按你给的顺序保留每个因子的”专属那一维”,更适合做因子归因。

六、小结#

Gram-Schmidt 把一组相关的因子依次投影去重,拆成互不重叠的纯净分量。本文从零实现,给出可复现数字:4 个被搅到最大相关 0.95 的原始因子,正交后离对角相关 = 0.0000;被前驱解释的 R² 从 0.984 降到 0;增量方差解释均匀切为四个 25%;截面回归系数 SE 从 [0.16, 0.07, 0.54, 0.54] 收缩到四个相等且最小的 0.98,R² 保持不变(0.066)。正交化的全部意义是:让每个因子的系数可加、可解释、SE 最小——当你要从 20 个缠在一起的候选因子里挑出真正有增量信息的那几个时,这一步是把”因子重要性”从噪音里捞出来的前提。

附完整 Python 与四张真实计算图(原始/正交相关热图 / 增量方差解释 / 截面回归系数)。

Gram-Schmidt 因子正交化:把相关因子拆成互不重叠的纯净信号
https://blog.halo26812.eu.org/blog/gram-schmidt-factor-orthogonalization
Author halo
Published at 2026年8月31日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨