知识图谱问答系列文档(三)——从零开始搭建一个通用知识图谱问答【构建知识图谱】

(三)构建知识图谱

知识图谱构建使用neo4j作为图谱数据库,因数据量庞大(4572万个实体,1.41亿条关系),需使用neo4j-admin import工具导入,导入前需准备节点和关系的文件。

  • 节点数据格式

    neo4j节点数据使用以下数据格式,本文统一给实体增加Entity标签,便于检索。

    id:IDname:LABEL
    1姚明Entity
    2姚沁蕾Entity
    3中国Entity

    说明:此处id应唯一,一一对应相关实体.

  • 关系数据格式

    neo4j关系数据使用以下数据格式,:START_ID表示起始实体的id,:END_ID表示终止的实体id,本文统一增加Relation关系类型,便于检索。

    :START_ID:END_ID:TYPEname
    12Relation女儿
    13Relation国籍
    1Relation身高

    说明:id对应关系为 (:START_ID)-[r]->(:END_ID)

  • 数据抽取

    数据抽取部分包括三元组抽取,属性映射,城市映射和带有单位的属性值标准化等部分。话不多说,先上代码,再谈本文所遇到的问题。

    spo_extract代码如下:

    import os
    import csv
    import re
    import build_prop_dict
    
    dir_path = os.getcwd()
    
    attr_map = build_prop_dict.load_attr_map(dir_path + '/data/prop_mapping.txt')
    city_map = build_prop_dict.load_city_map(dir_path + '/data/city_mapping.txt')
    zz_map = build_prop_dict.load_attr_map(dir_path + '/data/zhengze_mapping.txt')
    
    spo_file = open('/mnt/zjb/data/allTypeCsv.csv', 'w', encoding='utf-8')
    alltypeCsv = csv.writer(spo_file)
    
    entity_file = open('/mnt/zjb/data/entity.csv','w', encoding='utf-8')
    spo_file = open('/mnt/zjb/data/relation.csv', 'w', encoding='utf-8')
    
    ent_csv = csv.writer(entity_file)
    ent_csv.writerow(("id:ID", "name", ":LABEL"))
    spo_csv = csv.writer(spo_file)
    spo_csv.writerow((":START_ID", ":END_ID", ":TYPE", "name"))
    
    def remove_stopwords(s):
        ans = (re.sub(r'[;::;、,,.。%【】、`?*①②③④◇●◆/(& --·)\\()“”〗""》〖《 \s\d]',
                      '', s)).replace('\u200b', '').replace("[", '').replace("]", '')
        return ans
    
    entity_dict = dict()
    idx = 0
    index = 0
    rela_dict = dict()
    with open('/mnt/zjb/data/ownthink_v2.csv', 'r', encoding='utf-8') as ownfile:
        reader = csv.reader((line.replace('\0', '') for line in ownfile))
        next(reader)
        entity_list = []
        for row in reader:
            if len(row) == 3:
                row[1] = remove_stopwords(row[1])
                row[2] = re.sub('<[^<]+?>', '', row[2]).replace('\n', '').strip()
                if row[1] != '' and row[2] != '':
                    for i in range(3):
                        row[i] = row[i].replace('\u200b', '')
                        row[i] = row[i].strip()
                    if row[1] in attr_map:                  # 属性映射
                        if row[1] in zz_map:
                            row[2] = row[2] + zz_map[row[1]][0]
                        row[1] = attr_map[row[1]][0]
                    elif row[1] in zz_map:                  # 处理身高cm为身高,属性值+cm
                        temp_str = row[1]
                        row[1] = row[1].replace(
                            zz_map[row[1]][0], '')   # 替换掉cm kg等
                        row[2] = row[2] + zz_map[temp_str][0]
                    if row[0] in city_map:
                        row[0] = city_map[row[0]]
                    if row[0] not in entity_dict and row[0]:
                        idx += 1
                        entity_dict[row[0]] = idx
                        ent_csv.writerow((str(idx), row[0], 'Entity'))
                    if row[2] not in entity_dict and row[2]:
                        idx += 1
                        entity_dict[row[2]] = idx
                        ent_csv.writerow((str(idx), row[2], 'Entity'))
                    if row[0] and row[2] and row[1]:
                        rel = str(entity_dict[row[0]]) + \
                            str(entity_dict[row[2]])+'Relation'+row[1]
                        if rel not in rela_dict:
                            alltypeCsv.writerow(row)
                            spo_csv.writerow((str(entity_dict[row[0]]), str(
                                entity_dict[row[2]]), 'Relation', row[1]))
                            index += 1
                            rela_dict[rel] = index
                #    sys.stdout.write("%.2f"%(float(idx)*100/float(len_lines)))
                #    sys.stdout.write("%\r")
                #    sys.stdout.flush
    
    index_01 = index
    for key in list(entity_dict.keys()):
        entity = key
        ent_val = re.sub(u"\\[.*?\\]", '', key)
        if ent_val != entity:
            if ent_val in entity_dict:
                temp_rela = str(entity_dict[ent_val]) + \
                    str(entity_dict[entity])+'Relation'+'歧义关系'
                if temp_rela not in rela_dict:
                    alltypeCsv.writerow((ent_val, '歧义关系', entity))
                    spo_csv.writerow((str(entity_dict[ent_val]), str(
                        entity_dict[entity]), 'Relation', '歧义关系'))
                    index_01 += 1
                    rela_dict[temp_rela] = index_01
            else:
                idx += 1
                entity_dict[ent_val] = idx
                spo_csv.writerow((str(idx), str(entity_dict[entity]), 'Relation', '歧义关系'))
                ent_csv.writerow((str(idx), ent_val, 'Entity'))
                index_01 += 1
                temp_rela = str(entity_dict[ent_val]) + \
                    str(entity_dict[entity])+'Relation'+'歧义关系'
                rela_dict[temp_rela] = index_01
                alltypeCsv.writerow((ent_val, '歧义关系', entity))
    

    build_prop_dict 字典的程序如下:

    import ahocorasick
    import pickle
    from collections import defaultdict
    import os
    
    cwd = os.getcwd()
    
    def load_attr_map(attr_mapping_file):
        f = open(attr_mapping_file)
        mapping = defaultdict(list)
        for line in f:
            parts = line.strip().split(" ")
            for p in parts:
                if p != '':
                    mapping[p].append(parts[0])
        return mapping
    
    def load_city_map(attr_mapping_file):
        f = open(attr_mapping_file)
        mapping = dict()
        for line in f:
            parts = line.strip().split(" ")
            mapping[parts[1]] = parts[0]
        return mapping
    
  • 代码介绍

    三元组提取时,首先要提取具有完整信息的三元组,即包含实体-属性-属性值的信息,其次还要剔除包含空字符、首尾空格、html标签以及不可见字符"\u200b",最后对每个实体建立唯一的id,用id建立对应的节点和关系数据,因源数据中只有部分数据包含歧义关系,对于一些没有歧义关系的实体,本文建立了对应的歧义关系,主要针对实体[xxxx]类型,建立(实体)-[歧义关系]->(实体[xxxx])。

    build_prop_dict 为映射程序,将源数据中出现一些属性、城市名、带有单位的属性值映射为对应的标准数据,主要使用字典进行映射。

  • 数据导入

    neo4j数据批量导入使用neo4j-admin import工具,针对neo4j 4.0 和neo4j 3.5.17,在此给出两种不同的导入方法(进入neo4j目录)。

    neo4j-4.0:

    bin/neo4j-admin import --nodes import/entity.csv --relationships import/relation.csv --multiline-fields=true
    

    neo4j-3.5.17:

    bin/neo4j-admin import --database=graph.db --nodes import/entity.csv --relationships:relation import/relation.csv --multiline-fields=true
    

    说明:在数据导入前,需要清空对应的数据库文件(4.0为neo4j,3.5.17为graph.db)

  • 建立标签

    为了大大提高检索效率,数据导入后应建立对应的标签,此处仅建立实体的名称和id。

    Cypher:

    CREATE INDEX ON:Entity(name)
    CREATE INDEX ON:Entity(id)
    
  • 修改数据

    原始数据中存在一部分错误数据,对与此类数据,需要在知识图谱构建完毕予以更新,经过测试,本文共发现了如下三种错误:

    • 错误1

      错误关系:《北京市见义勇为人员奖励和保护条例》实施办法->[市长]->王安顺

      解决办法:删除此条关系

      Cypher:

      MATCH (m:Entity{name:'《北京市见义勇为人员奖励和保护条例》实施办法'})-[r:Relation{name:'市长'}]->(n:Entity{name:'王安顺'}) delete r
      
    • 错误2

      错误关系:杭州[浙江省省会、副省级市]-[歧义权重]->(15254060)。

      解决办法:删除(杭州[浙江省省会、副省级市]-[歧义权重]->(15254060)),增加(杭州[浙江省省会]-[歧义权重]->(15254060))。

      Cypher:

      MATCH (m:Entity{name:'杭州[浙江省省会、副省级市]'})-[r:Relation{name:'歧义权重' }]->(n:Entity{name:'15254060'}) delete r 
      MATCH (m:Entity{name:'杭州[浙江省省会]'}),(n:Entity{name:'15254060'}) create (m)-[r:Relation{name:'歧义权重'}]->(n)
      
    • 错误3

      错误关系:北京-[描述]->《北京-中关村》为王持久作词,孙川谱曲,蔡国庆演唱的晚会歌曲,赞美了北京中关村作为电子科技产品的聚集地飞速发展。

      解决办法:删除(北京-[描述]->《北京-中关村》为王持久作词,孙川谱曲,蔡国庆演唱的晚会歌曲,赞美了北京中关村作为电子科技产品的聚集地飞速发展。),增加(北京-[描述]->北京,简称“京”,是中华人民共和国的首都、直辖市、国家中心城市、超大城市、国际大都市,全国政治中心、文化中心、国际交往中心、科技创新中心,是中国共产党中央委员会、中华人民共和国中央人民政府、全国人民代表大会、中国人民政治协商会议全国委员会、中华人民共和国中央军事委员会所在地,也是中部战区司令部驻地。)。

      Cypher:

      MATCH (m:Entity{name:'北京'})-[r:Relation{name:'描述' }]->(n:Entity) delete r
      MATCH (m:Entity{name:'北京'}),(n:Entity{name:'北京,简称“京”,是中华人民共和国的首都、直辖市、国家中心城市、超大城市、国际大都市,全国政治中心、文化中心、国际交往中心、科技创新中心,是中国共产党中央委员会、中华人民共和国中央人民政府、全国人民代表大会、中国人民政治协商会议全国委员会、中华人民共和国中央军事委员会所在地,也是中部战区司令部驻地。'}) create (m)-[r:Relation{name:'描述'}]->(n)
      
      
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值