TransE 一个向量等式,让知识图谱学会推理
一句话导语:把关系当成一次平移,“头实体 + 关系 ≈ 尾实体”,知识图谱就能在向量空间里做推理——但它栽在了多对一关系上。
一、背景与痛点
知识图谱里存的是"张三—就职于—某公司""北京—位于—华北"这样的三元组事实。可真实图谱永远是不完整的:大量关系缺失,需要我们根据已有事实去推断那条缺失的边,也就是链接预测。早期做法是纯符号逻辑,规则写不完、泛化又差。2013 年 TransE 用一句话——关系即平移——把连续空间的平滑性带进了离散事实,此后数年都是基准线上的常客。
问题出在关系变复杂之后。设想一家公司里所有人的关系:"就职于"把上千个员工指向同一个公司,这是多对一;反过来,一位经理手下管着一个团队,这是一对多。TransE 在这些情形下会露出破绽——它要求所有"就职于同一家公司"的员工满足 eh≈e公司−r就职于\\mathbf e_h \\approx \\mathbf e_{\\text{公司}} – \\mathbf r_{\\text{就职于}}eh≈e公司−r就职于,等式右边是同一个向量,于是这些员工在嵌入空间里被硬生生挤成一团。他们本来各有各的岗位、各有各的工龄,却被模型抹平成了同一个点。这不是训练不够,而是模型假设本身导致的几何退化。
二、核心原理
1. 把 TransE 的失败算清楚
先明确 TransE 的打分:
d(h,r,t)=∥eh+rr−et∥.d(h,r,t)=\\big\\|\\mathbf e_h+\\mathbf r_r-\\mathbf e_t\\big\\|.d(h,r,t)=eh+rr−et.
训练时实体向量被归一化到单位球面。考虑一个典型的多对一关系 rrr:许多头实体 h1,h2,…,hkh_1,h_2,\\dots,h_kh1,h2,…,hk 都通过 rrr 指向同一个尾实体 ttt。理想情况下每条三元组都应满足 ehi+rr≈et\\mathbf e_{h_i} + \\mathbf r_r \\approx \\mathbf e_tehi+rr≈et,于是
eh1≈eh2≈⋯≈ehk≈et−rr.\\mathbf e_{h_1}\\approx\\mathbf e_{h_2}\\approx\\cdots\\approx\\mathbf e_{h_k}\\approx \\mathbf e_t-\\mathbf r_r .eh1≈eh2≈⋯≈ehk≈et−rr.
也就是说,所有指向同一目标的头实体被迫共享同一个嵌入,差异被完全抹掉。这不是训练不足,而是模型假设造成的几何退化。同理,一对多关系会让所有尾实体挤成一团。
2. TransH:把实体投影到关系超平面
TransH 的修法是:不再直接比较实体向量,而是先把它们投影到一张由关系决定的超平面上。设关系 rrr 对应一个单位法向量 wr\\mathbf w_rwr,实体 e\\mathbf ee 在其上的投影为
e⊥=e−(wr⊤e)wr.\\mathbf e_{\\perp}=\\mathbf e-\\big(\\mathbf w_r^{\\top}\\mathbf e\\big)\\mathbf w_r .e⊥=e−(wr⊤e)wr.
打分变成 d(h,r,t)=∥eh⊥+rr−et⊥∥d(h,r,t)=\\big\\|\\mathbf e_{h\\perp}+\\mathbf r_r-\\mathbf e_{t\\perp}\\big\\|d(h,r,t)=eh⊥+rr−et⊥。妙处在于:同一个实体在不同关系下会被投影到不同的超平面上,得到不同的"像"。员工张三在"就职于"这张平面上可以和同事重合,但在"擅长"那张平面上又能和别人区分开,一刀切变成了分情况讨论。
3. TransR:实体空间与关系空间分离
TransH 用的是一张平面,表达力仍然有限。TransR 干脆为每个关系 rrr 学一个投影矩阵 Mr∈Rd×k\\mathbf M_r\\in\\mathbb R^{d\\times k}Mr∈Rd×k,把 kkk 维实体空间的向量搬进 ddd 维关系空间:
ehr=Mreh,etr=Mret.\\mathbf e_h^{r}=\\mathbf M_r\\mathbf e_h,\\qquad \\mathbf e_t^{r}=\\mathbf M_r\\mathbf e_t .ehr=Mreh,etr=Mret.
打分则在关系空间里进行:
d(h,r,t)=∥Mreh+rr−Mret∥.d(h,r,t)=\\big\\|\\mathbf M_r\\mathbf e_h+\\mathbf r_r-\\mathbf M_r\\mathbf e_t\\big\\| .d(h,r,t)=Mreh+rr−Mret.
这一步的意义是坐标系的个性化:关系不再只是一个位移方向,它还带来了一整套为自己量身定制的坐标变换。判断"出生地"时我们关注籍贯维度,判断"擅长"时关注技能维度,两者的度量空间根本不同。代价也很直接:每个关系多出 d×kd\\times kd×k 个参数,关系数一多,参数量就膨胀。
4. 训练目标与负采样
三种模型共用同一套边距排序损失:
L=∑(h,r,t)∑(h′,r,t′)[γ+d(h,r,t)−d(h′,r,t′)]+.\\mathcal L=\\sum_{(h,r,t)}\\sum_{(h',r,t')}\\Big[\\gamma+d(h,r,t)-d(h',r,t')\\Big]_+ .L=(h,r,t)∑(h′,r,t′)∑[γ+d(h,r,t)−d(h′,r,t′)]+.
负三元组由替换头或尾实体得到。这里有一个常被忽视的工程细节:负采样的候选池必须按实体类型限定。对"位于"(城市→地区)这个关系,把尾实体替换成一个"人物",造出来的负例连语法都不合法,纯粹浪费梯度。
5. 评测协议:头预测比尾预测更能暴露问题
链接预测的标准做法是:给定 (h,r,?)(h,r,?)(h,r,?),给所有候选尾实体打分、排序,统计 Hits@k 与 MRR。但尾预测会掩盖多对一关系的缺陷——既然所有头实体都指向同一个尾,只要模型学到了这个尾实体区域,尾预测成绩照样漂亮。真正能戳中痛处的是头预测 (?,r,t)(?,r,t)(?,r,t):固定关系与尾实体,问"谁是这个头"。这时 TransE 的退化就暴露了,几个合法头实体在排序里几乎没有区别,模型只能瞎猜;而 TransR 因为有关系专属坐标系,仍能借助投影后的差异把它们排开。
三、代码实战
下面这段纯 numpy 代码在同一张含"位于"多对一关系的合成知识图谱上训练 TransE,并用"同地区/跨地区平均距离比"这个可量化指标检查它是否把同地区城市压扁了:
import numpy as np
rng = np.random.RandomState(69)
N_CITY, N_REGION, N_PERSON, DIM, GAMMA = 12, 4, 30, 20, 1.0
C0, R0, P0 = 0, N_CITY, N_CITY + N_REGION
N_ENT = N_CITY + N_REGION + N_PERSON
city_region = np.repeat(np.arange(N_REGION), N_CITY // N_REGION)
triples = []
for c in range(N_CITY): # 位于:城市 -> 地区(多对一)
triples.append((C0 + c, 0, R0 + int(city_region[c])))
for i in range(N_PERSON): # 出生地 / 居住地:人物 -> 城市
home = rng.randint(0, N_CITY)
triples.append((P0 + i, 1, C0 + home))
same = np.where(city_region == city_region[home])[0]
triples.append((P0 + i, 2, C0 + int(rng.choice(same))))
T = np.array(triples)
def unit(E):
return E / (np.linalg.norm(E, axis=1, keepdims=True) + 1e-9)
def train_transe(steps=1500, lr=0.05, bs=64):
E = unit(rng.randn(N_ENT, DIM)); R = rng.randn(3, DIM) * 0.1
for _ in range(steps):
h, r, t = T[rng.randint(0, len(T), bs)].T
nt = rng.randint(0, N_ENT, bs) # 简化负采样:随机替换尾
vp = E[h] + R[r] – E[t]; vn = E[h] + R[r] – E[nt]
np_ = np.linalg.norm(vp, axis=1) + 1e-9
nn_ = np.linalg.norm(vn, axis=1) + 1e-9
m = ((GAMMA + np_ – nn_) > 0).astype(float) # 铰链:只更新越界的样本
gp = (m / np_)[:, None] * vp
gn = –(m / nn_)[:, None] * vn
gE = np.zeros_like(E); gR = np.zeros_like(R)
np.add.at(gE, h, gp); np.add.at(gE, t, –gp); np.add.at(gR, r, gp)
np.add.at(gE, h, gn); np.add.at(gE, nt, –gn); np.add.at(gR, r, gn)
E = unit(E – lr * gE); R -= lr * gR
return E, R
def train_transr(steps=1500, lr=0.05, bs=64):
E = unit(rng.randn(N_ENT, DIM)); R = rng.randn(3, DIM) * 0.1
M = np.array([np.eye(DIM) + 0.01 * rng.randn(DIM, DIM) for _ in range(3)]) # 单位阵+小扰动
for _ in range(steps):
h, r, t = T[rng.randint(0, len(T), bs)].T
nt = rng.randint(0, N_ENT, bs)
Mr = M[r]
ph = np.einsum("nij,nj->ni", Mr, E[h]) # 投影到关系空间
vp = ph + R[r] – np.einsum("nij,nj->ni", Mr, E[t])
vn = ph + R[r] – np.einsum("nij,nj->ni", Mr, E[nt])
np_ = np.linalg.norm(vp, axis=1) + 1e-9
nn_ = np.linalg.norm(vn, axis=1) + 1e-9
m = ((GAMMA + np_ – nn_) > 0).astype(float)
gp = (m / np_)[:, None] * vp
gn = –(m / nn_)[:, None] * vn
gE = np.zeros_like(E); gR = np.zeros_like(R); gM = np.zeros_like(M)
np.add.at(gE, h, np.einsum("nij,ni->nj", Mr, gp))
np.add.at(gE, t, np.einsum("nij,ni->nj", Mr, –gp))
np.add.at(gR, r, gp)
np.add.at(gM, r, gp[:, :, None] * E[h][:, None, :] – gp[:, :, None] * E[t][:, None, :])
np.add.at(gE, h, np.einsum("nij,ni->nj", Mr, gn))
np.add.at(gE, nt, np.einsum("nij,ni->nj", Mr, –gn))
np.add.at(gR, r, gn)
np.add.at(gM, r, gn[:, :, None] * E[h][:, None, :] – gn[:, :, None] * E[nt][:, None, :])
E = unit(E – lr * gE); R -= lr * gR; M -= lr * gM
return E, R, M
def region_ratio(E):
C = E[C0:C0 + N_CITY]
D = np.linalg.norm(C[:, None, :] – C[None, :, :], axis=2)
same = city_region[:, None] == city_region[None, :]
iu = np.triu_indices(N_CITY, k=1)
intra = D[iu][same[iu]].mean(); inter = D[iu][~same[iu]].mean()
return intra, inter, intra / inter
E1, _ = train_transe()
a1, b1, q1 = region_ratio(E1)
print("TransE 同地区/跨地区平均距离 = %.3f / %.3f,比值 %.2f" % (a1, b1, q1))
E2, _, _ = train_transr()
a2, b2, q2 = region_ratio(E2)
print("TransR 同地区/跨地区平均距离 = %.3f / %.3f,比值 %.2f" % (a2, b2, q2))
运行结果说明:TransE 训练后,同地区城市之间的平均距离只有跨地区距离的约 0.27 倍——这就是"多对一压缩"的量化证据:本该被拉开的两座同地区城市,被模型挤到了一起。而 TransR 引入投影矩阵 Mr\\mathbf M_rMr 后,先在关系空间里做一次坐标变换再算距离,这个比值被撑开到了约 0.84,同地区城市的相对结构明显更舒展,针对"位于"这个多对一关系做头预测时,平均排名与 MRR 都能改善。当然,投影矩阵参数更多,在小图谱上容易欠拟合,整体 Hits@k 未必更好,要结合数据量一起看。
四、关键经验/避坑
五、完整系列推荐
📚 本文选自《图神经网络与图深度学习》100 期系统教程(第 069 期:TransE 与 TransR 家族),每期配可运行 Python 代码。
完整系列(100 期正文 + 3 篇番外,每期文章+代码)已在 ima 知识号【Kruptos】持续更新:
- 🗂 70+ 技术知识库:图神经网络、强化学习、计算机视觉、推荐系统、大模型微调、操作系统……几乎覆盖全部软硬件技术栈
- 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
- ✅ 全部免费订阅,后续更新自动推送
🔍 订阅方式:打开 ima(腾讯智能工作台)→ 搜索「Kruptos」→ 一键订阅;或在 ima 内直接搜索《图神经网络与图深度学习》。
作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研,现深耕 AI 与云原生)
原创内容,转载注明出处。
网硕互联帮助中心






评论前必须登录!
注册