前言
在自然语言处理(NLP)领域,预训练模型如 BERT、GPT 等已经展示了其强大的能力。然而,实际应用中,预训练模型往往需要进一步微调(Fine-tuning)以适应具体任务。Hugging Face Transformers 库提供了强大的 Trainer API,使得模型微调变得简单高效。本文将详细介绍如何使用 Trainer API 对模型进行微调。
二、 环境准备
首先,确保你已经安装了 Hugging Face Transformers 库和其他必要的依赖:
pip install transformers datasets
此外,我们还需要安装 PyTorch 或 TensorFlow。本教程使用 PyTorch:
pip install torch
三、 数据准备
Hugging Face 提供了丰富的数据集接口。我们将使用 datasets
库加载一个示例数据集。这里以 SQuAD 数据集为例:
from datasets import load_dataset
dataset = load_dataset("squad")
train_dataset = dataset["train"]
eval_dataset = dataset["validation"]
四、 模型选择与加载
接下来,我们选择并加载一个预训练模型和对应的分词器。这里我们选择 distilbert-base-uncased
:
from transformers import AutoTokenizer, AutoModelForQuestionAnswering
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForQuestionAnswering.from_pretrained(model_name)
五、 数据预处理
1. 代码
我们需要对数据进行预处理,将问题和上下文编码成模型可以接受的输入格式:
def preprocess_function(examples):
questions = [q.strip() for q in examples["question"]]
inputs = tokenizer(
questions,
examples["context"],
max_length=384,
truncation="only_second",
stride=128,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
sample_mapping = inputs.pop("overflow_to_sample_mapping")
offset_mapping = inputs.pop("offset_mapping")
start_positions = []
end_positions = []
for i, offset in enumerate(offset_mapping):
input_ids = inputs["input_ids"][i]
cls_index = input_ids.index(tokenizer.cls_token_id)
sequence_ids = inputs.sequence_ids(i)
sample_index = sample_mapping[i]
answers = examples["answers"][sample_index]
if len(answers["answer_start"]) == 0:
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
start_char = answers["answer_start"][0]
end_char = start_char + len(answers["text"][0])
token_start_index = 0
while sequence_ids[token_start_index] != 1:
token_start_index += 1
token_end_index = len(input_ids) - 1
while sequence_ids[token_end_index] != 1:
token_end_index -= 1
if not (offset[token_start_index][0] <= start_char and offset[token_end_index][1] >= end_char):
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
while token_start_index < len(offset) and offset[token_start_index][0] <= start_char:
token_start_index += 1
start_positions.append(token_start_index - 1)
while offset[token_end_index][1] >= end_char:
token_end_index -= 1
end_positions.append(token_end_index + 1)
inputs["start_positions"] = start_positions
inputs["end_positions"] = end_positions
return inputs
train_dataset = train_dataset.map(preprocess_function, batched=True, remove_columns=dataset["train"].column_names)
eval_dataset = eval_dataset.map(preprocess_function, batched=True, remove_columns=dataset["validation"].column_names)
2. 详细解析
这段预处理函数 preprocess_function
是用于将原始的问答数据转换为模型可以直接使用的格式。主要过程包括对问题和上下文的处理、生成输入张量(input tensors),以及计算答案在输入序列中的起始和结束位置。
1) 清理问题文本:
python questions = [q.strip() for q in examples["question"]]
这行代码对问题文本进行清理,移除每个问题开头和结尾的空格。
2) 使用 tokenizer 对问题和上下文进行编码:
python inputs = tokenizer( questions, examples["context"], max_length=384, truncation="only_second", stride=128, return_overflowing_tokens=True, return_offsets_mapping=True, padding="max_length", )
- questions
:清理后的问题列表。
- examples["context"]
:对应问题的上下文列表。
- max_length=384
:设置最大序列长度。
- truncation="only_second"
:只对第二段(上下文)进行截断。
- stride=128
:在进行截断时使用的步幅,确保上下文有重叠部分以捕获完整信息。
- return_overflowing_tokens=True
:返回溢出的 tokens,用于处理上下文超出 max_length
的情况。
- return_offsets_mapping=True
:返回每个 token 的字符偏移量,用于后续计算答案位置。
- padding="max_length"
:填充到最大长度。
3) 提取溢出映射和偏移量映射:
python sample_mapping = inputs.pop("overflow_to_sample_mapping") offset_mapping = inputs.pop("offset_mapping")
4) 初始化答案位置列表:
python start_positions = [] end_positions = []
5) 计算答案的起始和结束位置:
```python
for i, offset in enumerate(offset_mapping):
input_ids = inputs[“input_ids”][i]
cls_index = input_ids.index(tokenizer.cls_token_id)
sequence_ids = inputs.sequence_ids(i)
sample_index = sample_mapping[i]
answers = examples["answers"][sample_index]
if len(answers["answer_start"]) == 0:
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
start_char = answers["answer_start"][0]
end_char = start_char + len(answers["text"][0])
token_start_index = 0
while sequence_ids[token_start_index] != 1:
token_start_index += 1
token_end_index = len(input_ids) - 1
while sequence_ids[token_end_index] != 1:
token_end_index -= 1
if not (offset[token_start_index][0] <= start_char and offset[token_end_index][1] >= end_char):
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
while token_start_index < len(offset) and offset[token_start_index][0] <= start_char:
token_start_index += 1
start_positions.append(token_start_index - 1)
while offset[token_end_index][1] >= end_char:
token_end_index -= 1
end_positions.append(token_end_index + 1)
```
- `input_ids`:当前样本的输入 ID。
- `cls_index`:CLS token 的位置。
- `sequence_ids`:序列 ID,用于区分问题和上下文。
- `sample_index`:样本索引,对应的答案数据。
- 如果答案为空(没有找到答案),将 `start_positions` 和 `end_positions` 设为 CLS token 的位置。
- 否则,根据答案的字符起始和结束位置,找到对应的 token 索引。
- `token_start_index` 和 `token_end_index`:找到答案在上下文中的 token 起始和结束位置。
- 检查答案是否在当前 token 范围内,如果不在,将位置设为 CLS token。
- 如果在范围内,调整起始和结束位置以确保包含答案。
6) 将答案位置添加到 inputs:
python inputs["start_positions"] = start_positions inputs["end_positions"] = end_positions
7) 应用预处理函数到数据集:
python train_dataset = train_dataset.map(preprocess_function, batched=True, remove_columns=dataset["train"].column_names) eval_dataset = eval_dataset.map(preprocess_function, batched=True, remove_columns=dataset["validation"].column_names)
通过 `map` 函数将预处理函数应用于训练和验证数据集,并删除原始列以保留处理后的数据。
3. 小结
这段代码的预处理函数将原始的问答数据集转换为模型可以接受的输入格式,并计算答案在输入序列中的起始和结束位置。这样可以确保在训练时,模型能够正确地学习如何从上下文中找到答案。
六、 微调模型
现在,我们可以使用 Trainer API 进行模型微调。我们需要定义训练参数,并创建 Trainer 实例:
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=3,
weight_decay=0.01,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
tokenizer=tokenizer,
)
开始训练:
trainer.train()
七、 模型评估
训练完成后,我们可以对模型进行评估:
trainer.evaluate()
八、 保存模型
最后,我们可以保存微调后的模型:
model.save_pretrained("./finetuned_model")
tokenizer.save_pretrained("./finetuned_model")
总结
通过 Hugging Face Transformers 库的 Trainer API,我们可以方便快捷地对预训练模型进行微调。本文以 SQuAD 数据集为例,展示了从数据准备、模型选择、数据预处理到模型微调和评估的全过程。这种方法不仅简化了微调过程,还提高了训练效率和效果,适用于各种自然语言处理任务。