精华内容
下载资源
问答
  • TransE系列源码

    2017-12-29 11:03:36
    TransE的直观含义,就是TransE基于实体和关系的分布式向量表示,将每个三元组实例(head,relation,tail)中的关系relation看做从实体head到实体tail的翻译(其实我一直很纳闷为什么叫做translating,其实就是向量...
  • 重新实现TransE模式原始论文是 测试结果 数据集:WN18 原始实体点击数10:0.651 原始实体mean_rank:284 过滤器实体点击数@ 10:0.728 过滤器实体mean_rank:278 FB15K 原始实体点击数@ 10:0.332 原始实体mean_...
  • TranSE算法实现及测试

    2019-04-26 09:20:55
    这是我自己的代码,主要用来存储,若能帮到其他人,我也很愿意。
  • 该项目将不再维护,建议用户访问和使用新软件包 。
  • TransE模型的基本思想是使head向量和relation向量的和尽可能靠近tail向量。这里我们用L1或L2范数来衡量它们的靠近程度。 损失函数是使用了负抽样的max-margin函数。 L(y, y’) = max(0, margin - y + y’) y是正...

    模型介绍

    TransE模型的基本思想是使head向量和relation向量的和尽可能靠近tail向量。这里我们用L1或L2范数来衡量它们的靠近程度。

    损失函数是使用了负抽样的max-margin函数。

    L(y, y’) = max(0, margin - y + y’)

    y是正样本的得分,y'是负样本的得分。然后使损失函数值最小化,当这两个分数之间的差距大于margin的时候就可以了(我们会设置这个值,通常是1)。

    由于我们使用距离来表示得分,所以我们在公式中加上一个减号,知识表示的损失函数为:

    其中,d是:

    这是L1或L2范数。至于如何得到负样本,则是将head实体或tail实体替换为三元组中的随机实体。


    代码实现:

    具体的代码和数据集(YAGO、umls、FB15K、WN18)请见Github:
    https://github.com/Colinasda/TransE.git

    import codecs
    import numpy as np
    import copy
    import time
    import random
    
    entities2id = {}
    relations2id = {}
    
    
    def dataloader(file1, file2, file3):
        print("load file...")
    
        entity = []
        relation = []
        with open(file2, 'r') as f1, open(file3, 'r') as f2:
            lines1 = f1.readlines()
            lines2 = f2.readlines()
            for line in lines1:
                line = line.strip().split('\t')
                if len(line) != 2:
                    continue
                entities2id[line[0]] = line[1]
                entity.append(line[1])
    
            for line in lines2:
                line = line.strip().split('\t')
                if len(line) != 2:
                    continue
                relations2id[line[0]] = line[1]
                relation.append(line[1])
    
    
        triple_list = []
    
        with codecs.open(file1, 'r') as f:
            content = f.readlines()
            for line in content:
                triple = line.strip().split("\t")
                if len(triple) != 3:
                    continue
    
                h_ = entities2id[triple[0]]
                r_ = relations2id[triple[1]]
                t_ = entities2id[triple[2]]
    
    
                triple_list.append([h_, r_, t_])
    
        print("Complete load. entity : %d , relation : %d , triple : %d" % (
        len(entity), len(relation), len(triple_list)))
    
        return entity, relation, triple_list
    
    
    def norm_l1(h, r, t):
        return np.sum(np.fabs(h + r - t))
    
    
    def norm_l2(h, r, t):
        return np.sum(np.square(h + r - t))
    
    
    class TransE:
        def __init__(self, entity, relation, triple_list, embedding_dim=50, lr=0.01, margin=1.0, norm=1):
            self.entities = entity
            self.relations = relation
            self.triples = triple_list
            self.dimension = embedding_dim
            self.learning_rate = lr
            self.margin = margin
            self.norm = norm
            self.loss = 0.0
    
        def data_initialise(self):
            entityVectorList = {}
            relationVectorList = {}
            for entity in self.entities:
                entity_vector = np.random.uniform(-6.0 / np.sqrt(self.dimension), 6.0 / np.sqrt(self.dimension),
                                                  self.dimension)
                entityVectorList[entity] = entity_vector
    
            for relation in self.relations:
                relation_vector = np.random.uniform(-6.0 / np.sqrt(self.dimension), 6.0 / np.sqrt(self.dimension),
                                                    self.dimension)
                relation_vector = self.normalization(relation_vector)
                relationVectorList[relation] = relation_vector
    
            self.entities = entityVectorList
            self.relations = relationVectorList
    
        def normalization(self, vector):
            return vector / np.linalg.norm(vector)
    
        def training_run(self, epochs=1, nbatches=100, out_file_title = ''):
    
            batch_size = int(len(self.triples) / nbatches)
            print("batch size: ", batch_size)
            for epoch in range(epochs):
                start = time.time()
                self.loss = 0.0
                # Normalise the embedding of the entities to 1
                for entity in self.entities.keys():
                    self.entities[entity] = self.normalization(self.entities[entity]);
    
                for batch in range(nbatches):
                    batch_samples = random.sample(self.triples, batch_size)
    
                    Tbatch = []
                    for sample in batch_samples:
                        corrupted_sample = copy.deepcopy(sample)
                        pr = np.random.random(1)[0]
                        if pr > 0.5:
                            # change the head entity
                            corrupted_sample[0] = random.sample(self.entities.keys(), 1)[0]
                            while corrupted_sample[0] == sample[0]:
                                corrupted_sample[0] = random.sample(self.entities.keys(), 1)[0]
                        else:
                            # change the tail entity
                            corrupted_sample[2] = random.sample(self.entities.keys(), 1)[0]
                            while corrupted_sample[2] == sample[2]:
                                corrupted_sample[2] = random.sample(self.entities.keys(), 1)[0]
    
                        if (sample, corrupted_sample) not in Tbatch:
                            Tbatch.append((sample, corrupted_sample))
    
                    self.update_triple_embedding(Tbatch)
                end = time.time()
                print("epoch: ", epoch, "cost time: %s" % (round((end - start), 3)))
                print("running loss: ", self.loss)
    
            with codecs.open(out_file_title +"TransE_entity_" + str(self.dimension) + "dim_batch" + str(batch_size), "w") as f1:
    
                for e in self.entities.keys():
                    # f1.write("\t")
                    # f1.write(e + "\t")
                    f1.write(str(list(self.entities[e])))
                    f1.write("\n")
    
            with codecs.open(out_file_title +"TransE_relation_" + str(self.dimension) + "dim_batch" + str(batch_size), "w") as f2:
                for r in self.relations.keys():
                    # f2.write("\t")
                    # f2.write(r + "\t")
                    f2.write(str(list(self.relations[r])))
                    f2.write("\n")
    
        def update_triple_embedding(self, Tbatch):
            # deepcopy 可以保证,即使list嵌套list也能让各层的地址不同, 即这里copy_entity 和
            # entitles中所有的elements都不同
            copy_entity = copy.deepcopy(self.entities)
            copy_relation = copy.deepcopy(self.relations)
    
            for correct_sample, corrupted_sample in Tbatch:
    
                correct_copy_head = copy_entity[correct_sample[0]]
                correct_copy_tail = copy_entity[correct_sample[2]]
                relation_copy = copy_relation[correct_sample[1]]
    
                corrupted_copy_head = copy_entity[corrupted_sample[0]]
                corrupted_copy_tail = copy_entity[corrupted_sample[2]]
    
                correct_head = self.entities[correct_sample[0]]
                correct_tail = self.entities[correct_sample[2]]
                relation = self.relations[correct_sample[1]]
    
                corrupted_head = self.entities[corrupted_sample[0]]
                corrupted_tail = self.entities[corrupted_sample[2]]
    
                # calculate the distance of the triples
                if self.norm == 1:
                    correct_distance = norm_l1(correct_head, relation, correct_tail)
                    corrupted_distance = norm_l1(corrupted_head, relation, corrupted_tail)
    
                else:
                    correct_distance = norm_l2(correct_head, relation, correct_tail)
                    corrupted_distance = norm_l2(corrupted_head, relation, corrupted_tail)
    
                loss = self.margin + correct_distance - corrupted_distance
    
                if loss > 0:
                    self.loss += loss
                    print(loss)
                    correct_gradient = 2 * (correct_head + relation - correct_tail)
                    corrupted_gradient = 2 * (corrupted_head + relation - corrupted_tail)
    
                    if self.norm == 1:
                        for i in range(len(correct_gradient)):
                            if correct_gradient[i] > 0:
                                correct_gradient[i] = 1
                            else:
                                correct_gradient[i] = -1
    
                            if corrupted_gradient[i] > 0:
                                corrupted_gradient[i] = 1
                            else:
                                corrupted_gradient[i] = -1
    
                    correct_copy_head -= self.learning_rate * correct_gradient
                    relation_copy -= self.learning_rate * correct_gradient
                    correct_copy_tail -= -1 * self.learning_rate * correct_gradient
    
                    relation_copy -= -1 * self.learning_rate * corrupted_gradient
                    if correct_sample[0] == corrupted_sample[0]:
                        # if corrupted_triples replaces the tail entity, the head entity's embedding need to be updated twice
                        correct_copy_head -= -1 * self.learning_rate * corrupted_gradient
                        corrupted_copy_tail -= self.learning_rate * corrupted_gradient
                    elif correct_sample[2] == corrupted_sample[2]:
                        # if corrupted_triples replaces the head entity, the tail entity's embedding need to be updated twice
                        corrupted_copy_head -= -1 * self.learning_rate * corrupted_gradient
                        correct_copy_tail -= self.learning_rate * corrupted_gradient
    
                    # normalising these new embedding vector, instead of normalising all the embedding together
                    copy_entity[correct_sample[0]] = self.normalization(correct_copy_head)
                    copy_entity[correct_sample[2]] = self.normalization(correct_copy_tail)
                    if correct_sample[0] == corrupted_sample[0]:
                        # if corrupted_triples replace the tail entity, update the tail entity's embedding
                        copy_entity[corrupted_sample[2]] = self.normalization(corrupted_copy_tail)
                    elif correct_sample[2] == corrupted_sample[2]:
                        # if corrupted_triples replace the head entity, update the head entity's embedding
                        copy_entity[corrupted_sample[0]] = self.normalization(corrupted_copy_head)
                    # the paper mention that the relation's embedding don't need to be normalised
                    copy_relation[correct_sample[1]] = relation_copy
                    # copy_relation[correct_sample[1]] = self.normalization(relation_copy)
    
            self.entities = copy_entity
            self.relations = copy_relation
    
    
    if __name__ == '__main__':
        file1 = "/umls/train.txt"
        file2 = "/umls/entity2id.txt"
        file3 = "/umls/relation2id.txt"
    
        entity_set, relation_set, triple_list = dataloader(file1, file2, file3)
        
        # modify by yourself
        transE = TransE(entity_set, relation_set, triple_list, embedding_dim=30, lr=0.01, margin=1.0, norm=2)
        transE.data_initialise()
        transE.training_run(out_file_title="umls_")
    
    
    展开全文
  • transE

    千次阅读 2018-10-31 17:18:45
    刚才我们简单介绍了TransE很有意思的性能,但是TransE也有自身的缺陷,这里我们简单总结TransE面临的若干挑战,以及在这些方面的最新研究进展。 首先,很多情况下TransE关于h + r=t的假设其实本身并不符合实际。为...

    文章来源:http://chuansong.me/n/2553541
    我们为什么要关注表示学习这个问题呢?我们可以看关于机器学习的一个重要公式,这个公式有三个部分组成,第一部分是关于数据或者问题的表示,在表示的基础上我们要去设计或者构建一个目标,也就是说我们要实现一个什么样的目标。在设定了目标之后,开始看怎么实现这个目标,这就是优化的过程。对于机器学习来讲,表示是这三个环节中最基础的部分,也是我们为什么会关注它的重要原因。对于自然语言处理和多媒体处理而言,所处理的数据是典型的无结构数据。为了让计算机更好地对这些数据进行智能处理,如何很好地表示它们是一个至关重要的问题。

    什么是表示学习呢?在自然语言处理中,常用的表示方式是1-hot Representation,每一个词都可以表示成一个非常长的向量,这个向量的长度就是词汇的数量,例如汉语常用词有6000个,我们就把每个词表示成6000维的向量。每个词对应的向量中有一维设置为1,其他维度设置为0,这样很自然地就把人类语言中的所有词都独一无二地表示成一个向量,这样计算机就可以很好的区分某个词跟另外一个词是不一样的。这种方法非常简单,应用也非常广泛,例如搜索引擎中的百度、谷歌,当输入一个查询词,基本思路就是匹配哪些文档里面出现了这些查询词。其实背后本质就是把每一个词都表示成一个独一无二的符号。但是这种方法面临一个很大的问题,大家可以看到,其实很多的词互相之间有非常丰富的语义联系,比如说star和sun,一个是星星,一个是太阳,它们虽然是不同的词,但有密切的语义关联。但是计算机把它们表示成了两个独立的向量,忽略了它们之间的语义关系。这就是我们希望用表示学习解决的问题。

    表示学习的基本思想是提出一种所谓的Distributed Representation,或者是Embedding,用一个低维的向量空间,把每个词都表示到空间里面的某一个位置。这样,我们就可以利用词和词之间在这个空间中的距离来衡量词与词之间的语义关系,这就是表示学习的基本目标。

    表示学习的基础是什么?我为什么能够做这件事情?其实刚才蔡老师讲到了,这与人脑有非常密切联系。人脑有什么样的特点?第一个特点就是,人脑中的信号都是通过生物电或化学电传递的,这是一个非常低速的过程。但是我们又知道,人脑与计算机相比,虽然信号速度很慢,但认知能力却非常强,比如我们人可以快速地识别一张图片中哪个部分是老虎,哪个部分是草坪等等。但是对于计算机来讲,要实现这种认知能力却非常难。第二个,与计算机相比,人脑是一个非常低功耗的装置。也就是说对于人脑来讲,我们每天只需要吃几顿饭就可以让人脑一天到晚一直工作,但是对于计算机来讲,一台普通计算机每天消耗的能量比人脑要多得多。因此,人脑的工作原理非常值得我们学习。那么,人脑有什么样重要的工作机制呢?

    我们现实世界是一个离散的世界,这个世界里面每个物体相互之间都是独立的,有比较明确的界限,因此可以称为离散的世界。但是人脑表示这些物体的时候,都是表示为很多神经元上抑制和激活的状态。也就是说,我们用神经元上不同的抑制和激活状态来表示不同的物体。因此,虽然现实世界是离散的世界,但是在人脑的认知世界中,都被表示到连续的空间中。从这个角度来看,我们刚才所说的那种低维向量表示,其中每一维都可以看成是人脑的神经元。

    现实世界带有非常强的层次性,比如人有头、四肢和躯体,头又可以分成头发、眉毛、五官等等。在认知世界里面也会对应存在这种层次性,即神经网络的层次结构。现在非常流行的深度学习技术就是由于引入了“深度”的层次性结构,带来了很多任务性能的革命性提升。

    所以我们来看,人脑的这两个特点,对应到表示学习的两个特点,这两个特点就是:第一,采用分布式表示来表征现实世界的对象,即采用连续的空间表示对象。第二,采用深度的多层神经网络实现对现实世界的层次性建模,这也是表示学习的重要思想。

    可以说,表示学习有非常强的认知基础。而同时,对于自然语言处理而言,也有着非常重要的现实意义,主要体现在两个方面,第一个方面,大规模自然语言处理面临非常强的数据稀疏难题,传统方法无法很好解决。而通过构建低维向量表示空间,我们把所有对象映射到这个空间里面,就可以利用这个空间的连续性较好地处理数据稀疏问题。另外一个好处是,可以实现不同领域以及不同对象之间的知识迁移。在自然语言处理中,我们关心的对象非常多,从最基础的词到句子,到文档,以及知识,我们如何更好地计算它们之间的语义关联呢?比如说给你一个句子、一个文档,怎么判断他们之间的语义关系?对传统的自然语言处理而言,这是非常难的事情。通过将这些对象映射到统一的空间中,我们将能够非常容易地计算它们之间的语义关系。因此,我们认为对于自然语言处理来讲,表示学习是非常重要的技术,也是近年来自然语言处理领域非常关心的方向。深度学习技术则是这个方面的重要代表。今天由于时间关系,不可能介绍表示学习在自然语言处理所有方面的应用。这里将以知识表示学习为例,向大家简单介绍最新的进展。

    知识图谱是一种特殊网络,其中每个节点代表现实世界中的实体,而节点间的边表示实体之间的关系。知识图谱一般用三元组形式组织知识,每个三元组包括一个头实体、一个尾实体以及它们之间的关系。这是知识图谱的基本表示形式。有两种代表性的知识图谱,一个是语言知识图谱WordNet,包含了英语中词与词之间的同义、反义、上下位等关系。Wordnet是自然语言处理常用的语言知识库。另外一种知识图谱Freebace是世界知识图谱,包含了现实世界中人、地点、机构等实体以及它们之间的关系,例如,乔布斯是苹果公司的创始人等。

    传统的知识图谱表示方式是基于RDF的三元组,相当于把每一个实体和关系都表示成独一无二的符号。这种方法与one-hot representation类似无法很好地利用或计算实体之间的语义关系。例如,乔布斯和比尔盖茨都是IT里面非常有名的人物,但是在知识图谱中用两个独一无二的符号表示,因此无法很好地计算它们之间的语义关系。因此我们希望通过表示学习来解决这个问题。如果能做到这一点,将能够更好地利用知识图谱中的知识。

    现在主要介绍知识表示学习的一个最简单也是最有效的方案,叫TransE。在这个模型中,每个实体和关系都表示成低维向量。那么如何怎么学习这些低维向量呢?我们需要设计一个学习目标,这个目标就是,给定任何一个三元组,我们都将中间的relation看成是从head到tail的一个翻译过程,也就是说把head的向量加上relation的向量,要让它尽可能地等于tail向量。在学习过程中,通过不断调整、更新实体和关系向量的取值,使这些等式尽可能实现。这里面会有非常多技术实现细节,这里面就不作太多讲解,大家如果感兴趣可以去阅读相关论文。

    这些实体和关系的表示可以用来做什么呢?一个直观的应用就是Entity Prediction(实体预测)。就是说,如果给一个head entity,再给一个relation,那么可以利用刚才学到的向量表示,去预测它的tail entity可能是什么。思想非常简单,直接把h + r,然后去找跟h + r向量最相近的tail向量就可以了。实际上,我们也用这个任务来判断不同表示模型的效果。我们可以看到,以TransE为代表的翻译模型,需要学习的参数数量要小很多,但同时能够达到非常好的预测准确率。

    这里举一些例子。首先,利用TransE学到的实体表示,我们可以很容易地计算出跟某个实体最相似的实体。大家可以看到,关于中国、奥巴马、苹果,通过TransE向量得到的相似实体能够非常好地反映这些实体的关联。

    如果已知head entity和relation,我们可以用TransE模型判断对应的tail entity是什么。比如说与中国相邻的国家或者地区,可以看到比较靠前的实体均比较相关。比如说奥巴马曾经入学的学校,虽然前面的有些并不准确,但是基本上也都是大学或教育机构。

    如果同时知道heat entity和tail entity,我们也可以用TransE模型判断它们之间的关系。例如奥巴马和哥伦比亚大学之间就是一个入学学校的关系。这表明 TransE能够得到比较好的预测效果。

    刚才我们简单介绍了TransE很有意思的性能,但是TransE也有自身的缺陷,这里我们简单总结TransE面临的若干挑战,以及在这些方面的最新研究进展。

    首先,很多情况下TransE关于h + r=t的假设其实本身并不符合实际。为什么呢?假如头实体是美国,关系是总统,而美国总统其实有非常多,我们拿出任意两个实体来,比如奥巴马和布什,这两个人都可以跟USA构成同样的关系。在这种情况下,对这两个三元组学习TransE模型,就会发现,它倾向于让奥巴马和布什在空间中变得非常接近。而这其实不太符合常理,因为奥巴马和布什虽然都是美国总统,但是在其他方面有千差万别。这其实就是涉及到复杂关系的处理问题,即所谓的1对N,N对1、N对N这些关系。刚才例子就是典型的1对N关系,就是一个USA可能会对应多个tail entity。为了解决TransE在处理复杂关系时的不足,研究者提出很多扩展模型,基本思想是,首先把实体按照关系进行映射,然后与该关系构建翻译等式。

    TransH和TransR均为代表扩展模型之一,其中TransH由MSRA研究者提出,TransR由我们实验室提出。可以看到,TransE在实体预测任务能够达到47.1的准确率,而采用TransH和TransR,特别是TransR可以达到20%的提升。对于知识图谱复杂关系的处理,还有很多工作需要做。这里只是简介了一些初步尝试。

    对于TransH和TransR的效果我们给出一些例子。比如对于《泰坦尼克号》电影,想看它的电影风格是什么,TransE得到的效果比TransH和TransR都要差一些。再如剑桥大学的杰出校友有哪些?我们可以看到对这种典型的1对N关系,TransR和TransH均做得更好一些。

    人类知识除了在知识图谱中,更多地蕴藏在大量的互联网文本中。如何把文本信息与知识图谱信息结合起来,更好地实现知识表示,是一个重要的挑战问题。其实如何从文本中抽取知识,是自然语言处理的重要研究任务,其基本思想是寻找两个实体共同出现的文本,然后从这些文本中抽取特征,用来判断实体间的关系。

    我们来看知识表示与文本结合的重要意义。通过上图可以发现,如果单独利用文本信息进行关系抽取,效果如蓝线所示。而如果将知识表示信息结合进来,效果会有明显跃迁。这说明,如果能够将文本和知识图谱信息有效融合在一起,将有助于表示学习等任务的性能提升。另外一个非常重要的挑战是,实体在知识图谱中还有丰富的描述信息,这些信息也是文本形式的,怎么把它融合进来呢?我们尝试了采用卷积神经网络对文本建模。由于时间关系就不作详细介绍,如果大家感兴趣可以私下交流。

    总之,有非常多的知识是蕴藏在无结构的文本里面的,如何把无结构的文本和有结构的知识结合在一起,是非常重要的研究方向,也是不断扩充知识图谱的重要技术手段,值得深入研究。

    最后我想介绍的是,如何充分利用知识图谱中的关系路径进行表示学习。我们可以看到,任意两个实体之间的关系,其实跟它们之间的关系路径有非常强的联系。比如说《阿甘正传》的电影语言其实与导演的语言有密切联系。如何充分利用关系路径,对于知识表示学习有重要意义。

    过去就有人利用关系路径判断两个实体之间的关系,取得了非常好的效果。我们现在想说,在知识表示学习中,不只考虑直接的关联关系,还应当考虑两个实体之间的关系路径。这里有个重要问题,任给两个实体,如何把它们之间的关系路径也表示成向量,这就涉及到组合语义问题。

    我们提出利用相加、相乘、循环神经网络的形式来实现组合语义,利用关系路径中每个关系的表示,得到关系路径的表示。这样我们就得到一个扩展的TransE模型,把关系路径表示成向量,然后构建h + r = t等式。

    可以看一下扩展版TransE的性能。通过这些表格我们可以看到,考虑关系路径的模型在实体预测有超过35%的提升,这个提升是非常显著的。而在关系预测上,也有10%的提升,这个提升也非常明显。

    由于时间关系这里就只举一个例子。如果我们能够很好地考虑关系路径,任给两个关系,假如它们形成一个路径的话,我们可以得到这个路径对应的关系,比如说某个关系路径是:“出生地点”和“地点所在国家”,通过模型可以推测出这个路径对应着关系“国籍”。

    以上就是我们今天着重介绍的,如何在知识表示学习中考虑复杂关系,如何把文本和知识图谱信息相结合,以及如何考虑关系路径等等。当然,知识图谱的表示学习还有非常多值得研究的课题。

    知识表示学习与深度学习在自然语言处理中的应用密切相关,最近两年正处在爆发期,今年自然语言处理会议的大部分论文都是关于深度学习的。这表明,表示学习是非常重要的研究方向。

    最后想再强调一点,面向知识图谱的表示学习,对于更好地表示和利用知识图谱中的信息具有非常重要的意义。

    这个方向仍然处于探索阶段,有非常多的工作值得去做。其有很多开放性的问题没有得到答案。比如人类认知的举一反三的能力,这代表了人的泛化能力以及抽象能力,目前深度学习和表示学习对此还做得不够好。2015年《科学》杂志上发表了一篇重要工作,就探索了只给一个样例来学习分类的问题,这对于人来讲很容易,但是对于机器则很难做到,特别是深度学习在这方面的能力非常差。实际上,深度学习领域在2014年发布过一个非常有名的成果,能够利用大规模无标注的图片自动学习和识别猫脸。其实这个过程有非常多的限制,包括使用了非常大量的猫的图片,而且都是标准的正脸。采用非常规范的数据学习得到猫脸,与人脑相比没什么了不起,因为人脑根本不需要这么多图片才能学到猫的样子。人只需要根据有限个样例,就能总结出猫的特点。这是深度学习和表示学习需要继续努力的方向。

    展开全文
  • 知识表示学习 TransE 代码逻辑梳理 超详细解析

    千次阅读 多人点赞 2020-09-02 16:38:22
    "C:\\data\\train.txt" tripleNum, tripleList = openTrain(dirTrain) print("打开TransE") transE = TransE(entityList,relationList,tripleList, margin=1, dim = 100) print("TranE初始化") transE.initialize() ...

    知识表示学习

    网络上已经存在了大量知识库(KBs),比如OpenCyc,WordNet,Freebase,Dbpedia等等。
    在这里插入图片描述

    这些知识库是为了各种各样的目的建立的,因此很难用到其他系统上面。为了发挥知识库的图(graph)性,也为了得到统计学习(包括机器学习和深度学习)的优势,我们需要将知识库嵌入(embedding)到一个低维空间里(比如10、20、50维)。我们都知道,获得了向量后,就可以运用各种数学工具进行分析。它为许多知识获取任务和下游应用铺平了道路。
    总的来说,废话这么多,所谓知识表示学习,就是将知识库给映射成向量,同时满足属于同一个三元组的(h,t,l)满足h+l≈t,而不是一个三元组的不满足这个条件。

    TransE思路

    TranE是一篇Bordes等人2013年发表在NIPS上的文章提出的算法。它的提出,是为了解决多关系数据(multi-relational data)的处理问题。我们现在有很多很多的知识库数据knowledge bases (KBs),比如Freebase、 Google Knowledge Graph 、 GeneOntology等等。
    TransE的直观含义,就是TransE基于实体和关系的分布式向量表示,将每个三元组实例(head,relation,tail)中的关系relation看做从实体head到实体tail的翻译(其实我一直很纳闷为什么叫做translating,其实就是向量相加),通过不断调整h、r和t(head、relation和tail的向量),使(h + r) 尽可能与 t 相等,即 h + r = t。
    在这里插入图片描述
    损失函数是在这里插入图片描述

    TransE代码逻辑梳理

    首先注明,该代码不是出自我手,但由于最近需要使用并修改TransE,故从github上找到一个还不错的TransE实现,对其进行阅读,并梳理其逻辑,为后续工作做好铺垫。贴上其github链接,感谢前人辛苦付出。https://github.com/wuxiyu/transE/blob/master/tranE.py
    下面对其代码进行分析。
    首先,这里将整个代码封装成了一个类,该类的构造方法(由于平常用的语言是java,python只当做工具语言,没有系统学过语法,所以用词可能不当,见谅)中需要的参数如下所示:

            :param entityList: 实体列表,读取文本文件,实体+id
            :param relationList: 关系列表,读取文本文件,关系+id
            :param tripleList: 三元组列表,读取文本文件,实体+实体+关系
            :param margin: 表示正负样本之间的间距,是一个超参数,也就是公式中Loss里的γ
            :param learingRate: 学习率,其实就是梯度下降中的步长
            :param dim: 向量维度,即h,t,l向量的维度是1*dim,因为最终我们所有的实体和关系都是要表示为向量
            :param L1: 距离公式采用矩阵1范数还是矩阵2范数
    

    首先,我们将目光放到main方法,从main方法开始整个TransE的旅程。

        dirEntity = "C:\\data\\entity2id.txt"
        entityIdNum, entityList = openDetailsAndId(dirEntity)
        dirRelation = "C:\\data\\relation2id.txt"
        relationIdNum, relationList = openDetailsAndId(dirRelation)
        dirTrain = "C:\\data\\train.txt"
        tripleNum, tripleList = openTrain(dirTrain)
        print("打开TransE")
        transE = TransE(entityList,relationList,tripleList, margin=1, dim = 100)
        print("TranE初始化")
        transE.initialize()
        transE.transE(15000)
        transE.writeRelationVector("c:\\relationVector.txt")
        transE.writeEntilyVector("c:\\entityVector.txt")
    

    首先是通过三个Open方法分别获取实体数量和实体列表、关系总数量和关系列表、三元组总数量和三元组列表。获取需要的数据。
    例如,其中entityList是一个list,其样式就为[05451384,04958634,00620424,....];
    而relationList样式为["_member_of_domain_topic","_member_meronym"...];
    而tripleList例如[(03964744,04371774,_hyponym), (....)....],其中全是三元组,都是(h,t,l)的格式。
    至于那些Num们,都只是用于计数?并没发现用在哪里,也不用管

    然后就是实例化TransE这个类了,将实体列表,关系列表,和三元组列表放进去,设置间距γ为1(这个是超参数,可以调),然后对于输出向量,其维度设为100(这个也可以自己指定)。

    之后调用transE的initialize()方法,进行初始化。这里初始化具体做了什么呢?答曰初始化向量,构建字典集合,分别来装实体向量们和关系向量们。那么问题就来了,这个向量如何生成呢,之前我们手里只有05451384这串数字来代表实体,但是,并没有向量啊。这里采用的方式就是···随机生成,对于个100维的向量,随机生成它,方式为每一个数字都是在-6/(dim**0.5), 6/(dim**0.5)之间随机生成,然后构成一个100个元素的列表,即代表这个实体的向量,同时,将这个实体和其对应的随机生成的向量放入新创建的字典entityVectorList中去,同理对于关系也是如此操作。当然,在向量生成之后对其做一个归一化,保证它是单位向量,做法就是每个元素除以元素总和的平方和的开平方,具体见norm方法,这个很简单。

            entityVectorList = {}
            relationVectorList = {}
            for entity in self.entityList:
                n = 0
                entityVector = []
                while n < self.dim:
                    ram = init(self.dim)
                    entityVector.append(ram) #注意到这里的ram和entity是毫无关系的,是一个随机的值,所以这里append之后,就是一个dim个元素的列表
                    n += 1
                entityVector = norm(entityVector)#归一化
                entityVectorList[entity] = entityVector
    

    至此,我们便为每个关系和实体生成了一个向量,向量是一个100维的列表。
    然后我们将entityList和relationList赋值成这两个字典,也就是我们最初的entityList是列表,而经过初始化之后却变成了字典,字典的样式为{实体名:对应向量,…}

    之后,下一步就是进行训练了。调用transE的transE()方法,其中输入的15000意为迭代的次数。

            for cycleIndex in range(cI):#迭代cI次
                Sbatch = self.getSample(150) #随机获取150个三元组
                Tbatch = []#元组对(原三元组,打碎的三元组)的列表 :[((h,r,t),(h',r,t'))]
                for sbatch in Sbatch:#遍历获取到的元组,并获取它们的打碎三元组,从而获得<=150个元组对(防止重复)
                    tripletWithCorruptedTriplet = (sbatch, self.getCorruptedTriplet(sbatch)) #将sbatch传入,获取打碎的三元组,然后构成一个元组对
                    if(tripletWithCorruptedTriplet not in Tbatch):
                        Tbatch.append(tripletWithCorruptedTriplet)
                self.update(Tbatch)#对整个集合进行更新
                if cycleIndex % 100 == 0:
                    print("第%d次循环"%cycleIndex)
                    print(self.loss)
                    self.writeRelationVector("c:\\relationVector.txt")
                    self.writeEntilyVector("c:\\entityVector.txt")
                    self.loss = 0
    

    这里cI参数就是迭代次数。
    首先是调用getSample()方法,该方法作用为在tripleList中随机选取size个三元组并返回。所以这里的Sbatch就是随机获取的150个三元组。
    然后Tbatch是一个新创建的列表,用于存储元组(元组是tuple,是python中一种数据结构,而三元组是知识图谱的一种结构,不要搞乱了),其中的样式为[((h,r,t),(h',r,t'))]
    下面就是对Sbatch进行遍历,遍历每一个三元组,调用getCorruptedTriplet()方法来获取某个三元组的打碎的三元组,也就是在上面算法中提到的,对一个三元组,我们假定它是h+l=t的,此时我们创建一个范例,一个绝对不满足假设的,如何创建呢,任意用别的h或t来替换掉我们这里的h或t,从而得到一个错误的三元组,即打碎的三元组(我也不知道为啥叫打碎,不过挺有意思哈哈哈)。将打碎的三元组和正确的三元组放在一起组成一个新的元组,然后将其放入Tbatch列表中,当然这里有个去重的判断,很简单,就不说了哈。
    下面的操作就是最重要的了,进行更新。
    首先,要明确,这里的更新,只是针对我们随机选出来的150个三元组进行更新。然后,更新什么呢?当然是更新它们的向量,所以假设我们的h,t都互不相同,那么这里最多也就更新了300个实体的向量,(关系因为数量肯定没那么多,就不举例了)。然后更新的方式是什么,那就是通过梯度下降法来求得损失函数的最小值,从而获得一个最优的向量们。

    好,下面我们来看这个更新操作,这里是调用update()方法,将刚才的Tbatch传入。
    首先在该方法的开始,进行了两次拷贝,将实体列表(其实是实体-向量字典)和关系列表分别进行拷贝,目的是为了之后更新,不相互影响。然后关于deepcopy和copy的区别大家可以去查一下,简单来说就是前者copy的更彻底,列表或字典中的每个元素都单独拷贝了一份。
    然后便是遍历这里的Tbatch,对每个元组进行操作。
    首先是前面是一长串的赋值操作,选其中一个来说明。

    headEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][0]]
    

    首先我们知道tripletWithCorruptedTriplet的格式是这样的[((h,r,t),(h',r,t'))],那[0][0]就是获取其中的h实体,然后根据h实体在entityList字典中获取其对应的向量。如此便是,其余也皆是同理。
    然后根据L1参数是否为true来使用矩阵1范数或矩阵2范数,因为不同范数它的梯度是不一样的。
    我们接下来矩阵2范数即L1==false来进行说明。此时进行计算Loss损失函数的值,根据公式 γ + d ( h + l , t ) − d ( h ′ + l , t ′ ) \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)要进行展开,就是普通的距离公式,展开之后的Loss函数为 γ + ( h + l − t ) 2 − ( h ′ + l − t ′ ) 2 \gamma+(h+l-t)^{2}-(h'+l-t')^{2} γ+(h+lt)2(h+lt)2,等一下,是不是主要到这里和之前说的有些不同,对的,这里没有求和符号,因为这里相当于是把总的Loss给分开算的,所以没有求和符号了。累加起来便有。
    然后当这个损失函数的值>0时,才进行更新,否则不进行更新。这里解释一下为什么这么操作。如此操作的原因在于我们喜欢正确的三元组的向量们满足h+l≈t,而打碎的三元组不满足,则正确三元组距离应该接近于0,而错误的应为一个不小的正值(因为是矩阵2范数),然后此时必然有损失函数值e<0的情况。当然,你也会说那假如两个值都不小,刚好前者小于后者呢,这种情况少,且没必要要求这么高,毕竟可以近似,同时这是算法层级的问题,这里不再讨论。
    当e>0时,我们进行更新,这里更新的操作就是一个很简单的梯度下降方法。下面来介绍一下。首先损失函数Loss是 γ + ( h + l − t ) 2 − ( h ′ + l − t ′ ) 2 \gamma+(h+l-t)^{2}-(h'+l-t')^{2} γ+(h+lt)2(h+lt)2,我们对其h进行求导得其梯度,则其结果是 ∂ ∂ h = 2 ( h + l − t ) \frac{\partial }{\partial h} = 2(h+l-t) h=2(h+lt),则h更新为 h ∗ = h − u ∗ ∂ ∂ h = h − u ∗ 2 ∗ ( h + l − t ) = h + u ∗ 2 ∗ ( t − h − l ) h^{*}=h-u*\frac{\partial }{\partial h}=h-u*2*(h+l-t)=h+u*2*(t-h-l) h=huh=hu2(h+lt)=h+u2(thl),这里的u是梯度下降的步长,也就是上面提到的学习率,同理,t的更新也是一样, t ∗ = t − u ∗ 2 ∗ ( t − h − l ) t^{*}=t-u*2*(t-h-l) t=tu2(thl),然后同理l也是一样 l ∗ = l + u ∗ 2 ∗ ( t − h − l ) − u ∗ 2 ∗ ( t ′ − h ′ − l ) l^{*}=l+u*2*(t-h-l)-u*2*(t'-h'-l) l=l+u2(thl)u2(thl)
    如此,进行更新,然后进行归一化,最终更新总的entityList和relationList。

    至此,更新过程结束,至于后面的向量写入文件这里就不赘述了。

    完整代码

    这里代码我都加上了较为详细的注释,可以结合上面的代码梳理进行理解。

    from random import uniform, sample
    from numpy import *
    from copy import deepcopy
    
    class TransE:
        def __init__(self, entityList, relationList, tripleList, margin = 1, learingRate = 0.00001, dim = 10, L1 = True):
            '''
            :param entityList: 实体列表,读取文本文件,实体+id
            :param relationList: 关系列表,读取文本文件,关系+id
            :param tripleList: 三元组列表,读取文本文件,实体+实体+关系
            :param margin: gamma,目标函数的常数
            :param learingRate: 学习率
            :param dim: 向量维度,也就是h,t,l向量的维度是1*dim
            :param L1: 距离公式
            '''
            self.margin = margin
            self.learingRate = learingRate
            self.dim = dim#向量维度
            self.entityList = entityList#一开始,entityList是entity的list;初始化后,变为字典,key是entity,values是其向量(使用narray)。
            self.relationList = relationList#理由同上
            self.tripleList = tripleList#理由同上
            self.loss = 0
            self.L1 = L1
    
        def initialize(self):
            '''
            初始化向量
            '''
            entityVectorList = {}
            relationVectorList = {}
            for entity in self.entityList:
                n = 0
                entityVector = []
                while n < self.dim:
                    ram = init(self.dim)#初始化的范围
                    entityVector.append(ram) #注意到这里的ram和entity是毫无关系的,是一个随机的值,所以这里append之后,就是一个dim个元素的列表
                    n += 1
                entityVector = norm(entityVector)#归一化
                entityVectorList[entity] = entityVector
            print("entityVector初始化完成,数量是%d"%len(entityVectorList))
    
            for relation in self. relationList:
                n = 0
                relationVector = []
                while n < self.dim:
                    ram = init(self.dim)#初始化的范围
                    relationVector.append(ram)
                    n += 1
                relationVector = norm(relationVector)#归一化
                relationVectorList[relation] = relationVector
            print("relationVectorList初始化完成,数量是%d"%len(relationVectorList))
            self.entityList = entityVectorList
            self.relationList = relationVectorList
    
        def transE(self, cI = 20):
            print("训练开始")
            for cycleIndex in range(cI):#迭代cI次
                Sbatch = self.getSample(150) #随机获取150个三元组
                Tbatch = []#元组对(原三元组,打碎的三元组)的列表 :{((h,r,t),(h',r,t'))}
                for sbatch in Sbatch:#遍历获取到的元组,并获取它们的打碎三元组,从而获得<=150个元组对(防止重复)
                    tripletWithCorruptedTriplet = (sbatch, self.getCorruptedTriplet(sbatch)) #将sbatch传入,获取打碎的三元组,然后构成一个元组对
                    if(tripletWithCorruptedTriplet not in Tbatch):
                        Tbatch.append(tripletWithCorruptedTriplet)
                self.update(Tbatch)#对整个集合进行更新
                if cycleIndex % 100 == 0:
                    print("第%d次循环"%cycleIndex)
                    print(self.loss)
                    self.writeRelationVector("c:\\relationVector.txt")
                    self.writeEntilyVector("c:\\entityVector.txt")
                    self.loss = 0
    
        def getSample(self, size):
            '''
            随机选取部分三元关系 sbatch
            :param size:
            :return:
            '''
            return sample(self.tripleList, size) #从tripleList中随机获取size个元素
    
        def getCorruptedTriplet(self, triplet):
            '''
            training triplets with either the head or tail replaced by a random entity (but not both at the same time)
            随机替换三元组的实体,h和t中任意一个被替换,但不同时替换。
            也就是构建损坏的三元组集合
            :param triplet:
            :return corruptedTriplet:
            '''
            i = uniform(-1, 1)
            if i < 0:#小于0,打坏三元组的第一项
                while True:
                    entityTemp = sample(self.entityList.keys(), 1)[0]
                    if entityTemp != triplet[0]:
                        break
                corruptedTriplet = (entityTemp, triplet[1], triplet[2])
            else:#大于等于0,打坏三元组的第二项
                while True:
                    entityTemp = sample(self.entityList.keys(), 1)[0]
                    if entityTemp != triplet[1]:
                        break
                corruptedTriplet = (triplet[0], entityTemp, triplet[2])
            return corruptedTriplet
    
        def update(self, Tbatch):
            '''
            进行更新,更新的过程就是一个梯度下降
            :param Tbatch:
            :return:
            '''
            copyEntityList = deepcopy(self.entityList) #copy和deepcopy的区别在于,copy只拷贝整体,若局部改变,则拷贝整体的局部也改变,而deepcopy则全部拷贝过去
            copyRelationList = deepcopy(self.relationList)
            
            for tripletWithCorruptedTriplet in Tbatch:#遍历整个元组,最多迭代150次
                # 这里的索引很好理解((h,t,l)(h',t',l)) 但是copyEntityList[h]
                # 懂了,这里EntityList是类似于字典的,有id与向量这两个东西,所以是输入id,获取向量
                headEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][0]]#tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tuple
                tailEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][1]]
                relationVector = copyRelationList[tripletWithCorruptedTriplet[0][2]]
                headEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][0]]
                tailEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][1]]
                #下面的也是一模一样,感觉只是为了备份一份,进行比较
                headEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][0]]#tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tuple
                tailEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][1]]
                relationVectorBeforeBatch = self.relationList[tripletWithCorruptedTriplet[0][2]]
                headEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][0]]
                tailEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][1]]
                
                if self.L1:#这L1啥意思···哦是L1范数
                    distTriplet = distanceL1(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)
                    distCorruptedTriplet = distanceL1(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch ,  relationVectorBeforeBatch)
                else:#否则L2范数
                    distTriplet = distanceL2(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)
                    distCorruptedTriplet = distanceL2(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch ,  relationVectorBeforeBatch)
                eg = self.margin + distTriplet - distCorruptedTriplet #损失函数 就跟论文上公式是一样的
                if eg > 0: #[function]+ 是一个取正值的函数  似乎是只有大于0时才进行更新,想一下,也确实,因为前一个距离应该为0,后一个不为0,然后,0-正<0则不用改,正-正>则需要改
                    self.loss += eg
                    if self.L1:
                        #这个学习率有点懵
                        tempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)
                        tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)
                        tempPositiveL1 = []
                        tempNegtativeL1 = []
                        for i in range(self.dim):#不知道有没有pythonic的写法(比如列表推倒或者numpy的函数)?
                            if tempPositive[i] >= 0:
                                tempPositiveL1.append(1)
                            else:
                                tempPositiveL1.append(-1)
                            if tempNegtative[i] >= 0:
                                tempNegtativeL1.append(1)
                            else:
                                tempNegtativeL1.append(-1)
                        tempPositive = array(tempPositiveL1)  
                        tempNegtative = array(tempNegtativeL1)
    
                    else:
                        #这里学习率就是y?对,应该这里的学习率就是梯度下降中的步长
                        #然后括号里是t-h-l
                        tempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)
                        tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)
    
                    #进行更新
                    headEntityVector = headEntityVector + tempPositive #h* = h + 增量
                    tailEntityVector = tailEntityVector - tempPositive #t* = t - 增量
                    relationVector = relationVector + tempPositive - tempNegtative #l* = l +y*2(t-h-l) -y*2(t'-h'-l)
                    headEntityVectorWithCorruptedTriplet = headEntityVectorWithCorruptedTriplet - tempNegtative #同理
                    tailEntityVectorWithCorruptedTriplet = tailEntityVectorWithCorruptedTriplet + tempNegtative #同理
    
                    #只归一化这几个刚更新的向量,而不是按原论文那些一口气全更新了
                    copyEntityList[tripletWithCorruptedTriplet[0][0]] = norm(headEntityVector)
                    copyEntityList[tripletWithCorruptedTriplet[0][1]] = norm(tailEntityVector)
                    copyRelationList[tripletWithCorruptedTriplet[0][2]] = norm(relationVector)
                    copyEntityList[tripletWithCorruptedTriplet[1][0]] = norm(headEntityVectorWithCorruptedTriplet)
                    copyEntityList[tripletWithCorruptedTriplet[1][1]] = norm(tailEntityVectorWithCorruptedTriplet)
                    
            self.entityList = copyEntityList #进行更新
            self.relationList = copyRelationList
            
        def writeEntilyVector(self, dir):
            print("写入实体")
            entityVectorFile = open(dir, 'w')
            for entity in self.entityList.keys():
                entityVectorFile.write(entity+"\t")
                entityVectorFile.write(str(self.entityList[entity].tolist()))
                entityVectorFile.write("\n")
            entityVectorFile.close()
    
        def writeRelationVector(self, dir):
            print("写入关系")
            relationVectorFile = open(dir, 'w')
            for relation in self.relationList.keys():
                relationVectorFile.write(relation + "\t")
                relationVectorFile.write(str(self.relationList[relation].tolist()))
                relationVectorFile.write("\n")
            relationVectorFile.close()
    
    def init(dim):
        '''
        向量初始化,随机生成值
        :param dim: 维度
        :return:
        '''
        return uniform(-6/(dim**0.5), 6/(dim**0.5)) #uniform(a, b)#随机生成a,b之间的数,左闭右开
    
    def distanceL1(h, t ,r):
        s = h + r - t
        sum = fabs(s).sum()
        return sum
    
    def distanceL2(h, t, r):
        '''
        这里是对向量进行操作的,所以有个sum
        :param h: 这里的都是向量
        :param t:
        :param r:
        :return:
        '''
        s = h + r - t
        sum = (s*s).sum()
        return sum
    
    def norm(list):
        '''
        归一化
        :param 向量
        :return: 向量/向量的能量
        '''
        var = linalg.norm(list)
        i = 0
        while i < len(list):
            list[i] = list[i]/var
            i += 1
        return array(list)
    
    def openDetailsAndId(dir,sp="\t"):
        idNum = 0
        list = []
        with open(dir) as file:
            lines = file.readlines()
            for line in lines:
                DetailsAndId = line.strip().split(sp)
                list.append(DetailsAndId[0])
                idNum += 1
        return idNum, list
    
    def openTrain(dir,sp="\t"):
        num = 0
        list = []
        with open(dir) as file:
            lines = file.readlines()
            for line in lines:
                triple = line.strip().split(sp)
                if(len(triple)<3):
                    continue
                list.append(tuple(triple))
                num += 1
        return num, list
    
    if __name__ == '__main__':
        dirEntity = "C:\\data\\entity2id.txt"
        entityIdNum, entityList = openDetailsAndId(dirEntity)
        dirRelation = "C:\\data\\relation2id.txt"
        relationIdNum, relationList = openDetailsAndId(dirRelation)
        dirTrain = "C:\\data\\train.txt"
        tripleNum, tripleList = openTrain(dirTrain)
        print("打开TransE")
        transE = TransE(entityList,relationList,tripleList, margin=1, dim = 100)
        print("TranE初始化")
        transE.initialize()
        transE.transE(15000)
        transE.writeRelationVector("c:\\relationVector.txt")
        transE.writeEntilyVector("c:\\entityVector.txt")
    
    
    

    参考资料

    https://blog.csdn.net/u011274209/article/details/50991385
    https://blog.csdn.net/jiayalu/article/details/100543909
    https://github.com/wuxiyu/transE/blob/master/tranE.py

    展开全文
  • TransE代码实践(很详细)

    千次阅读 热门讨论 2019-09-27 10:36:45
    TransE的直观含义,就是TransE基于实体和关系的分布式向量表示,将每个三元组实例(head,relation,tail)中的关系relation看做从实体head到实体tail的翻译,通过不断调整h、r和t(head、relat...

    TranE是一篇Bordes等人2013年发表在NIPS上的文章提出的算法。它的提出,是为了解决多关系数据(multi-relational data)的处理问题。TransE的直观含义,就是TransE基于实体和关系的分布式向量表示,将每个三元组实例(head,relation,tail)中的关系relation看做从实体head到实体tail的翻译,通过不断调整h、r和t(head、relation和tail的向量),使(h + r) 尽可能与 t 相等,即 h + r = t。
    这篇文章主要用来记录下TransE的代码。代码难点有两点,一是生成随机数的过程相对复杂一些;第二是生成伪数据时的流程即corrupt_head,其他照着主函数的执行流程应该都没问题。附一张corrupt_head执行样例。

    #include <cstring>
    #include <cstdio>
    #include <cstdlib>
    #include <cmath>
    #include <ctime>
    #include <string>
    #include <algorithm>
    #include <pthread.h>
    #include <iostream>
    #include <sstream>
    
    using namespace std;
    
    const float pi = 3.141592653589793238462643383;
    
    int transeThreads = 8;
    int transeTrainTimes = 1000;
    int nbatches = 10;
    int dimension = 50;
    float transeAlpha = 0.001;
    float margin = 1;
    
    string inPath = "../../";
    string outPath = "../../";
    
    
    int *lefHead, *rigHead;
    int *lefTail, *rigTail;
    
    struct Triple {
        int h, r, t;
    };
    
    Triple *trainHead, *trainTail, *trainList;
    
    struct cmp_head {
        bool operator()(const Triple &a, const Triple &b) {
            return (a.h < b.h)||(a.h == b.h && a.r < b.r)||(a.h == b.h && a.r == b.r && a.t < b.t);
        }
    };
    
    struct cmp_tail {
        bool operator()(const Triple &a, const Triple &b) {
            return (a.t < b.t)||(a.t == b.t && a.r < b.r)||(a.t == b.t && a.r == b.r && a.h < b.h);
        }
    };
    
    /*
     There are some math functions for the program initialization.
     */
    // 转换数组next_random中index为id的值
    unsigned long long *next_random;
    // 转换next_random索引为id的值并返回
    unsigned long long randd(int id) {
        next_random[id] = next_random[id] * (unsigned long long)25214903917 + 11;
        return next_random[id];
    }
    
    int rand_max(int id, int x) { //小于x的随机数
        int res = randd(id) % x;
        while (res<0)
            res+=x;
        return res;
    }
    
    float rand(float min, float max) {
        return min + (max - min) * rand() / (RAND_MAX + 1.0);
    }
    
    // 返回x的概率密度函数
    float normal(float x, float miu,float sigma) {
        return 1.0/sqrt(2*pi)/sigma*exp(-1*(x-miu)*(x-miu)/(2*sigma*sigma));
    }
    
    //返回一个大于或等于均值miu的概率密度并且属于[min,max]的数
    float randn(float miu,float sigma, float min ,float max) {
        float 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;
    }
    // 向量标准化
    void norm(float * con) {
        float x = 0;
        for (int  ii = 0; ii < dimension; ii++)
            x += (*(con + ii)) * (*(con + ii));
        x = sqrt(x);
        if (x>1)
            for (int ii=0; ii < dimension; ii++)
                *(con + ii) /= x;
    }
    
    /*
     Read triples from the training file.
     */
    
    int relationTotal, entityTotal, tripleTotal;
    float *relationVec, *entityVec;
    float *relationVecDao, *entityVecDao;
    
    // 将实体id 关系id 三元组导入、初始化要训练的向量
    void init() {
        FILE *fin;
        int tmp;
        
        fin = fopen((inPath + "relation2id.txt").c_str(), "r"); //fopen创建或打开,c_str()返回一个指向正规C字符串的临时的指针常量
        tmp = fscanf(fin, "%d", &relationTotal); //获取总的total数
        fclose(fin);
        //构建关系向量
        relationVec = (float *)calloc(relationTotal * dimension, sizeof(float));//分配realtionTotal*dimension
        for (int i=0;i<relationTotal; i++) {
            for (int ii=0;ii<dimension; ii++)
                relationVec[i*dimension+ii] = randn(0,1.0/dimension,-6/sqrt(dimension),6/sqrt(dimension));//(miu,sigma,min,max)
        }
        fin = fopen((inPath + "entity2id.txt").c_str(), "r");
        tmp = fscanf(fin,"%d",&entityTotal);
        fclose(fin);
        entityVec = (float *)calloc(entityTotal * dimension, sizeof(float));
        for (int i=0;i<entityTotal;i++) {
            for (int ii=0;ii<dimension;ii++)
                entityVec[i * dimension + ii] = randn(0, 1.0 / dimension, -6 / sqrt(dimension), 6 / sqrt(dimension));
            norm(entityVec+i*dimension); // 单个entity向量标准化
        }
        fin = fopen((inPath + "triple2id.txt").c_str(), "r");
        tmp = fscanf(fin, "%d", &tripleTotal);
        trainHead = (Triple *)calloc(tripleTotal, sizeof(Triple));
        trainTail = (Triple *)calloc(tripleTotal, sizeof(Triple));
        trainList = (Triple *)calloc(tripleTotal, sizeof(Triple));
        tripleTotal = 0;
        //trainlist存储三元组,复制给 trainHead和trainTail
        while (fscanf(fin, "%d", &trainList[tripleTotal].h) == 1) {
            tmp = fscanf(fin, "%d", &trainList[tripleTotal].t);
            tmp = fscanf(fin, "%d", &trainList[tripleTotal].r);
            trainHead[tripleTotal].h = trainList[tripleTotal].h;
            trainHead[tripleTotal].t = trainList[tripleTotal].t;
            trainHead[tripleTotal].r = trainList[tripleTotal].r;
            trainTail[tripleTotal].h = trainList[tripleTotal].h;
            trainTail[tripleTotal].t = trainList[tripleTotal].t;
            trainTail[tripleTotal].r = trainList[tripleTotal].r;
            tripleTotal++;
        }
        fclose(fin);
        //按照head和tail排序
        sort(trainHead, trainHead + tripleTotal, cmp_head());
        sort(trainTail, trainTail + tripleTotal, cmp_tail());
        
        lefHead = (int *)calloc(entityTotal, sizeof(int));
        rigHead = (int *)calloc(entityTotal, sizeof(int));
        lefTail = (int *)calloc(entityTotal, sizeof(int));
        rigTail = (int *)calloc(entityTotal, sizeof(int));
        memset(rigHead, -1, sizeof(int)*entityTotal); //初始化
        memset(rigTail, -1, sizeof(int)*entityTotal);
        for (int i=1;i<tripleTotal;i++) {
            if (trainTail[i].t != trainTail[i - 1].t) {
                rigTail[trainTail[i - 1].t] = i - 1;  // 将索引为i-1的t的值置为i-1,意思TrainTail[i-1]的值的终止点
                lefTail[trainTail[i].t] = i; // 将索引为i的t的值置为i,意思是TrainTail[i]与左侧值不同,意思TrainTail[i]的值的终止点
            }
            if (trainHead[i].h != trainHead[i - 1].h) {
                rigHead[trainHead[i - 1].h] = i - 1;
                lefHead[trainHead[i].h] = i;
            }
        }
        rigHead[trainHead[tripleTotal - 1].h] = tripleTotal - 1;
        rigTail[trainTail[tripleTotal - 1].t] = tripleTotal - 1;
        
        relationVecDao = (float*)calloc(dimension * relationTotal, sizeof(float));
        entityVecDao = (float*)calloc(dimension * entityTotal, sizeof(float));
    }
    
    /*
     Training process of transE.
     */
    
    int transeLen;
    int transeBatch;
    float res;
    
    // 计算距离 d(e1-e2-r)=sum(|e1-e2-r|)
    float calc_sum(int e1, int e2, int rel) {
        float sum=0;
        int last1 = e1 * dimension;
        int last2 = e2 * dimension;
        int lastr = rel * dimension;
        for (int ii=0; ii < dimension; ii++) {
            // 从entityVec取值计算loss
            sum += fabs(entityVec[last2 + ii] - entityVec[last1 + ii] - relationVec[lastr + ii]);
        }
        return sum;
    }
    // 更新梯度,正样本试图缩小梯度,负样本试图增大梯度
    void gradient(int e1_a, int e2_a, int rel_a, int e1_b, int e2_b, int rel_b) {
        int lasta1 = e1_a * dimension;
        int lasta2 = e2_a * dimension;
        int lastar = rel_a * dimension;
        int lastb1 = e1_b * dimension;
        int lastb2 = e2_b * dimension;
        int lastbr = rel_b * dimension;
        for (int ii=0; ii  < dimension; ii++) {
            float x;
            x = (entityVec[lasta2 + ii] - entityVec[lasta1 + ii] - relationVec[lastar + ii]);
            if (x > 0)
                x = -transeAlpha;
            else
                x = transeAlpha;
            relationVec[lastar + ii] -= x;
            entityVec[lasta1 + ii] -= x;
            entityVec[lasta2 + ii] += x;
            x = (entityVec[lastb2 + ii] - entityVec[lastb1 + ii] - relationVec[lastbr + ii]);
            if (x > 0)
                x = transeAlpha;
            else
                x = -transeAlpha;
            relationVec[lastbr + ii] -=  x;
            entityVec[lastb1 + ii] -= x;
            entityVec[lastb2 + ii] += x;
        }
    }
    
    // 计算距离并更新梯度
    void train_kb(int e1_a, int e2_a, int rel_a, int e1_b, int e2_b, int rel_b) {
        float sum1 = calc_sum(e1_a, e2_a, rel_a);
        float sum2 = calc_sum(e1_b, e2_b, rel_b);
        // 不满足条件则需要更新梯度
        if (sum1 + margin > sum2) {
            res += margin + sum1 - sum2;
            gradient(e1_a, e2_a, rel_a, e1_b, e2_b, rel_b);
        }
    }
    // 根据相同的h返回一个假的样本t,获取三元组中相同h对应的r
    int corrupt_head(int id, int h, int r) {
        int lef, rig, mid, ll, rr;
        lef = lefHead[h] - 1;
        rig = rigHead[h];
        while (lef + 1 < rig) { //则该值不止一个
            mid = (lef + rig) >> 1; // 除2
            if (trainHead[mid].r >= r) rig = mid; else
                lef = mid;
        }
        ll = rig; // r值对应的index
        lef = lefHead[h];
        rig = rigHead[h] + 1;
        while (lef + 1 < rig) {
            mid = (lef + rig) >> 1;
            if (trainHead[mid].r <= r) lef = mid; else
                rig = mid;
        }
        rr = lef;
        int tmp = rand_max(id, entityTotal - (rr - ll + 1)); //生成一个小于entityTotal - (rr - ll + 1)的随机数
        if (tmp < trainHead[ll].t) return tmp; //小于初始t 直接返回
        if (tmp > trainHead[rr].t - rr + ll - 1) return tmp + rr - ll + 1; //
        lef = ll, rig = rr + 1;
        while (lef + 1 < rig) {
            mid = (lef + rig) >> 1;
            if (trainHead[mid].t - mid + ll - 1 < tmp)
                lef = mid;
            else
                rig = mid;
        }
        return tmp + lef - ll + 1;
    }
    
    int corrupt_tail(int id, int t, int r) {
        int lef, rig, mid, ll, rr;
        lef = lefTail[t] - 1;
        rig = rigTail[t];
        while (lef + 1 < rig) {
            mid = (lef + rig) >> 1;
            if (trainTail[mid].r >= r) rig = mid; else
                lef = mid;
        }
        ll = rig;
        lef = lefTail[t];
        rig = rigTail[t] + 1;
        while (lef + 1 < rig) {
            mid = (lef + rig) >> 1;
            if (trainTail[mid].r <= r) lef = mid; else
                rig = mid;
        }
        rr = lef;
        int tmp = rand_max(id, entityTotal - (rr - ll + 1));
        if (tmp < trainTail[ll].h) return tmp;
        if (tmp > trainTail[rr].h - rr + ll - 1) return tmp + rr - ll + 1;
        lef = ll, rig = rr + 1;
        while (lef + 1 < rig) {
            mid = (lef + rig) >> 1;
            if (trainTail[mid].h - mid + ll - 1 < tmp)
                lef = mid;
            else
                rig = mid;
        }
        return tmp + lef - ll + 1;
    }
    // 接受线程id作为输入,调用corrupt生成正负样本,train_kb进行训练
    void* transetrainMode(void *con) {
        int id;
        id = (unsigned long long)(con); //补0即可
        next_random[id] = rand();
        for (int k = transeBatch / transeThreads; k >= 0; k--) { // 一个batch训练的样本数按照线程均分
            int j;
            // 生成一个样本随机的样本id
            int i = rand_max(id, transeLen); // i为生成的随机数
            int pr = 500; //一半的概率1/2决定生成 伪head tail
            if (randd(id) % 1000 < pr) {
                // 选择正、负样本作为训练输入
                j = corrupt_head(id, trainList[i].h, trainList[i].r);
                train_kb(trainList[i].h, trainList[i].t, trainList[i].r, trainList[i].h, j, trainList[i].r);
            } else {
                j = corrupt_tail(id, trainList[i].t, trainList[i].r);
                train_kb(trainList[i].h, trainList[i].t, trainList[i].r, j, trainList[i].t, trainList[i].r);
            }
            norm(relationVec + dimension * trainList[i].r); // 标准化
            norm(entityVec + dimension * trainList[i].h);
            norm(entityVec + dimension * trainList[i].t);
            norm(entityVec + dimension * j);
        }
        pthread_exit(NULL);
    }
    
    // 创建线程执行 调用transetrainMode 模型训练
    void train_transe(void *con) {
        transeLen = tripleTotal;
        transeBatch = transeLen / nbatches; // 一个batch的样本大小
        next_random = (unsigned long long *)calloc(transeThreads, sizeof(unsigned long long)); // 根据线程数创建表示线程的数组
        for (int epoch = 0; epoch < transeTrainTimes; epoch++) {
            res = 0;
            // 一个epoch包含nbatches个batch,每个batch再按线程划分
            for (int batch = 0; batch < nbatches; batch++) {
                pthread_t *pt = (pthread_t *)malloc(transeThreads * sizeof(pthread_t)); // 表示线程id,可以认为unsigned long int类型
                for (long a = 0; a < transeThreads; a++)
                    pthread_create(&pt[a], NULL, transetrainMode,  (void*)a); // 创建线程(指向线程标识符的指针,线程属性,运行函数的地址,运行函数的参数)
                for (long a = 0; a < transeThreads; a++)
                    pthread_join(pt[a], NULL); //以阻塞的方式等待thread指定的线程结束,主线程等待直到等待的线程结束
                free(pt);
            }
            printf("epoch %d %f\n", epoch, res);
        }
    }
    
    /*
     save result
     */
    
    void out_transe() {
        stringstream ss;
        ss << dimension;
        string dim = ss.str();
        
        FILE* f2 = fopen((outPath + "TransE_relation2vec_" + dim + ".vec").c_str(), "w");
        FILE* f3 = fopen((outPath + "TransE_entity2vec_" + dim + ".vec").c_str(), "w");
        for (int i=0; i < relationTotal; i++) {
            int last = dimension * i;
            for (int ii = 0; ii < dimension; ii++)
                fprintf(f2, "%.6f\t", relationVec[last + ii]);
            fprintf(f2,"\n");
        }
        for (int  i = 0; i < entityTotal; i++) {
            int last = i * dimension;
            for (int ii = 0; ii < dimension; ii++)
                fprintf(f3, "%.6f\t", entityVec[last + ii] );
            fprintf(f3,"\n");
        }
        fclose(f2);
        fclose(f3);
    }
    /*
     Main function
     */
    int main() {
        time_t start = time(NULL);
        init();
        train_transe(NULL);
        out_transe();
        cout << time(NULL) - start << " s" << endl;
        return 0;
    }
    

    在这里插入图片描述

    展开全文
  • transe 简单代码实现

    千次阅读 2020-07-06 17:18:44
    @description: 用于对知识图谱中的实体、关系基于TransE算法训练获取向量 数据:三元关系 实体id和关系id 结果为:两个文本文件,即entityVector.txt和relationVector.txt 实体 [array向量] """ from random import...
  • 为了及时了解基于TransE的表示学习方法的最新研究进展,通过归纳与整理,将基于TransE的表示学习方法分为基于复杂关系、基于关系路径、基于图像信息以及基于其他方面的方法四种类型。对每一种方法的设计思路、优缺点...
  • TransE算法的整理

    2019-12-10 21:24:52
    TransE 1 TransE的作用 TransE 作用就是把三元组翻译成embedding词向量的方法 三元组,也就是(头实体,关系,尾实体)的形式,头实体和尾实体统称为实体。为了简化起见,我们用(h,r,t)来表示三元组。其中 h表示头...
  • TransE算法代码学习

    千次阅读 2020-03-28 22:30:29
    sample from numpy import * from copy import deepcopy class TransE: def __init__(self, entityList, relationList, tripleList, margin = 1, learingRate = 0.00001, dim = 10, L1 = True): self.margin = ...
  • TransE算法原理与案例

    千次阅读 热门讨论 2019-09-04 17:42:47
    文章目录TransE知识图谱基础知识表示算法描述代码分析数据 TransE 知识图谱基础 三元组(h,r,t) 知识表示 即将实体和关系向量化,embedding 算法描述 思想:一个正确的三元组的embedding会满足:h+r=t 定义距离d...
  • TransE算法介绍

    2021-05-12 22:55:36
    TransE 算法是一种用于表示图结构中节点及关系的嵌入表示的算法,可以广泛应用于后续各类基于图谱的任务,如基于知识图谱的推荐算法CFKG利用transE对图谱中的entity及relation进行embedding用于后续的推荐任务。...
  • TransE解读

    千次阅读 2019-11-18 16:05:57
    Paper地址 1 目标 学习三元组的向量表达 2 算法 大致思路: 初始化+归一化 遍历数据 构造错误样本 训练计算loss函数 ...loss函数(优化目标):正确的三元组比假造的三元组头向量+关系向量-尾向量的差小,优化正确三元组...
  • 知识图谱——TransE模型原理

    千次阅读 2020-02-12 16:27:20
    知识图谱——TransE模型原理 1 TransE模型介绍 1.1 TransE模型引入 在我们之前的文章中,提到了知识图谱补全任务的前提任务是知识表示学习,在知识表示学习中,最为经典的模型就是TransE模型,TransE模型的核心作用...
  • TransE如何进行向量更新?

    千次阅读 多人点赞 2019-04-27 11:09:30
    关于TransE,博客上各种博文漫天飞,对于原理我就不做重复性劳动,只多说一句,TransE是知识表示算法翻译算法系列中的最基础算法,此处还有TransH、TransD等等;个人觉得翻译算法的叫法是不太合适的,translating,...
  • 知识图谱表示学习-TransE算法表示学习知识图谱表示学习TransE (这是一篇小白入门笔记,请勿转载) 表示学习 表示学习是一个利用模型自动地学习数据的隐式特征的过程,以此来计算 得到对学习对象来说相比原始数据更...
  • TransE算法解析

    2020-12-04 21:17:54
    transE(Translating Embedding)详解+简单python实现 概念 transE算法是一个非常经典的知识表示学习,用分布式表示(distributed representation)来描述知识库中的三元组。 原理 transE算法利用了word2vec的平移不变...
  • 简单了解TransE

    2020-10-07 19:17:41
    接触TransE算法也有半年多了,从一开始的一脸懵逼到现在满脸懵逼,也算是有点收获。。。 于是,,,献丑了~ 关于算法的具体实现,网上有很多,也不过多赘述,推荐几篇比较好的(不保证是原创,因为同款文章太多...
  • TransE算法详解

    万次阅读 多人点赞 2018-12-25 20:31:31
    TransE 算法详解 @(机器学习)[知识图谱|知识表示|TransE] 文章目录TransE 算法详解算法背景知识图谱是什么知识表示是什么基本思想算法描述梯度参考文献 算法背景 知识图谱是什么 一条知识图谱可以表示为一个三元组...
  • 1、 transE 表示学习 知识图谱中的事实是用三元组 (h,l,t)(h,l,t)(h,l,t) 表示的,那么如何用低维稠密向量来表示它们,才能得到这种依赖关系呢?transE算法的思想非常简单,它受word2vec平移不变性的启发,希望h+l≈...
  • (2020)TransE论文笔记

    千次阅读 热门讨论 2019-12-27 22:00:44
    于是有了TransE,别看它训练容易,处理大型数据也不在话下,又确保了实验效果出色,作者还在FB15k和WN18上进行了link prediction,真的是“可盐可甜“了。 Introduction 论文主要面向多关系数据(wordnet...
  • transE(Translating Embedding)详解+简单python实现

    万次阅读 多人点赞 2019-05-15 18:55:43
    表示学习旨在学习一系列低维稠密向量来表征语义信息,而知识表示学习是面向知识库中实体和关系的表示学习。...transE算法就是一个非常经典的知识表示学习,用分布式表示(distributed representation)来描述知识库...
  • 知识图谱 - TransE算法

    2020-08-11 11:10:54
    这里写自定义目录标题论文TransE算法概览核心思想Tips参考代码问题 论文 Translating Embeddings for Modeling Multi-relational Data TransE 算法概览 核心思想 实体向量 + 关系向量 = 实体向量 (h+l = t) Tips ...
  • TransR 标题: Learning Entity and Relation Embeddings for Knowledge Graph Completion(2015) TransE和TransH模型都假设实体和关系是语义空间中的向量,因此相似的实体在同一实体空间中会非常接近。 然而,每个...
  • 本代码来源于github项目地址,项目实现了TransE算法。下面结合项目代码,对TransE算法原理及实现进行详细说明。 2基本思想 TransE是一篇Bordes等人2013年发表在NIPS上的文章提出的算法。它的提出,是为了解决多...
  • Knowledge Graph表示学习--TransE系列

    万次阅读 2018-01-01 10:21:21
    模型虽然效果比现有的模型好,但是模型参数较多,相比TransE、TransH模型而言,没有那么简洁高效。 TransD —— TransD可以说是TransR/CTransR的简化版本,它用两个向量来动态重构mapping矩阵。给定一个triplet...
  • 前言 原理 详细算法流程 前言 TransE算法是知识图谱补全的经典方法。 目前网络上存在大量知识库(KBs):如OpenCyc、WordNet、Freebase、Dbpedia等等,它们因不同目的建成,因此很难用到其他系统上。为发挥知识库的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,796
精华内容 19,118
关键字:

transe