表示学习
什么是表示学习呢?在自然语言处理中,常用的表示方式是1-hot Representation,每一个词都可以表示成一个非常长的向量,这个向量的长度就是词汇的数量,例如汉语常用词有6000个,我们就把每个词表示成6000维的向量。每个词对应的向量中有一维设置为1,其他维度设置为0,这样很自然地就把人类语言中的所有词都独一无二地表示成一个向量,这样计算机就可以很好的区分某个词跟另外一个词是不一样的。但是这种表示方法忽视了两个向量之间的语义关系。比如太阳和月亮,它们之间具有相关性,但是这种表示方法将它们表示成两个独立的向量。
表示学习的另一种思想是提出一种所谓的Distributed Representation,或者是Embedding,用一个低维的向量空间,把每个词都表示到空间里面的某一个位置。这样,我们就可以利用词和词之间在这个空间中的距离来衡量词与词之间的语义关系,这就是表示学习的基本目标。
TransE
TransE采用的是表示学习中的Distributed Representation方法。
在TransE中,将每个实体和关系都表示成低维向量,给定任何一个三元组,都将中间的relation看成是从head到tail的一个翻译过程,也就是说把head的向量加上relation的向量,要让它尽可能地等于tail向量(head+relation=tail)。在学习过程中,通过不断调整、更新实体和关系向量的取值,使这些等式尽可能实现,并且通过对每个三元组替换头结点或尾结点来添加负例元组,使负例元组的head的向量加上relation的向量,使其尽可能的远离tail向量。
一条知识图谱可以表示为一个三元组(head,rel,tail)。比如:小明的爸爸是大明,表示成三元组是(小明,爸爸,大明)。前者是主体,中间是关系,后者是客体。通过TransE模型,我们可以把三元组用向量来表示,通过训练模型,使h+r=t。
进行模型训练之后,任给两个关系,假如它们形成一个路径的话,我们可以得到这个路径对应的关系,比如说某个关系路径是:”出生地点”和“地点所在国家”,通过模型可以推测出这个路径对应着关系“国籍”。
推理方法
用TransE进行知识推理的方法采用的是单步推理的方法,也就是利用直接关系进行推理。而现实中有很多信息不能通过直接推理得到,所以这个时候单步推理就不适用了,在这种情况下我们需要采用多步推理的方法。
多步推理是在单步推理直接关系的基础上进一步推理出间接关系,即多步关系。比如:a和b存在关系impact,b和c存在关系puchase_goods,则可以推理出a和c之间存在某种关系。PTransE就是采用多步推理的方法进行知识推理。
PTransE
PTransE是TransE的拓展,TransE中仅仅考虑了实体之间的直接关系,例如:head+relation->tail,而PTransE除了在TransE中构建三元组(h1,r1,e),(e,r2,h2),还构建了一个三元组(h1,r1r2,h2),并且通过递归下降算法使h1+(r1r2)=h2尽可能相等,其中r1r2是将r1和r2连接在一起形成的新的关系,“”可以是用加法或者乘法或者使用神经网络来实现。下面我们介绍PTransE的实现过程。
数据
在本次实验中使用了FB15k数据集。
数据集包含六个文件:
train.txt:训练文件,格式(e1,e2,rel)。
valid.txt:验证文件,格式与train.txt相同
test.txt:测试文件,格式与train.txt相同。
entity2id.txt:所有实体和相应的id,每行一个。
relation2id.txt:所有关系和相应的id,每行一个。
e1_e2.txt:任务实体预测中提到的所有前500个实体对。
因为PTransE采用的是多步推理的方法,因此需要经过数据预处理找到实体与实体之间可能存在的路径和路径的长度信息,将生成的通过预处理得到的新的关系以及之间的路径存储起来进行训练。
下面介绍程序的执行过程。
数据提取
首先我们将程序需要用到的训练文件、验证文件、测试文件、所有实体和相应的id文件、所有关系和相应的id文件以及任务实体预测中提到的所有前500个实体对文件存储到表中。下面以a表为例:
//将训练数据中的实体关系对以“{‘/m/027rn’: {352: {‘/m/06cx9’: 1}}, ‘/m/06cx9’: {1697: {‘/m/027rn’: 1}}}”的形式存储到a表中。
a[e1][relation2id[rel]] = {}
a[e1][relation2id[rel]][e2] = 1
a[e2][relation2id[rel] + relation_num] = {}
a[e2][relation2id[rel] + relation_num][e1] = 1
获取单步路径信息
然后我们循环遍历a表中得到的所有的头实体。a表中的每个形如{‘X’: {352: {‘Y’: 1}}}的表存储的第一个数据”X”是entity2id中的所有实体,实体对应的“352”指的是实体X和实体Y的关系的id,“Y”指的是与实体X有关系“352”的实体。
然后遍历a表每个头实体的关系和对应的尾实体。
对相应的关系,遍历所有的尾实体。
通过上述操作将训练数据中的所有的实体对存储到path_dict向量表中。
然后将实体X到实体Y所有存在的路径的概率存储到h_e_p向量表中。
获取多步路径信息
通过上面的步骤我们已经将所有实体之间存在单步路径的实体对提取出来了,下面我们来推理存在多步路径关联的实体对。
我们还是在a表中,遍历a中的头实体e1,获得所有头实体e1的关系和尾实体e2。
然后我们在a表中,以a2作为头结点查找a2的关系和尾结点e3,并且将e1和e3作为一条新的路径连接起来,将该路径写入到path_dict表中。
然后根据e1到e2的路径的数量将每条路径的概率存入h_e_p表中。
信息存储
通过上述步骤,我们已经将实体对之间存在的单步路径和多步路径信息推理出来了,接下来我将路径的头结点,尾结点,头结点和尾结点之间的路径的条数以及每条路径经过的关系和从头结点到尾结点的每条路径之间的概率保存到confidence.txt、train_pra和test_pra文件中。
接下来通过我们处理得到的数据进行PTransE模型的训练。
准备
首先我们将训练过程中要用到的数据保存到向量表中。以entity2id数据为例:
FILE* f1 = fopen("../data/entity2id.txt","r");
int x;
while (fscanf(f1,"%s%d",buf,&x)==2) //读入实体
{
string st=buf;
entity2id[st]=x; //将实体信息导入entity2id
id2entity[x]=st; //将实体的id信息导入id2entity
entity_num++;
}
训练
首先我们设置关系向量和实体向量的数目和维数。
relation_vec.resize(relation_num); //设置关系向量的数目为关系的数量
for (int i=0; i
然后对关系向量和实体向量进行归一化处理,程序中对每个关系向量和实体向量进行赋值采用的方法如下,是用一个随机数函数,对每个向量进行赋值。
//返回一个大于或等于均值miu的概率密度并且属于[min,max]的数
double randn(double miu,double sigma, double min ,double max)
{
double x,y,dScope;
do{
x=rand(min,max);
y=normal(x,miu,sigma);
dScope=rand(0.0,normal(miu,miu,sigma));
}while(dScope>y);
return x;
}
首先我们将relation_vec和entity_vec向量赋值给relation_tmp和entity_tmp。
接下来进行模型训练,将下面的步骤迭代1000次。
首先我们随机从总实体数组中产生一个下标。
int i=rand_max(fb_h.size());
int e1 = fb_h[i], rel = fb_r[i], e2 = fb_l[i];
//其中fb_h[i],fb_r[i],fb_l[i]分别存储的是训练数据中第i对三元组
然后通过修改该三元组的头结点和尾结点的值来训练该三元组。
int j=rand_max(entity_num);//从头实体数组中产生一个下标
train_kb(e1,e2,rel,e1,j,rel,margin);//替换尾结点进行训练
train_kb(e1,e2,rel,j,e2,rel,margin);//替换尾结点进行训练
在train_kb中,采用梯度下降法对模型进行训练。
首先我们分别计算原三元组和修改后三元组e2-rel-e1的值,判断两者的差是否小于给定的数margin,如果大于margin则训练这两个三元组,让正确的三元组之间e2-rel-e1的值变小,让错误的三元组之间e2-rel-e1的值变大。
double x = 2*(entity_vec[e2][ii]-entity_vec[e1][ii]-relation_vec[rel][ii]); //判断e2-rel-e1的值
if (x>0) //如果e2-rel-e1的值大于0说明应该减小e2,增大e1和rel
x=1;
else //如果e2-rel-e1的值小于0说明应该增大e2,减小e1和rel
x=-1;
relation_tmp[rel][ii]-=belta*rate*x; //增大或减小rel
entity_tmp[e1][ii]-=belta*rate*x; //增大或减小e1
entity_tmp[e2][ii]+=belta*rate*x; //增大或减小e2
通过上述的步骤对三元组训练得到的新的三元组的e1、rel和e2的向量值,将这些向量存储到relation2vec.txtbern和entity2vec.txtbern中来进行PTransE模型的测试。
测试
首先将测试集、验证集和训练得出的向量数据提取出来存储到相应的表中。
然后对每个测试集,通过循环对测试集的头结点和尾结点替换成所有的实体,将替换后的结点算出的e2-rel-e1的值进行升序排列,将排序后的三元组放到a向量中。
对a向量中的三元组进行判断,判断实体对之间是否存在相应的路径,来进行模型准确率的判断。