赞
踩
本课程来自深度之眼,部分截图来自课程视频。
文章标题:Translating Embeddings for Modeling Multi-relational Data
知识图谱向量化表示
作者:Antoine Bordes, Nicolas Usunier, Alberto Garcia-Duran/Jason Weston, Oksana Yakhnenko
单位:CNRS, France/Google
发表会议及时间:NIPS 2013
公式输入请参考:在线Latex公式
文章虽然较早,但是有好几个衍生模型:trans算法系列,本文将一并介绍。
TransH:Knowledge Graph Embedding by Translating on Hyperplanes
TransR:Learning entity and relation embeddings for knowledge graph completion
TransD:Knowledge Graph Embedding via Dynamic Mapping Matrix
本文是GNN系列的第二篇非简单图,也是这个系列的最后一篇。
上篇论文讲的是异质图,这篇是知识图谱。
知识图谱常用三元组(triples)来表示,例如:(Beijing,Capital City,China)
上例中前后两个元素论文中都称为实体,中间元素称为关系,Beijing是Header,Capital City是Relation,China是Tail。从图的角度来看实体就是结点,关系就是边。本文的目的就是:Learn low-dimensional vectors for words and KB(knowledge base) entities and relations.
知识图谱的应用场景有:
Question answering
Search
Recommender Systems
Natural language understanding
论文中提到的多关系数据的定义,原文用的是Multi-relational Data,其实就是知识图谱。下面来看Multi-relational Data和GRAPH的对应关系。
Data is structured as a graph
Each node=an entity
Each edge=a relation/fact
A relation=(sub, rel, obj):
. sub=subject,
. rel=relation type,
. obj=object.
Nodes w/o features.
对于上面这个知识图谱的例子,我们如果可以把里面的实体进行embedding得到低维向量(Encode entities as vectors)那么就可以得到:
Paris (0.036,-0.12,.…,0.323)
capital (0.102,0.671,.…,-0.101)
France(0.138,0.551,…,0.222)
有三个方面用途:
下面来看knowledge graph的一个例子:
假如我们现在有如下三元组Triples
(beyonce_knowles, member_of, destinys_child)
(kelly_rowland, member_of, destinys_child)
(michelle_williams, member_of, destinys_child)
(destinys_child, start_date,1997)
(destinys_child, end_date,2005)
那么就可以构造下图的KG
这个算法的大概思想就是想把三元组(h,r,t)都映射到一个D维空间中,并使得三元组中的三个向量满足如下图所示的关系,就是
h
⃗
+
r
⃗
=
t
⃗
(1)
\vec{h}+\vec{r}=\vec{t}\tag1
h
+r
=t
(1)
因此整个Trans系列的算法中的Translation就是指的从h通过r Translation到t
第九行是负采样;
第十二行是梯度下降更新loss
对于TransH而言,是将h映射到r的超平面上得到
h
⊥
h_{\perp }
h⊥,然后t映射到r超平面上得到
t
⊥
t_{\perp }
t⊥,最后满足:
h
⊥
+
r
=
t
⊥
h_{\perp }+r=t_{\perp }
h⊥+r=t⊥
这个方法是思想是:实体h与多个实体t之间的存在关系,那么实体h的embedding和实体t的embedding可以通过映射,表达多个不同的关系,因此这个模型的表达能力更强。
在TransR模型中,是将h和t映射到一个空间,r映射到另外一个空间,两个空间维度不一样,之前的算法都是映射到同一个空间,维度一样。
然后用
M
r
M_r
Mr分别与h和t做运算(具体维度看总结表),可以把h和t映射到r的空间中得到
h
⊥
h_{\perp }
h⊥和
t
⊥
t_{\perp }
t⊥,最后满足:
h
⊥
+
r
=
t
⊥
h_{\perp }+r=t_{\perp }
h⊥+r=t⊥
两个数据集:Wordnet、Freebase
Baselines:Unstructured(这个重点讲)、RESCAL、SE、SME、LSM
Unstructured这个重点讲下,因为其他方法和GNN相差较远,这个方法中认为
r
⃗
=
0
\vec{r}=0
r
=0,因此其采用的损失函数是:
h
⃗
+
r
⃗
=
t
⃗
→
h
⃗
=
t
⃗
\vec{h}+\vec{r}=\vec{t}\rightarrow \vec{h}=\vec{t}
h
+r
=t
→h
=t
意思是h和t共现,那么希望他们两个的embedding越接近越好。
文中虽然只用了两个数据集,但是由于Freebase较大,所以对这个数据进行了处理,一个是精简版的15k个实体,一个是1M的实体。另外关于训练集、验证集、测试集的划分可以看上图。
参数规模,即空间复杂度不大。
本文应用了多个任务:边的预测(Link Prediction)、案例分析(Case Study)、新关系预测(New Relationship)具体在精读部分再细讲。
引用量2000+
启发了整个Trans系列知识表示学习,代表性工作有:transH,transR,transD
模型简单而有效,容易训练
学到了entity和relation的embedding表示
知识图谱上的经典工作,非常实用,还可以关注RGCN
1.使用低维向量表示三元组数据(知识图谱)的实体和关系;
We consider the problem of embedding entities and relationships of multirelational data in low-dimensional vector spaces.
2.易于训练,模型参数少(看表2),可以扩展到大的数据;
Our objective is to propose a canonical model which is easy to train, contains a reduced number of parameters and can scale up to very large databases.
3.模型的损失函数是基于实体和关系向量的计算(translations)(看公式1);
Hence, we propose TransE, a method which models relationships by interpreting them as translations operating on the low-dimensional embeddings of the entities.
4.通过边的预测等任务在两个数据集验证了模型的有效性。(SOTA)
Despite its simplicity, this assumption proves to be powerful since extensive experiments show that TransE significantly outperforms state-of-the-art methods in link prediction on two knowledge bases.
OpenKE是清华大学出品的开源库,该库实现了本课中提到的四种算法。
讲解顺序应该是:EHRD
知识图谱三元组:(h,r/l,t),其中h表示head,t表示tail,r表示关系、l表示label,本节内容中二者等价。
[
x
]
+
=
m
a
x
(
0
,
x
)
[x]_+=max(0,x)
[x]+=max(0,x)
看过ReLU就知道啥意思,不解释。
margin-based loss的理解,这个具体在算法中介绍。
n
=
∣
e
n
t
i
t
i
e
s
∣
,
m
=
∣
r
e
l
a
t
i
o
n
s
∣
n=|entities|,m=|relations|
n=∣entities∣,m=∣relations∣
n是实体数量即节点数量,m是关系数量,即边数量
原文:
Given a training set
S
S
S of triplets
(
h
,
l
,
t
)
(h,l, t)
(h,l,t) composed of two entities
h
,
t
∈
E
h, t ∈ E
h,t∈E (the set of entities) and a relationship
l
∈
L
l ∈ L
l∈L (the set of relationships), our model learns vector embeddings of the entities and the relationships. The embeddings take values in
R
k
R^k
Rk (
k
k
k is a model hyperparameter) and are denoted with the same letters, in boldface characters. The basic idea behind our model is that the functional relation induced by the
l
l
l-labeled edges corresponds to a translation of the embeddings, i.e. we want that
h
+
l
≈
t
h + l ≈ t
h+l≈t when
(
h
,
l
,
t
)
(h, l, t)
(h,l,t) holds (
t
t
t should be a nearest neighbor of
h
+
l
h + l
h+l), while
h
+
l
h + l
h+l should be far away from t otherwise. Following an energy-based framework, the energy of a triplet is equal to
d
(
h
+
l
,
t
)
\text{d}(h + l, t)
d(h+l,t) for some dissimilarity measure
d
d
d, which we take to be either the
L
1
L_1
L1 or the
L
2
L_2
L2-norm.
译文:
给定一个三元组训练集
S
=
(
h
,
l
,
t
)
S=(h,l, t)
S=(h,l,t),其中
h
,
t
∈
E
h, t ∈ E
h,t∈E代表实体集合,
l
∈
L
l ∈ L
l∈L代表关系集合,TransE模型可以学习到实体与关系的向量表示。这些向量均在
R
k
R^k
Rk的向量空间中,维度
k
k
k为超参数。TransE模型的基本思想是引入向量的translation来表示边,即
h
+
l
≈
t
h + l ≈ t
h+l≈t,当h和t有关系l的时候,我们希望上式左右两边越接近越好,如果二者没有关系则希望左右两边越远越好。这里的远近使用
d
(
h
+
l
,
t
)
\text{d}(h + l, t)
d(h+l,t)来表示,d可以用
L
1
L_1
L1 或者
L
2
L_2
L2-norm进行计算。相当于当h和t有关系l的时候,我们希望d=0,反之则d越大越好。
下面给出原文的损失函数:
L
=
∑
(
h
,
l
,
t
)
∈
S
∑
(
h
′
,
l
,
t
′
)
∈
S
(
h
,
l
,
t
)
′
[
γ
+
d
(
h
+
l
,
t
)
−
d
(
h
′
+
l
,
t
′
)
]
+
L=\sum_{(h,l, t)\in S}\sum_{(h',l, t')\in S'_{(h,l, t)}}[\gamma+\text{d}(h + l, t)-\text{d}(h' + l, t')]_+
L=(h,l,t)∈S∑(h′,l,t′)∈S(h,l,t)′∑[γ+d(h+l,t)−d(h′+l,t′)]+
其中第一个连加代表正样本,第二个连加代表相应的负样本,因此正样本
d
(
h
+
l
,
t
)
\text{d}(h + l, t)
d(h+l,t)比负样本
d
(
h
′
+
l
,
t
′
)
\text{d}(h' + l, t')
d(h′+l,t′)小,那么整个表达式就是负值(不考虑
γ
\gamma
γ)对其做
[
x
]
+
[x]_+
[x]+操作后,
[
d
(
h
+
l
,
t
)
−
d
(
h
′
+
l
,
t
′
)
]
+
=
0
[\text{d}(h + l, t)-\text{d}(h' + l, t')]_+=0
[d(h+l,t)−d(h′+l,t′)]+=0,此时loss就是最小,因此训练的目标就是要使得正样本的距离相比于负样本的距离小即可。如果考虑
γ
>
0
\gamma>0
γ>0(一般取值为1、2、10),那么训练的目标就要使得正样本的距离相比于负样本的距离小
γ
\gamma
γ,
γ
\gamma
γ就相当于一个SVM里面的Margin。
注意:同一个实体,无论其作为head或者作为tail,其embedding表示应该是一致的。
关于负样本(原文叫:corrupted triplet),文中给出的构造方法为,替换正样本中的
h
h
h,换为
h
′
h'
h′,或者替换正样本中的
t
t
t,换为
t
′
t'
t′,(和word2vec、node2vec的负样本产生思路一致)即可:
S
(
h
,
l
,
t
)
′
=
{
(
h
′
,
l
,
t
)
∣
h
′
∈
E
}
∪
(
h
,
l
,
t
′
)
∣
t
′
∈
E
}
S'_{(h,l, t)}=\{(h',l, t)|h'\in E\}\cup (h,l, t')|t'\in E\}
S(h,l,t)′={(h′,l,t)∣h′∈E}∪(h,l,t′)∣t′∈E}
下面将原文的算法重新表达一遍:
input Training set
S
=
{
(
h
,
l
,
t
)
}
S = \{(h, l, t)\}
S={(h,l,t)}, entities and rel. sets E and L, margin
γ
\gamma
γ, embeddings dim.
k
k
k.
初始化1-3
These metrics are indicative but can be flawed when some corrupted triplets end up being valid ones, from the training set for instance. In this case, those may be ranked above the test triplet, but this should not be counted as an error because both triplets are true. To avoid such a misleading behavior, we propose to remove from the list of corrupted triplets all the triplets that appear either in the training, validation or test set (except the test triplet of interest). This ensures that all corrupted triplets do not belong to the data set.
我们划分了训练集、验证集、测试集,在测试集负采样的时候,算法采样了一个它认为是负样本的三元组,这个三元组在测试集中不存在,但是在可能在训练集、验证集中这个三元组作为正样本出现过,这样进行计算结果就会出现偏差,因此算法提出一种filtered的策略,将所有数据集中出现过的正样本不出现在测试集的负样本列表中。在实验过程中也分成两组来分析,一种是使用filter,一种是不使用filter。
这个算法存在的问题在于只能解决节点间关系为1对1的关系,对于下面:
1 to many,many to 1,many to many
的关系无法解决。
在TransH中对TransE模型的不足给出了解释:
1.对于对称有向网络,根据TransE模型的假设,应该有:
h
+
r
−
t
=
0
,
t
+
r
−
h
=
0
h+r-t=0,t+r-h=0
h+r−t=0,t+r−h=0
则可以得如下结论:
r
=
0
,
h
=
t
r=0,h=t
r=0,h=t,意味着h和t两个实体的embedding一样,这样明显不对。
2.对于多对一的关系,多个
h
i
h_i
hi和t都满足
h
i
+
r
−
t
=
0
h_i+r-t=0
hi+r−t=0,则意味所有的与t有关系的
h
i
h_i
hi都相等。反之多个
t
i
t_i
ti和h有关系则意味所有
t
i
t_i
ti都相等。相等意味embedding一样,不同的实体,一样的embedding,这样明显也不合理。
To overcome the problems of TransE in modeling reflexive/one-to-many/many-to-one/many-to-many relations, we propose a model which enables an entity to have distributed representations when involved in different relations.
解决TransE存在的问题:1to many,many to 1,many to many
主要思路:entity在不同的关系上有不同的embedding
实现方式:不同的关系在hyperplane(超平面)的映射不一样
如下图所示:
h
h
h是实体header在原始坐标系的向量,用黑色箭头表示,用红色虚线表示超平面,上面红色的点是h在超平面上的投影
h
⊥
h_\perp
h⊥,从线性代数上看
h
⊥
h_\perp
h⊥实际上就是
h
h
h在超平面上以及超平面法向量
w
r
w_r
wr的两个分量,也就是说黑色箭头可以分解为蓝色和粉色箭头(从超平面平移过来),他们三个组成一个三角形,那么就是有:
蓝
色
向
量
+
粉
色
向
量
=
黑
色
向
量
蓝色向量+粉色向量=黑色向量
蓝色向量+粉色向量=黑色向量
那么
粉
色
向
量
=
黑
色
向
量
−
蓝
色
向
量
粉色向量=黑色向量-蓝色向量
粉色向量=黑色向量−蓝色向量
粉色向量就是
h
⊥
h_\perp
h⊥,黑色向量就是
h
h
h。
蓝色向量实际是
w
r
⊤
h
w
r
w_r^\top hw_r
wr⊤hwr,因为
w
r
w_r
wr刚才说了是法向量,而且是单位法向量(
∣
w
r
∣
=
1
|w_r|=1
∣wr∣=1),那么
w
r
⊤
h
=
∣
w
r
∣
∣
h
∣
c
o
s
θ
=
∣
h
∣
c
o
s
θ
w_r^\top h=|w_r||h|cos\theta=|h|cos\theta
wr⊤h=∣wr∣∣h∣cosθ=∣h∣cosθ
上面这个公式就是蓝色箭头的长度,这是因为粉色和蓝色是直角关系,蓝色和黑色夹角是
θ
\theta
θ,然后
∣
h
∣
c
o
s
θ
|h|cos\theta
∣h∣cosθ再乘上法向量
w
r
w_r
wr,就为蓝色长度加上
w
r
w_r
wr的方向。所以蓝色向量表示为
w
r
⊤
h
w
r
w_r^\top hw_r
wr⊤hwr。粉色向量就可以表示为:
h
⊥
=
h
−
w
r
⊤
h
w
r
h_\perp=h-w_r^\top hw_r
h⊥=h−wr⊤hwr
对于实体
t
t
t而言,当然相应的就有:
t
⊥
=
t
−
w
r
⊤
t
w
r
t_\perp=t-w_r^\top tw_r
t⊥=t−wr⊤twr
这里单位法向量
w
r
w_r
wr在计算t的时候不变,因为两个实体都是投影到相同的超平面上,因此二者计算过程中使用相同的法向量。
接下来就可以得到TransH算法中衡量三元组距离公式为:
f
r
(
h
,
t
)
=
∣
∣
h
⊥
+
d
r
−
t
⊥
∣
∣
2
2
=
∣
∣
(
h
−
w
r
⊤
h
w
r
)
+
d
r
−
(
t
−
w
r
⊤
t
w
r
)
∣
∣
2
2
损失函数为:
L
=
∑
(
h
,
r
,
t
)
∈
Δ
∑
(
h
′
,
r
′
,
t
′
)
∈
Δ
(
h
,
r
,
t
)
′
[
f
r
(
h
,
t
)
+
γ
−
f
r
′
(
h
′
,
t
′
)
]
+
L=\sum_{(h,r,t)\in\Delta}\sum_{(h',r',t')\in\Delta '_{(h,r,t)}}[f_r(h,t)+\gamma-f_{r'}(h',t')]_+
L=(h,r,t)∈Δ∑(h′,r′,t′)∈Δ(h,r,t)′∑[fr(h,t)+γ−fr′(h′,t′)]+
上式中,
γ
\gamma
γ是margin,
f
r
(
h
,
t
)
f_r(h,t)
fr(h,t)表示正样本(原文叫golden triplet),
f
r
′
(
h
′
,
t
′
)
f_{r'}(h',t')
fr′(h′,t′)代表负样本。损失函数目标就不描述了,和TransE一样。但是由于加了一个超平面,法向量这些东西,因此还有几个额外的约束:
∀
e
∈
E
,
∣
∣
e
∣
∣
2
≤
1
,
/
/
s
c
a
l
e
\forall e\in E,||e||_2\leq1,//scale
∀e∈E,∣∣e∣∣2≤1,//scale
∀
r
∈
R
,
∣
w
R
⊤
d
r
∣
∣
∣
d
r
∣
∣
2
≤
ϵ
,
/
/
二
者
正
交
\forall r\in R,\cfrac{|w_R^\top d_r|}{||d_r||_2}\leq\epsilon,//二者正交
∀r∈R,∣∣dr∣∣2∣wR⊤dr∣≤ϵ,//二者正交
∀
r
∈
R
,
∣
∣
w
r
∣
∣
2
=
1
,
/
/
w
r
是
单
位
法
向
量
\forall r\in R,||w_r||_2=1,//w_r是单位法向量
∀r∈R,∣∣wr∣∣2=1,//wr是单位法向量
带约束的损失函数是不好求的,作者给出了整合了约束条件的损失函数(可以参考数学基础的不等式约束约束优化):
L
=
∑
(
h
,
r
,
t
)
∈
Δ
∑
(
h
′
,
r
′
,
t
′
)
∈
Δ
(
h
,
r
,
t
)
′
[
f
r
(
h
,
t
)
+
γ
−
f
r
′
(
h
′
,
t
′
)
]
+
+
C
{
∑
e
∈
E
[
∣
∣
e
∣
∣
2
2
−
1
]
+
+
∑
r
∈
R
[
(
w
R
⊤
d
r
)
2
∣
∣
d
r
∣
∣
2
2
−
ϵ
2
]
+
}
L=\sum_{(h,r,t)\in\Delta}\sum_{(h',r',t')\in\Delta '_{(h,r,t)}}[f_r(h,t)+\gamma-f_{r'}(h',t')]_+\\+C\left \{\sum_{e\in E}[||e||_2^2-1]_++\sum_{r\in R}[\cfrac{(w_R^\top d_r)^2}{||d_r||_2^2}-\epsilon^2]_+\right \}
L=(h,r,t)∈Δ∑(h′,r′,t′)∈Δ(h,r,t)′∑[fr(h,t)+γ−fr′(h′,t′)]++C{e∈E∑[∣∣e∣∣22−1]++r∈R∑[∣∣dr∣∣22(wR⊤dr)2−ϵ2]+}
这里还要提一个比较重要的trick:Reducing False Negative Labels
因为这个模型处理多对多的关系,因此原来在TransE的模型中采样负样本的方法有可能采样到正样本,因为原来TransE的模型中采样负样本的方法是固定h或t,然后修改另外一个实体,但是由于存在多对多关系,修改为另外一个实体
t
′
t'
t′或
h
′
h'
h′有可能还是与固定的h或t存在关系,即sample后的样本是正样本。
We tend to give more chance to replacing the head entity if the relation is one-to-many and give more chance to replacing the tail entity if the relation is many-to-one.
对于一对多或者多对一情况,那就只sample一的那一方。
论文中使用了
t
p
h
tph
tph和
h
p
t
hpt
hpt来表达实体间关系,
t
p
h
tph
tph表示the average number of tail entities per head entity;
h
p
t
hpt
hpt表示the average number of head entities per tail entity。
t
p
h
>
1
tph>1
tph>1,代表一个head对应多个tail,同理
h
p
t
>
1
hpt>1
hpt>1,代表一个tail对应多个head。然后替换(sample)h或者t的概率就可以这样(分母表示归一化):
t
p
h
tph
tph越大替换head,替换概率为:
t
p
h
t
p
h
+
h
p
t
\cfrac{tph}{tph+hpt}
tph+hpttph
h
p
t
hpt
hpt越大替换tail,替换概率为:
h
p
t
t
p
h
+
h
p
t
\cfrac{hpt}{tph+hpt}
tph+hpthpt
解决TransE/TransH存在的问题:relation与entity由于是不同类型的点,可能存在于不同的空间。映射到超平面仍然是在相同空间中。
Both TransE and TransH assume embeddings of entities and relations being in the same space
R
k
R^k
Rk. But relations and entities are completely different objects, it may be not capable to represent them in a common semantic space.
主要思路:将entity映射到relation的空间上
实现方式:矩阵(matrix)的映射
M
r
M_r
Mr(projection matrix)
上图是论文原图,我在relation空间中映射后的
h
r
h_r
hr和
t
r
t_r
tr添加了蓝线和绿线,就相当于在右边的空间中做TransE。
明白了TransR模型思路后,可以知道映射到relation空间上的h和t可以表示为:
h
r
=
h
M
r
,
t
r
=
t
M
r
h_r=hM_r,t_r=tM_r
hr=hMr,tr=tMr
在relation空间上,计算距离的公式为:
f
r
(
h
,
t
)
=
∣
∣
h
r
+
r
−
t
r
∣
∣
2
2
f_r(h,t)=||h_r+r-t_r||_2^2
fr(h,t)=∣∣hr+r−tr∣∣22
损失函数就可以写出来(和TransE的一样样的):
L
=
∑
(
h
,
r
,
t
)
∈
S
∑
(
h
′
,
r
,
t
′
)
∈
S
′
[
f
r
(
h
,
t
)
+
γ
−
f
r
(
h
′
,
t
′
)
]
+
L=\sum_{(h,r,t)\in S}\sum_{(h',r,t')\in S '}[f_r(h,t)+\gamma-f_{r}(h',t')]_+
L=(h,r,t)∈S∑(h′,r,t′)∈S′∑[fr(h,t)+γ−fr(h′,t′)]+
进一步还可以对TransR的关系进行聚类得到CTransR( cluster-based TransR )
For example, the head-tail entities of the relation “location location contains” have many patterns such as country-city, country-university, continent-country and so on. Following the idea of piecewise linear regression (Ritzema and others 1994), we extend TransR by clustering diverse head-tail entity pairs into groups and learning distinct relation vectors for each group, named as cluster-based TransR (CTransR).
原文中提到的例子:国家-城市、国家-大学、洲-国家,这些实体之间的关系都是:地点-地点-包含关系。那么既然关系是可以分类的,那么论文中就用h-t来计算r,然后将r进行聚类,得到CTransR。
All entity pairs
(
h
,
t
)
(h, t)
(h,t) are represented with their vector offsets
(
h
−
t
)
(h − t)
(h−t) for clustering, where
h
h
h and
t
t
t are obtained with TransE. Afterwards, we learn a separate relation vector
r
c
r_c
rc for each cluster and matrix
M
r
M_r
Mr for each relation, respectively. We define the projected vectors of entities as
h
r
,
c
=
h
M
r
h_{r,c} = hM_r
hr,c=hMr and
t
r
,
c
=
t
M
r
t_{r,c} = tM_r
tr,c=tMr, and the score function is defined as:
f
r
(
h
,
t
)
=
∣
∣
h
r
,
c
+
r
c
−
t
r
,
c
∣
∣
2
2
+
α
∣
∣
r
c
−
r
∣
∣
2
2
f_r(h,t)=||h_{r,c}+r_c-t_{r,c}||_2^2+\alpha||r_c-r||_2^2
fr(h,t)=∣∣hr,c+rc−tr,c∣∣22+α∣∣rc−r∣∣22
这里先用TransE训练得到实体embedding表示
h
h
h和
t
t
t,然后用聚类算法(kNN什么的都可以)对关系进行聚类得到
r
c
r_c
rc,这里要注意,无论有多少类
r
c
r_c
rc,投影矩阵
M
r
M_r
Mr只有一个。上式中后面一项相当于一个正则,用来保证聚类后的关系
r
c
r_c
rc和原来的关系
r
r
r距离越小越好。
损失函数和TransR的一模一样,不写了。
负样本构造方法和TransH一样。
trick: To avoid overfitting, we initialize entity and relation embeddings with results of TransE, and initialize relation matrices as identity matrices
解决TransR存在的问题:
主要思路:将
M
r
M_r
Mr用projection vector替代,使得映射由实体和关系共同决定。
In TransD, we define two vectors for each entity and relation. The first vector represents the meaning of an entity or a relation, the other one (called projection vector) represents the way that how to project a entity embedding into a relation vector space and it will be used to construct mapping matrices. Therefore, every entity-relation pair has an unique mapping matrix.
优点:TransD has less parameters and has no matrix-vector multiplication operations, which makes it can be applied on large scale graphs.
TransD模型的映射矩阵为:
M
r
h
=
r
p
h
p
⊤
+
I
m
×
n
M_{rh}=r_ph_p^\top+I^{m\times n}
Mrh=rphp⊤+Im×n
M
r
t
=
r
p
t
p
⊤
+
I
m
×
n
M_{rt}=r_pt_p^\top+I^{m\times n}
Mrt=rptp⊤+Im×n
r
p
r_p
rp是列向量,
h
p
,
t
p
h_p,t_p
hp,tp也是列向量,然后
h
p
⊤
,
t
p
⊤
h_p^\top,t_p^\top
hp⊤,tp⊤变成了行向量,列向量乘行向量变成了矩阵,这样的好处就是参数量大大减少了,原来矩阵参数
m
×
n
m\times n
m×n,变成相乘的构造方式后就为:
m
+
n
m+n
m+n
下面操作就和TransR一样了,用新定义的映射矩阵替换TransR中的
M
r
M_r
Mr即可,因此映射操作为:
h
⊥
=
M
r
h
h
,
t
⊥
=
M
r
t
t
h_\perp=M_{rh}h,t_\perp=M_{rt}t
h⊥=Mrhh,t⊥=Mrtt
距离计算:
f
r
(
h
,
t
)
=
−
∣
∣
h
⊥
+
r
−
t
⊥
∣
∣
2
2
f_r(h,t)=-||h_\perp+r-t_\perp||_2^2
fr(h,t)=−∣∣h⊥+r−t⊥∣∣22
损失函数:
L
=
∑
ε
∈
Δ
∑
ε
′
∈
Δ
′
[
γ
+
f
r
(
ε
′
)
−
f
r
(
ε
)
]
+
L=\sum_{\varepsilon \in\Delta}\sum_{\varepsilon' \in\Delta'}[\gamma+f_r(\varepsilon')-f_r(\varepsilon)]_+
L=ε∈Δ∑ε′∈Δ′∑[γ+fr(ε′)−fr(ε)]+
下面把四种方法的异同放到一块来看:
Method | 实体表征 | 关系表征 | 距离函数 f r ( h , t ) f_r(h,t) fr(h,t) | 约束条件/正则 |
---|---|---|---|---|
TransE | h,t都是d维 | r是d维 | − ∥ h + r − t ∥ 1 / 2 -\|h+r-t\|_{1/2} −∥h+r−t∥1/2 | ∥ h ∥ 2 = 1 , ∥ t ∥ 2 = 1 \|h\|_2=1,\|t\|_2=1 ∥h∥2=1,∥t∥2=1 |
TransH | h,t都是d维 | r, w r w_r wr是d维 | − ∥ ( h − w r ⊤ h w r ) + r − ( t − w r ⊤ t w r ) ∥ 2 2 -\|(h-w^{\top}_r hw_r)+r-(t-w_r^{\top} tw_r)\|_2^2 −∥(h−wr⊤hwr)+r−(t−wr⊤twr)∥22 |
∥
h
∥
2
≤
1
,
∥
t
∥
2
≤
1
\|h\|_2\leq1,\|t\|_2\leq1
∥h∥2≤1,∥t∥2≤1 ∣ w r ⊤ r ∣ / ∥ r ∥ 2 ≤ ϵ ∥ w r ∥ 2 = 1 \|w_r\|_2=1 ∥wr∥2=1 |
TransR | h,t都是d维 | r是k维, M r M_r Mr是k×d维 | ∥ M r h + r − M r t ∥ 2 2 \|M_rh+r-M_rt\|_2^2 ∥Mrh+r−Mrt∥22 |
∥
h
∥
2
≤
1
,
∥
t
∥
2
≤
1
\|h\|_2\leq1,\|t\|_2\leq1
∥h∥2≤1,∥t∥2≤1 ∥ r ∥ 2 ≤ 1 \|r\|_2\leq1 ∥r∥2≤1 ∥ M r h ∥ 2 ≤ 1 \|M_rh\|_2\leq1 ∥Mrh∥2≤1 ∥ M r t ∥ 2 ≤ 1 \|M_rt\|_2\leq1 ∥Mrt∥2≤1 |
TransD | h,t, w h , w t w_h,w_t wh,wt都是d维 | r, w r w_r wr是k维 | − ∥ ( w r w h ⊤ + I ) h + r − ( w r w t ⊤ + I ) t ∥ 2 2 -\|(w_rw_h^{\top}+I)h+r-(w_rw_t^{\top}+I)t\|_2^2 −∥(wrwh⊤+I)h+r−(wrwt⊤+I)t∥22 |
∥
h
∥
2
≤
1
,
∥
t
∥
2
≤
1
,
∥
r
∥
2
≤
1
\|h\|_2\leq1,\|t\|_2\leq1,\|r\|_2\leq1
∥h∥2≤1,∥t∥2≤1,∥r∥2≤1 ∥ ( w r w h ⊤ + I ) h ∥ 2 ≤ 1 \|(w_rw_h^{\top}+I)h\|_2\leq1 ∥(wrwh⊤+I)h∥2≤1 ∥ ( w r w t ⊤ + I ) t ∥ 2 ≤ 1 \|(w_rw_t^{\top}+I)t\|_2\leq1 ∥(wrwt⊤+I)t∥2≤1 |
TransE 的损失函数中的1/2表示可以用L1也可以用L2loss(根据数据集用grid search)。
下面看下时间复杂度:
算法 | f r f_r fr空间复杂度(参数量) | 整体时间复杂度 |
---|---|---|
TransE | O ( N e d + N r d ) O(N_ed+N_rd) O(Ned+Nrd) | O ( N t ) O(N_t) O(Nt) |
TransH | O ( N e d + 2 N r d ) O(N_ed+2N_rd) O(Ned+2Nrd) | O ( 2 d N t ) O(2dN_t) O(2dNt) |
TransR | O ( N e d + N r d k + N r k ) O(N_ed+N_rdk+N_rk) O(Ned+Nrdk+Nrk) | O ( 2 d k N t ) O(2dkN_t) O(2dkNt) |
CTransR | O ( N e d + N r d k + N r c k ) O(N_ed+N_rdk+N_rck) O(Ned+Nrdk+Nrck) | O ( 2 d k N t ) O(2dkN_t) O(2dkNt) |
TransD | O ( 2 N e d + 2 N r k ) O(2N_ed+2N_rk) O(2Ned+2Nrk) | O ( 2 k N t ) O(2kN_t) O(2kNt) |
空间复杂度中,
N
e
N_e
Ne表示节点数量,
N
r
N_r
Nr表示边数量,d和k分布对应节点和边的维度,c是边聚类后的类别数量。
时间复杂度中,
N
t
N_t
Nt表示三元组的个数。TransE中,本来单个
f
r
f_r
fr的时间复杂度为
O
(
d
)
O(d)
O(d)(由于向量维度为d),但是这里不考虑维度,把这个复杂度视为
O
(
1
)
O(1)
O(1);TransH 中
w
r
⊤
h
w
r
w^{\top}_r hw_r
wr⊤hwr时间复杂度为
O
(
d
)
O(d)
O(d);TransR中
M
r
h
M_rh
Mrh时间复杂度为
O
(
d
×
k
)
O(d\times k)
O(d×k);TransD要看原文公式18.19.,其复杂度是
O
(
k
)
O(k)
O(k)。
TransR/CTransR 的空间/时间复杂度最高。
数据集有两个,一个是Wordnet,比较小,里面是词以及词的语法树作为关系。另外一个数据集是Freebase,这个数据集目前还在不断增加,论文发表时已有1.2亿个三元组,8千万个实体。由于该数据太大,作者对这个数据集进行了sample,这里要注意sample的方法,类似社交网络比较sparse的网络,如果随机sample就很容易出现非连通图,因此文中的sample方法也值得注意:
1.先从Freebase中选择实体,这些实体都要在Wikilinks数据集存在,且实体和关系要在Freebase出现频率大于100次,估计这个Wikilinks是一个更小的图数据集,且实体都比较真实,因此可保障不会出现孤立的节点;
2.将互为reverses的关系去掉,例如:’!/people/person/nationality’与’/people/person/nationality’,得到FB15k数据集。
3.将Freebase中实体出现频率前1亿排名的选出来,得到FB1M数据集。
TransE使用参考文献【3】的Mean rank metric,即:Learning Structured Embeddings of Knowledge Bases,原文:
We first assess the quality of our representations using the following ranking task: for all training and testing triplets
(
e
l
.
r
,
e
r
)
(e^l.r, e^r)
(el.r,er),(1) we remove
e
l
e^l
el,(2) we compute densities
f
k
d
e
(
(
e
,
r
,
e
r
)
)
f_{kde}((e,r, e^r))
fkde((e,r,er)) for all
e
∈
D
e
e \in D_e
e∈De,(3) we sort values by decreasing order, and (4) we record the rank of the correct entity
e
l
e^l
el. An identical process is repeated for predicting
e
r
e^r
er. This setting somewhat corresponds to question answering.
对于三元组
(
e
l
.
r
,
e
r
)
(e^l.r, e^r)
(el.r,er),将原来的
e
l
e^l
el(就是head)去掉,然后用函数
f
k
d
e
(
(
e
,
r
,
e
r
)
)
f_{kde}((e,r, e^r))
fkde((e,r,er))在所有的实体中算分数,然后排序,然后看原来
e
l
e^l
el在排序中的次序,然后计分,越靠前分数越高。同样的方法可以计算tail:
e
r
e^r
er。
可以看到,TransE的参数有两部分组成,一部分是和实体的个数和维度有关,一部分是和关系的数量和维度有关,由于Unstructured忽略关系实体,因此只有实体个数和维度一项。总体而言:TransE的参数规模(空间复杂度)是很有优势的。
For experiments with TransE, we selected the learning rate λ λ λ for the stochastic gradient descent (梯度下降方法SGD)among {0.001, 0.01, 0.1}(学习率有三种), the margin γ γ γ among {1, 2, 10} and the latent dimension k k k among {20, 50} on the validation set of each data set. The dissimilarity measure d d d was set either to the L 1 L_1 L1 or L 2 L_2 L2 distance according to validation performance as well. Optimal configurations were(最优配置应该是grid search出来的): k = 20 , λ = 0.01 , γ = 2 k = 20, λ = 0.01, γ = 2 k=20,λ=0.01,γ=2, and d = L 1 d = L_1 d=L1 on Wordnet; k = 50 , λ = 0.01 , γ = 1 k = 50, λ = 0.01, γ = 1 k=50,λ=0.01,γ=1, and d = L 1 d = L_1 d=L1 on FB15k; k = 50 , λ = 0.01 , γ = 1 , k = 50, λ = 0.01, γ = 1, k=50,λ=0.01,γ=1, and d = L 2 d = L_2 d=L2 on FB1M. For all data sets, training time was limited to at most 1, 000 epochs over the training set. The best models were selected by early stopping(早停trick) using the mean predicted ranks on the validation sets (raw setting). An open-source implementation of TransE is available from the project webpage(已失效).
下面是实验结果,可以看到有三个数据集,每个数据集有两种衡量方法,一个是Mean rank,一个是HITS@10%,前者是希望真实值在预测排序列表中越靠前越好,所以是值越小越好;后者是预测排序中的命中百分比,因此是值越大越好。每种衡量方法又分原始方式和使用filtered的方式。下表中带【-】的意味着该模型无法训练这么大的数据量,也证明了TransE就像其摘要中说的一样,可以在大的数据集也可以适用。
这里的多种关系就是结点之间的1对1,1对多,多对1,多对多的关系。在对TransE模型进行精讲的时候就提过这个模型除了1对1的关系处理比较好外,其他关系是有点缺点的。这里只比较HITS@10%在FB15k上的效果(带filtered的trick)。可以看到,下面预测X对Y的关系时候(X是被预测的那个),如果X是多,那么效果就比较差(可以看到下面第三列效果较差),如果X是1,那么效果就比较好。
We obtained that FB15k has 26.2% of 1-TO-1 relationships, 22.7% of 1-TO-MANY, 28.3% of MANY-TO-1, and 22.8% of MANY-TO-MANY. 1对1的数据不多,因此对模型改进很有必要。
输入head和relation,预测tail,下表中黑体是测试集中的数据,斜体是出现过在训练集中的数据。
例如,Lil Wayne这个实体,关系是born in ,预测结果是:New Orleans。
Learning to predict new relationships with few examples
这里Unstructed算法不考虑关系,这里预测关系中这个算法得到关系值就是0,是一个常数,所以这里是一条蓝色横线,然后左边是mean rank右边是 hits@10,随着数据量增加可以看到一个是下降,一个上升,都符合规律,但是可以看到TransE算法在10个点左右就基本得到一个比较好的结果了。
To that end, we randomly selected 40 relationships and split the data into two sets: a set (named FB15k-40rel) containing all triplets with these 40 relationships and another set (FB15k-rest) containing the rest. We made sure that both sets contained all entities. FB15k-rest has then been split into a training set of 353,788 triplets and a validation set of 53,266, and FB15k-40rel into a training set of 40,000 triplets (1,000 for each relationship) and a test set of 45,159.
这里只考虑新边(关系)不考虑新点(实体),做法是:随机选40条边,然后做两个数据集,一个数据集没有这40条边(旧时间点),一个数据集有这40条边(新时间点)。两个数据集都包含所有的entities(节点),不涉及新点的问题。旧时间点的数据集分成训练集和验证集,新时间点的数据集分成训练集和验证集。
Using these data sets, we conducted the following experiment: (1) models were trained and selected using FB15k-rest training and validation sets, (2) they were subsequently trained on the training set FB15k-40rel but only to learn the parameters related to the fresh 40 relationships, (3) they were evaluated in link prediction on the test set of FB15k-40rel (containing only relationships unseen during phase (1)). We repeated this procedure while using 0, 10, 100 and 1000 examples of each relationship in phase (2).
具体训练步骤如下:
1.在旧时间点的数据集上训练,得到所有点和边的embedding,更新模型参数;
2.将新时间点的数据集的训练集丢入步骤1的模型继续训练,更新模型参数;
3.将新时间点的数据集的测试集丢入步骤2的模型进行测试,记录的边的预测效果。
在步骤2中分别为边加入0,10,100,1000个三元组进行比较,得到上面的图。
关键点
·知识图谱的理解
·三元组的表示方式
·负样本的构建(transH用的trick)
·损失函数的理解
·参数/时间复杂度分析
创新点
·transE基础模型
·transH开始解决一对多等问题
·transR和transD不同空间的映射
·transD映射矩阵由向量表示
·丰富的实验论证效果
启发点
·知识图谱中实体和关系的向量化表达
·transE、transH、transR、transD是如何发展的,什么样的motivation
·负样本构建、损失函数统一的框架
·trans系列模型的对比和总结
·Parameter space(参数空间)的理解
·参数空间复杂度分析、时间复杂度分析
主要看TransE的代码,其他算法的在开源库也有,原理也一样,例如:负采样,margin based的损失函数等。
·Python 3.7
·torch 1.3.1
·numpy 1.18.1
·jupyter notebook
OpenKE-PyTorch
example目录下的
OpenKE\module\model下
开源库的安装(来源于官网):
数据集在benchmarks目录下面:
目录下面有各种关系的三元组:
对于训练和测试数据的说明(来自官网)
读取数据的py文件在data目录下:
负采样的py文件在:OpenKE\module\strategy
损失函数的py文件在:OpenKE\module\loss
训练和测试py在config文件夹下:
官网的安装是linux环境的,windows环境参考这里:
https://blog.csdn.net/qq_38970189/article/details/109188376
https://blog.csdn.net/wangdong1106/article/details/109597447 (新版)
编译器下载要注意下载安装版,不要下载解压版
https://sourceforge.net/projects/mingw-w64/files/
在线安装有点慢。
base目录下的文件:
所有导出函数(不只是base.cpp,还有其它.h文件)都需要把extern "C"修改为extern “C” __declspec(dllexport)
setting.h中 #define INT long 修改为 typedef int INT; (最后有分号)。类似地修改 #define REAL float 为 typedef double REAL;
corrupt.h和test.h中添加一句 #include “iso646.h”
base.cpp的getBatch函数最后,添加一行return ((void*)0);
直接用MinGW编译dll报错。。。编译为.o没问题,先留坑。
import openke from openke.config import Trainer, Tester #训练与测试 from openke.module.model import TransE # 模型 from openke.module.loss import MarginLoss # 损失函数 from openke.module.strategy import NegativeSampling # 负采样 from openke.data import TrainDataLoader, TestDataLoader # 数据读取 # 依赖的C++文件地址: # https://github.com/thunlp/OpenKE/blob/OpenKE-PyTorch/openke/base/Base.cpp # dataloader for training # torch.utils.data.DataLoader是torch中用来包装数据的工具,可以有效地迭代数据将自定义的Dataset根据batch # size大小、是否shuffle等封装成一个Batch Size大小的Tensor,用于后面的训练 # 两个对torch的dataloader写得比较清楚的文章: https://blog.csdn.net/He3he3he/article/details/105441083 # https://zhuanlan.zhihu.com/p/30934236 # 加载训练数据集 train_dataloader = TrainDataLoader( # 数据集路径 in_path = "./benchmarks/FB15K237_tiny/", # 每次训练100组三元组 nbatches = 100, # CPU或者GPU的数量 threads = 8, # 负采样模式 sampling_mode = "normal", # TransH的负采样trick,根据概率来替换实体 bern_flag = 1, # TransE中提到的负采样trick,是否使用filtered进行处理,保证负样本从未在测试或验证集中以正样本形式出现过。 filter_flag = 1, # 使用替换实体方式生成的负样本数量 neg_ent = 25, # 使用替换关系方式生成的负样本数量 neg_rel = 0) # dataloader for test # 加载测试数据集 test_dataloader = TestDataLoader("./benchmarks/FB15K237_tiny/", "link") # define the model transe = TransE( # 实体数量/size,记为e ent_tot = train_dataloader.get_ent_tot(), # 关系数量/size,记为r rel_tot = train_dataloader.get_rel_tot(), # 最后的表征维度,记为d dim = 200, # scoring function计算距离使用L1 norm,也可以用L2 p_norm = 1, norm_flag = True) # define the loss function model = NegativeSampling( model = transe, # 设置transe模型 loss = MarginLoss(margin = 5.0), #设置margin,对应原文损失函数中的gamma batch_size = train_dataloader.get_batch_size()# 100 ) # train the model #开始模型训练,这里面的model是Negativesampling,class NegativesSampling里面定义了TransE model #显卡支持则use_gpu设置为true,train_times应该设置为1000才正常,演示用5速度快 trainer = Trainer(model = model, data_loader = train_dataloader, train_times = 5, alpha = 1.0, use_gpu = False) trainer.run() transe.save_checkpoint('./checkpoint/transe.ckpt') # test the model transe.load_checkpoint('./checkpoint/transe.ckpt') tester = Tester(model = transe, data_loader = test_dataloader, use_gpu = False) tester.run_link_prediction(type_constrain = False)
# coding:utf-8 import torch import torch.nn as nn from torch.autograd import Variable import torch.optim as optim import os import time import sys import datetime import ctypes import json import numpy as np import copy from tqdm import tqdm class Trainer(object): def __init__(self, model = None, data_loader = None, train_times = 1000, alpha = 0.5, use_gpu = True, opt_method = "sgd", save_steps = None, checkpoint_dir = None): self.work_threads = 8 self.train_times = train_times # 默认使用SGD self.opt_method = opt_method self.optimizer = None # 学习率衰减 self.lr_decay = 0 # 避免过拟合 self.weight_decay = 0 self.alpha = alpha self.model = model self.data_loader = data_loader self.use_gpu = use_gpu self.save_steps = save_steps self.checkpoint_dir = checkpoint_dir def train_one_step(self, data): self.optimizer.zero_grad() # 前向计算loss值,这里面的model是Negativesampling中定义的 loss = self.model({ 'batch_h': self.to_var(data['batch_h'], self.use_gpu), 'batch_t': self.to_var(data['batch_t'], self.use_gpu), 'batch_r': self.to_var(data['batch_r'], self.use_gpu), 'batch_y': self.to_var(data['batch_y'], self.use_gpu), 'mode': data['mode'] }) # 反向更新参数 loss.backward() self.optimizer.step() return loss.item() def run(self): # 判断是否使用gpu还是cpu if self.use_gpu: self.model.cuda() # 定义优化器,有Adagrad、Adadelta、Adam等 if self.optimizer != None: pass elif self.opt_method == "Adagrad" or self.opt_method == "adagrad": self.optimizer = optim.Adagrad( self.model.parameters(), lr=self.alpha, lr_decay=self.lr_decay, weight_decay=self.weight_decay, ) elif self.opt_method == "Adadelta" or self.opt_method == "adadelta": self.optimizer = optim.Adadelta( self.model.parameters(), lr=self.alpha, weight_decay=self.weight_decay, ) elif self.opt_method == "Adam" or self.opt_method == "adam": self.optimizer = optim.Adam( self.model.parameters(), lr=self.alpha, weight_decay=self.weight_decay, ) else: self.optimizer = optim.SGD( self.model.parameters(), lr = self.alpha, weight_decay=self.weight_decay, ) print("Finish initializing...") training_range = tqdm(range(self.train_times)) for epoch in training_range: res = 0.0 # data loader是TrainDataLoader class ,里面存了训练集 for data in self.data_loader: loss = self.train_one_step(data) res += loss training_range.set_description("Epoch %d | loss: %f" % (epoch, res)) if self.save_steps and self.checkpoint_dir and (epoch + 1) % self.save_steps == 0: print("Epoch %d has finished, saving..." % (epoch)) self.model.save_checkpoint(os.path.join(self.checkpoint_dir + "-" + str(epoch) + ".ckpt"))#保存参数 def set_model(self, model): self.model = model def to_var(self, x, use_gpu): if use_gpu: return Variable(torch.from_numpy(x).cuda()) else: return Variable(torch.from_numpy(x)) def set_use_gpu(self, use_gpu): self.use_gpu = use_gpu def set_alpha(self, alpha): self.alpha = alpha def set_lr_decay(self, lr_decay): self.lr_decay = lr_decay def set_weight_decay(self, weight_decay): self.weight_decay = weight_decay def set_opt_method(self, opt_method): self.opt_method = opt_method def set_train_times(self, train_times): self.train_times = train_times def set_save_steps(self, save_steps, checkpoint_dir = None): self.save_steps = save_steps if not self.checkpoint_dir: self.set_checkpoint_dir(checkpoint_dir) def set_checkpoint_dir(self, checkpoint_dir): self.checkpoint_dir = checkpoint_dir
from .Strategy import Strategy class NegativeSampling(Strategy): def __init__(self, model = None, loss = None, batch_size = 256, regul_rate = 0.0, l3_regul_rate = 0.0): super(NegativeSampling, self).__init__() self.model = model self.loss = loss self.batch_size = batch_size # 默认为0,即不加正则化项 self.regul_rate = regul_rate self.l3_regul_rate = l3_regul_rate # 在base.cpp中实现的score存储 # https://github.com/thunlp/OpenKE/blob/OpenKE-PyTorch/openke/base/Base.cpp # getBatch函数中for (INT batch = lef; batch < rig; batch++)开始。 # score是放在一维数组中的,存的顺序是[i,i+batch,i+2*batch,...] # 就是第一个batch位置是正样本([:batch_size]),后面的都是负样本([batch_size:]) # 例如,batch_size为200,负样本数量是25,那么在score数组中,1-200是正样本,201-400是第一批负样本,401-600是第二批负样本 # 以此类推,共有25批负样本,第一个正样本是1,其对应的负样本就是201,401,601...以此类推。 # 当然每个样本是一个三元组,里面有两个实体和一个关系,因此,每个样本里面是3个分数。 # 假设正样本用[i,j,k]表示,负样本size=4,用i1,i2,i3,i4,j1,j2,j3,j4,k1,k2,k3,k4表示,batch_size取3 # 那么score数组为:(i,j,k,i1,j1,k1,i2,j2,k2,i3,j3,k3,i4,j4,k4) def _get_positive_score(self, score): positive_score = score[:self.batch_size]# 取score数组的1-batch_size位的元素,就是取出正样本(只有一个),按上例是[i,j,k] positive_score = positive_score.view(-1, self.batch_size).permute(1, 0)# permute进行转置,转置后是batch_size*1 return positive_score def _get_negative_score(self, score): negative_score = score[self.batch_size:]# 取score数组的batch_size+1到最后一位的元素,就是取出所有负样本集合 # 先按batch_size叠成矩阵(batch_size*3),接上面的例子: # [i1,j1,k1 # i2,j2,k2 # i3,j3,k3 # i4,j4,k4] # 然后再转置,得到batch_size*4,即batch_size*负样本数量 negative_score = negative_score.view(-1, self.batch_size).permute(1, 0) return negative_score def forward(self, data): # 调用相应模型计算forward结果 score = self.model(data) # 正样本分数 p_score = self._get_positive_score(score) # 负样本分数 n_score = self._get_negative_score(score) # 按原文公式计算损失值,是一个scalar loss_res = self.loss(p_score, n_score) # 加正则化项,具体正则化函数在TransE.py中 if self.regul_rate != 0: loss_res += self.regul_rate * self.model.regularization(data) if self.l3_regul_rate != 0: loss_res += self.l3_regul_rate * self.model.l3_regularization() return loss_res
import torch import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variable import numpy as np from .Loss import Loss class MarginLoss(Loss): def __init__(self, adv_temperature = None, margin = 6.0): super(MarginLoss, self).__init__() # margin是超参数,不参与反向传播求导 self.margin = nn.Parameter(torch.Tensor([margin])) self.margin.requires_grad = False if adv_temperature != None: self.adv_temperature = nn.Parameter(torch.Tensor([adv_temperature])) self.adv_temperature.requires_grad = False self.adv_flag = True else: self.adv_flag = False def get_weights(self, n_score): return F.softmax(-n_score * self.adv_temperature, dim = -1).detach() def forward(self, p_score, n_score): if self.adv_flag: return (self.get_weights(n_score) * torch.max(p_score - n_score, -self.margin)).sum(dim = -1).mean() + self.margin else: # 对应论文中的loss function,原文公式1 # 这里pscore size[batch,1],n_score[batch,negative sampling size]; # 这里用了广播机制,因为第二个维度正样本是1,负样本是negative sampling size,要按列进行复制后计算,最后用mean取平均 # 期望pscore<n_score # if pscore-n_score <=-self.margin,正负样本之差即足够小,至少要比margin小;loss=-self.margin+self.margin =0=>与公式一致 # if pscore-n_score>-self.margin,正负样本之差比margin大;loss=p_score-n_score+self.margin>0=>与公式一致 return (torch.max(p_score - n_score, -self.margin)).mean() + self.margin def predict(self, p_score, n_score): score = self.forward(p_score, n_score) return score.cpu().data.numpy()
import torch import torch.nn as nn import torch.nn.functional as F from .Model import Model class TransE(Model): def __init__(self, ent_tot, rel_tot, dim = 100, p_norm = 1, norm_flag = True, margin = None, epsilon = None): super(TransE, self).__init__(ent_tot, rel_tot) # 表征维度默认100,调用使用200 self.dim = dim # 初始化为None self.margin = margin # 初始化为None self.epsilon = epsilon # 默认使用L1 self.norm_flag = norm_flag self.p_norm = p_norm # 最后实体embedding矩阵,大小为e*d self.ent_embeddings = nn.Embedding(self.ent_tot, self.dim) # 最后关系embedding矩阵,大小为r*d self.rel_embeddings = nn.Embedding(self.rel_tot, self.dim) # 默认使用xavier_uniform 初始化embedding矩阵,否则使用其他方式初始化embedding矩阵 if margin == None or epsilon == None: nn.init.xavier_uniform_(self.ent_embeddings.weight.data) nn.init.xavier_uniform_(self.rel_embeddings.weight.data) else: self.embedding_range = nn.Parameter( torch.Tensor([(self.margin + self.epsilon) / self.dim]), requires_grad=False ) nn.init.uniform_( tensor = self.ent_embeddings.weight.data, a = -self.embedding_range.item(), b = self.embedding_range.item() ) nn.init.uniform_( tensor = self.rel_embeddings.weight.data, a= -self.embedding_range.item(), b= self.embedding_range.item() ) if margin != None: self.margin = nn.Parameter(torch.Tensor([margin])) self.margin.requires_grad = False self.margin_flag = True else: self.margin_flag = False def _calc(self, h, t, r, mode): if self.norm_flag: # 按行去做L2的归一化,每一行是一个node的embedding vector h = F.normalize(h, 2, -1) r = F.normalize(r, 2, -1) t = F.normalize(t, 2, -1) if mode != 'normal': h = h.view(-1, r.shape[0], h.shape[-1]) t = t.view(-1, r.shape[0], t.shape[-1]) r = r.view(-1, r.shape[0], r.shape[-1]) # 计算scoring function:/h+r-t/_L1/L2 if mode == 'head_batch': score = h + (r - t) else: score = (h + r) - t # 计算p-norm;flatten()将矩阵铺平 score = torch.norm(score, self.p_norm, -1).flatten() return score def forward(self, data): batch_h = data['batch_h']#要处理的head的index集合,是一个数组 batch_t = data['batch_t']#同上 batch_r = data['batch_r']#同上 mode = data['mode'] # 得到index对应的矩阵:embedding矩阵 # entities实体:head,tail;relation:关系 h = self.ent_embeddings(batch_h) t = self.ent_embeddings(batch_t) r = self.rel_embeddings(batch_r) score = self._calc(h ,t, r, mode) if self.margin_flag: return self.margin - score else: return score def regularization(self, data): batch_h = data['batch_h'] batch_t = data['batch_t'] batch_r = data['batch_r'] h = self.ent_embeddings(batch_h) t = self.ent_embeddings(batch_t) r = self.rel_embeddings(batch_r) regul = (torch.mean(h ** 2) + torch.mean(t ** 2) + torch.mean(r ** 2)) / 3 return regul def predict(self, data): score = self.forward(data) if self.margin_flag: score = self.margin - score return score.cpu().data.numpy() else: return score.cpu().data.numpy()
【思考题】回顾scoring function的写法与论文中公式的对应。
【思考题】总结Trans系列算法的通用框架,如negative sampling相似的构建方式以及相似的loss function。
【代码实践】尝试为算法设置不同的参数,比如margin值、scoring function取L1/L2 norm的不同设置对模型效果的影响。
【代码实践】在OpenKE/examples/文件夹下面运行不同的Trans系列算法,比较不同算法的效果。
【总结】总结TransE、TransH、TransR、TransD的关键技术以及如何代码实现,结合代码验证算法的思想以及参数个数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。