信息抽取实战:三元组抽取(限定领域 vs 开放领域)(附代码)

信息抽取实战:三元组抽取

一、限定领域的三元组抽取

  本项目将会介绍在2019语言与智能技术竞赛三元组抽取比赛方面的一次尝试。由于该比赛早已结束,当时也没有参加这个比赛,因此没有测评成绩,我们也只能拿到训练集和验证集。但是,这并不耽误我们在这方面做实验。

比赛介绍

  该比赛的网址为:http://lic2019.ccf.org.cn/kg,给定schema约束集合及句子sent,其中schema定义了关系P以及其对应的主体S和客体O的类别,例如(S_TYPE:人物,P:妻子,O_TYPE:人物)、(S_TYPE:公司,P:创始人,O_TYPE:人物)等。比如下面的例子:

{
  "text": "九玄珠是在纵横中文网连载的一部小说,作者是龙马",
  "spo_list": [
    ["九玄珠", "连载网站", "纵横中文网"],
    ["九玄珠", "作者", "龙马"]
  ]
}

  该比赛一共提供了20多万标注质量很高的三元组,其中17万训练集,2万验证集和2万测试集,实体关系(schema)50个
  在具体介绍的思路和实战前,先介绍下本次任务的处理思路
在这里插入图片描述

  首先是对拿到的数据进行数据分析,包括统计每个句子的长度及三元组数量,每种关系的数量分布情况。接着,对数据单独走序列标注模型和关系分析模型。最后在提取三元组的时候,用Pipeline模型,先用序列标注模型预测句子中的实体,再对实体(加上句子)走关系分类模型,预测实体的关系,最后形成有效的三元组。
  接下来将逐一介绍,项目结构图如下:
在这里插入图片描述

数据分析

  我们对训练集做数据分析,训练集数据文件为train_data.json
  数据分析会统计训练集中每个句子的长度及三元组数量,还有关系的分布图,代码如下:

# -*- coding: utf-8 -*-
import json
from pprint import pprint
import pandas as pd
from collections import defaultdict
import matplotlib.pyplot as plt

plt.figure(figsize=(18, 8), dpi=100)   # 输出图片大小为1800*800
# # Mac系统设置中文字体支持
# plt.rcParams["font.family"] = 'Arial Unicode MS'
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 加载数据集
def load_data(filename):
    D = []
    with open(filename, 'r', encoding='utf-8') as f:
        content = f.readlines()

    content = [_.replace(' ', '').replace('\u3000', '').replace('\xa0', '').replace('\u2003', '') for _ in content]

    for l in content:
        l = json.loads(l)
        D.append({
            'text': l['text'],
            'spo_list': [
                (spo['subject'], spo['predicate'], spo['object'])
                for spo in l['spo_list']
            ]
        })
    return D

filename = '../data/train_data.json'

D = load_data(filename=filename)
pprint(D)

# 创建text, text_length, spo_num的DataFrame
text_list = [_["text"] for _ in D]
spo_num = [len(_["spo_list"])for _ in D]

df = pd.DataFrame({"text": text_list, "spo_num": spo_num} )
df["text_length"] = df["text"].apply(lambda x: len(x))
print(df.head())
print(df.describe())

# 绘制spo_num的条形统计图
pprint(df['spo_num'].value_counts())
label_list = list(df['spo_num'].value_counts().index)
num_list = df['spo_num'].value_counts().tolist()

# 利用Matplotlib模块绘制条形图
x = range(len(num_list))
rects = plt.bar(x=x, height=num_list, width=0.6, color='blue', label="频数")
plt.ylim(0, 80000) # y轴范围
plt.ylabel("数量")
plt.xticks([index + 0.1 for index in x], label_list)
plt.xlabel("三元组数量")
plt.title("三元组频数统计图")

# 条形图的文字说明
for rect in rects:
    height = rect.get_height()
    plt.text(rect.get_x() + rect.get_width() / 2, height+1, str(height), ha="center", va="bottom")

plt.savefig('./spo_num_bar_chart.png')
plt.show()

plt.close()

# 关系统计图
plt.figure(figsize=(18, 8), dpi=100)   # 输出图片大小为1800*800
# # Mac系统设置中文字体支持
# plt.rcParams["font.family"] = 'Arial Unicode MS'
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 关系统计图
relation_dict = defaultdict(int)

for spo_dict in D:
    # print(spo_dict["spo_list"])
    for spo in spo_dict["spo_list"]:
        relation_dict[spo[1]] += 1

label_list = list(relation_dict.keys())
num_list = list(relation_dict.values())

# 利用Matplotlib模块绘制条形图
x = range(len(num_list))
rects = plt.bar(x=x, height=num_list, width=0.6, color='blue', label="频数")
# plt.ylim(0, 80000) # y轴范围
plt.ylabel("数量")
plt.xticks([index + 0.1 for index in x], label_list)
plt.xticks(rotation=45) # x轴的标签旋转45度
plt.xlabel("三元组关系")
plt.title("三元组关系频数统计图")

# 条形图的文字说明
for rect in rects:
    height = rect.get_height()
    plt.text(rect.get_x() + rect.get_width() / 2, height+1, str(height), ha="center", va="bottom")

plt.savefig('./relation_bar_chart.png')
plt.show()

  输出结果如下:
在这里插入图片描述
在这里插入图片描述

  句子的平均长度为54,最大长度为300;每句话中的三元组数量的平均值为2.1,最大值为25
  每句话中的三元组数量的分布图如下:
在这里插入图片描述

  关系数量的分布图如下:
在这里插入图片描述

序列标注模型

  我们将句子中的主体和客体作为实体,分别标注为SUBJ和OBJ,标注体系采用BIO。一个简单的标注例子如下:

如 O
何 O
演 O
好 O
自 O
己 O
的 O
角 O
色 O
, O
请 O
读 O
《 O
演 O
员 O
自 O
我 O
修 O
养 O
》 O
《 O
喜 B-SUBJ
剧 I-SUBJ
之 I-SUBJ
王 I-SUBJ
》 O
周 B-OBJ
星 I-OBJ
驰 I-OBJ
崛 O
起 O
于 O
穷 O
困 O
潦 O
倒 O
之 O
中 O
的 O
独 O
门 O
秘 O
笈 O

  序列标注的模型采用ALBERT+Bi-LSTM+CRF结构图如下:
在这里插入图片描述

  模型方面的代码不再具体给出,有兴趣的同学可以参考文章信息抽取实战:命名实体识别NER【ALBERT+Bi-LSTM模型 vs. ALBERT+Bi-LSTM+CRF模型】(附代码),也可以参考文章最后给出的Github项目网址。
  模型设置文本最大长度为128,利用ALBERT做特征提取,在自己的电脑上用CPU训练5个epoch,结果如下:


Train on 173109 samples, validate on 21639 samples
Epoch 1/10
173109/173109 [==============================] - 422s 2ms/step - loss: 0.4460 - crf_viterbi_accuracy: 0.8710 - val_loss: 0.1613 - val_crf_viterbi_accuracy: 0.9235
Epoch 2/10
173109/173109 [==============================] - 417s 2ms/step - loss: 0.1170 - crf_viterbi_accuracy: 0.9496 - val_loss: 0.0885 - val_crf_viterbi_accuracy: 0.9592
Epoch 3/10
173109/173109 [==============================] - 417s 2ms/step - loss: 0.0758 - crf_viterbi_accuracy: 0.9602 - val_loss: 0.0653 - val_crf_viterbi_accuracy: 0.9638
Epoch 4/10
173109/173109 [==============================] - 415s 2ms/step - loss: 0.0586 - crf_viterbi_accuracy: 0.9645 - val_loss: 0.0544 - val_crf_viterbi_accuracy: 0.9651
Epoch 5/10
173109/173109 [==============================] - 422s 2ms/step - loss: 0.0488 - crf_viterbi_accuracy: 0.9663 - val_loss: 0.0464 - val_crf_viterbi_accuracy: 0.9654
Epoch 6/10
173109/173109 [==============================] - 423s 2ms/step - loss: 0.0399 - crf_viterbi_accuracy: 0.9677 - val_loss: 0.0375 - val_crf_viterbi_accuracy: 0.9660
Epoch 7/10
173109/173109 [==============================] - 415s 2ms/step - loss: 0.0293 - crf_viterbi_accuracy: 0.9687 - val_loss: 0.0265 - val_crf_viterbi_accuracy: 0.9664
Epoch 8/10
173109/173109 [==============================] - 414s 2ms/step - loss: 0.0174 - crf_viterbi_accuracy: 0.9695 - val_loss: 0.0149 - val_crf_viterbi_accuracy: 0.9671
Epoch 9/10
173109/173109 [==============================] - 422s 2ms/step - loss: 0.0049 - crf_viterbi_accuracy: 0.9703 - val_loss: 0.0036 - val_crf_viterbi_accuracy: 0.9670
Epoch 10/10
173109/173109 [==============================] - 429s 2ms/step - loss: -0.0072 - crf_viterbi_accuracy: 0.9709 - val_loss: -0.0078 - val_crf_viterbi_accuracy: 0.9674
           precision    recall  f1-score   support

      OBJ     0.9593    0.9026    0.9301     44598
     SUBJ     0.9670    0.9238    0.9449     25521

micro avg     0.9621    0.9104    0.9355     70119
macro avg     0.9621    0.9104    0.9355     70119

  利用seqeval模块做评估,在验证集上的F1值约为93.55%
在这里插入图片描述

关系分类模型

  需要对关系做一下说明,因为会对句子(sent)中的主体(S)和客体(O)组合起来,加上句子,形成训练数据。举个例子,在句子历史评价李氏朝鲜的创立并非太祖大王李成桂一人之功﹐其五子李芳远功不可没,三元组为[{“predicate”: “父亲”, “object_type”: “人物”, “subject_type”: “人物”, “object”: “李成桂”, “subject”: “李芳远”}, {“predicate”: “国籍”, “object_type”: “国家”, “subject_type”: “人物”, “object”: “朝鲜”, “subject”: “李成桂”}]},在这句话中主体有李成桂,李芳远,客体有李成桂和朝鲜,关系有父亲(关系类型:2)和国籍(关系类型:22)。按照的思路,这句话应组成4个关系分类样本,如下:

2 李芳远$李成桂$历史评价李氏朝鲜的创立并非太祖大王###一人之功﹐其五子###功不可没
0 李芳远$朝鲜$历史评价李氏##的创立并非太祖大王李成桂一人之功﹐其五子###功不可没
0 李成桂$李成桂$历史评价李氏朝鲜的创立并非太祖大王###一人之功﹐其五子李芳远功不可没
22 李成桂$朝鲜$历史评价李氏##的创立并非太祖大王###一人之功﹐其五子李芳远功不可没

  因此,就会出现关系0(表示“未知”),这样我们在提取三元组的时候就可以略过这条关系,形成真正有用的三元组。
  因此,关系一共为51个(加上未知关系:0)。关系分类模型采用ALBERT+Bi-GRU+ATT,结构图如下:
在这里插入图片描述

  模型方面的代码不再具体给出,有兴趣的同学可以参考文章信息抽取实战:人物关系抽取【BERT模型】(附代码),也可以参考文章最后给出的Github项目网址。
  模型设置文本最大长度为128,利用ALBERT做特征提取,在自己的电脑上用CPU训练30个epoch(实际上,由于有early stopping机制,训练不到30个eopch),在验证集上的评估结果如下:

Epoch 23/30
396766/396766 [==============================] - 776s 2ms/step - loss: 0.1770 - accuracy: 0.9402 - val_loss: 0.2170 - val_accuracy: 0.9308

Epoch 00023: val_accuracy did not improve from 0.93292
49506/49506 [==============================] - 151s 3ms/step

在测试集上的效果: [0.21701653493155634, 0.930776059627533]
    precision    recall  f1-score   support

          未知       0.87      0.76      0.81      5057
          祖籍       0.92      0.73      0.82       181
          父亲       0.79      0.88      0.83       609
        总部地点       0.95      0.95      0.95       310
         出生地       0.94      0.95      0.94      23301.00      1.00      1.00      1271
          面积       0.90      0.92      0.91        79
          简称       0.97      0.99      0.98       138
        上映时间       0.94      0.98      0.96       463
          妻子       0.91      0.83      0.87       680
        所属专辑       0.97      0.97      0.97      1282
        注册资本       1.00      1.00      1.00        63
          首都       0.92      0.96      0.94        47
          导演       0.92      0.94      0.93      26030.96      0.97      0.97       339
          身高       0.98      0.98      0.98       393
        出品公司       0.96      0.96      0.96       851
        修业年限       1.00      1.00      1.00         2
        出生日期       0.99      0.99      0.99      2892
         制片人       0.69      0.88      0.77       127
          母亲       0.75      0.88      0.81       425
          编剧       0.82      0.80      0.81       771
          国籍       0.92      0.92      0.92      1621
          海拔       1.00      1.00      1.00        43
        连载网站       0.98      1.00      0.99      1658
          丈夫       0.84      0.91      0.87       678
          朝代       0.85      0.92      0.88       419
          民族       0.98      0.99      0.99      14340.95      0.99      0.97       197
         出版社       0.98      0.99      0.99      2272
         主持人       0.82      0.86      0.84       200
        专业代码       1.00      1.00      1.00         3
          歌手       0.89      0.94      0.91      2857
          作词       0.85      0.81      0.83       884
          主角       0.86      0.77      0.81        39
         董事长       0.81      0.74      0.78        47
        毕业院校       0.99      0.99      0.99      1433
        占地面积       0.89      0.89      0.89        61
        官方语言       1.00      1.00      1.00        15
        邮政编码       1.00      1.00      1.00         4
        人口数量       1.00      1.00      1.00        45
        所在城市       0.90      0.94      0.92        77
          作者       0.97      0.97      0.97      4359
        成立日期       0.99      0.99      0.99      1608
          作曲       0.78      0.77      0.78       849
          气候       1.00      1.00      1.00       103
          嘉宾       0.76      0.72      0.74       158
          主演       0.94      0.97      0.95      7383
         改编自       0.95      0.82      0.88        71
         创始人       0.86      0.87      0.86        75

    accuracy                           0.93     49506
   macro avg       0.92      0.92      0.92     49506
weighted avg       0.93      0.93      0.93     49506

在这里插入图片描述

三元组提取

  最后一部分,也是本次比赛的最终目标,就是三元组提取
  三元组提取采用Pipeline模式先用序列标注模型预测句子中的实体,然后再用关系分类模型判断实体关系的类别,过滤掉关系为未知的情形,就得到提取的三元组了
  三元组提取的代码如下:

# -*- coding: utf-8 -*-
import os, re, json, traceback

import json
import numpy as np
from keras_contrib.layers import CRF
from keras_contrib.losses import crf_loss
from keras_contrib.metrics import crf_accuracy, crf_viterbi_accuracy
from keras.models import load_model
from collections import defaultdict
from pprint import pprint
from text_classification.att import Attention

from albert_zh.extract_feature import BertVector

# 读取label2id字典
with open("./sequence_labeling/ccks2019_label2id.json", "r", encoding="utf-8") as h:
    label_id_dict = json.loads(h.read())

id_label_dict = {v: k for k, v in label_id_dict.items()}
# 利用ALBERT提取文本特征
bert_model = BertVector(pooling_strategy="NONE", max_seq_len=128)
f = lambda text: bert_model.encode([text])["encodes"][0]

# 载入NER模型
custom_objects = {'CRF': CRF, 'crf_loss': crf_loss, 'crf_viterbi_accuracy': crf_viterbi_accuracy}
ner_model = load_model("./sequence_labeling/ccks2019_ner.h5", custom_objects=custom_objects)

# 载入分类模型
best_model_path = './text_classification/models/per-rel-19-0.9329.h5'
classification_model = load_model(best_model_path, custom_objects={"Attention": Attention})

# 分类与id的对应关系
with open("./data/relation2id.json", "r", encoding="utf-8") as g:
    relation_id_dict = json.loads(g.read())

id_relation_dict = {v: k for k, v in relation_id_dict.items()}


# 从预测的标签列表中获取实体
def get_entity(sent, tags_list):

    entity_dict = defaultdict(list)
    i = 0
    for char, tag in zip(sent, tags_list):
        if 'B-' in tag:
            entity = char
            j = i+1
            entity_type = tag.split('-')[-1]
            while j < min(len(sent), len(tags_list)) and 'I-%s' % entity_type in tags_list[j]:
                entity += sent[j]
                j += 1

            entity_dict[entity_type].append(entity)

        i += 1

    return dict(entity_dict)

class TripleExtract(object):

    def __init__(self, text):
        self.text = text.replace(" ", "")    # 输入句子

    # 获取输入句子中的实体(即:主体和客体)
    def get_entity(self):
        train_x = np.array([f(self. text)])
        y = np.argmax(ner_model.predict(train_x), axis=2)
        y = [id_label_dict[_] for _ in y[0] if _]

        # 输出预测结果
        return get_entity(self.text, y)

    # 对实体做关系判定
    def relation_classify(self):
        entities = self.get_entity()
        subjects = list(set(entities.get("SUBJ", [])))
        objs = list(set(entities.get("OBJ", [])))

        spo_list = []

        for subj in subjects:
            for obj in objs:
                sample = '$'.join([subj, obj, self.text.replace(subj, '#'*len(subj)).replace(obj, "#"*len(obj))])
                vec = bert_model.encode([sample])["encodes"][0]
                x_train = np.array([vec])

                # 模型预测并输出预测结果
                predicted = classification_model.predict(x_train)
                y = np.argmax(predicted[0])

                relation = id_relation_dict[y]
                if relation != "未知":
                    spo_list.append([subj, relation, obj])

        return spo_list

    # 提取三元组
    def extractor(self):

        return self.relation_classify()

  运行三元组提取脚本,代码如下:

# -*- coding: utf-8 -*-
import os, re, json, traceback
from pprint import pprint

from triple_extract.triple_extractor import TripleExtract

text = "真人版的《花木兰》由新西兰导演妮基·卡罗执导,由刘亦菲、甄子丹、郑佩佩、巩俐、李连杰等加盟,几乎是全亚洲阵容。"
# text = "《冒险小王子》作者周艺文先生,教育、文学领域的专家学者以及来自全国各地的出版业从业者参加了此次沙龙,并围绕儿童文学创作这一话题做了精彩的分享与交流。"
# text = "宋应星是江西奉新人,公元1587年生,经历过明朝腐败至灭亡的最后时期。"
# text = "韩愈,字退之,河阳(今河南孟县)人。"
# text = "公开资料显示,李强,男,汉族,出生于1971年12月,北京市人,北京市委党校在职研究生学历,教育学学士学位,1996年11月入党,1993年7月参加工作。"
# text = "杨牧,本名王靖献,早期笔名叶珊,1940年生于台湾花莲,著名诗人、作家。"
# text = "杨广是隋文帝杨坚的第二个儿子。"
# text = "此次权益变动后,何金明与妻子宋琦、其子何浩不再拥有对上市公司的控制权。"
# text = "线上直播发布会中,谭维维首次演绎了新歌《章存仙》,这首歌由钱雷作曲、尹约作词,尹约也在直播现场透过手机镜头跟网友互动聊天。"
# text = "“土木之变”后,造就了明代杰出的民族英雄于谦。"
# text = "真纳大学坐落在伊斯兰堡市,是一所创建于1967年7月的公立研究型大学。"
# text = "黄书豪毕业于泰国博仁大学,并签约北京经纪公司,2018年参加《偶像练习生》,曾在节目中表演洗脑神曲《容易动情的人》,让全场嗨到又唱又跳,节目最终排名第43位。"
# text = "浙江金裕包装有限公司坐落于杭州湾畔金国最大的铝氧化生产基地----江南古镇沥海工业园区,公司西近杭州,紧靠杭州湾世纪大道,距上海100公里余,是一家专业生产氧化铝化妆品配套产品,兼产酒瓶盖、小家电等铝包装产品的制造企业"
# text = "另外,哈尔滨历史博物馆也是全国面积最小的国有博物馆,该场馆面积只有50平方米,可称之“微缩博物馆”。"
# text = "孙杨的妈妈叫杨明,孙杨的名字后面一个字也是来源于她的名字。"
# text = "企查查显示,达鑫电子成立于1998年6月,法定代表人张高圳,注册资本772.33万美元,股东仅新加坡达鑫控股有限公司一名。"


triple_extract = TripleExtract(text)
print("原文: %s" % text)
entities = triple_extract.get_entity()
print("实体: ", end='')
pprint(entities)

spo_list = triple_extract.extractor()
print("三元组: ", end='')
pprint(spo_list)

  我们在网上找几条样本进行测试,测试的结果如下:
在这里插入图片描述

总结

  本项目标题为限定领域的三元组抽取,之所以取名为限定领域,是因为该任务的实体关系是确定,一共为50种关系
  当然,上述方法还存在着诸多不足,参考苏建林的文章基于DGCNN和概率图的轻量级信息抽取模型 ,我们发现不足之处如下:

  • 主体和客体的标注策略有问题,因为句子中有时候主体和客体会重叠在一起;
  • 新引入了一类关系:未知,是否有办法避免引入;
  • 其他(暂时未想到)

  从比赛的角度将,本项目的办法效果未知,应该会比联合模型的效果差一些。但是,这是作为自己的模型,算法是一种尝试,之所以采用这种方法,是因为一开始是从开放领域的三元组抽取入手的,而这种方法方便扩展至开放领域。关于开放领域的三元组抽取,稍后就会写文章介绍。
  本项目的源代码已经公开至Github,网址为:https://github.com/chenlian-zhou/ccks_triple_extract/tree/master/limite_extract_platform

二、开放领域的三元组抽取

  关于三元组抽取的基本介绍和常用办法,之前已经在不少文章中描述过,这里不再过多介绍,有兴趣的读者可以参考文章利用关系抽取构建知识图谱的一次尝试 。本文将会介绍在开放领域做三元组抽取的一次尝试。
  本项目已经开源至Github,文章最后会给出相应的网址。本项目的项目结构如下:
在这里插入图片描述

  本项目一共分为四部分,主要模块介绍如下:

  • extract_example: 利用训练好的模型对基本小说和新闻进行三元组抽取,形成知识图谱例子
  • sequence_labeling训练标注,对标注的实体数据进行序列标注算法训练
  • spo_tagging_platform标注平台,标注subject,predicate和object以及三元组是否有效
  • text_classification文本分类,用于判别抽取的三元组是否有效

  本项目的抽取系统流程图如下:
在这里插入图片描述

标注平台

  用tornado搭建了简易的标注平台,在标注页面中,标注人员需要输入标注的句子(句子级别的抽取)以及subject,predicate,object,点击“显示SPO”,将有效的三元组标注为1,无效的三元组标注为0。之所以采取这种标注方法,是因为我们可以在句子中标注subject,predicate,object,这些标注的实体就会形成可能的三元组组合,再利用0,1来标注这种三元组是否有效,这样就能做到在开放领域进行三元组抽取。
  一个简单的标注例子如下:
在这里插入图片描述

  再对以上的标注结果做一些说明,我们的标注是以句子为单位,进行句子级别的标注,不同要素在标注的时候加 # 区分,标注了两个subject,1个predicate(共用)和2个object,其中predidate是这些subject和object公用的,所以只需要标注一次。这样,点击“显示SPO”,一共会显示4个三元组,s,p,o用#隔开,0,1表示是否是有效三元组,默认为0
  利用空余时间,一共标注了3200多个样本,对于序列标注来说,就是3200多个样本,对于文本分类来说,就是9000多个样本了

序列标注

  对于上述的标注例子,会形成如下的标注序列

美	B-SUBJ
国	I-SUBJ
疾	I-SUBJ
控	I-SUBJ
中	I-SUBJ
心	I-SUBJ
主	B-PRED
任	I-PRED
雷	B-OBJ
德	I-OBJ
菲	I-OBJ
尔	I-OBJ
德	I-OBJ
(	O
左	O
圈	O
)	O
和	O
美	B-SUBJ
国	I-SUBJ
国	I-SUBJ
立	I-SUBJ
卫	I-SUBJ
生	I-SUBJ
研	I-SUBJ
究	I-SUBJ
院	I-SUBJ
过	I-SUBJ
敏	I-SUBJ
和	I-SUBJ
传	I-SUBJ
染	I-SUBJ
病	I-SUBJ
研	I-SUBJ
究	I-SUBJ
所	I-SUBJ
主	B-PRED
任	I-PRED
福	B-OBJ
西	I-OBJ
(	O
右	O
圈	O
)	O

  将数据集分为训练集和测试集,比例为8:2。采用经典的深度学习模型ALBERT+Bi-LSTM+CRF进行实体识别,设置最大文本长度为128,训练100个epoch。关于该模型的介绍,可以参考文章信息抽取实战:命名实体识别NER【ALBERT+Bi-LSTM模型 vs. ALBERT+Bi-LSTM+CRF模型】(附代码)
  在测试集上的训练结果如下:

accuracy:  93.69%; precision:  76.26%; recall:  82.33%; FB1:  79.18

OBJ: precision:  80.47%; recall:  88.81%; FB1:  84.44  927
PRED: precision:  76.89%; recall:  83.69%; FB1:  80.14  1021
SUBJ: precision:  71.72%; recall:  75.32%; FB1:  73.48  983

  在测试集上的总体F1值接近80%

文本分类

  关于文本分类,需要多做一些说明。
  虽然本文的题目是关于在开发领域的三元组抽取的尝试,但实际我在标注的时候,还是更多地标注人物头衔,人物关系,公司与人的关系,影视剧主演、导演信息等。形成的有效的文本分类的样本为9000多个,一共有关系1365个,数量最多的前20个关系,如下图:
在这里插入图片描述

  以上述的标注数据为例,形成的标注数据如下:

美国疾控中心#主任#雷德菲尔德#1#美国疾控中心主任雷德菲尔德(左圈)和美国国立卫生研究院过敏和传染病研究所主任福西(右圈)
美国疾控中心#主任#福西#0#美国疾控中心主任雷德菲尔德(左圈)和美国国立卫生研究院过敏和传染病研究所主任福西(右圈)
美国国立卫生研究院过敏和传染病研究所#主任#雷德菲尔德#0#美国疾控中心主任雷德菲尔德(左圈)和美国国立卫生研究院过敏和传染病研究所主任福西(右圈)
美国国立卫生研究院过敏和传染病研究所#主任#福西#1#美国疾控中心主任雷德菲尔德(左圈)和美国国立卫生研究院过敏和传染病研究所主任福西(右圈)

  在实际模型训练的时候,会将原文中的subject用S*len(subject)代替,predicate用P,object用O
  将数据集分为训练集和测试集,比例为8:2。采用经典的深度学习模型ALBERT+Bi-GRU+ATT+FC,设置文本的最大长度为为128,训练30个epoch,采用early stopping机制,训练过程的loss和acc图像如下:
在这里插入图片描述

  最终在测试集上的accuracy约为96%

新数据进行三元组抽取

  上述的模型训练完毕后,我们就可以将其封装成HTTP服务。对于新输入的句子,我们先利用序列标注模型预测出其中的subject,predicate和object,组合成三元组与句子的拼接,输入到文本分类模型,判别该三元组是否有效,0为无效,1为有效
  从网上找几个例子,预测的结果如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

  extract_example目录中为抽取的效果,包括几本小说和一些新闻上的效果,关于这方面的演示,可以参考另一个项目:https://github.com/percent4/knowledge_graph_demo 。也可以参考文章知识图谱构建举例 https://blog.csdn.net/jclian91/article/details/104685424 中给出的几个知识图谱的建构的例子。

总结

  本文写的过程较为简单,也没有代码,这是因为在之前的文章中做了大量的铺垫,主要是集中在模型方面。况且,这个项目比较大,也不适合在这里详细讲述,只在这里给出思路和大概的处理流程,具体的实现代码可以参考下方的Github地址。
  在实际的抽取过程中,一些句子也存在抽取出大量无用的三元组的情况,导致召回率高,这是因为本项目针对的是开放领域的三元组抽取,因此效果比不会有想象中的那么好,提升抽取效果的办法如下:

  • 增加数据标注量,目前序列标注算法的样本仅3200多个
  • 模型方面:现在是pipeline形式,各自的效果还行,但总体上不如Joint形式好
  • 对于自己想抽的其他三元组的情形,建议增加这方面的标注
  • 文本预测耗时长(该问题已经解决)。

  本项目作为在开放领域的三元组抽取的一次尝试,在此之前关于这方面的文章或者项目还很少,因此可以说是探索阶段。
  源码和数据已经在Github项目中给出,网址为 https://github.com/chenlian-zhou/ccks_triple_extract/tree/master/spo_extract_platform/spo_extract_platform

参考文献

  1. 信息抽取实战:命名实体识别NER【ALBERT+Bi-LSTM模型 vs. ALBERT+Bi-LSTM+CRF模型】(附代码)
  2. 信息抽取实战:人物关系抽取【BERT模型】(附代码)
  3. 基于DGCNN和概率图的轻量级信息抽取模型
  4. 利用关系抽取构建知识图谱的一次尝试: https://www.cnblogs.com/jclian91/p/11107323.html
  5. 《知识图谱 方法、实践与应用》 王昊奋、漆桂林、陈华钧著,中国工信出版集团、电子工业出版社出版。
  • 25
    点赞
  • 220
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
Python中进行三元组信息抽取,可以使用自然语言处理(NLP)工具包,如NLTK(自然语言工具包)或SpaCy。这里以NLTK为例,展示一个简单的三元组信息抽取示例: 首先,确保已经安装了NLTK库,并下载了相应的语料库和模型。可以使用以下代码进行下载: ```python import nltk nltk.download('punkt') nltk.download('averaged_perceptron_tagger') nltk.download('maxent_ne_chunker') nltk.download('words') ``` 接下来,使用NLTK进行句子分词、词性标注和命名实体识别(NER)。然后,从NER结果中提取三元组信息。 ```python import nltk def extract_triplets(sentence): triplets = [] # 句子分词 tokens = nltk.word_tokenize(sentence) # 词性标注 tagged = nltk.pos_tag(tokens) # 命名实体识别 entities = nltk.ne_chunk(tagged) # 提取三元组信息 for subtree in entities.subtrees(filter=lambda t: t.label() == 'PERSON' or t.label() == 'ORGANIZATION'): triplet = [] for leaf in subtree.leaves(): triplet.append(leaf[0]) if len(triplet) == 3: triplets.append(tuple(triplet)) return triplets # 示例句子 sentence = "Steve Jobs founded Apple Inc." # 提取三元组信息 triplets = extract_triplets(sentence) # 打印结果 for triplet in triplets: print(triplet) ``` 上述代码将输出句子中的三元组信息: ``` ('Steve', 'Jobs', 'Apple Inc.') ``` 请注意,这只是一个简单的示例,您可以根据具体需求进行更复杂的三元组信息抽取。此外,还可以尝试使用其他NLP工具包,如SpaCy,以获取更高级和更准确的信息抽取结果。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南有芙蕖

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值