精华内容
下载资源
问答
  • 知识图谱学习笔记(三)—— 实体识别与链接
    2021-05-19 15:13:44

    1. 定义、目标和研究意义

    实体是文本中承载信息的重要语言单位,一段文本的语义可以表述为其包含的实体及这些实体相互之间的关联和交互。实体识别也就成为了文本意义理解的基础。例如,“26 日下午,一架叙利亚空军 L-39 教练机在哈马省被 HTS 使用的肩携式防空导弹击落”中的信息可以通过其包含的时间实体“26 号下午”,机构实体“叙利亚空军”、“HTS”,地点实体“哈马省”和武器实体“L-39 教练机”、“肩携式防空导弹”有效描述。实体也是知识图谱的核心单元,一个知识图谱通常是一个以实体为节点的巨大知识网络,包括实体、实体属性以及实体之间的关系。例如,一个医学领域知识图谱的核心单元是医学领域的实体,如疾病、症状、药物、医院、医生等。

    命名实体识别是指识别文本中的命名性实体,并将其划分到指定类别的任务。常用实体类别包括人名、地名、机构名、日期等。

    实体链接主要解决实体名的歧义性和多样性问题,是指将文本中实体名指向其所代表的真实世界实体的任务,也通常被称为实体消歧。例如,给一句话“苹果发布了最新产品 iPhone X”,实体链接系统需要将文本中的“苹果”与其真实世界所指的“苹果公司”进行对应。

    2.研究内容与挑战

    实体识别与链接处理各种非结构化/半结构化的输入(如文本、新闻网页、商品页面、微博、论坛页面等),使用多种技术(统计方法、深度学习方法、知识挖掘方法),提取各种类型的实体(如人名、地名、商品、药物等),并将这些信息与现有知识图谱进行集成(实体链接)。以下分别介绍具体研究内容。

    实体识别
    命名实体识别的目的是识别文本中指定类别的实体,主要包括人名、地名、机构名、专有名词等的任务。例如,识别“2016 年 6 月 20 日,骑士队在奥克兰击败勇士队获得 NBA 冠军”这句中的地名(奥克兰)、时间(2016 年 6月 20 日)、球队(骑士队、勇士队)和机构(NBA)。命名实体识别系统通常包含两个部分:实体边界识别实体分类,其中实体边界识别判断一个字符串是否组成一个完整实体,而实体分类将识别出的实体划分到预先给定的不同类别中去。

    实体链接
    实体链接的目的是将实体提及与知识库中对应实体进行链接。给定一段文本(如“在旧金山的发布会上,苹果为开发者推出新编程语言 Swift”),一个实体链接系统包括如下研究内容:

    1. 识别文档中的目标提及(mention)。所谓提及,就是我们想要链接的对象,例如上述例子文本中的提及{“旧金山”,“苹果”,“Swift”};
    2. 针对每一个提及,识别该提及在知识图谱中可能指向的候选目标实体。例如,上述文本中的提及“苹果”可能指向的目标实体包括 {苹果(水果),苹果公司,苹果(电影),苹果(银行), …};
    3. 基于提及的上下文等信息对目标实体进行排序。例如,系统需要根据“苹果”的上下文词语{发布会,编程语言,开发者,…}识别出该段文本中“苹果”指的是苹果公司,而不是苹果(水果)或者苹果(电影);
    4. 空提及检测与聚类。考虑到知识的规模和更新速度,知识库往往不能覆盖
      所有真实世界实体。为了解决上述问题,需要识别出知识库尚未包含其目标实体的提及,并将这些提及按其指向的真实世界实体进行聚类。例如,由于现有知识库没有包含上文中提及“Swift”指向的目标实体 Swift(编程语言),实体链接系统需要将“Swift”的目标实体设置为空实体“NIL”,表示该提及在知识库中没有链接对象。

    概况说来,上述实体分析任务主要面临以下几个关键科学问题:

    1. 实体名的歧义性和多样性
      在实体识别中,实体可以有各种各样不同的表达,导致除了少数规范性实体(如电话号码,email 地址)之外,大部分实体都无法使用名字规则来捕捉其规律,而是需要构建统计上下文模型来进行识别。在实体链接中,实体的歧义导致一个实体名有许多可链接的对象,这使得如何挖掘更多的消歧证据、设计更高性能的消歧算法、构建覆盖度更高的实体引用表仍然是实体链接系统的核心问题。

    2. 资源缺乏(Low Resource)问题
      目前绝大部分的实体分析算法都依赖于有监督模型,需要大量的训练语料来达到实用性能。然而,考虑到标注语料的成本,在绝大部分情况下都不可能获得足够的训练语料来处理不同的领域、面向不同风格的文本(规范、非规范)、不同的语言(中文、英文、一带一路小语种等)等多种多样的情况。无需大量训练语料的无监督/半监督技术,资源自动构建技术,以及迁移学习等技术是解决上述问题的核心研究问题。

    3. 实体的开放性问题
      实体具有复杂性和开放性的特点。实体的复杂性指的是实体的类型多种多样,同时类型之间具有复杂的层次结构。实体的开放性指实体并不是一个封闭的集合,而是随着时间增加、演化和失效。实体的开放性和复杂性给实体分析带来了巨大的挑战:开放性使得现有有监督方法无法适应开放知识的抽取;实体的巨大规模使得无法使用枚举或者人工编写的方式来进行处理,同时随着时间变化现有模型的性能会下降。

    3.技术方法和研究现状

    根据模型的不同,实体分析方法可以分为基于统计模型的方法基于深度学习的方法基于文本挖掘的方法;根据对监督知识的依赖,可以划分为无监督方法、弱监督方法、知识监督方法和有监督方法。

    3.1 传统统计模型方法

    实体识别
    自 90 年代以来,统计模型一直是实体识别的主流方法。有非常多的统计方法被用来抽取文本中的实体识别,如最大熵分类模型、SVM、隐马尔可夫模型、条件随机场模型等等。

    基于统计模型的方法通常将实体识别任务形式化为从文本输入到特定目标结构的预测,使用统计模型来建模输入与输出之间的关联,并使用机器学习方法来学习模型的参数。例如,最大熵分类模型将命名实体识别转换为子字符串的分类任务,实体识别的代表性统计模型条件随机场模型(CRF)它将实体识别问题转化为序列标注问题。

    实体链接
    实体链接的核心是计算实体提及(mention)和知识库中实体的相似度,并基于上述相似度选择特定实体提及的目标实体。上述过程的核心在于挖掘可用于识别提及目标实体相互关联的证据信息,将这些证据表示为供计算机处理的形式,并构建高性能的算法来综合不同证据进行链接决策。目前主要使用的证据信息包括实体统计信息、名字统计信息、上下文词语分布、实体关联度、文章主题等信息。

    传统统计模型的主要缺点在于需要大量的标注语料来学习,这导致构建开放域或Web环境下的信息抽取系统时往往会遇到标注语料瓶颈。为解决上述问题,近年来已经开始研究高效的弱监督或无监督策略,如半监督算法、远距离监督算法、基于海量数据冗余性的自学习方法等等。传统统计模型的另外一个缺点是其需要人工构建大量的特征,其训练并非一个端到端的过程。为解决上述问题,越来越多深度学习模型被用于实体识别和链接。

    3.2 深度学习方法

    实体识别
    目前存在两类用于命名实体识别的典型深度学习架构。

    一种是 NN-CRF 架构,在该架构中,CNN/LSTM 被用来学习每一个词位置处的向量表示,基于该向量表示,NN-CRF 解码该位置处的最佳标签。
    第二种是采用滑动窗口分类的思想,使用神经网络学习句子中的每一个ngram 的表示,然后预测该 ngram 是否是一个目标实体。

    实体链接
    实体链接的核心是构建多类型多模态上下文及知识的统一表示,并建模不同信息、不同证据之间的相互交互。通过将不同类型的信息映射到相同的特征空间,并提供高效的端到端训练算法,深度学习方法给上述任务提供了强有力的工具。

    目前的相关工作包括多源异构证据的向量表示学习、以及不同证据之间相似度的学习等工作。

    相比传统统计方法,深度学习方法的主要优点是其训练是一个端到端的过程,无需人工定义相关的特征。另外一个优点是深度学习可以学习任务特定的表示,建立不同模态、不同类型、不同语言之间信息的关联,从而取得更好的实体分析性能。

    3.3 文本挖掘方法

    Web 中往往还存在大量的半结构高质量数据源,如维基百科、网页中的表格、列表、搜索引擎的查询日志等等。这些结构往往蕴含有丰富的语义信息。因此,半结构 Web 数据源上的语义知识获取(knowledge harvesting),如大规模知识共享社区(如百度百科、互动百科、维基百科)上的实体知识抽取,往往采用文本挖掘的方法 。

    代表性文本挖掘抽取系统包 DBPedia[Auer et al., 2007] 、Yago[Suchanek & Kasneci, 2008、BabelNet、NELL 和 Kylin 等等

    文本挖掘方法的核心是构建从特定结构(如列表、Infobox)构建实体挖掘的特定规则。由于规则本身可能带有不确定性和歧义性,同时目标结构可能会有一定的噪音,文本挖掘方法往往基于特定算法来对语义知识进行评分和过滤。

    更多相关内容
  • Fast Entity Linker 用于训练模型,以将实体链接到文档和查询中的知识库(维基百科),是一款无监督、准确、可扩展多语言实体名称识别和链接系统,同时包含英语、西班牙语和中文数据包。在算法上,使用了实体嵌入,...
  • 针对2013年CCF自然语言处理与中文计算会议(NLP&CC2013)中文微博实体链接的任务,使用CCF提供的新浪微博数据作为训练和测试数据,利用西南交通大学耶宝智慧中文分词平台作为自然语言预处理工具,提出一种实体链接的...
  • 中文短文本的实体链指

    千次阅读 热门讨论 2020-06-20 22:39:50
    examples_to_features, ) DEVICE = torch.device('cuda:0' if torch.cuda.is_available else 'cpu') # 预训练模型路径 PRETRAINED_PATH = './chinese_roberta_wwm_ext_pytorch/' # 实体链接训练路径 EL_SAVE_PATH = ...

    中文短文本的实体链指任务

    1. 任务描述

    本评测任务围绕实体链指技术,结合其对应的AI智能应用需求,在CCKS 2019面向中文短文本的实体链指任务的基础上进行了拓展与改进,主要改进包括以下几部分:
    (1)去掉实体识别,专注于中文短文本场景下的多歧义实体消歧技术;
    (2)增加对新实体(NIL实体)的上位概念类型判断;
    (3)对标注文本数据调整,增加多模任务场景下的文本源,同时调整了多歧义实体比例。
    面向中文短文本的实体链指,简称EL(Entity Linking)。即对于给定的一个中文短文本(如搜索Query、微博、对话内容、文章/视频/图片的标题等),EL将其中的实体与给定知识库中对应的实体进行关联。
    传统的实体链指任务主要针对长文本,长文本拥有丰富的上下文信息,能辅助实体进行歧义消解并完成实体链指,相比之下,针对中文短文本的实体链指存在很大的挑战,主要原因如下:
    (1)口语化严重,导致实体歧义消解困难;
    (2)短文本上下文语境不丰富,须对上下文语境进行精准理解;
    (3)相比英文,中文由于语言自身的特点,在短文本的链指问题上更有挑战。
    此次任务的输入输出定义如下:
    输入:
    中文短文本以及该短文本中的实体集合。
    输出:
    输出文本此中文短文本的实体链指结果。每个结果包含:实体mention、在中文短文本中的位置偏移、其在给定知识库中的id,如果为NIL情况,需要再给出实体的上位概念类型(封闭体系的概念详见附件)
    示例输入:
    在这里插入图片描述
    示例输出:
    在这里插入图片描述

    说明:对于实体有歧义的查询,系统应该有能力来区分知识库中链接的候选实体中哪个实体为正确链指的实体结果。例如,知识库中有8个不同的实体都可能是『琅琊榜』的正确链指结果,因为知识库中的这8个实体都可以通过『琅琊榜』的字面表达查找到,但是我们在给定的上下文中(『海燕』、『原创小说』、『权谋小说』),有足够的信息去区分这些候选实体中,哪个才是应该被关联上的结果。

    2. 数据描述

    2.1. 知识库

    该任务知识库来自百度百科知识库。知识库中的每个实体都包含一个subject_id(知识库id),一个subject名称,实体的别名,对应的概念类型,以及与此实体相关的一系列二元组< predicate,object>(<属性,属性值>)信息形式。知识库中每行代表知识库的一条记录(一个实体信息),每条记录为json数据格式。
    示例如下所示:
    在这里插入图片描述

    2.2 标准数据集

    标注数据集由训练集、验证集和测试集组成,整体标注数据大约10万条左右,数据均通过百度众包标注生成,详细标注质量将会在数据发布时一并给出。
    标注数据集中每条数据的格式为:
    在这里插入图片描述

    标注数据集主要来自于:真实的互联网网页标题数据、视频标题数据、搜索Query
    标注文本对象的示例数据如下:
    在这里插入图片描述

    3. 评价指标

    在这里插入图片描述

    import torch
    if torch.cuda.is_available():
        # Tell PyTorch to use the GPU.
        device = torch.device("cuda")
        print('There are %d GPU(s) available.' % torch.cuda.device_count())
        print('We will use the GPU:', torch.cuda.get_device_name(0))
    else:
        print('No GPU available, using the CPU instead.')
        device = torch.device("cpu")
    
    There are 1 GPU(s) available.
    We will use the GPU: GeForce GTX 1070
    

    编写路径

    import os
    import json
    import logging
    import random
    from collections import defaultdict
    
    import numpy as np
    import pandas as pd
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import pytorch_lightning as pl
    from tqdm import tqdm
    from sklearn.metrics import accuracy_score
    from transformers import (
        DataProcessor,
        InputExample,
        BertConfig,
        BertTokenizer,
        BertForSequenceClassification,
        glue_convert_examples_to_features,
    )
    
    DEVICE = torch.device('cuda:0' if torch.cuda.is_available else 'cpu')
    
    # 预训练模型路径
    PRETRAINED_PATH = './chinese_roberta_wwm_ext_pytorch/'
    # 实体链接训练路径
    EL_SAVE_PATH = './pytorch-lightning-checkpoints/EntityLinking/'
    # 实体类别推断训练路径
    ET_SAVE_PATH = './pytorch-lightning-checkpoints/EntityTyping/'
    
    # 项目数据路径
    DATA_PATH = './data/'
    
    # CCKS2020实体链指竞赛原始路径
    RAW_PATH = DATA_PATH + 'ccks2020_el_data_v1/'
    
    # 预处理后导出的pickle文件路径
    PICKLE_PATH = DATA_PATH + 'pickle/'
    if not os.path.exists(PICKLE_PATH):
        os.mkdir(PICKLE_PATH)
    
    # 预测结果的文件路径
    RESULT_PATH = DATA_PATH + 'result/'
    if not os.path.exists(RESULT_PATH):
        os.mkdir(RESULT_PATH)
    
    # 训练、验证、推断所需的tsv文件路径
    TSV_PATH = DATA_PATH + 'tsv/'
    if not os.path.exists(TSV_PATH):
        os.mkdir(TSV_PATH)
    
    # 训练结果的CheckPoint文件路径
    CKPT_PATH = './ckpt/'
    
    PICKLE_DATA = {
        # 实体名称对应的KBID列表
        'ENTITY_TO_KBIDS': None,
        # KBID对应的实体名称列表
        'KBID_TO_ENTITIES': None,
        # KBID对应的属性文本
        'KBID_TO_TEXT': None,
        # KBID对应的实体类型列表(注意:一个实体可能对应'|'分割的多个类型)
        'KBID_TO_TYPES': None,
        # KBID对应的属性列表
        'KBID_TO_PREDICATES': None,
    
        # 索引类型映射列表
        'IDX_TO_TYPE': None,
        # 类型索引映射字典
        'TYPE_TO_IDX': None,
    }
    
    for k in PICKLE_DATA:
        filename = k + '.pkl'
        if os.path.exists(PICKLE_PATH + filename):
            PICKLE_DATA[k] = pd.read_pickle(PICKLE_PATH + filename)
        else:
            print(f'File {filename} not Exist!')
    
    
    def set_random_seed(seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
        torch.backends.cudnn.benchmark = False
        torch.backends.cudnn.deterministic = True
    
    

    处理实体数据,把空数据去掉

    logger = logging.getLogger(__name__)
    
    class PicklePreprocessor:
        """生成全局变量Pickle文件的预处理器"""
    
        def __init__(self):
                   
            # 实体名称对应的KBID列表  {"张健"} -> "10001"
            self.entity_to_kbids = defaultdict(set)
            
            # KBID对应的实体名称列表 "10001" -> {"张健"}
            self.kbid_to_entities = dict()
            
            # KBID对应的属性文本  "10001" -> {"政治面貌:中共党员","义项描述:潜山县塔畈乡副主任科员、纪委副书记","性别:男",
            # "学历:大专","中文名:张健"} 
            self.kbid_to_text = dict()
            
            # KBID对应的实体类型列表 "10001" -> {"Person"}
            self.kbid_to_types = dict()
            
            # KBID对应的属性列表 "10001" -> {"政治面貌","义项描述","性别","学历","中文名"} 
            self.kbid_to_predicates = dict()
    
            # 索引类型映射列表 ["Person"]
            self.idx_to_type = list()
            
            # 类型索引映射字典 {"Person":0}
            self.type_to_idx = dict()
    
        def run(self, shuffle_text=True):
            with open(RAW_PATH + 'kb.json', 'r',encoding='utf-8') as f:
                for line in tqdm(f):
                    line = json.loads(line)
    
                    kbid = line['subject_id']
                    # 将实体名与别名合并
                    entities = set(line['alias'])
                    entities.add(line['subject'])
                    for entity in entities:
                        self.entity_to_kbids[entity].add(kbid)
                    self.kbid_to_entities[kbid] = entities
    
                    text_list, predicate_list = [], []
                    for x in line['data']:
                        # 简单拼接predicate与object,这部分可以考虑别的方法尝试
                        text_list.append(':'.join([x['predicate'].strip(), x['object'].strip()]))
                        predicate_list.append(x['predicate'].strip())
                    if shuffle_text:  # 对属性文本随机打乱顺序
                        random.shuffle(text_list)
                    self.kbid_to_predicates[kbid] = predicate_list
                    self.kbid_to_text[kbid] = ' '.join(text_list)
                    
                    # 删除文本中的特殊字符
                    for c in ['\r', '\t', '\n']:
                        self.kbid_to_text[kbid] = self.kbid_to_text[kbid].replace(c, '')
    
                    type_list = line['type'].split('|')
                    self.kbid_to_types[kbid] = type_list
                    for t in type_list:
                        if t not in self.type_to_idx:
                            self.type_to_idx[t] = len(self.idx_to_type)
                            self.idx_to_type.append(t)
    
            # 保存pickle文件
            pd.to_pickle(self.entity_to_kbids, PICKLE_PATH + 'ENTITY_TO_KBIDS.pkl')
            pd.to_pickle(self.kbid_to_entities, PICKLE_PATH + 'KBID_TO_ENTITIES.pkl')
            pd.to_pickle(self.kbid_to_text, PICKLE_PATH + 'KBID_TO_TEXT.pkl')
            pd.to_pickle(self.kbid_to_types, PICKLE_PATH + 'KBID_TO_TYPES.pkl')
            pd.to_pickle(self.kbid_to_predicates, PICKLE_PATH + 'KBID_TO_PREDICATES.pkl')
            pd.to_pickle(self.idx_to_type, PICKLE_PATH + 'IDX_TO_TYPE.pkl')
            pd.to_pickle(self.type_to_idx, PICKLE_PATH + 'TYPE_TO_IDX.pkl')
            logger.info('Process Pickle File Finish.')
    
    

    生成模 训练,验证集,把数据保存在tsv文件中

    class DataFramePreprocessor:
        """生成模型训练、验证、推断所需的tsv文件"""
    
        def __init__(self):
            pass
    
        def process_link_data(self, input_path, output_path, max_negs=-1):
           
            entity_to_kbids = PICKLE_DATA['ENTITY_TO_KBIDS']
            #print("entity_to_kbids")
            kbid_to_text = PICKLE_DATA['KBID_TO_TEXT']
            #print(kbid_to_text)
            kbid_to_predicates = PICKLE_DATA['KBID_TO_PREDICATES']
            link_dict = defaultdict(list)
    
            with open(input_path, 'r',encoding='utf-8') as f:
                for line in tqdm(f):
                    line = json.loads(line)
    
                    for data in line['mention_data']:
                        # 对测试集特殊处理
                        if 'kb_id' not in data:
                            data['kb_id'] = '0'
    
                        # KB中不存在的实体不进行链接
                        if not data['kb_id'].isdigit():
                            continue
    
                        entity = data['mention']
                        kbids = list(entity_to_kbids[entity])
                        random.shuffle(kbids)
    
                        num_negs = 0
                        for kbid in kbids:
                            if num_negs >= max_negs > 0 and kbid != data['kb_id']:
                                continue
    
                            link_dict['text_id'].append(line['text_id'])
                            link_dict['entity'].append(entity)
                            link_dict['offset'].append(data['offset'])
                            link_dict['short_text'].append(line['text'])
                            link_dict['kb_id'].append(kbid)
                            link_dict['kb_text'].append(kbid_to_text[kbid])
                            link_dict['kb_predicate_num'].append(len(kbid_to_predicates[kbid]))
                            if kbid != data['kb_id']:
                                link_dict['predict'].append(0)
                                num_negs += 1
                            else:
                                link_dict['predict'].append(1)
    
            link_data = pd.DataFrame(link_dict)
            link_data.to_csv(output_path, index=False, sep='\t')
    
        def process_type_data(self, input_path, output_path):
            kbid_to_types = PICKLE_DATA['KBID_TO_TYPES']
            type_dict = defaultdict(list)
    
            with open(input_path, 'r',encoding='utf-8') as f:
                for line in tqdm(f):
                    line = json.loads(line)
    
                    for data in line['mention_data']:
                        entity = data['mention']
    
                        # 测试集特殊处理
                        if 'kb_id' not in data:
                            entity_type = ['Other']
                        elif data['kb_id'].isdigit():
                            entity_type = kbid_to_types[data['kb_id']]
                        else:
                            entity_type = data['kb_id'].split('|')
                            for x in range(len(entity_type)):
                                entity_type[x] = entity_type[x][4:]
                        for e in entity_type:
                            type_dict['text_id'].append(line['text_id'])
                            type_dict['entity'].append(entity)
                            type_dict['offset'].append(data['offset'])
                            type_dict['short_text'].append(line['text'])
                            type_dict['type'].append(e)
    
            type_data = pd.DataFrame(type_dict)
            type_data.to_csv(output_path, index=False, sep='\t')
    
        def run(self):
            self.process_link_data(
                input_path=RAW_PATH + 'train.json',
                output_path=TSV_PATH + 'EL_TRAIN.tsv',
                max_negs=2,
            )
            logger.info('Process EL_TRAIN Finish.')
            self.process_link_data(
                input_path=RAW_PATH + 'dev.json',
                output_path=TSV_PATH + 'EL_VALID.tsv',
                max_negs=-1,
            )
            logger.info('Process EL_VALID Finish.')
            self.process_link_data(
                input_path=RAW_PATH + 'test.json',
                output_path=TSV_PATH + 'EL_TEST.tsv',
                max_negs=-1,
            )
            logger.info('Process EL_TEST Finish.')
    
            self.process_type_data(
                input_path=RAW_PATH + 'train.json',
                output_path=TSV_PATH + 'ET_TRAIN.tsv',
            )
            logger.info('Process ET_TRAIN Finish.')
            self.process_type_data(
                input_path=RAW_PATH + 'dev.json',
                output_path=TSV_PATH + 'ET_VALID.tsv',
            )
            logger.info('Process ET_VALID Finish.')
            self.process_type_data(
                input_path=RAW_PATH + 'test.json',
                output_path=TSV_PATH + 'ET_TEST.tsv',
            )
            logger.info('Process ET_TEST Finish.')
    

    简单查看一下训练数据

    train_data = pd.read_csv(TSV_PATH + 'ET_TRAIN.tsv', sep='\t')
    train_data.head()
    
    text_identityoffsetshort_texttype
    01小品0小品《战狼故事》中,吴京突破重重障碍解救爱人,深情告白太感人Other
    11战狼故事3小品《战狼故事》中,吴京突破重重障碍解救爱人,深情告白太感人Work
    21吴京10小品《战狼故事》中,吴京突破重重障碍解救爱人,深情告白太感人Person
    31障碍16小品《战狼故事》中,吴京突破重重障碍解救爱人,深情告白太感人Other
    41爱人20小品《战狼故事》中,吴京突破重重障碍解救爱人,深情告白太感人Other

    简单查看一下效验数据

    valid_data = pd.read_csv(TSV_PATH + 'ET_VALID.tsv', sep='\t')
    valid_data.head()
    
    text_identityoffsetshort_texttype
    01天下没有不散的宴席0天下没有不散的宴席 - ╰つ雲中帆╰つWork
    11╰つ雲中帆╰つ12天下没有不散的宴席 - ╰つ雲中帆╰つOther
    22永嘉0永嘉厂房出租Location
    32厂房2永嘉厂房出租Location
    42出租4永嘉厂房出租Other

    简单查看一下测试数据

    test_data = pd.read_csv(TSV_PATH + 'ET_TEST.tsv', sep='\t')
    test_data.head()
    
    text_identityoffsetshort_texttype
    01林平之0林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了Other
    11岳灵珊5林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了Other
    21师娘18林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了Other
    31令狐冲21林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了Other
    42思追0思追原来是个超级妹控,不愿妹妹嫁人,然而妹妹却喜欢一博老师Other

    实体链接数据处理

    class EntityTypingProcessor(DataProcessor):
        """实体链接数据处理"""
    
        def get_train_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='train',
            )
    
        def get_dev_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='valid',
            )
    
        def get_test_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='test',
            )
    
        def get_labels(self):
            return PICKLE_DATA['IDX_TO_TYPE']
    
        def _create_examples(self, lines, set_type):
            examples = []
            for i, line in enumerate(lines):
                if i == 0:
                    continue
                guid = f'{set_type}-{i}'
                text_a = line[1]
                text_b = line[3]
                label = line[-1]
                examples.append(InputExample(
                    guid=guid,
                    text_a=text_a,
                    text_b=text_b,
                    label=label,
                ))
            return examples
    
        def create_dataloader(self, examples, tokenizer, max_length=64,
                              shuffle=False, batch_size=64, use_pickle=False):
            pickle_name = 'ET_FEATURE_' + examples[0].guid.split('-')[0].upper() + '.pkl'
            if use_pickle:
                features = pd.read_pickle(PICKLE_PATH + pickle_name)
            else:
                features = glue_convert_examples_to_features(
                    examples,
                    tokenizer,
                    label_list=self.get_labels(),
                    max_length=max_length,
                    output_mode='classification',
                )
                pd.to_pickle(features, PICKLE_PATH + pickle_name)
    
            dataset = torch.utils.data.TensorDataset(
                torch.LongTensor([f.input_ids for f in features]),
                torch.LongTensor([f.attention_mask for f in features]),
                torch.LongTensor([f.token_type_ids for f in features]),
                torch.LongTensor([f.label for f in features]),
            )
    
            dataloader = torch.utils.data.DataLoader(
                dataset,
                shuffle=shuffle,
                batch_size=batch_size,
                num_workers=2,
            )
            return dataloader
    
        def generate_feature_pickle(self, max_length):
            tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
    
            train_examples = self.get_train_examples(TSV_PATH + 'ET_TRAIN.tsv')
            valid_examples = self.get_dev_examples(TSV_PATH + 'ET_VALID.tsv')
            test_examples = self.get_test_examples(TSV_PATH + 'ET_TEST.tsv')
    
            self.create_dataloader(
                examples=train_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=True,
                batch_size=32,
                use_pickle=False,
            )
            self.create_dataloader(
                examples=valid_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=False,
                batch_size=32,
                use_pickle=False,
            )
            self.create_dataloader(
                examples=test_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=False,
                batch_size=32,
                use_pickle=False,
            )
    

    实体链接数据处理

    class EntityLinkingProcessor(DataProcessor):
        """实体链接数据处理"""
    
        def get_train_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='train',
            )
    
        def get_dev_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='valid',
            )
    
        def get_test_examples(self, file_path):
            return self._create_examples(
                self._read_tsv(file_path),
                set_type='test',
            )
    
        def get_labels(self):
            return ['0', '1']
    
        def _create_examples(self, lines, set_type):
            examples = []
            for i, line in enumerate(lines):
                if i == 0:
                    continue
                guid = f'{set_type}-{i}'
                text_a = line[1] + ' ' + line[3]
                text_b = line[5]
                label = line[-1]
                examples.append(InputExample(
                    guid=guid,
                    text_a=text_a,
                    text_b=text_b,
                    label=label,
                ))
            return examples
    
        def create_dataloader(self, examples, tokenizer, max_length=384,
                              shuffle=False, batch_size=32, use_pickle=False):
            pickle_name = 'EL_FEATURE_' + examples[0].guid.split('-')[0].upper() + '.pkl'
            if use_pickle:
                features = pd.read_pickle(PICKLE_PATH + pickle_name)
            else:
                features = glue_convert_examples_to_features(
                    examples,
                    tokenizer,
                    label_list=self.get_labels(),
                    max_length=max_length,
                    output_mode='classification',                
                )
    
                pd.to_pickle(features, PICKLE_PATH + pickle_name)
    
            dataset = torch.utils.data.TensorDataset(
                torch.LongTensor([f.input_ids for f in features]),
                torch.LongTensor([f.attention_mask for f in features]),
                torch.LongTensor([f.token_type_ids for f in features]),
                torch.LongTensor([f.label for f in features]),
            )
    
            dataloader = torch.utils.data.DataLoader(
                dataset,
                shuffle=shuffle,
                batch_size=batch_size,
                num_workers=2,
            )
            return dataloader
    
        def generate_feature_pickle(self, max_length):
            tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
            print("asdgfsdfg")
    
            train_examples = self.get_train_examples(TSV_PATH + 'EL_TRAIN.tsv')
            valid_examples = self.get_dev_examples(TSV_PATH + 'EL_VALID.tsv')
            test_examples = self.get_test_examples(TSV_PATH + 'EL_TEST.tsv')
    
            self.create_dataloader(
                examples=train_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=True,
                batch_size=32,
                use_pickle=False,
            )
            self.create_dataloader(
                examples=valid_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=False,
                batch_size=32,
                use_pickle=False,
            )
            self.create_dataloader(
                examples=test_examples,
                tokenizer=tokenizer,
                max_length=max_length,
                shuffle=False,
                batch_size=32,
                use_pickle=False,
            )
    
    

    实体链接模型

    class EntityLinkingModel(pl.LightningModule):
        """实体链接模型"""
    
        def __init__(self, max_length=384, batch_size=32, use_pickle=True):
            super(EntityLinkingModel, self).__init__()
            # 输入最大长度
            self.max_length = max_length
            self.batch_size = batch_size
            self.use_pickle = use_pickle
            
            self.tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")       
    
            self.bert = BertForSequenceClassification.from_pretrained(
            "hfl/chinese-roberta-wwm-ext",
            num_labels = 1,
            )
    
            # 二分类损失函数
            self.criterion = nn.BCEWithLogitsLoss()
    
        def forward(self, input_ids, attention_mask, token_type_ids):
            logits = self.bert(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids,
            )[0]
            return logits.squeeze()
    
        def prepare_data(self):
       
            self.processor = EntityLinkingProcessor()
            self.train_examples = self.processor.get_train_examples(TSV_PATH + 'EL_TRAIN.tsv')
            self.valid_examples = self.processor.get_dev_examples(TSV_PATH + 'EL_VALID.tsv')
            self.test_examples = self.processor.get_test_examples(TSV_PATH + 'EL_TEST.tsv')
    
            self.train_loader = self.processor.create_dataloader(
                examples=self.train_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=True,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
            self.valid_loader = self.processor.create_dataloader(
                examples=self.valid_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
            self.test_loader = self.processor.create_dataloader(
                examples=self.test_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
            print("finish")
    
        def training_step(self, batch, batch_idx):
            input_ids, attention_mask, token_type_ids, labels = batch
            logits = self(input_ids, attention_mask, token_type_ids)
            loss = self.criterion(logits, labels.float())
    
            preds = (logits > 0).int()
            acc = (preds == labels).float().mean()
    
            tensorboard_logs = {'train_loss': loss, 'train_acc': acc}
            return {'loss': loss, 'log': tensorboard_logs, 'progress_bar': tensorboard_logs}
    
        def validation_step(self, batch, batch_idx):
            input_ids, attention_mask, token_type_ids, labels = batch
            logits = self(input_ids, attention_mask, token_type_ids)
            loss = self.criterion(logits, labels.float())
    
            preds = (logits > 0).int()
            acc = (preds == labels).float().mean()
    
            return {'val_loss': loss, 'val_acc': acc}
    
        def validation_epoch_end(self, outputs):
            val_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
            val_acc = torch.stack([x['val_acc'] for x in outputs]).mean()
    
            tensorboard_logs = {'val_loss': val_loss, 'val_acc': val_acc}
            return {'val_loss': val_loss, 'log': tensorboard_logs, 'progress_bar': tensorboard_logs}
    
        def configure_optimizers(self):
            return torch.optim.Adam([p for p in self.parameters() if p.requires_grad], lr=2e-5, eps=1e-8)
    
        def train_dataloader(self):
            return self.train_loader
    
        def val_dataloader(self):
            return self.valid_loader
    

    实体链接推断

    class EntityLinkingPredictor:
    
        def __init__(self, ckpt_name, batch_size=8, use_pickle=True):
            self.ckpt_name = ckpt_name
            self.batch_size = batch_size
            self.use_pickle = use_pickle
    
        def generate_tsv_result(self, tsv_name, tsv_type='Valid'):
            processor = EntityLinkingProcessor()
            tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
    
            if tsv_type == 'Valid':
                examples = processor.get_dev_examples(TSV_PATH + tsv_name)
            elif tsv_type == 'Test':
                examples = processor.get_test_examples(TSV_PATH + tsv_name)
            else:
                raise ValueError('tsv_type error')
            dataloader = processor.create_dataloader(
                examples=examples,
                tokenizer=tokenizer,
                max_length=384,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
    
            model = EntityLinkingModel.load_from_checkpoint(
                checkpoint_path=CKPT_PATH + self.ckpt_name,
            )
            model.to(DEVICE)
            model = nn.DataParallel(model)
            model.eval()
    
            result_list, logit_list = [], []
            for batch in tqdm(dataloader):
                for i in range(len(batch)):
                    batch[i] = batch[i].to(DEVICE)
    
                input_ids, attention_mask, token_type_ids, labels = batch
                logits = model(input_ids, attention_mask, token_type_ids)
                preds = (logits > 0).int()
    
                result_list.extend(preds.tolist())
                logit_list.extend(logits.tolist())
    
            tsv_data = pd.read_csv(TSV_PATH + tsv_name, sep='\t')
            tsv_data['logits'] = logit_list
            tsv_data['result'] = result_list
            result_name = tsv_name.split('.')[0] + '_RESULT.tsv'
            tsv_data.to_csv(RESULT_PATH + result_name, index=False, sep='\t')
    

    实体类型推断模型

    import torch.nn as nn
    
    class EntityTypingModel(pl.LightningModule):
        """实体类型推断模型"""
    
        def __init__(self, max_length=64, batch_size=64, use_pickle=True):
            super(EntityTypingModel, self).__init__()
            # 输入最大长度
            self.max_length = max_length
            self.batch_size = batch_size
            self.use_pickle = use_pickle
            # 二分类损失函数
            self.criterion = nn.CrossEntropyLoss()
    
            self.tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
    
            # 预训练模型
            self.bert = BertForSequenceClassification.from_pretrained(
                "hfl/chinese-roberta-wwm-ext",
                num_labels=len(PICKLE_DATA['IDX_TO_TYPE']),
            )
    
    
        def forward(self, input_ids, attention_mask, token_type_ids):
            return self.bert(
                input_ids=input_ids,
                attention_mask=attention_mask,
                token_type_ids=token_type_ids,
            )[0]
    
        def prepare_data(self):
            self.processor = EntityTypingProcessor()
            self.train_examples = self.processor.get_train_examples(TSV_PATH + 'ET_TRAIN.tsv')
            self.valid_examples = self.processor.get_dev_examples(TSV_PATH + 'ET_VALID.tsv')
            self.test_examples = self.processor.get_test_examples(TSV_PATH + 'ET_TEST.tsv')
    
            self.train_loader = self.processor.create_dataloader(
                examples=self.train_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=True,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
            self.valid_loader = self.processor.create_dataloader(
                examples=self.valid_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
            self.test_loader = self.processor.create_dataloader(
                examples=self.test_examples,
                tokenizer=self.tokenizer,
                max_length=self.max_length,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
    
        def training_step(self, batch, batch_idx):
            input_ids, attention_mask, token_type_ids, labels = batch
            outputs = self(input_ids, attention_mask, token_type_ids)
            loss = self.criterion(outputs, labels)
    
            _, preds = torch.max(outputs, dim=1)
            acc = (preds == labels).float().mean()
    
            tensorboard_logs = {'train_loss': loss, 'train_acc': acc}
            return {'loss': loss, 'log': tensorboard_logs, 'progress_bar': tensorboard_logs}
    
        def validation_step(self, batch, batch_idx):
            input_ids, attention_mask, token_type_ids, labels = batch
            outputs = self(input_ids, attention_mask, token_type_ids)
            loss = self.criterion(outputs, labels)
    
            _, preds = torch.max(outputs, dim=1)
            acc = (preds == labels).float().mean()
    
            return {'val_loss': loss, 'val_acc': acc}
    
        def validation_epoch_end(self, outputs):
            val_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
            val_acc = torch.stack([x['val_acc'] for x in outputs]).mean()
    
            tensorboard_logs = {'val_loss': val_loss, 'val_acc': val_acc}
            return {'val_loss': val_loss, 'log': tensorboard_logs, 'progress_bar': tensorboard_logs}
    
        def configure_optimizers(self):
            return torch.optim.Adam([p for p in self.parameters() if p.requires_grad], lr=2e-5, eps=1e-8)
    
        def train_dataloader(self):
            return self.train_loader
    
        def val_dataloader(self):
            return self.valid_loader
    
    

    实体类型推断

    class EntityTypingPredictor:
    
        def __init__(self, ckpt_name, batch_size=8, use_pickle=True):
            self.ckpt_name = ckpt_name
            self.batch_size = batch_size
            self.use_pickle = use_pickle
    
        def generate_tsv_result(self, tsv_name, tsv_type='Valid'):
            processor = EntityTypingProcessor()
            tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
    
            if tsv_type == 'Valid':
                examples = processor.get_dev_examples(TSV_PATH + tsv_name)
            elif tsv_type == 'Test':
                examples = processor.get_test_examples(TSV_PATH + tsv_name)
            else:
                raise ValueError('tsv_type error')
            dataloader = processor.create_dataloader(
                examples=examples,
                tokenizer=tokenizer,
                max_length=64,
                shuffle=False,
                batch_size=self.batch_size,
                use_pickle=self.use_pickle,
            )
    
            model = EntityTypingModel.load_from_checkpoint(
                checkpoint_path=CKPT_PATH + self.ckpt_name,
            )
            model.to(DEVICE)
            model = nn.DataParallel(model)
            model.eval()
    
            result_list = []
            for batch in tqdm(dataloader):
                for i in range(len(batch)):
                    batch[i] = batch[i].to(DEVICE)
    
                input_ids, attention_mask, token_type_ids, labels = batch
                outputs = model(input_ids, attention_mask, token_type_ids)
                _, preds = torch.max(outputs, dim=1)
                result_list.extend(preds.tolist())
    
            idx_to_type = PICKLE_DATA['IDX_TO_TYPE']
            result_list = [idx_to_type[x] for x in result_list]
            tsv_data = pd.read_csv(TSV_PATH + tsv_name, sep='\t')
            tsv_data['result'] = result_list
            result_name = tsv_name.split('.')[0] + '_RESULT.tsv'
            tsv_data.to_csv(RESULT_PATH + result_name, index=False, sep='\t')
    

    导入数据

    def preprocess_pickle_file():
        processor = PicklePreprocessor()
        processor.run()
    
    
    def preprocess_tsv_file():
        processor = DataFramePreprocessor()
        processor.run()
    
    
    def generate_feature_pickle():
        processor = EntityLinkingProcessor()
        processor.generate_feature_pickle(max_length=384)
    
        processor = EntityTypingProcessor()
        processor.generate_feature_pickle(max_length=64)
    
    def train_entity_linking_model(ckpt_name):
    
        model = EntityLinkingModel(max_length=384, batch_size=32)
        trainer = pl.Trainer(
            max_epochs=1,
            gpus=1,
            distributed_backend='dp',
            default_save_path=EL_SAVE_PATH,
            profiler=True,
        )
        trainer.fit(model)
        trainer.save_checkpoint(CKPT_PATH + ckpt_name)
    
    def train_entity_typing_model(ckpt_name):
        model = EntityTypingModel(max_length=64, batch_size=64)
        trainer = pl.Trainer(
            max_epochs=1,
            gpus=1,
            distributed_backend='dp',
            default_save_path=ET_SAVE_PATH,
            profiler=True,
        )
        trainer.fit(model)
        trainer.save_checkpoint(CKPT_PATH + ckpt_name)
    
    
    def generate_link_tsv_result(ckpt_name):
        predictor = EntityLinkingPredictor(ckpt_name, batch_size=24, use_pickle=True)
        predictor.generate_tsv_result('EL_VALID.tsv', tsv_type='Valid')
        predictor.generate_tsv_result('EL_TEST.tsv', tsv_type='Test')
    
    
    def generate_type_tsv_result(ckpt_name):
        predictor = EntityTypingPredictor(ckpt_name, batch_size=64, use_pickle=True)
        predictor.generate_tsv_result('ET_VALID.tsv', tsv_type='Valid')
        predictor.generate_tsv_result('ET_TEST.tsv', tsv_type='Test')
    
    
    def make_predication_result(input_name, output_name, el_ret_name, et_ret_name):
        entity_to_kbids = PICKLE_DATA['ENTITY_TO_KBIDS']
        
        el_ret = pd.read_csv(
            RESULT_PATH + el_ret_name, sep='\t', dtype={
                'text_id': np.str_,
                'offset': np.str_,
                'kb_id': np.str_
            })
        
        et_ret = pd.read_csv(RESULT_PATH + et_ret_name, sep='\t', dtype={'text_id': np.str_, 'offset': np.str_})
     
        result = []
        with open(RAW_PATH + input_name, 'r',encoding="utf-8") as f:
            for line in tqdm(f):
                line = json.loads(line)
                for data in line['mention_data']:
                    text_id = line['text_id']
                    offset = data['offset']
    
                    candidate_data = el_ret[(el_ret['text_id'] == text_id) & (el_ret['offset'] == offset)]
                    # Entity Linking
                    if len(candidate_data) > 0 and candidate_data['logits'].max() > 0:
                        max_idx = candidate_data['logits'].idxmax()
                        data['kb_id'] = candidate_data.loc[max_idx]['kb_id']
                    # Entity Typing
                    else:
                        type_data = et_ret[(et_ret['text_id'] == text_id) & (et_ret['offset'] == offset)]
                        data['kb_id'] = 'NIL_' + type_data.iloc[0]['result']
                result.append(line)
    
        with open(RESULT_PATH + output_name, 'w',encoding="utf-8") as f:
            for r in result:
                json.dump(r, f, ensure_ascii=False)
                f.write('\n')
    
    
    
    
    set_random_seed(20200619)
    preprocess_pickle_file()
    preprocess_tsv_file()
    generate_feature_pickle()
    
    
    train_entity_linking_model('EL_BASE_EPOCH0.ckpt')
    generate_link_tsv_result('EL_BASE_EPOCH0.ckpt')
    
    train_entity_typing_model('ET_BASE_EPOCH1.ckpt')
    generate_type_tsv_result('ET_BASE_EPOCH1.ckpt')
    

    简单查看一下测试数据的准确率

    el_ret = pd.read_csv("./data/result/ET_TEST_RESULT.tsv", sep='\t')
    el_ret.head()
    
    text_identityoffsetshort_texttyperesult
    01林平之0林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了OtherPerson
    11岳灵珊5林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了OtherPerson
    21师娘18林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了OtherOther
    31令狐冲21林平之答应岳灵珊报仇之事,从长计议,师娘想令狐冲了OtherPerson
    42思追0思追原来是个超级妹控,不愿妹妹嫁人,然而妹妹却喜欢一博老师OtherPerson
    el_ret = pd.read_csv("./data/result/ET_VALID_RESULT.tsv", sep='\t')        
    el_ret.head()
    
    text_identityoffsetshort_texttyperesult
    01天下没有不散的宴席0天下没有不散的宴席 - ╰つ雲中帆╰つWorkWork
    11╰つ雲中帆╰つ12天下没有不散的宴席 - ╰つ雲中帆╰つOtherPerson
    22永嘉0永嘉厂房出租LocationLocation
    32厂房2永嘉厂房出租LocationOther
    42出租4永嘉厂房出租OtherOther
    path = "./data/result/ET_VALID_RESULT.tsv"        
    data = pd.read_csv(
        path, sep='\t', dtype={
            'text_id': np.str_,
            'offset': np.str_,
            'kb_id': np.str_
        })
    data.head()
    
    text_identityoffsetshort_texttyperesult
    01天下没有不散的宴席0天下没有不散的宴席 - ╰つ雲中帆╰つWorkWork
    11╰つ雲中帆╰つ12天下没有不散的宴席 - ╰つ雲中帆╰つOtherPerson
    22永嘉0永嘉厂房出租LocationLocation
    32厂房2永嘉厂房出租LocationOther
    42出租4永嘉厂房出租OtherOther
    make_predication_result('dev.json', 'valid_result.json', 'EL_VALID_RESULT.tsv', 'ET_VALID_RESULT.tsv')
    
    10000it [13:17, 12.55it/s]
    
    make_predication_result('test.json', 'test_result.json', 'EL_TEST_RESULT.tsv', 'ET_TEST_RESULT.tsv')
    
    10000it [31:54,  5.22it/s]
    

    计算准确率

    # !/bin/env python
    # -*- coding: utf-8 -*-
    #####################################################################################
    #
    #  Copyright (c) CCKS 2020 Entity Linking Organizing Committee.
    #  All Rights Reserved.
    #
    #####################################################################################
    """
    @version 2020-03-30
    @brief:
        Entity Linking效果评估脚本,评价指标Micro-F1
    """
    # import sys
    
    # reload(sys)
    # sys.setdefaultencoding('utf-8')
    import json
    from collections import defaultdict
    
    
    class Eval(object):
        """
        Entity Linking Evaluation
        """
    
        def __init__(self, golden_file_path, user_file_path):
            self.golden_file_path = golden_file_path
            self.user_file_path = user_file_path
            self.tp = 0
            self.fp = 0
            self.total_recall = 0
            self.errno = None
    
        def format_check(self, file_path):
            """
            文件格式验证
            :param file_path: 文件路径
            :return: Bool类型:是否通过格式检查,通过为True,反之False
            """
            flag = True
            for line in open(file_path,encoding='utf-8'):
                json_info = json.loads(line.strip())
                if 'text_id' not in json_info:
                    flag = False
                    self.errno = 1
                    break
                if 'text' not in json_info:
                    flag = False
                    self.errno = 2
                    break
                if 'mention_data' not in json_info:
                    flag = False
                    self.errno = 3
                    break
                if not json_info['text_id'].isdigit():
                    flag = False
                    self.errno = 5
                    break           
                if not isinstance(json_info['mention_data'], list):
                    flag = False
                    self.errno = 7
                    break
                for mention_info in json_info['mention_data']:
                    if 'kb_id' not in mention_info:
                        flag = False
                        self.errno = 7
                        break
                    if 'mention' not in mention_info:
                        flag = False
                        self.errno = 8
                        break
                    if 'offset' not in mention_info:
                        flag = False
                        self.errno = 9
                        break                
                    if not mention_info['offset'].isdigit():
                        flag = False
                        self.errno = 13
                        break
            return flag
    
        def micro_f1(self):
            """
            :return: float类型:精确率,召回率,Micro-F1值
            """
            # 文本格式验证
            flag_golden = self.format_check(self.golden_file_path)
            flag_user = self.format_check(self.user_file_path)
            # 格式验证失败直接返回None
            if not flag_golden or not flag_user:
                return None, None, None
            precision = 0
            recall = 0
            self.tp = 0
            self.fp = 0
            self.total_recall = 0
            golden_dict = defaultdict(list)
            for line in open(self.golden_file_path,encoding='utf-8'):
                golden_info = json.loads(line.strip())
                text_id = golden_info['text_id']
                text = golden_info['text']
                mention_data = golden_info['mention_data']
                for mention_info in mention_data:
                    kb_id = mention_info['kb_id']
                    mention = mention_info['mention']
                    offset = mention_info['offset']
                    key = '\1'.join([text_id, text, mention, offset]).encode('utf8')
                    # value的第二个元素表示标志位,用于判断是否已经进行了统计
                    golden_dict[key] = [kb_id, 0]
                    self.total_recall += 1
    
            # 进行评估
            for line in open(self.user_file_path,encoding='utf-8'):
                golden_info = json.loads(line.strip())
                text_id = golden_info['text_id']
                text = golden_info['text']
                mention_data = golden_info['mention_data']
                for mention_info in mention_data:
                    kb_id = mention_info['kb_id']
                    mention = mention_info['mention']
                    offset = mention_info['offset']
                    key = '\1'.join([text_id, text, mention, offset]).encode('utf8')
                    if key in golden_dict:
                        kb_result_golden = golden_dict[key]
                        if kb_id.isdigit():
                            if kb_id in [kb_result_golden[0]] and kb_result_golden[1] in [0]:
                                self.tp += 1
                            else:
                                self.fp += 1
                        else:
                            # nil golden结果
                            nil_res = kb_result_golden[0].split('|')
                            if kb_id in nil_res and kb_result_golden[1] in [0]:
                                self.tp += 1
                            else:
                                self.fp += 1
                        golden_dict[key][1] = 1
                    else:
                        self.fp += 1
            if self.tp + self.fp > 0:
                precision = float(self.tp) / (self.tp + self.fp)
            if self.total_recall > 0:
                recall = float(self.tp) / self.total_recall
            a = 2 * precision * recall
            b = precision + recall
            if b == 0:
                return 0, 0, 0
            f1 = a / b
            return precision, recall, f1
    
    eval = Eval('./data/ccks2020_el_data_v1/dev.json', './data/result/valid_result.json')
    
    prec, recall, f1 = eval.micro_f1()
    print(prec, recall, f1)
    if eval.errno:
        print(eval.errno)
    
    
    0.8488185641242852 0.8488185641242852 0.8488185641242852
    
    
    

    相关链接:2020全国知识图谱与语义计算大会 http://sigkg.cn/ccks2020/?page_id=69

    展开全文
  • 实体命名识别相关知识Stanford CoreNLP 命名实体识别一、简介:二、java版本使用三、python版本使用NLTK 命名实体识别一、简介:二、搭建环境三、nltk使用1、英文实体命名初体验2、使用nltk来处理中文资料结巴分词...

    相关知识

    信息抽取:从数据库中抽取信息是容易的,但对于从自然文本中抽取信息则不那么直观。通常信息抽取的流程如下图:

    img
    分块是实体识别(NER)使用的基本技术,词性标注是分块所需的最主要信息。下面以名词短语(NP)为例,展示如何分块。类似的还可以对动词短语,介词短语等进行分块。
    分块示意图

    命名实体识别(Named Entity Recognition,简称NER)用于识别文本中具有特定意义的实体。需要识别的实体可以分为三大类(实体类、时间类和数字类)和七小类(人名、机构名、地名、时间、日期、货币和百分比)。

    中文命名实体识别工具(NER)哪家强?

    介绍几个专门面向中文的命名实体识别和关系抽取工具

    Stanford CoreNLP 命名实体识别

    一、简介:

    CoreNLP是Java自然语言处理的一站式服务!CoreNLP使用户能够导出文本的语言注释,包括标记和句子边界、词性、命名实体、数值和时间值、依赖和选区解析、共指、情感、引用属性和关系。CoreNLP的核心是管道。管道接收原始文本,对文本运行一系列NLP注释器,并生成最终的注释集。管道产生核心文档,包含所有注释信息的数据对象,可以通过简单的API访问,并且可以序列化到Google协议缓冲区。

    中文语料模型包中有一个默认的配置文件

    StanfordCoreNLP-chinese.properties 
    

    指定pipeline的操作步骤以及对应的语料文件的位置,可以自定义配置文件,再引入代码中。实际上我们可能用不到所有的步骤,或者要使用不同的语料库,因此可以自定义配置文件,然后再引入。那在我的项目中,我就直接读取了该properties文件。(有时候我们只想使用ner功能,但不想使用其他功能,想去掉。然而,Stanford CoreNLP有一些局限,就是在ner执行之前,一定需要tokenize, ssplit, pos, lemma 的引入,大大增加了耗时。)

    更多用法参见官网

    二、java版本使用

    idea+maven搭建工程

    1、在pom.xml 添加依赖:

    <properties>
        <corenlp.version>3.9.1</corenlp.version>
    </properties>
    
    <dependencies>
        <!--CoreNLP的算法包-->
        <dependency>
            <groupId>edu.stanford.nlp</groupId>
            <artifactId>stanford-corenlp</artifactId>
            <version>${corenlp.version}</version>
        </dependency>
        <!--英文语料包-->
        <dependency>
            <groupId>edu.stanford.nlp</groupId>
            <artifactId>stanford-corenlp</artifactId>
            <version>3.9.1</version>
            <classifier>models</classifier>
        </dependency>
        <!--中文预料包-->
        <dependency>
            <groupId>edu.stanford.nlp</groupId>
            <artifactId>stanford-corenlp</artifactId>
            <version>${corenlp.version}</version>
            <classifier>models-chinese</classifier>
        </dependency>
    </dependencies>
    

    2、编写java程序

    package com;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    
    import edu.stanford.nlp.coref.CorefCoreAnnotations;
    import edu.stanford.nlp.coref.data.CorefChain;
    import edu.stanford.nlp.ling.CoreAnnotations;
    import edu.stanford.nlp.ling.CoreLabel;
    import edu.stanford.nlp.pipeline.Annotation;
    import edu.stanford.nlp.pipeline.StanfordCoreNLP;
    import edu.stanford.nlp.semgraph.SemanticGraph;
    import edu.stanford.nlp.semgraph.SemanticGraphCoreAnnotations;
    import edu.stanford.nlp.trees.Tree;
    import edu.stanford.nlp.trees.TreeCoreAnnotations;
    import edu.stanford.nlp.util.CoreMap;
    /**
     代码思想:
     将text字符串交给Stanford CoreNLP处理,
     StanfordCoreNLP的各个组件(annotator)按“tokenize(分词), ssplit(断句), pos(词性标注), lemma(词元化), ner(命名实体识别), parse(语法分析), dcoref(同义词分辨)”顺序进行处理。
     处理完后 List<CoreMap> sentences = document.get(SentencesAnnotation.class); 中包含了所有分析结果,遍历即可获知结果。
     **/
    
    public class StanfordChineseNlpExample2 {
    
        public static void main(String[] args) throws  Exception {
            StanfordChineseNlpExample2 nlp=new StanfordChineseNlpExample2();
            nlp.test();
        }
    
        public void test() throws Exception {
            //构造一个StanfordCoreNLP对象,配置NLP的功能,如lemma是词干化,ner是命名实体识别等
            StanfordCoreNLP pipeline = new StanfordCoreNLP("StanfordCoreNLP-chinese.properties");
            String text = "袁隆平是中国科学院的院士,他于2009年10月到中国山东省东营市东营区永乐机场附近承包了一千亩盐碱地,";
    
    
            long startTime = System.currentTimeMillis();
            // 创造一个空的Annotation对象
            Annotation document = new Annotation(text);
            // 对文本进行分析
            pipeline.annotate(document);
            //获取文本处理结果
            List<CoreMap> sentences = document.get(CoreAnnotations.SentencesAnnotation.class);
            for (CoreMap sentence : sentences) {
                // traversing the words in the current sentence
                // a CoreLabel is a CoreMap with additional token-specific methods
                for (CoreLabel token : sentence.get(CoreAnnotations.TokensAnnotation.class)) {
                    // 获取句子的token(可以是作为分词后的词语)
                    String word = token.get(CoreAnnotations.TextAnnotation.class);
                    System.out.println(word);
                    //词性标注
                    String pos = token.get(CoreAnnotations.PartOfSpeechAnnotation.class);
                    System.out.println(pos);
                    // 命名实体识别
                    String ne = token.get(CoreAnnotations.NormalizedNamedEntityTagAnnotation.class);
                    String ner = token.get(CoreAnnotations.NamedEntityTagAnnotation.class);
                    System.out.println(word + " | analysis : {  original : " + ner + "," + " normalized : "
                            + ne + "}");
                    //词干化处理
                    String lema = token.get(CoreAnnotations.LemmaAnnotation.class);
                    System.out.println(lema);
                }
    
                // 句子的解析树
                Tree tree = sentence.get(TreeCoreAnnotations.TreeAnnotation.class);
                System.out.println("句子的解析树:");
                tree.pennPrint();
    
                // 句子的依赖图
                SemanticGraph graph =
                        sentence.get(SemanticGraphCoreAnnotations.CollapsedCCProcessedDependenciesAnnotation.class);
                System.out.println("句子的依赖图");
                System.out.println(graph.toString(SemanticGraph.OutputFormat.LIST));
            }
    
            long endTime = System.currentTimeMillis();
            long time = endTime - startTime;
            System.out.println("The analysis lasts " + time + " seconds * 1000");
    
            // 指代词链
            // 每条链保存指代的集合
            // 句子和偏移量都从1开始
            Map<Integer, CorefChain> corefChains = document.get(CorefCoreAnnotations.CorefChainAnnotation.class);
            if (corefChains == null) {
                return;
            }
            for (Map.Entry<Integer, CorefChain> entry : corefChains.entrySet()) {
                System.out.println("Chain " + entry.getKey() + " ");
                for (CorefChain.CorefMention m : entry.getValue().getMentionsInTextualOrder()) {
                    // We need to subtract one since the indices count from 1 but the Lists start from 0
                    List<CoreLabel> tokens = sentences.get(m.sentNum - 1).get(CoreAnnotations.TokensAnnotation.class);
                    // We subtract two for end: one for 0-based indexing, and one because we want last token of mention
                    // not one following.
                    System.out.println(
                            "  " + m + ", i.e., 0-based character offsets [" + tokens.get(m.startIndex - 1).beginPosition()
                                    +
                                    ", " + tokens.get(m.endIndex - 2).endPosition() + ")");
                }
            }
        }
    }
    

    实体识别结果:

    袁隆平 | analysis : {  original : PERSON, normalized : null}| analysis : {  original : O, normalized : null}
    中国 | analysis : {  original : ORGANIZATION, normalized : null}
    科学院 | analysis : {  original : ORGANIZATION, normalized : null}| analysis : {  original : O, normalized : null}
    院士 | analysis : {  original : TITLE, normalized : null}
    , | analysis : {  original : O, normalized : null}| analysis : {  original : O, normalized : null}| analysis : {  original : O, normalized : null}
    2009年 | analysis : {  original : DATE, normalized : 2009-10-XX}
    10月 | analysis : {  original : DATE, normalized : 2009-10-XX}| analysis : {  original : O, normalized : null}
    中国 | analysis : {  original : COUNTRY, normalized : null}
    山东省 | analysis : {  original : STATE_OR_PROVINCE, normalized : null}
    东营市 | analysis : {  original : CITY, normalized : null}
    东营区 | analysis : {  original : FACILITY, normalized : null}
    永乐 | analysis : {  original : FACILITY, normalized : null}
    机场 | analysis : {  original : FACILITY, normalized : null}
    附近 | analysis : {  original : O, normalized : null}
    承包 | analysis : {  original : O, normalized : null}| analysis : {  original : O, normalized : null}
    一千 | analysis : {  original : NUMBER, normalized : 1000}| analysis : {  original : O, normalized : null}| analysis : {  original : O, normalized : null}
    碱地 | analysis : {  original : O, normalized : null}
    , | analysis : {  original : O, normalized : null}
    The analysis lasts 989 seconds * 1000
    

    大概可以识别到的类型有:人person、数字number、组织organization、头衔title、省/市/区/位置province/city/facility/location、日期/时间date/time

    实时在线演示:https://corenlp.run/

    三、python版本使用

    1. 安装斯坦福大学NLP组的Stanza 。(要求:Python3.6及以上的版本)
    pip install stanza 
    
    1. 下载中文模型打包文件
    import stanza
    stanza.download('zh')
    

    出现问题:[WinError 10054] 远程主机强迫关闭了一个现有的连接
    解决方法:用梯子

    1. 使用
    # coding utf-8
    import stanza
    
    # 可以通过pipeline预加载不同语言的模型,也可以通过pipeline选择不同的处理模块,还可以选择是否使用GPU:
    zh_nlp = stanza.Pipeline('zh', use_gpu=False)
    text = "马云在1998年7月31日出生于江苏省盐城市大丰区。"
    
    doc = zh_nlp(text)
    for sent in doc.sentences:
        print("Sentence:" + sent.text)  # 断句
        print("Tokenize:" + ' '.join(token.text for token in sent.tokens))  # 中文分词
        print("UPOS: " + ' '.join(f'{word.text}/{word.upos}' for word in sent.words))  # 词性标注(UPOS)
        print("XPOS: " + ' '.join(f'{word.text}/{word.xpos}' for word in sent.words))  # 词性标注(XPOS)
        print("NER: " + ' '.join(f'{ent.text}/{ent.type}' for ent in sent.ents))  # 命名实体识别
    
    Sentence:马云在1998年7月31日出生于江苏省盐城市大丰区。
    Tokenize:马云 在 1998 年 7 月 31 日 出生 于 江苏 省 盐城 市 大丰 区 。
    UPOS: 马云/PROPN 在/ADP 1998/NUM 年/NOUN 7/NUM 月/NOUN 31/NUM 日/NOUN 出生/VERB 于/ADP 江苏/PROPN 省/PART 盐城/PROPN 市/PART 大丰/PROPN 区/PART 。/PUNCT
    XPOS: 马云/NNP 在/IN 1998/CD 年/NNB 7/CD 月/NNB 31/CD 日/NNB 出生/VV 于/IN 江苏/NNP 省/SFN 盐城/NNP 市/SFN 大丰/NNP 区/SFN 。/.
    NER: 马云/PERSON 1998年7月31日/DATE 江苏/GPE 盐城/GPE 大丰/GPE
    

    更多详细信息见:斯坦福大学NLP组Python深度学习自然语言处理工具Stanza试用

    NLTK 命名实体识别

    一、简介:

    NLTK对于自然语言处理有很多开箱即用的API,本文主要介绍如何使用NLTK进行中文命名实体识别。由于NLTK不支持中文分词,所以本文使用了结巴分词。

    二、搭建环境

    环境:windows64+python3

    前提:安装好python3,并且安装了numpy、matplotlib、pandas等一些常用的库

    1、安装PyYAML模块和nltk模块

     pip install pyyaml nltk
    

    2、下载NLTK的数据包

    方式一:界面下载

    在pycharm中写一个python脚本,如下:

    import nltk
    nltk.download()
    

    运行脚本,出现如下界面,选择all,设置下载路径,点击下载:
    在这里插入图片描述
    下载时间很长,如果有个别数据包无法下载,可切换到All Packages标签页,双击指定的包来进行下载。
    在这里插入图片描述

    方式二:命令行下载

    创建名称为 nltk_data 的文件夹(比如我创建在了anacondas的目录下)
    在这里插入图片描述
    文件夹位置要求,程序会按照如下顺序去找该文件夹,所以,你创建的文件夹在以下目录即可:

    Searched in:

    • ‘C:\Users\10840/nltk_data’
      • ‘D:\develop\python\Anaconda3\nltk_data’
        • ‘D:\develop\python\Anaconda3\share\nltk_data’
        • ‘D:\develop\python\Anaconda3\lib\nltk_data’
        • ‘C:\Users\10840\AppData\Roaming\nltk_data’
        • ‘C:\nltk_data’
        • ‘D:\nltk_data’
        • ‘E:\nltk_data’
        • ’ ’

    cmd 进入 nltk_data 文件夹目录,执行命令 python -m nltk.downloader all
    在这里插入图片描述
    关于下载的问题:[win error 10054] 远程主机强迫关闭了一个现有的连接

    解决方法:1.使用梯子 2.从国内别人上传的云盘下载(文末链接中有)3. 直接到官网下载数据包。

    只要将下载的数据包复制到你的 Download Directory目录下即可

    三、nltk使用

    用NLTK来实现文本信息提取的方法,包含4步:分词,词性标注,(分块)命名实体识别,实体关系识别。

    分块可以简单的基于经验,使用正则表达式来匹配,也可以使用基于统计的分类算法来实现,NLTK有提供基于正则的分块器。

    nltk 不提供中文分词。

    1、英文实体命名初体验

    import sys
    import importlib
    importlib.reload(sys)
    import nltk
    
    article = "I came to Tsinghua University in Beijing"  # 文章
    tokens = nltk.word_tokenize(article)  # 分词
    print("tokens",tokens)
    '''
    tokens ['I', 'came', 'to', 'Tsinghua', 'University', 'in', 'Beijing']
    '''
    
    tagged = nltk.pos_tag(tokens)  # 词性标注
    print("tagged",tagged)
    '''
    tagged [('I', 'PRP'), ('came', 'VBD'), ('to', 'TO'), ('Tsinghua', 'NNP'), ('University', 'NNP'), ('in', 'IN'), ('Beijing', 'NNP')]
    '''
    
    entities = nltk.chunk.ne_chunk(tagged)  # 命名实体识别
    print(entities)
    # 命名实体:确切的名词短语,指特定类型的个体,如日期、人、组织等
    '''
    (S
      I/PRP
      came/VBD
      to/TO
      (ORGANIZATION Tsinghua/NNP University/NNP)
      in/IN
      (GPE Beijing/NNP))
    '''    
    

    NLTK 采用的是宾州中文树库标记

    2、使用nltk来处理中文资料

    nltk 目前只能比较好的处理英文和其他的一些拉丁语系。由于中文汉字一个挨一个,nltk不支持。

    所以可以采用其他分词工具对中文语料进行处理,再使用nltk对其进行实体识别。

    分词工具有很多,这里使用 结巴分词主页有详细介绍

    结巴分词使用

    1、安装

    pip install jieba
    

    2、使用:中文分词初体验

    # encoding=utf-8
    import jieba
    
    jieba.enable_paddle()  # 启动paddle模式。
    strs=["我来到北京清华大学","乒乓球拍卖完了","中国科学技术大学"]
    for str in strs:
        seg_list = jieba.cut(str,use_paddle=True) # 使用paddle模式
        print("Paddle Mode: " + '/'.join(list(seg_list)))
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
    print("【全模式】: " + "/ ".join(seg_list))
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
    print("【精确模式】: " + "/ ".join(seg_list))
    
    seg_list = jieba.cut("他来到了网易杭研大厦")
    print("【新词识别】: "+", ".join(seg_list))
    
    seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")
    print("【搜索引擎模式】: "+", ".join(seg_list))
    
    '''
    Paddle Mode: 我/来到/北京清华大学
    Paddle Mode: 乒乓球/拍卖/完/了
    Paddle Mode: 中国科学技术大学
    
    【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
    【精确模式】: 我/ 来到/ 北京/ 清华大学
    【新词识别】: 他, 来到, 了, 网易, 杭研, 大厦
    【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, ,, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
    '''
    

    这样,把分词的结果输出到文件中,NLTK就可以拿来做实体识别了,比如下面:

    # encoding=utf-8
    import sys
    import importlib
    importlib.reload(sys)
    import nltk
    
    # 上中文
    tokens = nltk.word_tokenize("我 来到 北京 清华大学")  # 分词
    print("tokens",tokens)
    tagged = nltk.pos_tag(tokens)  # 词性标注
    print("tagged",tagged)
    entities = nltk.chunk.ne_chunk(tagged)  # 命名实体识别
    print(entities)
    
    '''
    tokens ['我', '来到', '北京', '清华大学']
    tagged [('我', 'JJ'), ('来到', 'NNP'), ('北京', 'NNP'), ('清华大学', 'NN')]
    (S 我/JJ 来到/NNP 北京/NNP 清华大学/NN)
    '''
    

    参考资料:
    python的nltk中文使用和学习资料汇总帮你入门提高
    NLTK学习之四:文本信息抽取

    foolnltk 命名实体识别

    一、简介

    foolnltk一个基于深度学习的中文分词工具,具有以下特点:

    • 可能不是最快的开源中文分词,但很可能是最准的开源中文分词
    • 基于 BiLSTM 模型训练而成
    • 包含分词,词性标注,实体识别, 都有比较高的准确率
    • 用户自定义词典
    • 可以定制自己的模型

    有python版本和java版本,详情请见

    二、python版本使用

    1、 安装

    pip install foolnltk
    

    2、 使用

    #coding utf-8
    import fool
    import os
    
    # 分词
    text = "我来到北京清华大学"
    print(fool.cut(text))
    '''
    [['我', '来到', '北京', '清华大学']]
    '''
        
    # 用户自定义词典
    # 词典格式格式如下,词的权重越高,词的长度越长就越越可能出现, 权重值请大于 1
    # 难受香菇 10
    # 什么鬼 10
    # 分词工具 10
    # 北京 10
    # 北京天安门 10
    fool.load_userdict(os.getcwd()+'\\mydictionary')
        
    # 词性标注
    print(fool.pos_cut(text))
    '''
    [[('我', 'r'), ('来到', 'v'), ('北京', 'ns'), ('清华大学', 'nt')]]
    '''
        
    # 实体识别
    words, ners = fool.analysis(text)
    print(ners)
    '''
    [[(3, 9, 'org', '北京清华大学')]]
    '''
    

    Ltp 实体命名识别

    一、简介

    哈工大的LTP,免费使用但限流量,需要给钱才行

    LTP4文档,啊!其实官方文档里面已经写的清清楚楚了!

    这个也能支持用户自定义词典

    二、使用

    1、安装

    pip install ltp
    

    2、使用

    import os
    from ltp import LTP
    
    ltp = LTP()  # 默认加载 Small 模型
    # user_dict.txt 是词典文件, max_window是最大前向分词窗口
    ltp.init_dict(path=os.getcwd()+'\\mydictionary', max_window=4)
    
    seg, hidden = ltp.seg(["马云在1996年11月29日来到杭州的阿里巴巴公司。"])  # 分词
    print(seg)
    '''
    [['马云', '在', '1996年', '11月', '29日', '来到', '杭州', '的', '阿里巴巴', '公司', '。']]
    '''
        
    pos = ltp.pos(hidden)  # 词性标注
    print(pos)
    '''
    [['nh', 'p', 'nt', 'nt', 'nt', 'v', 'ns', 'u', 'nz', 'n', 'wp']]
    '''
        
    ner = ltp.ner(hidden)  # 命名实体识别
    tag, start, end = ner[0][0]
    print(ner)
    for tag, start, end in ner[0]:
        print(tag, ":", "".join(seg[0][start:end + 1]))
    '''
    [[('Nh', 0, 0), ('Ns', 6, 6), ('Ni', 8, 9)]]
    Nh : 马云
    Ns : 杭州
    Ni : 阿里巴巴公司
    '''
    

    LTP 提供最基本的三种实体类型人名 Nh地名 Ns机构名 Ni 的识别。

    LAC 实体命名

    LAC项目地址

    一、简介

    LAC全称Lexical Analysis of Chinese,是百度自然语言处理部研发的一款联合的词法分析工具,实现中文分词、词性标注、专名识别等功能。该工具具有以下特点与优势:

    • 效果好:通过深度学习模型联合学习分词、词性标注、专名识别任务,词语重要性,整体效果F1值超过0.91,词性标注F1值超过0.94,专名识别F1值超过0.85,效果业内领先。
    • 效率高:精简模型参数,结合Paddle预测库的性能优化,CPU单线程性能达800QPS,效率业内领先。
    • 可定制:实现简单可控的干预机制,精准匹配用户词典对模型进行干预。词典支持长片段形式,使得干预更为精准。
    • 调用便捷支持一键安装,同时提供了Python、Java和C++调用接口与调用示例,实现快速调用和集成。
    • 支持移动端: 定制超轻量级模型,体积仅为2M,主流千元手机单线程性能达200QPS,满足大多数移动端应用的需求,同等体积量级效果业内领先。

    二、python版本使用

    1、安装

    pip install lac
    

    2、使用

    from LAC import LAC
    
    # 装载LAC模型
    lac = LAC(mode='lac')
    
    # 单个样本输入,输入为Unicode编码的字符串
    text = u"马云来到北京清华大学"
    lac_result = lac.run(text)
    print(lac_result)
    '''
    [['马云', '来到', '北京清华', '大学'], ['PER', 'v', 'ORG', 'n']]
    '''
    
    # 批量样本输入, 输入为多个句子组成的list,平均速率更快
    texts = [u"LAC是个优秀的分词工具", u"百度是一家高科技公司"]
    lac_result = lac.run(texts)
    print(lac_result)
    '''
    [[['LAC', '是', '个', '优秀', '的', '分词', '工具'], ['nz', 'v', 'q', 'a', 'u', 'n', 'n']],
     [['百度', '是', '一家', '高科技', '公司'], ['ORG', 'v', 'm', 'n', 'n']]]
    '''
    

    在这里插入图片描述

    控制台输出以上内容,这个是初始化异常日志,不碍事,不喜欢的可以通过升级 paddlepaddle 版本到1.8以上 来关闭

    pip install paddlepaddle==1.8
    

    词性和专业名词类别:(专业名词只能识别4种:人物、地名、机构名、时间)
    在这里插入图片描述

    BosonNLP 实体识别

    这个现在官方不给 SDK 的 tooken 了,所以不能用了,但是在线演示平台挺绚丽的。

    bosonnlp的SDK文档

    一、简介

    BosonNLP实体识别引擎基于自主研发的结构化信息抽取算法,F1分数达到81%,相比于StanfordNER高出7个百分点。通过对行业语料的进一步学习,可以达到更高的准确率。

    二、python版本使用

    1、安装

    pip install bosonnlp
    

    2、使用

    from bosonnlp import BosonNLP
    import os
    nlp = BosonNLP(os.environ['BOSON_API_TOKEN'])
    nlp.ner('杨超越在1998年7月31日出生于江苏省盐城市大丰区。', sensitivity=2)
    

    Hanlp 实体识别

    pyhanlp 项目官方地址

    一、简介

    HanLP是一系列模型与算法组成的NLP工具包,目标是普及自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。内部算法经过工业界和学术界考验。目前,基于深度学习的HanLP 2.0正处于alpha测试阶段,未来将实现知识图谱、问答系统、自动摘要、文本语义相似度、指代消解、三元组抽取、实体链接等功能。

    我们介绍的Pyhanlp是HanLP1.x的Python接口,支持自动下载与升级HanLP1.x,兼容py2、py3。

    二、python版本使用

    1、安装

    安装JDK

    JDK官方下载地址
    JDK的安装与环境变量配置
    注意 保证JDK的位数、操作系统位数和Python位数一致。

    安装Pyhanlp

    pip install pyhanlp
    

    2、使用

    # coding utf-8
    import pyhanlp
    text = '杨超越在1998年7月31日出生于江苏省盐城市大丰区。'
    NLPTokenizer = pyhanlp.JClass('com.hankcs.hanlp.tokenizer.NLPTokenizer')  # 加载模型
    NER = NLPTokenizer.segment(text)  # 命名实体识别
    print(NER)
    '''
    [杨超越/nr, 在/p, 1998年7月31日/t, 出生/v, 于/p, 江苏省盐城市/ns, 大丰区/ns, 。/w]
    '''
    

    不像前面介绍的几个工具可以直接获得实体,hanlp需要从词性标注里面提取实体,
    人名nr、地名ns、机名nt、时间t。

    展开全文
  • 现在已经有了很多相关的具体算法和模型,对于这些大家可以看顶会论文和技术分享,我们主要来介绍几个专门面向中文的命名实体识别和关系抽取的工具。 1. 中文分词 中文自然语言处理与其它语言比如英语的最大不同...

    知识图谱已经在人工智能的各个领域发挥越来越重要的作用,例如视觉问答、对话系统、推荐系统等。知识图谱构建是应用这些知识图谱的基础,而面对生活和企业中数据的爆发式增长,自动化知识图谱构建显得越来越重要。从非结构化文本中自动抽取三元组知识并构建知识图谱需要用到的核心技术就是命名实体识别和关系抽取,现在已经有了很多相关的具体算法和模型,对于这些大家可以看顶会论文和技术分享,我们主要来介绍几个专门面向中文的命名实体识别和关系抽取的工具。

     

    1. 中文分词

    中文自然语言处理与其它语言比如英语的最大不同之处在于英文中每个词直接由空格分隔开,但是中文是以字来分隔的,对于机器来说并不知道一句话中哪些字组成一个词。因此,在实现很多中文的自然语言处理功能时,需要先进行分词处理,将一句话中的中文词语分隔开。首先,我们来介绍一种最流行的中文分词工具jieba

    jieba的官方github地址:https://github.com/fxsjy/

    ieba在官方对jieba(标星24.5k+)的介绍中,我们看到是这样说的:

    “结巴”中文分词:做最好的 Python 中文分词组件

    不是人自吹,jieba确实是目前最好的中文分词工具,我们来看看这个小东西都有什么特点。

    1. 支持全自动安装:
      easy_install jieba
      或者
      pip install jieba / pip3 install jieba

      2. 对于同一个句子,支持四种分词模式:

    • 精确模式:试图将句子最精确地切开,适合文本分析。
    • 全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但不能解决歧义。
    • 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
    • paddle模式:利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词。同时支持词性标注。paddle模式使用需安装paddlepaddle-tiny:
    pip install paddlepaddle-tiny==1.6.1

    3. 支持繁体分词

    4. 支持自定义词典

    5. 算法上,采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合。对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法。具体的jieba和用法可以参考官方github文档。

     

    2. 命名实体识别

    目前较为成熟的中文命名实体识别(NER)工具包括LTP,PyHanlp,Lac等。

    2.1 语言技术平台(LTP)

    语言技术平台(LTP) 是哈工大社会计算与信息检索研究中心 11 年的持续研发和推广,LTP 已经成为国内外最具影响力的中文处理基础平台,曾获 CoNLL 2009七国语言句法语义分析评测 总成绩第一名,中文信息学会钱伟长一等奖等重要成绩和荣誉。LTP提供包括中文分词、词性标注、命名实体识别、依存句法分析、语义角色标注等丰富、高效、精准的自然语言处理技术。目前,LTP 已经被 500 多家国内外研究机构和企业使用,多家大企业和科研机构付费使用。

    LTP官方地址:http://ltp.ai/

    要想用LTP实现中文命名实体识别非常简单,首选,只需要一行指令便可自动安装部署LTP:

    pip install ltp

    接着,需要加载模型:

    from ltp import LTltp = LTP() # 默认加载 Small 模型

    这时,就可以愉快的使用命名实体识别了:

    from ltp import LTP
    ltp = LTP()
    seg, hidden = ltp.seg(["杨超越在1998年7月31日出生于江苏省盐城市大丰区。"])
    ner = ltp.ner(hidden)tag, start, end = ner[0][0]print(tag,":", "".join(seg[0][start:end + 1]))]

    利用LTP官方提供的在线演示系统:http://ltp.ai/demo.html

    可以通过可视化直接看到对“杨超越在1998年7月31日出生于江苏省盐城市大丰区。”这句话的命名实体识别的效果:

    从上面的结果可以看到,LTP可以识别出“杨超越”为人名,“江苏省”为地名,但是无法识别出例如“1998年”这种时间类型的实体,同时对于“盐城市”和“大丰区”这类地名也无法识别。

     

    2.2 Pyhanlp

    HanLP是一系列模型与算法组成的NLP工具包,目标是普及自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。内部算法经过工业界和学术界考验。目前,基于深度学习的HanLP 2.0正处于alpha测试阶段,未来将实现知识图谱、问答系统、自动摘要、文本语义相似度、指代消解、三元组抽取、实体链接等功能。

    Pyhanlp的github项目官方地址:

    https://github.com/hankcs/pyhanlp

    这里我们介绍的Pyhanlp是HanLP1.x的Python接口,支持自动下载与升级HanLP1.x,兼容py2、py3。安装Pyhanlp的步骤比LTP稍微复杂一步,首先需要安装JDK,官方下载地址为:

    https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html

    需要注意的是,这里需要保证JDK的位数、操作系统位数和Python位数一致。然后设置JAVA_HOME环境变量,接着,就可以根据如下指令安装Pyhanlp了:

    conda-forge jpype1==0.7.0      # (可选)conda安装jpype1更方便pip install pyhanlp

    然后,就可以使用Pyhanlp来进行命名实体识别了,仍然需要通过类似于LTP的流程,先加载模型,再执行命名实体识别:

    import pyhanlp
    text = '杨超越在1998年7月31日出生于江苏省盐城市大丰区。'
    NLPTokenizer = pyhanlp.JClass('com.hankcs.hanlp.tokenizer.NLPTokenizer')
    NER = NLPTokenizer.segment(text)
    print(NER)

    为了和LTP的命名实体识别的效果进行对比,使用Pyhanlp官方提供的在线演示系统:http://hanlp.hankcs.com/

    并针对同样的一个实例“杨超越在1998年7月31日出生于江苏省盐城市大丰区。”,用Pyhanlp对这句话的命名实体识别的效果为:

     

    2.3 BosonNLP

    BosonNLP实体识别引擎基于自主研发的结构化信息抽取算法,F1分数达到81%,相比于StanfordNER高出7个百分点。通过对行业语料的进一步学习,可以达到更高的准确率。

    BosonNLP项目官方地址:http://static.bosonnlp.com/

    BosonNLP 是一个更加商业化的中文语义分析API,其中,对于命名实体识别任务,可以识别时间、地点、人名、组织名、公司名、产品名、职位这几类实体。

    安装这个工具非常简便:

    pip install bosonnlp

    接着,就可以调用这个API进行命名实体识别的任务了:

    from bosonnlp import BosonNLP
    import os
    nlp = BosonNLP(os.environ['BOSON_API_TOKEN'])
    nlp.ner('杨超越在1998年7月31日出生于江苏省盐城市大丰区。', sensitivity=2)

    同样,为了和前两个工具LTP和Pyhanlp进行对比,针对同样的一个实例“杨超越在1998年7月31日出生于江苏省盐城市大丰区。”,使用BosonNLP官方提供的在线演示系统:http://static.bosonnlp.com/demo#overview-ner

    用BosonNLP对这句话的命名实体识别的效果为:

    可以看到,用BosonNLP这个工具,能够同时识别出这句话中的人名,时间和地名,相比前两个系统具有最好的命名实体识别的效果

     

    当然,除了前面介绍的这个三个命名实体识别的工具,还有很多开源的工具可以使用,包括:

     

    3. 关系抽取

    目前,专门面向中文的关系抽取工具并不多,泽宇搜集到的中文关系抽取工具有DeepKE,Jiagu和DeepDive。

    3.1 DeepKE

    DeepKE是浙江大学知识引擎实验室开发的中文关系抽取开源工具,项目官方地址是:

    https://github.com/zjunlp/deepke

    安装DeepKE,首先需要安装一些配置环境所需的依赖包:

    • python >= 3.6
    • torch >= 1.2
    • hydra-core >= 0.11
    • tensorboard >= 2.0
    • matplotlib >= 3.1
    • transformers >= 2.0
    • jieba >= 0.39

    相比于前面介绍的命名实体识别工具,DeepKE的封装性相对没有那么强,目前只能通过源码安装方式:

    git clone https://github.com/zjunlp/deepke.gitcd deepke
    再采用 pip install -r requirements.txt 安装上述的相关依赖包。

    然后,对于我们自己的关系抽取任务,使用DeepKE提供的模型对我们的数据集进行训练,所有的训练数据需要存储在 data/origin 文件夹中。接着,就可以训练关系抽取的模型了,DeepKE提供了7种不同的关系抽取模型,包括:

    • 基于 CNN 的关系抽取模型
    • 基于 BiLSTM 的关系抽取模型
    • 基于 PCNN 的远程监督关系抽取模型
    • 基于 Capsule 的关系抽取模型
    • 基于 Transformer 的关系抽取模型
    • 基于 GCN 的关系抽取模型
    • 基于 BERT 语言预训练模型的关系抽取模型

    可以看到,DeepKE提供的所有模型都是基于深度学习的关系抽取模型,其中包括最新的基于Transformer、GCN和BERT的模型,具体的每种模型的使用可以参考官方提供的文档:

    https://github.com/zjunlp/deepke/wiki

     

    3.2 Jiagu

    Jiagu使用大规模语料训练而成。将提供中文分词、词性标注、命名实体识别、情感分析、知识图谱关系抽取、关键词抽取、文本摘要、新词发现、情感分析、文本聚类等常用自然语言处理功能。

    Jiagu是思知机器人公司开发的一个针对中文关系抽取的开源工具,Jiagu的项目官方地址是:

    https://github.com/ownthink/Jiagu

    安装Jiagu就非常方便,可以直接自动安装:

    pip install -U jiagu

    但是,Jiagu的关系抽取目前仅用于测试的功能,只能使用百科的描述进行测试。使用方法为:

    import jiagu
    text = '姚明1980年9月12日出生于上海市徐汇区,祖籍江苏省苏州市吴江区震泽镇,前中国职业篮球运动员,司职中锋,现任中职联公司董事长兼总经理。'
    knowledge = jiagu.knowledge(text)
    print(knowledge)

     

    3.3 DeepDive

    deepdive是由斯坦福大学InfoLab实验室开发的一个开源知识抽取系统。它通过弱监督学习,从非结构化的文本中抽取结构化的关系数据 。

    斯坦福开源的DeepDive原本只能支持英文的关系抽取,国内最大的知识图谱开放平台OPENKG修改了DeepDive中自然语言处理的model包,使它支持中文,并提供中文使用手册,后续将持续更新一些针对中文的优化。

    支持中文的DeepDive项目的官方地址为:

    http://www.openkg.cn/dataset/cn-deepdive

    希望这几个非常实用的面向中文的命名实体识别和关系抽取工具能够帮助大家实现非结构化知识抽取,快速自动构建知识图谱,并赋能各种融入知识的人工智能项目中。

     

    往期精选:

    速览EMNLP 2020上录取的知识图谱相关论文

    介绍一些知识图谱的实际应用类项目

    已嵌入微信公众号内的小图聊天机器人介绍和使用说明

    知识图谱最新权威综述论文解读:知识表示学习部分

    手把手教你搭建一个中式菜谱知识图谱可视化系统

     

    如果对文章感兴趣欢迎关注知乎专栏人工智能遇上知识图谱“,也欢迎关注同名微信公众号人工智能遇上知识图谱”,让我们一起学习并交流讨论人工智能与知识图谱技术。

                                                          

    展开全文
  • 视觉实体链接概述,它由两部分独立组成,即特征提取模块和视觉实体链接模块。特征提取模块从三种模式中提取特征。 在本文中,我们提出了一个新的框架来实现视觉场景理解中的视觉实体链接。具体地说,我们首先为图像...
  • Hanlp作为一款重要的中文分词工具,在GitHub的用户量已经非常之高,应该可以看得出来大家对于hanlp这款分词工具还是很认可的。本篇继续分享一篇关于hanlp的使用实例即Python调用hanlp进行中文实体识别。 想要在...
  • OpenKG地址:... OpenKG OpenKG(中文开放知识图谱)旨在推动以中文为核心的知识图谱数据的开放、互联及众包,并促进知识图谱算法、工具及平台的开源开放。 点击阅读原文,进入 OpenKG 网站。
  • Limes是一个基于度量空间的实体匹配发现框架,适合于大规模数据链接,编程语言是Java。其整体框架如下图所示: 该整体流程用文字表述为: 给定源数据集S,目标数据集T,阈值 θθθ; 样本选取: 从T中选取样本点E来...
  • 你每次打开客户端(如 Yahoo News 或者 Yahoo Sports),你想优先获得...自动搜寻此类信息的系统被称为「实体名称识别和链接系统(named entity recognition and linking systems)」。它是文本分析中最重要的系统,...
  • 1 问题背景 精准直达的知识问答能力对营造小布“懂知识、更懂你”的形象非常重要。在语音助手场景,经常会出现一词多义或者口语化表达等问题...接下来让我们看看小布和Ograph将如何碰撞,通过实体链接技术解决实体歧义
  • 你每次打开客户端(如 Yahoo News 或者 Yahoo Sports),你想优先获得什么样的文本信息?...自动搜寻此类信息的系统被称为「实体名称识别和链接系统(named entity recognition and linking systems)
  • 每天给你送来NLP技术干货!来自:AI自然语言处理与知识图谱作者:大林一、任务定义 首先,我们要明确实体链接(Entity Linking, EL)是什么,它的目标是什么,这将有助于建立我...
  • 一、什么事命名实体识别 ​ 命名实体识别(Named Entity Recognition,简称NER),又称作“专名识别”,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分:(1)实体...
  • ©Copyright 蕃薯耀 2017年7月15日 ...   import java.util.HashMap; import java.util.Map;... * 向url链接追加参数 * @param url * @param params Map&lt;String, String&gt; *
  • 今天要介绍一款来自斯坦福NLP研究组的高质量的开源自然语言处理工具 Stanford CoreNLP,主要功能有分词、词性标注、命名实体识别、短语结构分析、依存句法分析。相较于市面上较为流行的自然语言处理工具,Stanford ...
  • 使用斯坦福大学的分词器,下载地址http://nlp.stanford.edu/software/segmenter.shtml,从上面链接中下载stanford-segmenter-2014-10-26,解压之后,如下图所示 data目录下有两个gz压缩文件,分别是ctb.gz和pku....
  • 中文分词工具比较

    千次阅读 2020-02-09 14:02:17
    在本篇文章中,将介绍9个常用的分词工具及其对比。 jieba、SnowNLP、北京大学PKUseg、清华大学THULAC、HanLP、FoolNLTK、哈工大LTP、CoreNLP、BaiduLac。 * 部分分词中有可参考的论文。如北大、清华,可引用 文章...
  • 中文 NLP 工具总结

    千次阅读 多人点赞 2019-04-09 11:00:44
    中文 NLP 工具总结 前言: 最近由于实验室研究需要,需要调研一下目前已有的中文 NLP 工具,于是在调研完了之后就写了这篇总结,如果哪里有错误还请指出。 1. Jieba 分词 1.1 简介 官网介绍:“结巴”中文分词:做...
  • NULL 博文链接:https://zhaohaiyang.iteye.com/blog/1483811
  • 中文文本标注工具调研以及BRAT安装使用

    万次阅读 多人点赞 2019-04-16 16:54:51
    原来文本数量少的时候可以手工标注,随着文本数量的增多,需要借助标注工具,调研了目前常用的几种: 更多详细信息请联系https://www.jianshu.com/u/50ba27f06c3d 1,BRAThttps://github.com/nlplab/brat 首先是...
  • 序列标注问题如中文分词、命名实体识别,分类问题如关系识别、情感分析、意图分析等,均需要标注数据进行模型训练。深度学习大行其道的今天,基于深度学习的 NLP 模型更是数据饥渴。本文分享一个中文文本标注工具...
  • 转自:AINLP这篇文章事实上整合了之前文章的相关介绍,同时添加一些其他的Python中文分词相关资源,甚至非Python的中文分词工具,仅供参考。首先介绍之前测试过的8...
  • Maltego是一个情报(OSINT)和图形链接分析工具,用于收集和连接用于调查任务的信息。 矿。 从分散的数据源轻松收集信息。 合并。 自动链接所有信息并将其组合到一张图中。 地图。 直观地探索数据中的关系。 ...
  • 序列标注问题如中文分词、命名实体识别,分类问题如关系识别、情感分析、意图分析等,均需要标注数据进行模型训练。深度学习大行其道的今天,基于深度学习的 NLP 模型更是数据饥渴。  最前沿的 NLP 技术...
  • 对于一个给定的实体链接任务,首先需要使用命名实体识别方法和工具识别文本中的实体,然后对每个实体指称利用候选实体生成技术生成对应候选实体集,最后利用文本信息和知识库的信息消除候选实体的歧义得到相匹配...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,729
精华内容 10,291
关键字:

中文实体链接工具