共计 6423 个字符,预计需要花费 17 分钟才能阅读完成。
原文:https://yubincloud.github.io/notebook/pages/paper/kg/TransE/
TransE 及其实现
1. What is TransE?
TransE (Translating Embedding), an <u>energy-based</u> model for learning low-dimensional embeddings of entities.
核心思想:将 relationship 视为一个在 embedding space 的 translation。如果 (h, l, t) 存在,那么 $h + l \approx t$。
Motivation:一是在 Knowledge Base 中,层次化的关系是十分常见的,translation 是一种很天然的用来示意它们的变换;二是近期一些从 text 中学习 word embedding 的钻研发现,一些不同类型的实体之间的 1-to-1 的 relationship 能够被 model 示意为在 embedding space 中的一种 translation。
2. Learning TransE
TransE 的训练算法如下:
<center><img src=”https://notebook-img-1304596351.cos.ap-beijing.myqcloud.com/img/image-20220917220547146.png” alt=”image-20220917220547146″ style=”zoom:80%;” /></center>
2.1 输出参数
- training set $S$:用于训练的三元组的汇合,entity 的汇合为 $E$,rel. 的汇合为 $L$
- margin $\gamma$:损失函数中的距离,这个在原 paper 中形容很含糊
- 每个 entity 或 rel. 的 embedding dim $k$
2.2 训练过程
初始化:对每一个 entity 和 rel. 的 embedding vector 用 xavier_uniform 散布来初始化,而后对它们施行 L1 or L2 正则化。
loop:
- 在 entity embedding 被更新前进行一次归一化,这是通过人为减少 embedding 的 norm 来避免 loss 在训练过程中极小化。
- sample 出一个 mini-batch 的正样本汇合 $S_{batch}$
- 将 $T_{batch}$ 初始化为空集,它示意本次 loop 用于训练 model 的数据集
-
for $(h,l,t) \in S_{batch}$ do:
- 依据 (h, l, t) 结构出一个谬误的三元组 $(h’, l, t’)$
- 将 positive sample $(h,l,t)$ 和 negative sample $(h’,l,t’)$ 退出到 $T_{batch}$ 中
- 计算 $T_{batch}$ 每一对 positive sample 和 negative sample 的 loss,而后累加起来用于更新 embedding matrix。每一对的 loss 计算形式为:$loss = [\gamma + d(h+l,t) – d(h’+l,t’)]_+$
这个过程中,triplet 的 energy 就是指的 $d(h+l,t)$,它掂量了 $h+l$ 与 $t$ 的间隔,能够采纳 L1 或 L2 norm,即 $||h + r – t||$ 具体计算形式可见代码实现。
loss 的计算中,$[x]_+ = \max(0,x)$。
对于 margin $\gamma$ 的含意,它相当于是一个正确 triple 与谬误 triple 之前的距离修改,margin 越大,则两个 triple 之前被修改的距离就越大,则对于 embedding 的修改就越严格。咱们看 $loss = [\gamma + d(h+l,t) – d(h’+l,t’)]_+$,咱们心愿是 $d(h+l,t)$ 越小越好,所以这一项后面为正号,心愿 $d(h’+l,t’)$ 越大越好,所以这一项后面为负号。失常状况下,$d(h+l,t)$ 肯定是小于 $d(h’+l,t’)$ 的,所以 $d(h+l,t) – d(h’+l,t’)$ 的后果应该是负值,那么 loss function 的外层取正就使得 loss = 0 了,所以 margin $\gamma$ 的存在就使得负样本的 distance 必须与比正样本的 distance 大出 margin 的大小来才行,当两者差距足够大时,loss 就等于 0 了。
假如 $d(h+l,t)$ 处于现实状况下等于 0,那么因为 $\gamma$ 的存在,$d(h’+l,t’)$ 如果不是很大的话,依然会产生 loss,只有当 $d(h’+l,t’)$ 大于 $\gamma$ 时才会让 loss = 0,所以 $\gamma$ 越大,对 embedding 的修改就越严格。
谬误三元组的构造方法:将 $(h,l,t)$ 中的头实体、关系和尾实体其中之一随机替换为其余实体或关系来失去。
2.3 评估指标
链接预测 是用来预测三元组 (h,r,t) 中缺失实体 h, t 或 r 的工作,对于每一个缺失的实体,模型将被要求用所有的常识图谱中的实体作为候选项进行计算,并进行排名,而不是单纯给出一个最优的预测后果。
- <mark>Mean rank</mark> – <font color=”red”> 正确三元组在测试样本中的得分排名,越小越好 </font>
首先对于每个 testing triple,以预测 tail entity 为例,咱们将 $(h,r,t)$ 中的 t 用 KG 中的每个 entity 来代替,而后通过 $f_r(h,t)$ 来计算分数,这样就能够失去一系列的分数,而后将这些分数排列。咱们晓得 f 函数值越小越好,那么在后面的排列中,排地越靠前越好。重点来了,咱们去看每个 testing triple 中正确答案(也就是实在的 t)在上述序列中排多少位,比方 $t_1$ 排 100,$t_2$ 排 200,$t_3$ 排 60 ….,之后对这些排名求均匀,就失去 mean rank 值了。
- <mark>Hits@10</mark> – <font color=”red”> 得分排名前 n 名的三元组中,正确三元组的占比,越大越好 </font>
还是依照上述进行 f 函数值排列,而后看每个 testing triple 正确答案是否排在序列的前十,如果在的话就计数 +1,最终 (排在前十的个数) / (总个数) 就等于 Hits@10。
在原论文中,因为这个 model 比拟老了,其 baseline 也没啥参考性,就不做钻研了,具体的试验可参考论文。
3. TransE 优缺点
长处:与以往模型相比,TransE 模型参数较少,计算复杂度低,却能间接建设实体和关系之间的简单语义分割,在 WordNet 和 Freebase 等 dataset 上较以往模型的 performance 有了显著晋升,特地是在大规模稠密 KG 上,TransE 的性能尤其惊人。
毛病:在解决简单关系(1-N、N-1 和 N-N)时,性能显著升高,这与 TransE 的模型假如有密切关系。假如有 (美国,总统,奥巴马)和(美国,总统,布什),这里的“总统”关系是典型的 1-N 的简单关系,如果用 TransE 对其进行学习,则会有:
<center><img src=”https://notebook-img-1304596351.cos.ap-beijing.myqcloud.com/img/image-20220917220710170.png” alt=”image-20220917220710170″ style=”zoom:67%;” /></center>
那么这将会使奥巴马和布什的 vector 变得雷同。所以因为这些简单关系的存在,导致 TransE 学习失去的实体示意辨别性较低。
4. TransE 实现
这里抉择用 pytorch 来实现 TransE 模型。
4.1 __init__
函数
其参数有:
- ent_num:entity 的数量
- rel_num:relationship 的数量
- dim:每个 embedding vector 的维度
- norm:在计算 $d(h+l,t)$ 时是应用 L1 norm 还是 L2 norm,即 $d(h+l,t)=||h+l-t||_{L1 \ or \ L2}$
- margin:损失函数中的距离,是个 hyper-parameter
- $\alpha$:损失函数计算中的正则化项参数
class TransE(nn.Module):
def __init__(self, ent_num, rel_num, device, dim=100, norm=1, margin=2.0, alpha=0.01):
super(TransE, self).__init__()
self.ent_num = ent_num
self.rel_num = rel_num
self.device = device
self.dim = dim
self.norm = norm # 应用 L1 范数还是 L2 范数
self.margin = margin
self.alpha = alpha
# 初始化实体和关系示意向量
self.ent_embeddings = nn.Embedding(self.ent_num, self.dim)
torch.nn.init.xavier_uniform_(self.ent_embeddings.weight.data)
self.ent_embeddings.weight.data = F.normalize(self.ent_embeddings.weight.data, 2, 1)
self.rel_embeddings = nn.Embedding(self.rel_num, self.dim)
torch.nn.init.xavier_uniform_(self.rel_embeddings.weight.data)
self.rel_embeddings.weight.data = F.normalize(self.rel_embeddings.weight.data, 2, 1)
# 损失函数
self.criterion = nn.MarginRankingLoss(margin=self.margin)
初始化 embedding matrix 时,间接用 nn.Embedding
来实现,参数别离是 entity 的数量和每个 embedding vector 的维数,这样失去的就是一个 ent_num * dim 大小的 Embedding Matrix。
torch.nn.init.xavier_uniform_
是一个遵从均匀分布的 Glorot 初始化器,在这里做的就是对 Embedding Matrix 中每个地位填充一个 xavier_uniform 初始化的值,这些值从均匀分布 $U(-a,a)$ 中采样失去,这里的 $a$ 是:
$$a = gain \times \sqrt{\frac{6}{fan\_in + fan\_out}}$$
在这里,对于 Embedding 这样的二维矩阵来说,fan_in 和 fan_out 就是矩阵的长和宽,gain 默认为 1。其残缺具体行为可参考 pytorch 初始化器文档。
F.normalize(self.ent_embeddings.weight.data, 2, 1)
这一步就是对 ent_embeddings 的每一个值除以 dim = 1 上的 2 范数值,留神 ent_embeddings.weight.data 的 size 是 (ent_num, embs_dim)。具体来说就是这一步把每行都除以该行下所有元素平方和的开方,也就是 $l \leftarrow l / ||l||$。
损失函数这里先跳过,之后计算损失的步骤一起来看。
4.2 从 ent_idx 到 ent_embs
因为 network 的输出是 ent_idx,因而须要将其依据 embedding matrix 转换成 ent_embs。咱们通过 get_ent_resps
函数来实现,其实就是个动态查表的操作:
class TransE(nn.Module):
...
def get_ent_resps(self, ent_idx): #[batch]
return self.ent_embeddings(ent_idx) # [batch, emb]
4.3 计算 energy $d(h+l, t)$
它掂量了 $h+l$ 与 $t$ 的间隔,能够采纳 L1 或 L2 norm 来算,具体采纳哪个由 __init__
函数中的 self.norm 来决定:
class TransE(nn.Module):
...
def distance(self, h_idx, r_idx, t_idx):
h_embs = self.ent_embeddings(h_idx) # [batch, emb]
r_embs = self.rel_embeddings(r_idx) # [batch, emb]
t_embs = self.ent_embeddings(t_idx) # [batch, emb]
scores = h_embs + r_embs - t_embs
# norm 是计算 loss 时的正则化项
norms = (torch.mean(h_embs.norm(p=self.norm, dim=1) - 1.0)
+ torch.mean(r_embs ** 2) +
torch.mean(t_embs.norm(p=self.norm, dim=1) - 1.0)) / 3
return scores.norm(p=self.norm, dim=1), norms
4.4 计算 loss
self.criterion 是通过实例化 MarginRankingLoss 失去的,这个类的初始化接管 margin 参数,实例化失去 self.criterion,其计算形式如下:
$$criterion(x_1,x_2,y) = \max(0, -y \times (x_1 – x_2) + margin)$$
借助于此,咱们能够实现计算 loss 的代码:
class TransE(nn.Module):
...
def loss(self, positive_distances, negative_distances):
target = torch.tensor([-1], dtype=torch.float, device=self.device)
return self.criterion(positive_distances, negative_distances, target)
positive_distances 就是 $d(h+l,t)$,negative_distances 就是 $d(h’+l, t’)$,target = [-1],代入 criterion 的计算公式就是咱们计算 一对正样本和负样本的 loss 了。
4.5 forward
class TransE(nn.Module):
...
def forward(self, ph_idx, pr_idx, pt_idx, nh_idx, nr_idx, nt_idx):
pos_distances, pos_norms = self.scoring(ph_idx, pr_idx, pt_idx)
neg_distances, neg_norms = self.scoring(nh_idx, nr_idx, nt_idx)
tmp_loss = self.loss(pos_distances, neg_distances)
tmp_loss += self.alpha * pos_norms # 正则化项
tmp_loss += self.alpha * neg_norms # 正则化项
return tmp_loss, pos_distances, neg_distances
以上咱们讲完了 TransE 模型的定义,接下来就是讲对 TransE 模型的训练了,只有了解了 TransE 模型的定义,其训练应该不是难事。
对于我的 TransE 及常识示意学习模型的实现:https://github.com/yubinCloud/KRL,仓库中的 transe.ipynb 在更改一下 dataset 的地位后即可运行,dataset 能够在 GitHub 中下载,比方 KGDatasets。
如果有疑难,欢送探讨。
本文由 mdnice 多平台公布