NLP transformer抽取式问答项目详解

文章介绍了自然语言处理中的多种任务,如句子分类、词级分类和文本生成,并重点讨论了基于HuggingFaceTransformers的问答系统。它区分了抽取式和生成式问答,前者适用于事实性问题,后者用于开放式问题。文章详细阐述了数据预处理过程,包括处理过长输入的方法,以及如何确定答案的token范围。此外,还提到了模型的构建和预测阶段,以及如何从模型输出中提取最终的答案文本。

Introduction

在自然语言处理(Natural Language Processing)中,任务很多种,大体可以分为以下几种:

  1. 句子级别分类任务,例如情感分类任务,检测电子邮件是否为垃圾邮件任务等;
  2. 单词级别的分类任务,例如命名实体识别(Named Entity Recognition, NER),词性标注(Part-of-Speech tagging, POS);
  3. 文本生成任务,包括根据提示prompt生成内容,以及完形填空等;
  4. 抽取式问答任务,给定一个问题和上下文,根据上下文中提供的信息提取问题的答案;
  5. 基于给定句子生成新句子任务,例如翻译任务,summary任务;

而基于HuggingFace Transformer的问答形式分为两种:

抽取式问答: 采用纯Encoder架构(例如BERT),适用于处理事实性问题,例如“谁发明了Transformer架构?”,这些问题的答案通常包含在上下文中;
生成式问答: 采用Encoder-Decoder架构(例如T5、BART),适用于处理开放式问题,例如“天空为什么是蓝色的?”,这些问题的答案通常结合上下文语义再进行抽象表达;

注:项目代码在脚注里的超链接中均可找到,有包含PretrainedModelForQuestionAnswering + Trainer的代码,也有Bert + 自定义模型的代码

Dataset

网上相关的公开数据集有很多,中文的数据集有CMRC2018等,英文的数据集有SQUAD等,数据集形式如下:

id: 用于标记文本唯一性
title: 文本所属的标题
context: 上下文句
question: 问题句
answer: 答案,其中包括:
	answer_text: 答案文本内容
	answer_start: 答案在context中的起始下标(char级别)

Preprocess train data

对于输入数据,采用的是“问题 + 上下文”对的输入形式,两端和中间用特殊符号进行连接,例如:

[CLS]question[SEP]context[SEP]

处理train数据集的最终目标是根据给定的answer text和answer start返回answer token span,并把start和end token级别的下标分别保存在start_positions以及end_positions中。

一般模型可接受的最长输入为384,即question和context加起来最长不能超过384,这就会出现一个问题,即:如果长度超过最长长度怎么办?方法是在做tokenize的时候多加两个参数:truncation="only_second"和return_overflowing_tokens=True,完整的tokenize的函数如下:

batch_inputs = tokenizer(
    batch_question,
    batch_context,
    max_length=384,
    padding='max_length',
    truncation="only_second",
    stride=128,
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
    return_tensors="pt"
)

若输入的句子过长,例如question = 100,context = 500,则切完之后变为(其中stride表明每两句sample之间重合的token有多少个):

  1. 100 + [0:284],
  2. 100 + [156:440],
  3. 100 + [312:596] (最后96个token是加了padding的)

其中return_overflowing_tokens会多返回一个键值对,即overflow_to_sample_mapping,该key表明当前第[j]个sample是来自原句的第[i]句example,

例如overflow_to_sample_mapping = [0,0,1,2,3,3],即第[0]和[1]子句是来自原句的第[0]句,第[2]子句是来自原句第[1]句,第[3]子句是原句第[2]句,第[4]和[5]子句是来自原句的第[3]句,这么处理了之后会产生一个问题,原来的句子答案的下标answer_start已经不准了,不准在于两点:

  1. 当把context拼到question后面时,原来的answer_start位置就不对了,需要加上len(question)的长度;
  2. 若把一句example切成两句sample后,答案只可能出现在其中一个sample里了,另外一句中就没有答案了;

针对以上两点问题,我们要做以下处理,在做处理之前,要把question和context以batch的形式放入tokenizer中,这样得到最终的结果的个数要比之前多,因为有一些example被切成了更多的sample了,同时,若某一句的offset_mapping被切开了,则第二句是接续编号的,例如,

offset_1 [(0,0),(0,3),(3,5),(5,10),(10,12),(12,20),(0,0),(0,6),(6,9),(9,13),(13,15),(15,18),(18,20),(20,23),(23,25),(25,30),(30,34),(34,35),(35.38),(38.40)]
offset_2 [(0,0),(0,3),(3,5),(5,10),(10,12),(12,20),(0,0),(40,42),(42,45),(45,50),(50,51),(51,54),(54,60),(60,62),(62,64),(64,68),(68,70),(0,0),(0,0),(0,0),(0,0)]

故针对上面这种原句被切分成多个samples的情况,已知在原句的answer_char_start为[index]的情况,返回answer_token_start和answer_token_end,操作如下:

  1. 根据answer char start以及answer text计算出answer char end的位置,方法为先根据sample_id以及sample mapping找到当前sample属于原句第几句,并拿到对应的answer,根据answer char start以及answer text计算出answer char end;
sample_mapping = batch_inputs.pop("overflow_to_sample_mapping")
example_id = sample_mapping[sample_idx]
answer_char_start = batch_answers[example_id]['answer_start'][0]
answer_char_end = answer_c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值