Project5总结

基于transformers框架的三元组抽取

1. 背景说明:

信息抽取(Information Extraction, IE)是从自然语言文本中抽取实体Subject、属性Object、关系Predicate/Relation及事件等事实类信息的文本处理技术,是信息检索、智能问答、智能对话等人工智能应用的重要基础,一直受到业界的广泛关注。信息抽取任务涉及命名实体识别、指代消解、关系分类等复杂技术。本次任务的主要内容是:在给定schema集合下,使用Transformers框架,从自然语言文本中抽取出符合schema要求的SPO三元组知识。 并对比了Bert和Roberta模型的效果。

2. 数据分析

2.1 数据介绍

本项目共有两份数据集,一份是 【2019年百度的三元组抽取比赛】提供的百度百科数据集,数据地址如下: 百度下载地址.

2.2 数据统计

第一份数据集中训练样本的简单统计结果如下:

统计spo_numtext_length
count173095173095
mean2.1054.63
std1.5732.05
min15
25%133
50%245
75%268
max25300

共有约17万的训练样本,其中长度最长的文本为300,最短文本为5,平均文本长度为54.6。
文本中平均三元组的数量为2.1,最多三元组有25个。从更详细的统计数据上看,88%的文本中三元组的数量在3个以内。
从三元组的关系频率上看,49类关系的数据分布差异较大,数量最多的“主演类” 与 数量最少的 “修业年限”类,差了3个数量级。
在这里插入图片描述
在这里插入图片描述

# 数据分析代码
import json
from collections import defaultdict, Counter
import matplotlib.pyplot as plt
import pandas as pd
D = json.load(open('./data/train.json', 'r', encoding='utf-8'))
text_list = [l['text'] for l in D]
spu_num = [len(l['spo_list']) for l in D]
df = pd.DataFrame({'text': text_list, 'spo_num': spu_num})
df['text_length'] = df['text'].apply(lambda x : len(x))

# 数据描述
print(df.describe())

spo_num_list = dict(Counter(df['spo_num']))
by_value = sorted(spo_num_list.items(), key=lambda item: item[0], reverse=False)

x = []
y = []
for d in by_value:
    x.append(d[0])
    y.append(d[1])

rects = plt.bar(x = x, height=y, color='blue', label='频数')
plt.ylabel('数量')
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.show()
plt.close()

for spo_dict in D:
    for spo in spo_dict['spo_list']:
        predicate_dict[spo[1]] += 1

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

x = range(len(num_list))
rects = plt.bar(x=label_list, height=num_list, color='blue', label='频数')
plt.ylabel('数量')
plt.xticks(rotation=60)
plt.xlabel('predicate')
plt.title('predicate count')
plt.show()

一份是医疗领域的数据集,样本描述(后续添加)***

3. 数据预处理

常规的数据预处理是使用BIO标注法,对text中的数据进行标注。此处我们采用另外一种半指针半标注的方法对数据进行处理,处理方法如下:

  1. 将text、subject、object中的字符,根据bert提供的字典转换为对应的identity值
  2. 根据subject的identity值,获取subject在text中的开始和结束位置;生成一个2列,len(text)行的全0矩阵,第一列对应subject开始位置置为1,第二列对应subject的结束位置置为1;该矩阵作为subject的label
  3. 每个predicate生成一个2列,len(text)行的全0矩阵;根据object的信息,将对应的predicate的第一列对应object的开始位置置为1,第二列对应object结束位置置为1,该矩阵作为object的label。此时共可以得到49*2个label,对应object在不同predicate下的标注信息**(此处可以进行优化,若subject对应多个object的时候,可随机选取其中一个object;因为49个predicate的分布有交大差异,因此在采样object的时候应该适当考虑占比信息)**
  4. 按照上述步骤处理完成后的数据结构如下所示:
    在这里插入图片描述
# 数据预处理代码
def data_generator(data, batch_size=3):
    batch_input_ids, batch_attention_mask = [], []
    batch_subject_labels, batch_subject_ids, batch_object_labels = [], [], []
    texts = []
    for i, d in enumerate(data):
        text = d['text']
        texts.append(text)
        encoding = tokenizer(text=text)
        input_ids, attention_mask = encoding.input_ids, encoding.attention_mask
        # 整理三元组 {s: [(o, p)]}
        spoes = {}
        for s, p, o in d['spo_list']:
            s_encoding = tokenizer(text=s).input_ids[1:-1]
            o_encoding = tokenizer(text=o).input_ids[1:-1]
            s_idx = search(s_encoding, input_ids)
            o_idx = search(o_encoding, input_ids)
            p = predicate2id[p]
            if s_idx != -1 and o_idx != -1:
                s = (s_idx, s_idx + len(s_encoding) - 1)
                o = (o_idx, o_idx + len(o_encoding) - 1, p)
                if s not in spoes:
                    spoes[s] = []
                spoes[s].append(o)
        if spoes:
            subject_labels = np.zeros((len(input_ids), 2))
            start, end = np.array(list(spoes.keys())).T
            start = np.random.choice(start)
            end = end[end >= start][0]
            subject_ids = (start, end)
            subject_labels[subject_ids[0], 0] = 1
            subject_labels[subject_ids[1], 1] = 1
            # 对应的object标签
            object_labels = np.zeros((len(input_ids), len(predicate2id), 2))
            for o in spoes.get(subject_ids, []):
                object_labels[o[0], o[2], 0] = 1
                object_labels[o[1], o[2], 1] = 1
            # 构建batch
            batch_input_ids.append(input_ids)
            batch_attention_mask.append(attention_mask)
            batch_subject_labels.append(subject_labels)
            batch_subject_ids.append(subject_ids)
            batch_object_labels.append(object_labels)
            if len(batch_subject_labels) == batch_size or i == len(data) - 1:
                batch_input_ids = sequence_padding(batch_input_ids)
                batch_attention_mask = sequence_padding(batch_attention_mask)
                batch_subject_labels = sequence_padding(batch_subject_labels)
                batch_subject_ids = np.array(batch_subject_ids)
                batch_object_labels = sequence_padding(batch_object_labels)
                yield [
                          torch.from_numpy(batch_input_ids).long(), torch.from_numpy(batch_attention_mask).long(),
                          torch.from_numpy(batch_subject_labels), torch.from_numpy(batch_subject_ids),
                          torch.from_numpy(batch_object_labels)
                      ], None
                batch_input_ids, batch_attention_mask = [], []
                batch_subject_labels, batch_subject_ids, batch_object_labels = [], [], []


3 模型介绍

3.1 预训练模型简单介绍

Attention Is All You Need是一篇Google提出的将Attention思想发挥到极致的论文。这篇论文中提出一个全新的模型,叫 Transformer,抛弃了以往深度学习任务里面使用到的 CNN 和 RNN ,目前大热的Bert就是基于Transformer构建的,这个模型广泛应用于NLP领域,例如机器翻译,问答系统,文本摘要和语音识别等等方向。Transformer模型中采用了 encoer-decoder 架构。但其结构相比于Attention更加复杂,论文中encoder层由6个encoder堆叠在一起,decoder层也一样。

BERT的全称为Bidirectional Encoder Representation from Transformers,是一个预训练的语言表征模型。它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练,而是采用新的masked language model(MLM)和 next sentence predicate两种训练策略,生成深度的双向语言表征。BERT论文发表时提及在11个NLP任务中获得了新的state-of-the-art的结果。使用预训练模型,只需要添加一个额外的输出层进行fine-tune,就可以在各种各样的下游任务中取得良好的表现,在这过程中并不需要对BERT进行任务特定的结构修改。

3.2 本文模型架构

本文使用基于Transformers框架,使用半指针半标注的方法。首先根据text的编码预测出subject,然后再根据subject的编码和text的编码一同作为输入,对object进行预测。模型架构图如下所示:
在这里插入图片描述

# 构建模型
from transformers import BertModel, BertPreTrainedModel
import torch.nn as nn
import torch
class SubjectModel(BertPreTrainedModel):
    def __init__(self, config):
        super().__init__(config)
        self.bert = BertModel(config)
        self.dense = nn.Linear(config.hidden_size, 2)

    def forward(self, input_ids, attention_mask=None):
        output = self.bert(input_ids, attention_mask=attention_mask)
        subject_out = self.dense(output[0])
        subject_out = torch.sigmoid(subject_out)
        return output[0], subject_out

class ObjectModel(nn.Module):
    def __init__(self, subject_model):
        super().__init__()
        self.encoder = subject_model
        self.dense_subject_position = nn.Linear(2, 768)
        self.dense_object = nn.Linear(768, 49 * 2)

    def forward(self, input_ids, subject_position, attention_mask=None):
        output, subject_out = self.encoder(input_ids, attention_mask)
        subject_position = self.dense_subject_position(subject_position).unsqueeze(1)
        object_out = output + subject_position
        object_out = self.dense_object(object_out)
        object_out = torch.reshape(object_out, (object_out.shape[0], object_out.shape[1], 49, 2))
        object_out = torch.sigmoid(object_out)
        object_out = torch.pow(object_out, 4)
        return subject_out, object_out
3.3 模型效果说明

模型在训练集上能够达到较好的效果,能够达到99%的准确率。
在验证集上,模型能够达到最好的效果是F1_score = 63%,模型稳定后,F1基本上保持在55%左右。
模型效果并不是很理想,后续需要进行优化。
截取其中的一段训练过程的指标数据:

f1: 0.38861, precision: 0.29000, recall: 0.58883: : 100it [00:03, 27.74it/s]
f1: 0.36563, precision: 0.26835, recall: 0.57353: : 100it [00:03, 30.23it/s]
f1: 0.38623, precision: 0.29452, recall: 0.56087: : 100it [00:03, 28.00it/s]
f1: 0.39352, precision: 0.27526, recall: 0.68996: : 100it [00:03, 27.29it/s]
f1: 0.39803, precision: 0.30175, recall: 0.58454: : 100it [00:03, 28.03it/s]
f1: 0.39851, precision: 0.31195, recall: 0.55155: : 100it [00:03, 27.24it/s]
f1: 0.39259, precision: 0.32515, recall: 0.49533: : 100it [00:03, 27.57it/s]
f1: 0.45570, precision: 0.36986, recall: 0.59341: : 100it [00:03, 29.89it/s]
f1: 0.44291, precision: 0.35854, recall: 0.57919: : 100it [00:03, 27.53it/s]
f1: 0.47002, precision: 0.37859, recall: 0.61966: : 100it [00:03, 30.57it/s]

4 总结与后续优化

4.1 总结:
  • 该项目提供了一种新的标注方法(即半指针-半标注的方式)对spo三元组数据进行标注,而没有采用传统的BIO标注方式。
  • 在构建模型的时候,使用一种同时抽取实体和关系的模型,提升了模型效率。先根据text的embedding信息预测subject,然后再将subject的预测结果结合text的embedding,共同作为预测object的数据数据。
4.2 后续优化
  • 数据处理部分:训练阶段,若数据集存在多个subject,则随机选取其中一个subject;而验证集上需要预测所有的subejct,因此训练集与验证集的数据分布不一致。后续需要对数据标注部分进行优化,使数据更加合理。
  • 数据采样部分:三元组类别分布差异非常大,从而导致小类别的预测效果非常差,应该选择一种合适的抽样方法,在训练阶段缩小不同类别样本的分布差异。
  • 模型部分:在预测subject和object的时候,将subject和object的开始和结束位置分别设置了label,人为增加了模型的复杂度;该方法是否有助于模型的学习,暂时还不太确定,后续需要对模型进行更详细的分析。
  • 超参数设置:模型中存在多个超参数,如subject_loss和object_loss各自权重应该如何设置;subject预测概率和object的预测概率的阈值设置等。这些参数是否可以通过学习的方式,让模型自动学习。
  • 这里transformers模型只使用了最基础的bert模型,后续可以尝试bert的优化模型,如roberta等。
  • 后续增加在自己的数据集(医疗数据集提取三元组)上的验证。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值