目录
1)通过AutoModelForMultipleChoice定义后如何查看模型定义
1 多项选择任务介绍
不同于之前的机器阅读理解任务是“要从文本中总结答案”,多项选择任务的本质是“给定一个或多个文档,以及一个问题Q和对应的多个答案候选项,要求从候选项中输出一个答案”。
例子:
不再需要像机器阅读理解任务一样定义起始位置和结束位置,本质是一个分类任务。
2 基于Transfromers的解决方案
2.1 数据预处理:
为了适应 BERT 的结构,每个问题 和 每个选项 会被拼接在一起,作为一个序列输入。
这里,[CLS]
是 BERT 特殊的标记符,表示序列开始;[SEP]
是分隔符,用来区分问题和选项。
数据处理后我们得到的形状是 (batch_size, num_choices, sequence_length)
,表示每个问题有 num_choices
个选项,每个选项的输入序列长度为 sequence_length
。
2.2 模型结构:
1)通过AutoModelForMultipleChoice定义后如何查看模型定义
可以看到模型名称为BertForMultipleChoice。
方式一:直接导入模型,然后Crtl点击BertForMultipleChoice查看即可
方式二:我们只需要Crtl点击AutoModelForMultipleChoice,然后在上方路径中选择models\bert\modeling_bert.py即可查看源码
2)初始化模型__init__
- BERT主干模型:
self.bert = BertModel(config)
实例化了一个BERT模型,这部分负责处理输入的文本序列,生成上下文相关的词嵌入。- Dropout层:
self.dropout
用于防止模型过拟合,随机丢弃部分神经元,具体的丢弃概率通过classifier_dropout
或hidden_dropout_prob
决定。- 分类头部:
self.classifier = nn.Linear(config.hidden_size, 1)
添加了一个线性分类器,它的输入是BERT模型的输出(hidden states
),输出是每个选项的分数(logits),用于多项选择分类任务。由于任务是多选项分类,这个分类器输出一个值,表示当前选项被选择的概率。
3)前向传播过程 (forward
)
输入数据的处理:在多项选择任务中,输入的数据格式是
(batch_size, num_choices, sequence_length)
,其中num_choices
是选项的数量。模型需要将这种格式转化为 BERT 能处理的形状,即(batch_size * num_choices, sequence_length)
。这通过input_ids.view(-1, input_ids.size(-1))
来实现,同样的操作也适用于attention_mask
,token_type_ids
等。
BERT模型处理
- 将处理好的
input_ids
,attention_mask
,token_type_ids
等输入传给self.bert()
方法。- BERT模型返回的是
outputs
,其中包含了多个输出值。outputs[1]
通常是池化后的最后一层隐藏状态(pooled_output
),表示整个序列的表示。分类头部处理
- 池化层输出:将
pooled_output
通过 Dropout 层,然后传给线性分类器。- 生成logits:线性分类器输出 logits,表示每个选项的得分。最后通过
logits.view(-1, num_choices)
将输出形状调整为(batch_size, num_choices)
,方便后续计算损失。损失计算
- 如果
labels
(正确答案标签)存在,则计算交叉熵损失(CrossEntropyLoss()
),损失值用于训练过程中的反向传播。
2.3 数据集clue_C3
clue/clue · Datasets at Hugging Face
有的choice是4个,有的不足4个,不足4个的要填充一下
2.4 代码实战演练
1)导包
这块因为我们要对数据进行手动padding处理,所以就不需要导入DataCollator。
- 使用
DataCollator
:当你的输入数据长度不一致或需要批量化处理时,特别是在多项选择任务中,DataCollatorForMultipleChoice
可以帮助你处理动态填充和数据的批量化。- 不使用
DataCollator
:如果你的数据已经经过填充且长度一致,可以直接将数据传递给模型。
2)加载数据集
简单查看一下,发现context是个列表形式,所以后续还要对其做拼接处理,至于test的answer是空的,所以需要pop出去
3)数据预处理
加载Tokenizer:
tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-macbert-base")
tokenizer
数据处理:
这块要做的内容就是将原始的数据(包含["context", "quesiton", "choice", "answer"]的数据集)处理为符合Bert模型输入的样式。
按照我们之前提到过的Context+Question+Choice的方式,我们需要对其进行处理,加入examples包含1000条数据样本,我们对其处理先是得到了(4000,256)的shape,为了将其转换为符合Bert模型输入的形状(batch,choices,max_length),还需要对其进行转换得到(1000,4,256)的shape,至于其后续的(batch*choices,max_length)的shape是在模型内部完成的,我们就无需进行考虑了。
def process_function(examples):
# examples, dict, keys: ["context", "quesiton", "choice", "answer"]
# examples, 1000
context = []
question_choice = []
labels = []
for idx in range(len(examples["context"])):
ctx = "\n".join(examples["context"][idx])
question = examples["question"][idx]
choices = examples["choice"][idx]
for choice in choices:
context.append(ctx)
question_choice.append(question + " " + choice)
if len(choices) < 4:
for _ in range(4 - len(choices)):
context.append(ctx)
question_choice.append(question + " " + "不知道")
labels.append(choices.index(examples["answer"][idx]))
tokenized_examples = tokenizer(context, question_choice, truncation="only_first", max_length=256, padding="max_length") # input_ids: 4000 * 256,
tokenized_examples = {k: [v[i: i + 4] for i in range(0, len(v), 4)] for k, v in tokenized_examples.items()} # 1000 * 4 *256
tokenized_examples["labels"] = labels
return tokenized_examples
取前10条数据查看处理效果:
处理整体:
4)创建模型
5)创建评估函数
以准确率作为评估指标即可
6)配置训练参数
7)创建训练器
8)模型训练
9)模型预测
MultipleChoice 没有Pipeline来进行模型预测的,因此需要手写一个
from typing import Any
import torch
class MultipleChoicePipeline:
def __init__(self, model, tokenizer) -> None:
self.model = model
self.tokenizer = tokenizer
self.device = model.device
def preprocess(self, context, quesiton, choices):
cs, qcs = [], []
for choice in choices:
cs.append(context)
qcs.append(quesiton + " " + choice)
return tokenizer(cs, qcs, truncation="only_first", max_length=256, return_tensors="pt")
def predict(self, inputs):
inputs = {k: v.unsqueeze(0).to(self.device) for k, v in inputs.items()}
return self.model(**inputs).logits
def postprocess(self, logits, choices):
predition = torch.argmax(logits, dim=-1).cpu().item()
return choices[predition]
def __call__(self, context, question, choices) -> Any:
inputs = self.preprocess(context, question, choices)
logits = self.predict(inputs)
result = self.postprocess(logits, choices)
return result
如果要涉及到多选等任务类型,就需要自己写决策的逻辑,需要改模型,一个是数据处理的标签,你要把正确的都放进去,另外就是Forward里面loss部分,具体选择什么loss需要确定下。