用微调后的BERT模型做问答任务

1,623 阅读3分钟

为下游任务微调BERT预训练模型 文本分类举了个文本分类任务的微调,这次我们看一下问答任务。

在问答任务中,BERT模型的输入包含一个问题和一个文本段落,其中文本段落可能包含与问题相关的答案。BERT模型需要从文本段落中提取与问题相关的答案。因此,BERT模型的输出是一个包含答案的文本段落,其中答案被标记为特殊的标记。因此,从本质上讲,BERT模型必须能够正确识别和标记文本段落中与问题相关的答案。

要找到包含答案的文本段的起始索引和结束索引,可以使用BERT模型的输出来计算每个标记是答案起始标记和结束标记的概率。

为此,可以使用两个额外的向量,称为起始向量和结束向量。这两个向量的大小与输入文本段的标记数相同。这两个向量中的每个元素都代表着与相应标记相关的概率,即该标记是答案的起始或结束位置。这些概率可以通过将输入文本段的每个标记的表示与起始向量和结束向量进行点积得到。

为了计算这个概率,对于每个标记 ii ,计算标记特征 Ri\boldsymbol{R}_i 和起始向量 S\boldsymbol{S} 之间的点积。然后,将softmax函 数应用于点积 SRi\boldsymbol{S} \cdot \boldsymbol{R}_i ,得到概率。计算公式如下所示。

Pi=eSRijeSRjP_i=\frac{\mathrm{e}^{\boldsymbol{S} \cdot \boldsymbol{R}_i}}{\sum_j \mathrm{e}^{\boldsymbol{S} \cdot \boldsymbol{R}_j}}

接下来,选择其中具有最高概率的标记,并将其索引值作为起始索引。

以同样的方式,计算该段落中每个标记是答案的结束标记的概率。为了计算这个概率,为每个标记 ii 计算标记特征 Rl\boldsymbol{R}_l 和结束向量 E\boldsymbol{E} 之间的点积。然后,将softmax函数应用于点积 ERi\boldsymbol{E} \cdot \boldsymbol{R}_i ,得到概率。计 算公式如下所示。

Pi=eERijeERjP_i=\frac{\mathrm{e}^{\boldsymbol{E} \cdot \boldsymbol{R}_i}}{\sum_j \mathrm{e}^{\boldsymbol{E} \cdot \boldsymbol{R}_j}}

接着,选择其中具有最高概率的标记,并将其索引值作为结束索引。现在,我们就可以使用起始索引和结束索引选择包含答案的文本段了。

!pip install transformers

首先,导入必要的库模块。

import torch
from transformers import BertForQuestionAnswering, BertTokenizer

然后,下载并加载该模型。我们使用的是bert-large-uncased-whole-word-masking-fine-tuned-squad模型。

SQuAD(Stanford Question Answering Dataset)是一个非常流行的机器阅读理解数据集,用于测试模型对自然语言问题的理解和回答能力。BERT-large-uncased-whole-word-masking-fine-tuned-SQuAD 是在 SQuAD 数据集上进行了微调的 BERT 模型。

接下来,下载并加载词元分析器。

model = BertForQuestionAnswering.from_pretrained('bert-large-uncased-whole-word-masking-finetuned-squad')

tokenizer = BertTokenizer.from_pretrained('bert-large-uncased-whole-word-masking-fine-tuned-squad')

对模型输入进行预处理首先,定义BERT的输入。输入的问题和段落文本如下所示。

question = "What is the immune system?"
paragraph = "The immune system is a system of many biological structures and processes within an organism that protects against disease. To function properly, an immune system must detect a wide variety of agents, known as pathogens, from viruses to parasitic worms, and distinguish them from the organism's own healthy tissue."

接着,在问题的开头添加[CLS]标记,在问题和段落的结尾添加[SEP]标记。

question = '[CLS] ' + question + '[SEP]'
paragraph = paragraph + '[SEP]'

然后,标记问题和段落。

question_tokens = tokenizer.tokenize(question)
paragraph_tokens = tokenizer.tokenize(paragraph)

合并问题标记和段落标记,并将其转换为input_ids。

tokens = question_tokens + paragraph_tokens 
input_ids = tokenizer.convert_tokens_to_ids(tokens)

设置segment_ids。对于问题的所有标记,将segment_ids设置为0;对于段落的所有标记,将segment_ids设置为1。

segment_ids = [0] * len(question_tokens)
segment_ids += [1] * len(paragraph_tokens)

把input_ids和segment_ids转换成张量。

input_ids = torch.tensor([input_ids])
segment_ids = torch.tensor([segment_ids])

把input_ids和segment_ids送入模型。模型将返回所有标记的起始分数和结束分数。

output = model(input_ids, token_type_ids = segment_ids)
start_scores, end_scores = output.start_logits, output.end_logits

这时,我们需要选择start_index和end_index,前者是具有最高起始分数的标记的索引,后者是具有最高结束分数的标记的索引。

start_index = torch.argmax(start_scores)
end_index = torch.argmax(end_scores)

然后,打印起始索引和结束索引之间的文本段作为答案。

print(' '.join(tokens[start_index:end_index+1]))

输出

a system of many biological structures and processes within an organism that protects against disease


本文正在参加「金石计划」