四、节点特征与边特征工程
好,咱们今天聊聊特征工程。图神经网络的输入,说白了就是节点和边上的信息。你想想看,如果给模型喂的数据很烂,再牛的 GraphSAGE 也白搭。我在项目中见过太多这种情况:模型结构改来改去,效果纹丝不动,最后发现是特征没做好。
4.1 节点特征:不只是“你是谁”
节点特征分两类:固有属性和结构衍生特征。固有属性好理解,比如用户的年龄、性别、历史购买金额。但光靠这些,模型学不到图的结构信息。所以我们需要自己动手,造一些结构特征出来。
常用节点特征一览
| 特征类型 | 典型例子 | 何时使用 |
|---|---|---|
| 度中心性 | 节点的邻居数量 | 基础结构信息,几乎所有场景都需要 |
| PageRank | 节点在图中的“重要性” | 社交网络、推荐系统,找“大V”节点 |
| 局部聚类系数 | 邻居之间的连接密度 | 社交网络中的“小团体”检测 |
| K-core 值 | 节点所在的最密子图层级 | 识别图的核心与边缘节点 |
我个人习惯,拿到图数据第一件事就是算 度中心性。为什么?因为它在大部分场景下都有用,而且计算简单。我遇到过一个问题:一个交易网络里,有些节点明明很活跃,但度很高反而意味着风险。所以你别死板,要结合业务理解特征的含义。
如何生成节点特征?
用 NetworkX 库,几行代码搞定:
import networkx as nx
G = nx.karate_club_graph()
# 度中心性
degree_centrality = nx.degree_centrality(G)
# PageRank
pagerank = nx.pagerank(G)
# 局部聚类系数
clustering = nx.clustering(G)
# K-core
k_core = nx.core_number(G)
4.2 边特征:连接也有“质量”
边特征决定了两个节点之间的关系强弱。在知识图谱里,边往往带有类型标签,比如“朋友”、“同事”、“家人”。但在很多场景下,我们还需要给边赋予权重。
常用的边特征工程方法
- 边权重:交易金额、通话时长、共同好友数
- 边类型编码:独热编码、Label Encoding
- Jaccard 相似度:两个节点的共同邻居占比
- Adamic-Adar 指数:对共同邻居取对数再求和,更关注稀有邻居
我曾经做过一个社交推荐项目。一开始只用“是否好友”这种 0/1 边特征,效果很差。后来我改成用共同好友数和互动频率做权重,模型 AUC 直接涨了 5 个点。你想想看,同样是朋友,天天聊天和一年说一次话,能一样吗?
4.3 特征衍生:从“原始”到“高级”
光有基础特征还不够。我们需要做一些交叉组合,让模型看到更多信息。
我的经典衍生策略
- 节点特征 × 边特征:比如“用户活跃度 × 联系人亲密度”
- 邻居特征聚合:取邻居特征的均值、最大值、标准差
- 图距离特征:计算两个节点之间的最短路径长度
- 子图特征:比如“两个节点是否在同一个三角结构里”
这里重点说一下邻居特征聚合。你不需要把所有邻居信息都塞进去,那样特征维度会爆炸。我一般只取 Top-K 重要邻居,或者用 GraphSAGE 自带的聚合函数(Mean、Pool、LSTM)来处理。
4.4 特征分箱与离散化
连续特征在图上往往不好处理。比如年龄 25 岁和 26 岁,对模型来说区别不大,但“青年”和“中年”的边界可能很重要。所以我们要做分箱。
| 分箱方法 | 适用场景 | 我的经验 |
|---|---|---|
| 等距分箱 | 数据分布均匀时 | 简单粗暴,但容易忽略分布 |
| 等频分箱 | 长尾分布时 | 常用方法,保证每箱样本量一致 |
| 聚类分箱 | 数据有明显簇结构 | 效果好但计算成本高,适合离线 |
| 决策树分箱 | 追求和标签强相关 | 我个人最喜欢,用 LightGBM 的叶子节点做箱 |
我习惯把分箱后的结果再做一个目标编码:用每个箱内的正样本比例代替原始值。比如“年龄 25-30 岁”这个箱,欺诈率是 10%,那所有在这个箱里的节点都填 0.1。这样既保留了分箱的泛化能力,又注入了标签信息。
4.5 特征工程整体流程
来看一张图,总结一下我们今天的核心内容。
流程不复杂:原始数据拆成节点和边,各自做特征工程,再交叉衍生,最后分箱离散化。我一般把这一步和后续建模分开,先离线跑完特征,保存成文件,训练时直接加载。这样效率高,迭代也快。
嗯,内容不少。节点特征、边特征、衍生、分箱,每一个环节都能深挖。但我建议你先从最简单的开始——比如先算个度中心性和 Jaccard 相似度,跑一版基线,看看效果。别一上来就想搞多复杂,图模型最怕的就是特征矩阵太大,跑都跑不动。