一起学Hugging Face Transformers(10)- 使用Transformers 库的 Trainer API 进行模型微调


前言

在自然语言处理(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 数据集为例,展示了从数据准备、模型选择、数据预处理到模型微调和评估的全过程。这种方法不仅简化了微调过程,还提高了训练效率和效果,适用于各种自然语言处理任务。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值