方法一 lightKG

lightKG简介

  • lightKG是一个基于Pytorch和torchtext的知识图谱深度学习框架,涵盖知识图谱领域的一些简单算法,具有轻量、简单等特点,适合知识图谱领域的初学者。github地址
  • 使用时,先从lightKG导入算法模型,创建对应的模型对象,然后调用模型对象的train方法训练模型,并保存训练好的模型;在测试时,先加载训练好的模型,然后进行测试,以x目录下的模型Y为例:

    1. from lightkg.x import Y # 导入算法模型
    2. Ymodel = Y() # 创建模型对象
    3. Ymodel.train(...) # 训练模型和保存
    4. Ymodel.load(...) # 加载训练好的模型
    5. Ymodel.test(...) # 测试模型
  • 方法概述:

TransE将关系看成实体间的转移,即:如果三元组(头实体,关系,尾实体)成立,头实体向量h与关系向量r的和与尾实体向量t相近,否则远离。
[9] 知识图谱嵌入实战代码 - 图1

任务描述

  • 知识图谱中的知识通常以(头实体,关系,尾实体)的三元组形式表达。
  • 链接预测旨在给定三元组中的任意两个元素,预测第三个元素,即(?,关系,尾实体),(头实体,关系,?)和(头实体,?,尾实体),其中?表示待预测的元素,分别称为头实体预测,尾实体预测和关系预测。
  • 数据集:来自github的链接预测数据集,可以从这里下载。每行有三个字段,用“,”分隔,分别表示头实体,关系和尾实体,数据样例如下:
  1. 科学,包涵,自然、社会、思维等领域
  2. 科学,外文名,science
  3. 科学,拼音,kē xué
  4. 科学,中文名,科学
  5. 科学,解释,发现、积累的真理的运用与实践

所需依赖

  1. 测试环境 Python 3.6.8 Pytorch 1.4.0
  2. # 所需依赖---
  3. torchtext>=0.4.0
  4. tqdm>=4.28.1
  5. torch>=1.0.0
  6. pytorch_crf>=0.7.0
  7. scikit_learn>=0.20.2
  8. networkx>=2.2
  9. revtok
  10. jieba
  11. regex
  12. ---------------
  13. # 运行前需要按如下方式安装lightKG库
  14. !pip install -i https://pypi.douban.com/simple/ lightKG

TransE

训练

基于上述转移假设,TransE设计三元组的得分函数为:
[9] 知识图谱嵌入实战代码 - 图2
即用[9] 知识图谱嵌入实战代码 - 图3[9] 知识图谱嵌入实战代码 - 图4范数衡量距离(本教程采用[9] 知识图谱嵌入实战代码 - 图5范数)。得分函数用于衡量三元组有效的可能性,得分越高,三元组越可能有效。因此,正例三元组的得分高,负例三元组的得分低。由于关系数量相对较少,负例只通过替换头实体或尾实体得到。

基于上述原则建模知识图谱中的正例三元组及其对应的负例三元组,类似支持向量机,最小化基于Margin的损失,使正例的得分比负例的得分至少高一个 Margin [9] 知识图谱嵌入实战代码 - 图6,即:

[9] 知识图谱嵌入实战代码 - 图7%20%5Cin%20%5Cbigtriangleup%7D%5C%20%7B%5Csum%7B%5Cleft(h’%2Cr%2Ct’%20%5Cright)%20%5Cin%20%5Cbigtriangleup’%7D%7B%5Cmax%20%5Cleft(0%2C%20f%5Cleft(h%2C%20r%2C%20t%20%5Cright)%20%2B%20%5Cgamma-f%5Cleft(h’%2C%20r%2C%20t’%20%5Cright)%20%5Cright)%7D%7D%0A#card=math&code=L%3D%5Csum%7B%5Cleft%28h%2Cr%2Ct%20%5Cright%29%20%5Cin%20%5Cbigtriangleup%7D%5C%20%7B%5Csum_%7B%5Cleft%28h%27%2Cr%2Ct%27%20%5Cright%29%20%5Cin%20%5Cbigtriangleup%27%7D%7B%5Cmax%20%5Cleft%280%2C%20f%5Cleft%28h%2C%20r%2C%20t%20%5Cright%29%20%2B%20%5Cgamma-f%5Cleft%28h%27%2C%20r%2C%20t%27%20%5Cright%29%20%5Cright%29%7D%7D%0A&id=Nqm6u)

其中,[9] 知识图谱嵌入实战代码 - 图8为知识图谱中的正例三元组集合,[9] 知识图谱嵌入实战代码 - 图9为基于[9] 知识图谱嵌入实战代码 - 图10,通过替换其中正例三元组的头实体或尾实体得到的负例三元组集。最小化该损失得到实体和关系的向量表示。

  1. # 查看数据集
  2. import pandas as pd
  3. train = pd.read_csv('datasets/train.sample.csv',header=None)
  4. train.head(20)

image.png

  1. import os
  2. from lightkg.krl import KRL
  3. from lightkg.krl.config import DEFAULT_CONFIG
  4. # 修改epoch,默认1000
  5. DEFAULT_CONFIG['epoch']=10
  6. # 数据路径
  7. dataset_path = 'datasets/train.sample.csv'
  8. model_type = 'TransE'
  9. # 初始化实例
  10. krl = KRL()
  11. if not os.path.exists('./temp/models'):
  12. os.makedirs('./temp/models')
  13. # 训练
  14. krl.train(dataset_path,
  15. model_type=model_type,
  16. dev_path=dataset_path,
  17. save_path='./temp/models/LP_{}'.format(model_type))

预测

  1. # 读取 模型
  2. krl.load(save_path='./temp/models/LP_{}'.format(model_type), model_type=model_type)
  3. krl.test(dataset_path)

读取模型后调用对应的predict_head、predict_tail、predict_rel 即可预测头尾实体或关系。

  1. # 打印函数
  2. def topk_print(l,name):
  3. print("{}预测:".format(name))
  4. for i in l:
  5. print(i)
  6. print()
  7. # 头实体预测
  8. head_pred = krl.predict_head(rel='外文名', tail='science', topk=3)
  9. # 尾实体预测
  10. tail_pred = krl.predict_tail(head='科学', rel='外文名', topk=3)
  11. # 关系预测
  12. relation_pred = krl.predict_rel(head='科学', tail='science', topk=3)
  13. print("三元组:科学 - 外文名 - science \n")
  14. topk_print(head_pred,"头实体")
  15. topk_print(tail_pred,"尾实体")
  16. topk_print(relation_pred,"关系")

image.png

方法二 AmpliGraph

ECAI 2020 给出很好的视频讲解和jupyter notebook指导
注意:ampligraph 需要tensorflow1.14.0及以上版本
ECAI_2020_KGE_Tutorial_Hands_on_Session.ipynb

方法三 dgl-ke (命令行训练)

dgl-ke是亚马逊出品的KGE工具,依赖dgl框架,dgl现已适配pytorch、mxnet、tensorflow深度学习框架,但目前dgl-ke只适配pytorch,且仅可在Ubuntu或maxOS系统上通过命令行执行,可以再colab训练,反正只是拿一个Embedding。

  1. # 使用colab加载dgl-ke训练KGE
  2. # 使用命令行创建data文件夹,手动或者request获取文件
  3. # 参考https://github.com/MaastrichtU-IDS/KGRulEm/blob/7a696485f9506ba6af886b6cc86658a5fa6c696b/embeddings/Train_embeddings.ipynb
  4. !mkdir my_task
  5. # 处理自定义文件
  6. import os
  7. import numpy as np
  8. import pandas as pd
  9. triples_path = "./data/freebase-237-merged-and-remapped.csv"
  10. df = pd.read_csv(triples_path, names=['sub', 'pred', 'obj'])
  11. triples = df.values.tolist()
  12. print(len(triples))
  13. # Please make sure the output directory exist.
  14. seed = np.arange(num_triples)
  15. np.random.seed(666)
  16. np.random.shuffle(seed)
  17. train_cnt = int(num_triples * 0.9)
  18. valid_cnt = int(num_triples * 0.05)
  19. train_set = seed[:train_cnt]
  20. train_set = train_set.tolist()
  21. valid_set = seed[train_cnt:train_cnt+valid_cnt].tolist()
  22. test_set = seed[train_cnt+valid_cnt:].tolist()
  23. with open("./data/FB15K237_train.tsv", 'w+') as f:
  24. for idx in train_set:
  25. f.writelines("{}\t{}\t{}\n".format(triples[idx][0], triples[idx][1], triples[idx][2]))
  26. with open("./data/FB15K237_valid.tsv", 'w+') as f:
  27. for idx in valid_set:
  28. f.writelines("{}\t{}\t{}\n".format(triples[idx][0], triples[idx][1], triples[idx][2]))
  29. with open("./data/FB15K237_test.tsv", 'w+') as f:
  30. for idx in test_set:
  31. f.writelines("{}\t{}\t{}\n".format(triples[idx][0], triples[idx][1], triples[idx][2]))
  32. # 使用命令行
  33. !DGLBACKEND=pytorch dglke_train --dataset FB15K237 --data_path ./data --data_files FB15K237_train.tsv FB15K237_valid.tsv FB15K237_test.tsv --format 'raw_udd_hrt' --model_name TransE_l2 --dataset FB15K237 --batch_size 1000 \
  34. --neg_sample_size 200 --hidden_dim 400 --gamma 19.9 --lr 0.25 --max_step 500 --log_interval 100 \
  35. --batch_size_eval 16 -adv --regularization_coef 1.00E-09 --test --num_thread 1 --num_proc 8

image.png

TransE源码剖析

TransE论文和KGE相关理论点击这里
17.[知识图谱嵌入]经典嵌入模型论文集合
算法主要流程:
image.png
复现源码剖析:

  1. 超参数
  2. """
  3. 目标函数的常数——margin
  4. 学习率——learningRate
  5. 向量维度——dim k
  6. 实体列表——entityList(读取文本文件,实体+id)
  7. 关系列表——relationList(读取文本文件,关系 + id)
  8. 三元关系列表——tripleList(读取文本文件,实体 + 实体 + 关系)
  9. 损失值——loss
  10. 距离公式——L1
  11. """

初始化
image.png

  1. # U分布初始化向量
  2. def init(dim):
  3. # uniform() 方法将随机生成下一个实数,它在[x, y]范围内。
  4. return uniform(-6/(dim**0.5), 6/(dim**0.5))
  5. # 归一化向量
  6. def norm(list):
  7. '''
  8. 归一化
  9. :param 向量
  10. :return: 返回元素除以平方和后的数组
  11. '''
  12. var = linalg.norm(list)
  13. #x_norm=np.linalg.norm(x, ord=None, axis=None, keepdims=False)
  14. # 求范数 默认情况下,是求整体的矩阵元素平方和,再开根号。
  15. i = 0
  16. while i < len(list):
  17. list[i] = list[i]/var #list中每一元素/var
  18. i += 1
  19. return array(list)
  20. # 初始化向量
  21. def initialize(self):
  22. entityVectorList = {}
  23. relationVectorList = {}
  24. for entity in self.entityList: # 对entityList进行遍历
  25. n = 0
  26. entityVector = []
  27. while n < self.dim:
  28. ram = init(self.dim) #调用init函数,返回一个实数类似1.3266
  29. entityVector.append(ram) # 将ram 添加到实体向量中
  30. n += 1
  31. entityVector = norm(entityVector) #调用norm函数,单位化
  32. entityVectorList[entity] = entityVector
  33. print("entityVector初始化完成,数量是%d"%len(entityVectorList))
  34. for relation in self. relationList:
  35. n = 0
  36. relationVector = []
  37. while n < self.dim: # 循环dim次
  38. ram = init(self.dim) #调用init函数,返回一个实数类似1.3266
  39. relationVector.append(ram) # 将ram 添加到关系向量中
  40. n += 1
  41. relationVector = norm(relationVector) #归一化
  42. relationVectorList[relation] = relationVector
  43. print("relationVectorList初始化完成,数量是%d"%len(relationVectorList))
  44. # 初始化传入
  45. self.entityList = entityVectorList
  46. self.relationList = relationVectorList

随机采样三元组
sample函数中的size即为minibatch的大小
image.png

  1. def getSample(self, size):
  2. #—随机选取部分三元关系,Sbatch
  3. # sample(序列a,n)
  4. # 功能:从序列a中随机抽取n个元素,并将n个元素生以list形式返回。
  5. return sample(self.tripleList, size)

负采样:固定关系随机替换头尾实体中的一个,即不能同时替换头尾实体。
image.png

  1. def getCorruptedTriplet(self, triplet):
  2. '''
  3. training triplets with either the head or tail replaced by a random entity (but not both at the same time)
  4. #随机替换三元组的实体,h、t中任意一个被替换,但不同时替换。
  5. :param triplet:
  6. :return corruptedTriplet:
  7. '''
  8. i = uniform(-1, 1) #uniform(a, b)#随机生成a,b之间的数,左闭右开。
  9. if i < 0:#小于0,打坏三元组的第一项
  10. while True:
  11. entityTemp = sample(self.entityList.keys(), 1)[0] #从entityList.key()中sample一个元素,以列表行驶返回第一个元素
  12. if entityTemp != triplet[0]:
  13. break
  14. corruptedTriplet = (entityTemp, triplet[1], triplet[2])
  15. else:#大于等于0,打坏三元组的第二项
  16. while True:
  17. entityTemp = sample(self.entityList.keys(), 1)[0]
  18. if entityTemp != triplet[1]:
  19. break
  20. corruptedTriplet = (triplet[0], entityTemp, triplet[2])
  21. return corruptedTriplet

loss函数设计
image.png

  1. # L1范数 L1范数是指向量中各个元素绝对值之和
  2. def distanceL1(h, t ,r):
  3. """
  4. trans e
  5. :param h: head embendding
  6. :param t: tail
  7. :param r: relation
  8. :return: 返回绝对误差和
  9. """
  10. s = h + r - t
  11. sum = fabs(s).sum() # fabs() 方法返回数字的绝对值,如math.fabs(-10) 返回10.0。
  12. return sum
  13. # L2范数L2范数是指向量各元素的平方和然后求平方根
  14. def distanceL2(h, t, r):
  15. """
  16. trans r
  17. :param h:
  18. :param t:
  19. :param r:
  20. :return: 返回误差平方和
  21. """
  22. s = h + r - t
  23. sum = (s*s).sum()
  24. return sum
  25. # 迭代更新
  26. def update(self, Tbatch):
  27. copyEntityList = deepcopy(self.entityList) # 深拷贝 作为一个独立的存在 不会改变原来的值
  28. copyRelationList = deepcopy(self.relationList)
  29. # 遍历batch的三元组
  30. for tripletWithCorruptedTriplet in Tbatch:
  31. # [((h,t,r),(h',t',r)),(())]
  32. headEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][0]]
  33. #tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tuple
  34. tailEntityVector = copyEntityList[tripletWithCorruptedTriplet[0][1]]
  35. relationVector = copyRelationList[tripletWithCorruptedTriplet[0][2]]
  36. headEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][0]]
  37. tailEntityVectorWithCorruptedTriplet = copyEntityList[tripletWithCorruptedTriplet[1][1]]
  38. headEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][0]]
  39. #tripletWithCorruptedTriplet是原三元组和打碎的三元组的元组tuple
  40. tailEntityVectorBeforeBatch = self.entityList[tripletWithCorruptedTriplet[0][1]]
  41. relationVectorBeforeBatch = self.relationList[tripletWithCorruptedTriplet[0][2]]
  42. headEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][0]]
  43. tailEntityVectorWithCorruptedTripletBeforeBatch = self.entityList[tripletWithCorruptedTriplet[1][1]]
  44. if self.L1:
  45. # 计算正常情况下的误差
  46. distTriplet = distanceL1(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)
  47. # 计算负采样情况下的误差
  48. distCorruptedTriplet = distanceL1(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch , relationVectorBeforeBatch)
  49. else:
  50. distTriplet = distanceL2(headEntityVectorBeforeBatch, tailEntityVectorBeforeBatch, relationVectorBeforeBatch)
  51. distCorruptedTriplet = distanceL2(headEntityVectorWithCorruptedTripletBeforeBatch, tailEntityVectorWithCorruptedTripletBeforeBatch , relationVectorBeforeBatch)
  52. # margin loss = max(0, margin + pos - neg) 约束 + 正常(越小越好) - 负采样(越大越好)
  53. eg = self.margin + distTriplet - distCorruptedTriplet
  54. if eg > 0: #[function]+ 是一个取正值的函数
  55. self.loss += eg
  56. if self.L1:
  57. # tempos = 2 * lr * (t - h - r)
  58. tempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)
  59. tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)
  60. tempPositiveL1 = []
  61. tempNegtativeL1 = []
  62. for i in range(self.dim):#不知道有没有pythonic的写法(比如列表推倒或者numpy的函数)?
  63. if tempPositive[i] >= 0:
  64. tempPositiveL1.append(1)
  65. else:
  66. tempPositiveL1.append(-1)
  67. if tempNegtative[i] >= 0:
  68. tempNegtativeL1.append(1)
  69. else:
  70. tempNegtativeL1.append(-1)
  71. tempPositive = array(tempPositiveL1)
  72. tempNegtative = array(tempNegtativeL1)
  73. else:
  74. tempPositive = 2 * self.learingRate * (tailEntityVectorBeforeBatch - headEntityVectorBeforeBatch - relationVectorBeforeBatch)
  75. tempNegtative = 2 * self.learingRate * (tailEntityVectorWithCorruptedTripletBeforeBatch - headEntityVectorWithCorruptedTripletBeforeBatch - relationVectorBeforeBatch)
  76. headEntityVector = headEntityVector + tempPositive
  77. tailEntityVector = tailEntityVector - tempPositive
  78. relationVector = relationVector + tempPositive - tempNegtative
  79. headEntityVectorWithCorruptedTriplet = headEntityVectorWithCorruptedTriplet - tempNegtative
  80. tailEntityVectorWithCorruptedTriplet = tailEntityVectorWithCorruptedTriplet + tempNegtative
  81. #只归一化这几个刚更新的向量,而不是按原论文那些一口气全更新了
  82. copyEntityList[tripletWithCorruptedTriplet[0][0]] = norm(headEntityVector)
  83. copyEntityList[tripletWithCorruptedTriplet[0][1]] = norm(tailEntityVector)
  84. copyRelationList[tripletWithCorruptedTriplet[0][2]] = norm(relationVector)
  85. copyEntityList[tripletWithCorruptedTriplet[1][0]] = norm(headEntityVectorWithCorruptedTriplet)
  86. copyEntityList[tripletWithCorruptedTriplet[1][1]] = norm(tailEntityVectorWithCorruptedTriplet)
  87. # 赋权更新
  88. self.entityList = copyEntityList
  89. self.relationList = copyRelationList

整体训练代码
image.png

  1. def transE(self, cI = 20):
  2. print("训练开始")
  3. for cycleIndex in range(cI):
  4. # 采样
  5. Sbatch = self.getSample(150) #随机选取150个元素
  6. Tbatch = [] # 初始空 元组对(原三元组,打碎的三元组)的列表 :{((h,r,t),(h',r,t'))}
  7. # 负采样
  8. for sbatch in Sbatch:
  9. tripletWithCorruptedTriplet = (sbatch, self.getCorruptedTriplet(sbatch)) #{((h,r,t),(h',r,t'))}
  10. if(tripletWithCorruptedTriplet not in Tbatch):
  11. Tbatch.append(tripletWithCorruptedTriplet)
  12. # 迭代更新
  13. self.update(Tbatch)
  14. # 打印输出
  15. if cycleIndex % 100 == 0:
  16. print("第%d次循环"%cycleIndex)
  17. print(self.loss)
  18. self.writeRelationVector(r".\data\FB15k\relationVector10.txt")
  19. self.writeEntilyVector(r".\data\FB15k\entityVector10.txt")
  20. self.loss = 0