4. 节点相似性特征:共同邻居、Jaccard系数、Adamic-Adar指数、资源分配指数

图数据里最基础也最实用的特征,就是节点相似性。

说白了,就是判断两个节点“像不像”。

我刚开始做图算法时,觉得这玩意儿太简单了。后来发现,相似性特征做得好,模型效果能直接提升5-10个点。今天咱们就把这四种经典方法掰开揉碎了讲。

4.1 共同邻居(Common Neighbors)

这是最直观的思路。

两个节点共享的邻居越多,它们就越可能相似。比如在社交网络里,你和某个人有10个共同好友,那你们大概率认识。

数学定义:

CN(u, v) = |N(u) ∩ N(v)|

其中 N(u) 表示节点 u 的邻居集合。

我个人的习惯:在稀疏图上,共同邻居往往比复杂指标更稳定。我曾经在一个电商推荐项目里,只用共同邻居就干掉了好几个花里胡哨的模型。

代码实现:

import networkx as nx

def common_neighbors(G, u, v):
    return len(list(nx.common_neighbors(G, u, v)))

小技巧:如果图特别大,可以先用度数过滤一下。只计算度数大于阈值的节点对,能省不少时间。

4.2 Jaccard系数

共同邻居有个问题:它没考虑节点本身的度数。

你想想看,一个超级大V有1000个好友,你和他的共同邻居有50个。另一个普通用户只有100个好友,你和他的共同邻居也是50个。这两个50能一样吗?

Jaccard系数就是来解决这个问题的。

数学定义:

Jaccard(u, v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|

说白了,就是共同邻居占所有邻居的比例。

我曾经踩过的坑:在计算Jaccard时,如果两个节点没有邻居,分母为0。一定要加个平滑处理,比如加1或者直接返回0。

代码实现:

def jaccard_coefficient(G, u, v):
    neighbors_u = set(G.neighbors(u))
    neighbors_v = set(G.neighbors(v))
    
    intersection = len(neighbors_u & neighbors_v)
    union = len(neighbors_u | neighbors_v)
    
    if union == 0:
        return 0.0
    return intersection / union

4.3 Adamic-Adar指数

这个指标很有意思。它给每个共同邻居加了个权重——度数越低的邻居,权重越高

为什么?因为你和某个冷门用户有共同好友,比你和大众网红有共同好友,更能说明问题。

数学定义:

AA(u, v) = Σ [1 / log(|N(w)|)]  (w ∈ N(u) ∩ N(v))

每个共同邻居 w 的贡献是 1/log(度数)。度数越大,贡献越小。

我建议:在链路预测任务中,Adamic-Adar通常比Jaccard表现更好。我做过一个论文合作网络的项目,AA指数比Jaccard的AUC高了将近3个点。

代码实现:

import math

def adamic_adar(G, u, v):
    common = list(nx.common_neighbors(G, u, v))
    if not common:
        return 0.0
    
    score = 0.0
    for w in common:
        degree = G.degree(w)
        if degree > 1:
            score += 1.0 / math.log(degree)
    return score

注意:log里的度数如果为1,分母为0。我一般会加个判断,度数为1的节点直接跳过。

4.4 资源分配指数(Resource Allocation Index)

这个指标和Adamic-Adar很像,但更激进。

它把每个共同邻居看作一个资源分配器。度数越高,分配能力越弱。公式里直接用 1/度数,而不是 1/log(度数)。

数学定义:

RA(u, v) = Σ [1 / |N(w)|]  (w ∈ N(u) ∩ N(v))

说白了,就是对高度数节点的惩罚更狠。

代码实现:

def resource_allocation(G, u, v):
    common = list(nx.common_neighbors(G, u, v))
    if not common:
        return 0.0
    
    score = 0.0
    for w in common:
        degree = G.degree(w)
        if degree > 0:
            score += 1.0 / degree
    return score

4.5 四种方法对比

指标 核心思想 是否考虑度数 适用场景
共同邻居 邻居交集大小 稠密图、快速筛选
Jaccard系数 交集占并集比例 是(归一化) 度数差异大的图
Adamic-Adar 对数惩罚高度数节点 是(对数惩罚) 社交网络、论文合作
资源分配 线性惩罚高度数节点 是(线性惩罚) 稀疏图、极端度数分布

我的经验:实际项目中,我通常会把这四个指标都算出来,作为特征一起喂给模型。有时候模型自己会学到哪个指标更重要。别偷懒,全上就对了。

4.6 知识体系图

下面这张图展示了四种相似性指标的核心逻辑和关系:

节点相似性 共同邻居 交集大小 Jaccard系数 交集/并集 Adamic-Adar 1/log(度数) 资源分配 1/度数 共同邻居 → Jaccard → Adamic-Adar → 资源分配 复杂度递增,对高度数节点惩罚递增

从这张图可以看得很清楚:四种方法一脉相承。从最简单的共同邻居开始,逐步加入对节点度数的考量,惩罚力度也越来越大。

选型建议:

  • 图很稠密?用共同邻居或Jaccard,速度快
  • 图很稀疏?用资源分配,对高度数节点惩罚更狠
  • 不确定?四个全算,让模型自己选

嗯,这四种相似性特征就讲完了。别看公式简单,实际项目里它们往往是最稳定、最解释性强的特征。我每次做图项目,都会先把这四个特征跑一遍,看看效果再说。


专注资料整理