halo 的技术博客

返回

传统因子模型有个隐含假设:每只股票是独立同分布的一行样本。你把茅台、五粮液、宁德时代排成一张表,每行算各自的动量、估值、波动,然后丢进回归。模型眼里,茅台和五粮液的关系,跟茅台和宁德时代的关系,没有任何区别——都只是表里的两行而已。

但你我都知道这不对。茅台和五粮液会一起涨一起跌,因为它们共享同一个白酒行业因子;宁德时代和它的上游锂矿供应商会联动,因为供应链把它们的现金流绑在了一起。资产从来不是孤岛,它们嵌在一张密密麻麻的关系网里。

结论先放这:图神经网络(Graph Neural Network, GNN)的核心价值,就是把「谁和谁有关系」这件事直接写进模型的结构里。 它把资产建成一张图——节点是股票,边是关系(行业、相关、供应链、共同持仓都行)——然后用图卷积让每个节点在做预测时,主动去借用它邻居的信息。在本文的自洽合成数据上,一层最朴素的 GCN 把半监督行业分类的测试准确率从「只用节点特征的 MLP」的 0.75,直接拉到 1.00。下面从零实现,并诚实拆穿它最容易翻车的五个地方。

资产关系图:同行业节点由相关阈值连边,灰色是同行业边、橙色是跨行业边

一、为什么因子模型需要「图」#

先想清楚一个问题:关系信息到底值不值钱?

假设你要预测一只小盘股下个月的收益,但它自己的历史数据又短又噪。如果你知道「它和另外 20 只已经充分研究过的股票高度联动」,那这 20 只股票的信息,其实就是这只小盘股的免费先验。这正是量化里”用行业均值填补个股缺失""用同行相对估值做锚”这类操作的本质——你一直在用关系,只是过去用得很粗糙(简单取均值)。

GNN 把这件事做成了可学习、可端到端优化的模块。它不再是”取行业均值”这种拍脑袋的聚合,而是让模型自己学”该从哪个邻居借多少信息”。

一张资产图由两部分组成:

  • 节点特征矩阵 XRN×FX \in \mathbb{R}^{N \times F}NN 只股票,每只 FF 个特征(动量、波动、估值、beta……)
  • 邻接矩阵 ARN×NA \in \mathbb{R}^{N \times N}Aij=1A_{ij}=1 表示股票 iijj 有关系(相关超阈值 / 同行业 / 供应链相连)

图卷积做的事,一句话概括:每个节点的新表征 = 它自己和所有邻居的特征的加权平均,再过一个可学习的线性变换。

二、一层 GCN 的数学:就三步#

Kipf & Welling (2017) 的 GCN 传播公式看着吓人,其实拆开只有三步:

H=σ ⁣(A^XW),A^=D~1/2A~D~1/2,A~=A+IH = \sigma\!\left( \hat{A}\, X\, W \right), \quad \hat{A} = \tilde{D}^{-1/2}\,\tilde{A}\,\tilde{D}^{-1/2}, \quad \tilde{A} = A + I

第一步:加自环。 A~=A+I\tilde{A} = A + I。为什么?因为聚合邻居时,节点自己的信息也不能丢,加个单位阵 II 就等于”把自己也算作自己的邻居”。

第二步:对称归一化。 A^=D~1/2A~D~1/2\hat{A} = \tilde{D}^{-1/2}\tilde{A}\tilde{D}^{-1/2},其中 D~\tilde{D} 是度矩阵(对角线是每个节点的邻居数)。这一步至关重要——不归一化的话,度数高的节点(连了 50 条边)聚合出来的值会比度数低的节点(连了 2 条边)大一个数量级,模型会被”社交达人”节点带偏。对称归一化让每条边的权重变成 1/didj1/\sqrt{d_i d_j},度高的节点被适当压制。

第三步:线性变换 + 激活。 A^X\hat{A}X 完成了”邻居聚合”,右乘 WW 做特征变换(把 FF 维映射到你要的输出维度),最后过一个非线性 σ\sigma

关键洞察:A^X\hat{A}X 这个操作,就是”每个节点用归一化权重把邻居的特征平均过来”。 图卷积的全部魔法,都在这个矩阵乘法里。

三、纯 numpy 从零实现#

我们造一个自洽的合成世界:60 只股票分属 3 个行业,同行业股票的日收益由共同的行业因子驱动。这样,用相关阈值建出来的图,天然就该呈现”同行业内部密集连边、跨行业稀疏”的块结构。任务是半监督行业分类:只给每个行业 4 个标注节点,看模型能不能把剩下的 48 只股票正确归类。

注意这里的因果链:先有共同因子 → 同行业收益联动 → 相关高 → 建出边。这是自洽合成的关键,图结构不是我们手动画上去的,而是从数据生成过程里”长”出来的。

接着实现归一化邻接和一层 GCN 前向:

训练部分——半监督,只在标注节点上算梯度,但前向传播用到了整张图(每个节点都聚合了邻居,包括未标注的邻居):

对照组是不用图的 MLP:一模一样的特征、一模一样的训练循环,唯一区别是把 XW_input = Ahat @ feat 换成 feat——即去掉图卷积那步聚合。

四、结果:图结构值 25 个百分点#

跑完打印的数字:

节点 60 | 边(单向) 1140 | 平均度 19.0
边落在同行业内的比例: 100.00% (随机基线≈32.20%)
GCN(用图) 测试集行业分类准确率: 1.000
MLP(不用图) 测试集行业分类准确率: 0.750
plaintext

三个数字,每个都说明一件事:

边落在同行业内的比例 100%。 相关阈值建图,把同行业的联动关系几乎完美地抓成了边——随机情况下同行业边只该占 32%,实际 100%,说明”共同因子 → 高相关”这条链条极其干净。这是合成数据的理想情况,真实市场会脏得多(见陷阱一)。

GCN 1.00 vs MLP 0.75。 这是全文最关键的对比。两个模型用的特征完全相同,唯一区别是 GCN 多做了 A^X\hat{A}X 那步邻居聚合。就靠这步,测试准确率提升了 25 个百分点。原因很直白:一个未标注节点,只要它的邻居里有标注节点,图卷积就能把标注信息”传染”过来。 而 MLP 只能靠这个节点自己那 8 个特征硬判,在半监督(标注极少)的场景下必然吃亏。

左:训练损失收敛;右:GCN 测试准确率登顶 1.00,MLP 卡在 0.75

从收敛曲线看,GCN 不仅终点更高,收敛也更快更稳——因为图平滑相当于给优化加了一个强先验(相邻节点表征应该接近),缩小了假设空间。

再看邻接矩阵本身的结构:

邻接矩阵按行业排序后呈现清晰的块对角结构,同行业边占比 100%

把节点按行业重排后,邻接矩阵呈现教科书般的块对角结构——三个行业各自内部密集连边,跨块几乎空白。这张图直观解释了为什么 GCN 有效:图卷积的信息流动被牢牢限制在行业块内部,等于自动实现了”行业中性”的信息聚合。

五、五类真实陷阱(务必读完再上手)#

合成数据上的 1.00 准确率很漂亮,但真实市场会用五种方式教你做人:

陷阱一:邻接矩阵怎么建,决定了模型的天花板。 本文用”相关 > 0.3”建图,在合成数据上干净得不真实。真实相关矩阵充满噪声,NN 只股票估 N2N^2 个相关系数,样本量根本不够,估出来的图一半是噪声边。阈值高了图太稀疏(信息传不动),低了图太稠密(所有节点糊成一团,等于没建图)。实务里更稳的是用先验图(行业分类、供应链、共同持仓),而不是纯数据驱动的相关图。

陷阱二:过平滑(over-smoothing)。 GCN 每多一层,节点就多聚合一圈邻居。层数一深,所有节点的表征会收敛到同一个值——整张图糊成一片,节点之间再也分不开。这就是为什么实务里 GCN 很少超过 2-3 层。别以为堆层数能提升效果,图神经网络在这点上和 CNN 完全相反。

陷阱三:图泄漏(look-ahead via graph)。 这是量化里最致命的。如果你用全样本的相关矩阵建图,那么 tt 时刻的节点表征就偷看了 tt 之后的相关信息——回测会好得离谱,实盘瞬间打回原形。正确做法:邻接矩阵必须只用截止到 tt 的数据滚动重建,和所有因子一样严守时间因果。本文合成数据没有时间维度上的预测任务,所以用了全样本图,真实回测绝不能这么干。

陷阱四:图的时变性。 资产关系不是常数。牛市里所有股票齐涨(相关趋近 1,图变成全连接,等于没信息);危机里跨行业相关飙升(行业块结构瓦解)。一张静态图假设关系永恒,这在 regime 切换时会系统性失效。严肃的做法是用动态图(时变邻接)或滚动重估,但这会大幅增加过拟合风险。

陷阱五:半监督的标注质量。 本文的”标注”是行业标签,干净无争议。但真实任务里如果你的标注是”未来收益是否为正”这类含大量噪声的标签,图卷积会把错误标注也一起传染给邻居——GCN 放大信号的能力,反过来也会放大标注噪声。图结构是杠杆,既能放大 alpha,也能放大你的错误。

六、总结#

GNN 在量化里的定位,不是又一个更强的黑箱,而是一种把领域先验(谁和谁有关系)注入模型结构的方式。它最适合的场景是:单个资产数据稀疏、但资产之间关系明确且稳定(比如用供应链图做产业链联动预测、用共同持仓图做风格传染分析)。

本文的 1.00 vs 0.75 是合成数据的理想上界,真实收益要打很大折扣。但那个 25 个百分点的差距背后的道理是真的:当你的样本孤立且稀缺时,关系就是免费的信息。 GNN 只是把这句话,写成了可以端到端优化的矩阵乘法。

上手顺序建议:先用最保守的先验图(行业分类)+ 一层 GCN 跑通,确认图确实带来增量,再考虑相关图、动态图这些更花哨也更容易过拟合的东西。记住陷阱三——图泄漏是这条路上最深的坑,绝大多数”GNN 选股神效”的回测,都栽在这里。

图神经网络 GNN 资产关系建模:把「谁和谁像」直接写进模型的结构里
https://blog.halo26812.eu.org/blog/gnn-asset-relations
Author halo
Published at 2026年7月22日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨