简单实现⼏篇知识图谱嵌⼊(KnowledgeGraphEmbedding ,KGE )模型关于知识图谱嵌⼊的理论介绍:
KGE 的诸多⽅法
KGE就是将实体和关系嵌⼊到低维向量空间中,同时保留KG的结构和语义信息
现有的KGE⽅法可以划分为三类:
1. 基于翻译距离的(translational distance bad)
2. 基于语义匹配的(mantic matching bad)
3. 基于神经⽹络的(neural network bad)
下⾯我们来实现⼏个最经典的KGE模型:基于翻译距离的模型TransE、基于语义匹配的模型RESCAL和DistMult,
TransE
该模型将关系看作头实体到尾实体的翻译。
TransE受到word2vec的启发,如果你已经训练好了词向量,那么针对三个单词:国家country、城市city,⾸都captial-of,就会有如下关系:得到的向量与这个向量就很相近。TransE也认为,在KG的embedding空间中,也⼀定存在这种关系,即两个实体的嵌⼊向量的差值(或者其它操作)就代表这两个实体之间的关系。
因为关系和实体都被表⽰为向量,所以另⼀种数学化的说法就是在向量(vector)空间中,TransE将关系看作是头实体到尾实体的平移操作,即:
数学定义中:
平移前点的坐标+平移向量的坐标=平移后点的坐标
所以我们称之为平移
TransE的得分函数就是向量之间的欧⽒距离的相反数:
损失函数定义为:
也就是在embedding space中,正例三元组的得分要⽐负例三元组的得分⾼出,⼜由于得分函数表⽰为距离的相反数,所以得分⾼代表距离近。即:正例三元组的距离要⽐负例三元组的距离⼩⾄少长度的距离。
RESCAL
该模型的得分函数定义为:
其中都是从实体embedding矩阵(记为)中的取出(根据实体id获取)的vector,的形状是(num_entities,d)。
党支部总结
是整个关系tensor(三维的)中的根据关系id获取的matrix(⼆维的)。整个关系tensor记为,形状是(num_relations,d,d),所以的shape是()。
DistMult
V −country V city V captial −of v +h v ≈r v t
Score (h ,r ,t )=−∣∣v +h v −r v ∣∣t 22青春期孩子
max (0,γ−Score (h ,r ,t )+Score (h ,r ,t ))
′′γγScore (h ,r ,t )=v M v h T
r t
v ∈h R ,v ∈d t R ,M ∈d r R d ×d v ,v h t E E M r R M r d ,d
DistMult是RESCAL的简化。具体来说就是RESCAL中每⼀个head和tail实体之间的关系r是⽤⼀个matrix表⽰。⽽DistMult中则⽤⼀个vector表⽰两个实体间的关系。
所以得分函数是三个vector之间的内积:
代码实现
获取数据
我们使⽤
下载解压:
FB15K知识库就是TransE这篇论⽂的作者从Freeba知识库中选取的⼀部分三元组构成的⼀个⼩规模的知识库
三元组数量
实体数量关系数量592213149511345
592213个三元组的划分情况是:
数据集划分
三元组数量训练集
483142验证集
50000测试集59017
实现
实现代码需要说明的⼀点是,下⾯的代码采⽤Binary Cross Entropy loss作为损失函数,即输⼊是头实体和关系的id,经过模型之后,输出⼀个向量,长度是num_entities,也就是所有实体的数量。
这个向量的每⼀个值都进⾏sigmoid运算,此时这个向量的第i个位置的值代表模型预测第i个实体是尾实体的概率
标签是尾实体的id,即告诉模型第⼏个位置的实体才是真正的尾实体,需要增加这个位置的概率,降低其余位置的概率。
也就是说达到了:要求正例三元组的得分⼤于负例三元组的得分。
导包
import numpy as np
import torch
import torch .nn as nn
import torch .nn .functional as F
import time
from collections import defaultdict怎么开通qq邮箱
import argpar
from tqdm import tqdm
import os
处理数据
<v ,v ,v >
h r t v ,v ,v ∈h r t R d
加载数据
def load_data(data_dir,data_type):
with open("%"%(data_dir, data_type),"r")as f:
data = f.read().strip().split("\n")
data =[i.split('\t')for i in data]
print(len(data),data_type)
return data
train_data=load_data(data_dir='/mnt/cfs/speech/nlp/work/xhsun/KGQA/Trans/FB15k/',data_type='freeba_mtr100_mte100-train')
valid_data=load_data(data_dir='/mnt/cfs/speech/nlp/work/xhsun/KGQA/Trans/FB15k/',data_type='freeba_mtr100_mte100-valid')
test_data=load_data(data_dir='/mnt/cfs/speech/nlp/work/xhsun/KGQA/Trans/FB15k/',data_type='freeba_mtr100_mte100-test')
data=train_data+valid_data+test_data
print(len(data))
风诗句统计所有的头实体、尾实体以及关系:
entities =sorted(list(t([d[0]for d in data]+[d[2]for d in data])))
print(len(entities))
relations =sorted(list(t([d[1]for d in data])))
print(len(relations))
即14951个实体,1345个关系
构造entity2id和relation2id的字典映射
entity_idxs={entities[i]:i for i in range(len(entities))}
relation_idxs={relations[i]:i for i in range(len(relations))}
构造实体到id,关系到id的映射,这⼀步是NLP中必做的⼀步,因为我们要根据输⼊的实体,找到对应的id,进⽽找到对应的embedding ⽣成训练数据
train_data_idxs=[[entity_idxs[triplet[0]],relation_idxs[triplet[1]],entity_idxs[triplet[2]]]for triplet in train_data]
⽣成批次的数据输⼊
er_vocab=defaultdict(list)
for triplet in train_data_idxs:
er_vocab[(triplet[0],triplet[1])].append(triplet[2])
er_vocab_pairs=list(er_vocab.keys())
batch_inputs=er_vocab_pairs[:4]
batch_s([len(batch_inputs),len(entity_idxs)],dtype=torch.float32)
for i,pair in enumerate(batch_inputs):
batch_targets[i,er_vocab[pair]]=1
行进管乐batch_inputs=np.array(batch_inputs)
以前4个三元组为例
输⼊的是头实体和关系的id,输出的标签是尾实体的id。
需要注意的是,同⼀组头实体和关系,会有很多个尾实体存在的。
以第⼀个三元组为例:
即,(3920,791,9220)是⼀个三元组,也就是输⼊数据的⼀个样本。(3920,791,3799)也是⼀个三元组。所以标签有两个1。
了解了输⼊输出,接下来就可以定义模型。
珍珠的英文模型
class KGE(nn.Module):
def__init__(lf,model_name,ent_vec_dim,num_entities,num_relations):
'''
num_entities是所有实体的数量
num_relations是所有关系的数量
ent_vec_dim是每⼀个实体向量的维度
如果model_name是RESCAL,那么每⼀个关系⽤⼀个矩阵matrix表⽰,shape==(ent_vec_dim,ent_vec_dim)
'''
super(KGE,lf).__init__()
lf.E=nn.Embedding(num_embeddings=num_entities,embedding_dim=ent_vec_dim,padding_idx=0)
<_vec_dim=ent_vec_dim
lf.num_entities=num_entities
del_name=='RESCAL':
lf.R=nn.Embedding(num_embeddings=num_relations,embedding_dim=ent_vec_dim*ent_vec_dim,padding_idx=0) lf.scoreFun=lf.RESCAL
中午微信问候语
el:
lf.R=nn.Embedding(num_embeddings=num_relations,embedding_dim=ent_vec_dim,padding_idx=0)
lf.scoreFun=lf.DistMult
def RESCAL(lf,head_embed,rel_embed):
家庭的拼音
'''
RESCAL模型将每⼀个关系⽤⼀个matrix表⽰。
输⼊:
head_embed.size()==(batch__vec_dim)
rel_embed.size()==(batch__vec_dim*2)
输出:
score.size()==(batch_size,lf.num_entities)
'''
batch_size=head_embed.size(0)
head_embed=head_embed.view(batch_size,_vec_dim)
rel_embed=rel_embed.view(batch__vec__vec_dim)
(torch.squeeze(torch.bmm(head_embed,rel_embed),dim=1),lf.anspo(1,0)) return score
def DistMult(lf,head_embed,rel_embed):
'''
DistMult是RESCAL的简化版,将每⼀个关系⽤⼀个vector表⽰。
输⼊:
head_embed.size()==(batch__vec_dim)
rel_embed.size()==(batch__vec_dim)
输出:
score.size()==(batch_size,lf.num_entities)
'''
(head_embed*rel_embed,lf.anspo(1,0))
return score
def forward(lf,head_idx,rel_idx):
'''
输⼊:
head_idx.size()==rel_idx.size()==(batch_size,)
输出:
probabilities.size()==(batch_size,lf.num_entities)
即:预测每⼀个实体可以作为尾实体的概率
'''
batch_size=head_idx.size(0)
score=lf.scoreFun(head_embed=lf.E(head_idx),rel_embed=lf.R(rel_idx))
asrt score.size()==(batch_size,lf.num_entities)
probabilities=torch.sigmoid(score)
return probabilities
前向传播
head_idx=torch.LongTensor(batch_inputs[:,0])
rel_idx=torch.LongTensor(batch_inputs[:,1])