数学解释

对,这几个公式如果只看符号会很抽象。我们用一个小表格 + 手算式例子来讲。

先说结论:

边特征 h_e = φ(D[i,j])
作用是:把“单元格的具体值”变成向量,放到图的边上。
GNN 消息传播时,不只看“谁和谁相连”,还看“这条边上的单元格值是什么”。

论文里把表格构造成二部图:行是 tuple node,列是 attribute node,每个单元格是一条边;并且每条边都有特征 h_e = φ(D[i,j]),其中 φ 是把 cell value 编成数值向量的 hash-based embedding。GNN 的消息传播会把邻居节点表示和边表示拼接起来,所以边特征会直接参与信息聚合。


1. 为什么需要边特征?

假设有一个表:

CustomerID Email Age
t1 C100 a@email.com 20
t2 C100 b@email.com 21
t3 C200 c@email.com 30

构成二部图后:

行节点:t1, t2, t3
列节点:CustomerID, Email, Age

边:
t1 -- CustomerID,边值是 C100
t1 -- Email,边值是 a@email.com
t1 -- Age,边值是 20

t2 -- CustomerID,边值是 C100
t2 -- Email,边值是 b@email.com
t2 -- Age,边值是 21
...

如果没有边特征,GNN 只知道:

t1 连着 CustomerID、Email、Age
t2 也连着 CustomerID、Email、Age
t3 也连着 CustomerID、Email、Age

那所有行结构都差不多,它不知道:

t1 的 CustomerID 是 C100
t2 的 CustomerID 也是 C100
但 t1.Email 和 t2.Email 不一样

这样它就很难发现:

同一个 CustomerID = C100
对应了两个不同 Email
可能违反 CustomerID -> Email

所以边特征 h_e = φ(D[i,j]) 的作用就是把单元格值塞进图里。

你可以把它理解成:

边特征 = 这条连接线上携带的单元格内容

没有边特征,图只有“表格结构”;有边特征,图才有“表格内容”。


2. φ(D[i,j]) 是什么?

公式:

h_e = φ(D[i,j])

意思是:

把单元格值 D[i,j] 转成一个向量

比如:

φ("C100")        = [0.8, 0.1, 0.3]
φ("a@email.com") = [0.2, 0.7, 0.4]
φ("20")          = [0.1, 0.9, 0.5]

论文里说它用的是 hash-based embedding,也就是用哈希方法把值编码成数值向量。你不用纠结具体数值,只要理解:

原始值:C100、a@email.com、20
↓
向量化后:机器能计算的 h_e

3. GNN 消息传播公式到底在说什么?

论文里的消息聚合公式大致是:

在这里插入图片描述

h_N(v)^(p)
=
AGG {
  σ( CONCAT( h_v'^(p-1), h_e(v',v) ) )
  | v' ∈ N(v)
}

这个公式可以拆成一句话:

对节点 v 来说,把每个邻居 v' 的表示,和连接它们那条边的单元格值表示拼起来,形成消息;然后把所有邻居消息聚合起来。

逐个符号看:

v:当前节点
v':v 的某个邻居节点
N(v):v 的所有邻居
h_v'^(p-1):邻居节点上一层的表示
h_e(v',v):连接 v' 和 v 的边特征,也就是单元格值 embedding
CONCAT:拼接
σ:激活函数,可以先理解成“做一次非线性变换”
AGG:聚合函数,论文里是 mean,也就是求平均
h_N(v)^(p):当前节点 v 从邻居那里收到的综合信息

4. 用一个列节点 Email 举例

我们看列节点 Email

它的邻居是所有行节点:

N(Email) = {t1, t2, t3}

因为每一行都有一个 Email 单元格。

对应边特征是:

t1 -- Email:φ("a@email.com")
t2 -- Email:φ("b@email.com")
t3 -- Email:φ("c@email.com")

那么 Email 节点聚合邻居信息时,会收到三条消息:

来自 t1 的消息:
CONCAT( h_t1, φ("a@email.com") )

来自 t2 的消息:
CONCAT( h_t2, φ("b@email.com") )

来自 t3 的消息:
CONCAT( h_t3, φ("c@email.com") )

然后做平均聚合:

h_N(Email)
=
mean(
  message(t1 -> Email),
  message(t2 -> Email),
  message(t3 -> Email)
)

这是什么意思?

就是让 Email 这个列节点学到:

Email 这一列整体上出现了哪些值、分布是什么、有没有异常模式

比如如果大部分 Email 都像:

xxx@email.com

但有一个是:

bob@emaix.com

那么这个异常值的信息会通过边特征参与聚合。


5. 再用行节点 t2 举例

现在看行节点 t2

它的邻居是所有列节点:

N(t2) = {CustomerID, Email, Age}

对应边特征:

t2 -- CustomerID:φ("C100")
t2 -- Email:φ("b@email.com")
t2 -- Age:φ("21")

所以 t2 收到的消息是:

来自 CustomerID 列节点的消息:
CONCAT( h_CustomerID, φ("C100") )

来自 Email 列节点的消息:
CONCAT( h_Email, φ("b@email.com") )

来自 Age 列节点的消息:
CONCAT( h_Age, φ("21") )

聚合后:

h_N(t2)
=
mean(
  message(CustomerID -> t2),
  message(Email -> t2),
  message(Age -> t2)
)

这是什么意思?

就是让 t2 这个行节点学到:

这一整行的上下文是什么

也就是:

这个人/客户的 CustomerID 是 C100,
Email 是 b@email.com,
Age 是 21。

6. 节点更新公式是什么意思?

论文的节点更新公式是:

在这里插入图片描述

h_v^(p)
=
σ( W^(p) · CONCAT( h_v^(p-1), h_N(v)^(p) ) )

这句话翻译成人话:

节点的新表示 = 把“自己原来的表示”和“邻居传来的综合信息”拼起来,再经过一层神经网络变换。

逐个符号看:

h_v^(p-1):节点 v 原来的表示
h_N(v)^(p):邻居聚合后的信息
CONCAT:拼接
W^(p):可训练参数矩阵
σ:激活函数
h_v^(p):更新后的节点表示

7. 用 t2 节点完整走一遍

假设现在更新行节点 t2

第一步:t2 原来的表示

一开始:

h_t2^(0) = [1, 1, 1]

论文里说 tuple node 初始化为常量向量。也就是说,所有行节点一开始可能都差不多。

第二步:t2 收集邻居消息

t2 连着三个列节点:

CustomerID, Email, Age

每条消息都包含:

邻居列节点表示 + 单元格值边特征

例如:

message(CustomerID -> t2)
=
CONCAT( h_CustomerID^(0), φ("C100") )

message(Email -> t2)
=
CONCAT( h_Email^(0), φ("b@email.com") )

message(Age -> t2)
=
CONCAT( h_Age^(0), φ("21") )

第三步:聚合邻居消息

假设聚合函数是平均:

h_N(t2)^(1)
=
mean(
  message(CustomerID -> t2),
  message(Email -> t2),
  message(Age -> t2)
)

这个 h_N(t2)^(1) 就表示:

t2 这行从所有列和单元格值里汇总来的上下文信息

第四步:更新 t2 的表示

然后用节点更新公式:

h_t2^(1)
=
σ( W^(1) · CONCAT( h_t2^(0), h_N(t2)^(1) ) )

意思是:

t2 新表示
=
神经网络(
  t2 原来的表示
  +
  t2 从邻居那里汇总来的信息
)

更新完以后,h_t2^(1) 就不再只是一个普通行节点,而是带有这一行内容的信息了。


8. 两层传播为什么有用?

一层传播后:

行节点知道自己这一行的值
列节点知道自己这一列的值

两层传播后,信息可以走得更远。

比如要检查:

CustomerID -> Email

我们关心的是:

t1.CustomerID = C100
t2.CustomerID = C100
但 t1.Email ≠ t2.Email

这个关系涉及:

t1 行
t2 行
CustomerID 列
Email 列

信息传播路径可以理解成:

t1 的 CustomerID 信息
→ CustomerID 列节点
→ t2 行节点

t1 的 Email 信息
→ Email 列节点
→ t2 行节点

这样 t2 的表示里就有机会感知:

和自己共享 CustomerID 模式的其他行,
它们的 Email 分布是什么样

当然,论文里的二部图结构本身比较粗,具体能学到多少取决于 edge embedding、训练标签和 GNN 参数。但设计意图就是:通过行节点和列节点之间的信息传播,让模型捕捉跨行、跨列的关系模式。论文也说,二部图允许信息在 tuple 和 attribute 两个维度上传播,适合捕捉 functional dependencies、cross-column constraints 等局部规则难以表达的错误模式。


9. 一个更直观的“聊天”比喻

你可以把二部图看成一个班级群:

行节点 = 每个学生
列节点 = 每门课程
边特征 = 学生在这门课的成绩

比如:

学生 t2 -- 数学课,边特征是 95
学生 t2 -- 英语课,边特征是 60

如果数学课节点想知道“这门课整体情况”,它要听所有学生发来的消息:

学生是谁 + 他数学多少分

如果学生 t2 想知道“自己的整体画像”,他要听所有课程发来的消息:

课程是什么 + 自己这门课多少分

所以消息传播公式就是:

每个节点听邻居说话;
邻居说话时,不仅说“我是哪个节点”,还要说“我们之间这条边上的值是什么”。

边特征就是“说话内容里最重要的那个值”。


10. 最后这些节点表示怎么用于判断单元格?

经过两层 GNN 后,要判断单元格 D[i,j],比如:

D[2, Email]

它对应图中的边:

t2 -- Email

模型取:

h_t2^(2):第 2 行节点最终表示
h_Email^(2):Email 列节点最终表示

拼接后送入 MLP:

q̂_i,j,k = MLP( h_t2^(2) || h_Email^(2) )

输出:

这个单元格在第 k 个 GNN 节点上走 true_child 的概率

论文也是这样:经过两层传播后,拼接 tuple node 和 attribute node 的 embedding,再输入两层 MLP 输出分支概率,并用二元交叉熵训练。


11. 用一句话把三个公式串起来

h_e = φ(D[i,j])

表示:

先把每个单元格值变成边上的向量。
h_N(v) = AGG{ σ(CONCAT(h_neighbor, h_edge)) }

表示:

节点从所有邻居那里收消息,每条消息由“邻居是谁 + 单元格值是什么”组成。
h_v = σ(W · CONCAT(h_v_old, h_N(v)))

表示:

节点把自己的旧信息和邻居汇总信息合并,得到新的节点表示。

最终:

MLP(h_row || h_column)

表示:

用更新后的行表示和列表示,判断这个单元格在 GNN 节点该走 true 还是 false。

所以你可以这样记:

边特征负责提供“单元格的值”;
消息传播负责让“值的信息”在行和列之间流动;
节点更新负责把这些信息变成新的行/列表示;
MLP 负责用行/列表示判断某个单元格的分支。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐