跳至主要内容

FLAN-T5 教程:指南与微调

使用 transformers 库对 FLAN-T5 模型进行问答任务微调的完整指南,并在真实场景中运行优化的推理。
· 15分钟 阅读

使用 AI 探索

什么是 FLAN-T5?

FLAN-T5 是一个开源的序列到序列大型语言模型,也可用于商业用途。该模型由 Google 研究人员于 2022 年底发布,并在多种任务上进行了微调。

T5 模型将多种任务重构为文本到文本的格式,例如翻译、语言可接受性、句子相似度和文档摘要,如下所示:

T5 如何将一系列任务重构为文本到文本框架(来源)

T5 如何将一系列任务重构为文本到文本框架(来源)

类似地,T5 模型的架构与原始 Transformer 论文中采用的编码器-解码器结构高度一致。主要区别在于训练数据的规模和性质;T5 在一个名为 Colossal Clean Crawled Corpus(C4)的 750GB 大规模文本语料上进行训练。

相比之下,原始的 Transformer 专为语言翻译而设计,因此在由 1.4GB 英德句对组成的数据集上训练。

我们的文章《Transformers 与 Hugging Face 入门》可帮助读者理解 Transformer,并利用其能力解决实际问题。

Transformer 模型架构(来源)

Transformer 模型架构(来源)

微调 FLAN-T5 有多重要?

毫无疑问,FLAN-T5 可用于多种自然语言处理任务。

然而,要充分发挥其潜力并确保在特定应用中的最佳表现,微调是关键一步。以下要点突出了微调 FLAN-T5 的重要性:

  • 微调 FLAN-T5 可使模型适应特定任务,并提升其在这些任务上的表现。
  • 微调允许根据用户的需求和数据对模型进行定制。
  • 可以在仅配备 CPU 的本地工作站上微调 FLAN-T5,使其更易于为更广泛的用户所用。
  • 这种可达性对可能无法使用 GPU 资源的小型组织或个人研究者尤为有利。
  • 总体而言,微调 FLAN-T5 是为特定用例优化模型并最大化其潜在收益的重要步骤。

本教程的目标是提供在问答场景下微调 FLAN-T5 的完整指南。

在本教程中,我们将涵盖一系列主题,帮助您理解并实施针对 FLAN-T5 的微调。我们首先讨论此过程所需的库和工具,然后介绍设置微调环境的步骤。

接着,我们将引导您加载 FLAN-T5 模型并准备数据以进行微调。数据就绪后,我们会带您完成训练和微调模型的步骤,使其满足您的具体需求。

最后,我们将探讨微调后 FLAN-T5 的潜在应用,展示如何在各种场景中使用它以获得更好的性能和更准确的结果。

微调后 FLAN-T5 的潜在应用

在深入技术实现之前,让我们先看看微调后 FLAN-T5 的一些潜在应用。下面给出聊天与对话摘要、文本分类以及快速医疗保健互操作性资源(FHIR)等示例。

微调后 FLAN-T5 的三种潜在应用

微调后 FLAN-T5 的三种潜在应用

  • 聊天与对话摘要:FLAN-T5 可对对话进行压缩,快速回顾客服交互或商务会议内容。
  • 文本分类:适用于将文本自动归类到预定义类别,如情感分析、垃圾信息检测或主题建模。
  • FHIR 资源生成:FLAN-T5 可将临床文本转换为结构化的 FHIR(快速医疗保健互操作性资源),便于在医疗系统中共享与集成。

在深入本文核心技术内容之前,我们的教程《如何使用 LangChain 构建 LLM 应用》将带您借助 LangChain(一个用于构建高级 AI 应用的开源 Python 框架)探索大型语言模型的潜力。

先决条件

现在我们已经更好地了解了 FLAN-T5,接下来看看如何针对问答用例进行微调,完整笔记本可在GitHub获取。

首先,需要以下库和工具:

  • Hugging Face:提供访问 FLAN-T5 模型的平台,便于下载并用于微调
  • Transformers:用于简化加载预训练 FLAN-T5 模型的过程,并提供微调所需的实用函数
  • Datasets:可直接使用的数据集集合,是获取微调相关数据的关键
  • Sentencepiece:主要用于处理大型和多语言文本数据的分词工具
  • Tokenizers:用于将文本转换为适合用例格式的分词库
  • Evaluate:该库提供了广泛的模型评估指标,确保微调后的模型达到预期性能标准
  • Rouge score:用于评估大型语言模型生成文本质量的特定指标
  • NLTK:适用于诸如分词与词干提取等数据预处理步骤

库的安装

可以在 Jupyter 笔记本中使用 pip(Python 包管理器)按如下方式安装。

%%bash
pip install nltk
pip install datasets
pip install transformers[torch]
pip install tokenizers
pip install evaluate
pip install rouge_score
pip install sentencepiece
pip install huggingface_hub

在笔记本中使用 %%bash 命令,可以将相应单元作为 bash 脚本运行,而无需逐条执行每个命令。

导入库

安装完成后,我们可以按如下方式导入各个库:

import nltk
import evaluate
import numpy as np
from datasets import load_dataset
from transformers import T5Tokenizer, DataCollatorForSeq2Seq
from transformers import T5ForConditionalGeneration, Seq2SeqTrainingArguments, Seq2SeqTrainer

加载 FLAN-T5 模型

在 Hugging Face 上可以获取多种规格的 FLAN-T5 模型,从小型到超大型。模型越大,参数越多。

下面是来自 Hugging Face 模型卡的不同可用模型尺寸:

FLAN-T5 各变体的参数规模与内存占用

FLAN-T5 各变体的参数规模与内存占用

选择合适的模型尺寸

在各类 FLAN-T5 变体中选择合适的模型尺寸主要取决于以下标准:

  • 项目的具体需求
  • 可用的计算资源
  • 期望的性能水平

本次实验使用 NVIDIA A100 GPU,模型选择 google/flan-t5-base,以在计算效率与性能兼容性之间取得平衡。

模型与分词器初始化

创建模型需要以下三条指令。

# Load the tokenizer, model, and data collator
MODEL_NAME = "google/flan-t5-base"

tokenizer = T5Tokenizer.from_pretrained(MODEL_NAME)
model = T5ForConditionalGeneration.from_pretrained(MODEL_NAME)
data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)
  • 使用 T5Tokenizer 模块与模型名称实例化分词器
  • 使用 T5ForConditionalGeneration 的 from_pretrained 函数加载模型
  • 通过 DataCollectorForSeq2Seq 创建数据收集器,用于问答任务

为微调准备数据

在具备模型、分词器和数据收集器后,下一步是加载用于微调的实验数据,此处使用的是 Hugging Face 上提供的 Yahoo 讨论数据。

数据加载过程很直接,可使用 load_dataset 函数完成,该函数以数据集名称为参数。在此特定案例中,我们关注训练数据。

# Acquire the training data from Hugging Face
DATA_NAME = "yahoo_answers_qa"
yahoo_answers_qa = load_dataset(DATA_NAME)

获取数据后,将其按 70% 与 30% 的比例分别划分为训练集与测试集,这可通过 train_test_split 函数实现。

yahoo_answers_qa = yahoo_answers_qa["train"].train_test_split(test_size=0.3)

下面的语句展示了训练与测试数据的观测总数。

# Check the length of the data and its structure
yahoo_answers_qa

训练与验证数据集的观测总数

训练与验证数据集的观测总数

数据格式化与分词

我们在训练集与测试集中都拥有相当数量的数据可用于微调。但在此之前,需要将数据处理为符合微调需求的格式。

接下来所用的大多数函数灵感来自 Toughdata 的文章。

在推理模式下,调用模型的过程将采用以下格式:

“Please answer this question: <USER_QUESTION>”

其中 <USER_QUESTION> 是用户希望得到回答的问题。为实现该功能,我们需要通过为任务添加字符串 “Please answer this question,” 来格式化训练数据,这可通过下方的 preprocess_function 函数完成。

除了格式化外,该函数还会使用 tokenizer 函数对输入与输出进行分词。

# We prefix our tasks with "answer the question"
prefix = "Please answer this question: "

# Define the preprocessing function

def preprocess_function(examples):
   """Add prefix to the sentences, tokenize the text, and set the labels"""
   # The "inputs" are the tokenized answer:
   inputs = [prefix + doc for doc in examples["question"]]
   model_inputs = tokenizer(inputs, max_length=128, truncation=True)
  
   # The "labels" are the tokenized outputs:
   labels = tokenizer(text_target=examples["answer"], 
                      max_length=512,         
                      truncation=True)

   model_inputs["labels"] = labels["input_ids"]
   return model_inputs

接下来,使用下面的 map 函数将其应用于整个数据集:

# Map the preprocessing function across our dataset
tokenized_dataset = yahoo_answers_qa.map(preprocess_function, batched=True)

FLAN-T5 的训练与微调

在开始训练过程之前,最好先确定用于评估微调整体性能的指标。

在任何深度学习与机器学习项目中,良好的评估指标都至关重要,用于评估模型在训练期间以及后续生产环境中的表现。

评估文本生成模型性能的两个最常见指标是 BLEU 与 ROUGE,在本案例中用它们来将生成的答案与参考答案对比评估质量。

本教程重点关注 ROUGE,而这篇维基百科文章提供了有关 BLEU 分数的更多信息。

什么是 ROUGE 分数?

ROUGE 是 Recall-Oriented Understudy for Gisting Evaluation 的缩写。用于问答的 ROUGE 若干关键组成包括:

  • ROUGE-L:衡量候选答案与参考答案之间的最长公共子序列,侧重对全文的召回。
  • ROUGE-1、ROUGE-2、ROUGE-SU4:比较候选与参考之间的一元、二元、四元重叠,关注关键部分/片段的召回。
  • 更高的 ROUGE 分数通常表示更好的问答性能。接近或高于 0.70+ 的分数被认为表现强劲。
  • 使用该指标时,进行词干提取、去除停用词等处理有助于提升整体表现。

在此基础上,下面的辅助函数 compute_metrics 可用于计算底层 ROUGE 分数。在实现该函数之前,需要设置 ROUGE 与 NLTK。

nltk.download("punkt", quiet=True)
metric = evaluate.load("rouge")

下面是该函数的实现。

def compute_metrics(eval_preds):
   preds, labels = eval_preds

   # decode preds and labels
   labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
   decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
   decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

   # rougeLSum expects newline after each sentence
   decoded_preds = ["\n".join(nltk.sent_tokenize(pred.strip())) for pred in decoded_preds]
   decoded_labels = ["\n".join(nltk.sent_tokenize(label.strip())) for label in decoded_labels]

   result = metric.compute(predictions=decoded_preds, references=decoded_labels, use_stemmer=True)
  
   return result

训练过程

要启动微调,我们需要设置一些超参数,主要包括:

  • 学习率:控制模型从数据中学习的快慢,典型取值为 1e-5 到 5e-5,本用例设置为 3e-4
  • 批大小:在更新模型权重前处理的样本总数。较大的批可以加速训练,但可能导致性能欠佳。本用例使用 8
  • 每设备训练批大小:与批大小类似,但按每个设备(GPU)指定
  • 权重衰减:用于防止模型过拟合。0.01 是可接受的取值
  • 保存总限制:训练过程中保存的检查点总数。保存次数越多,回滚的可能性越高,但占用磁盘也更多。本用例保存 3 次
  • 训练轮数:完整遍历训练数据集的次数。轮数越多,训练时间越长,但也可能提升模型性能。通常选择 3 到 10,此处使用 3

上述参数如下所示,并用于设置模型的训练参数,所有训练产物将保存在 results 文件夹中:

# Global Parameters
L_RATE = 3e-4
BATCH_SIZE = 8
PER_DEVICE_EVAL_BATCH = 4
WEIGHT_DECAY = 0.01
SAVE_TOTAL_LIM = 3
NUM_EPOCHS = 3

# Set up training arguments
training_args = Seq2SeqTrainingArguments(
   output_dir="./results",
   evaluation_strategy="epoch",
   learning_rate=L_RATE,
   per_device_train_batch_size=BATCH_SIZE,
   per_device_eval_batch_size=PER_DEVICE_EVAL_BATCH,
   weight_decay=WEIGHT_DECAY,
   save_total_limit=SAVE_TOTAL_LIM,
   num_train_epochs=NUM_EPOCHS,
   predict_with_generate=True,
   push_to_hub=False
)

接下来,设置训练器以启动模型的训练过程。

trainer = Seq2SeqTrainer(
   model=model,
   args=training_args,
   train_dataset=tokenized_dataset["train"],
   eval_dataset=tokenized_dataset["test"],
   tokenizer=tokenizer,
   data_collator=data_collator,
   compute_metrics=compute_metrics
)

最后,使用 train 函数触发模型训练:

trainer.train()

大约四小时后,每个轮次的训练整体表现如下所示。

训练轮次及其对应的性能分数

训练轮次及其对应的性能分数

让我们来理解上述性能指标面板。

  • 训练损失与验证损失:这两个指标数值越低越好,意味着模型对数据的拟合更好。训练与验证损失在各轮次中均有所下降,第三轮达到最低值。

  • Rouge 指标(Rouge1、Rouge2、Rougel 与 Rougelsum):这些指标数值越高越好,表示更好的文本摘要性能。在四个 ROUGE 指标上,数值均随轮次上升,第三轮达到最高值。


总体来看,模型在第三轮表现最佳,因为此时训练损失与验证损失最低,且所有 ROUGE 指标最高。下图为 results 文件夹的内容及所有检查点:

“results” 文件夹内容

“results” 文件夹内容

模型推理

既然模型已经过微调,不妨将其应用于真实场景?

这正是模型推理的意义所在。使用现有模型解决现实问题,这里我们就使用微调后的模型来实现。

在此之前,我们需要完成以下步骤:

  • 加载最后一个检查点的微调模型
last_checkpoint = "./results/checkpoint-22500"

finetuned_model = T5ForConditionalGeneration.from_pretrained(last_checkpoint)
tokenizer = T5Tokenizer.from_pretrained(last_checkpoint)
  • 定义一个需要回答的具体问题
my_question = "What do you think about the benefit of Artificial Intelligence?"
inputs = "Please answer to this question: " + my_question
  • 运行预测
inputs = tokenizer(inputs, return_tensors="pt")
outputs = finetuned_model.generate(**inputs)
answer = tokenizer.decode(outputs[0])
from textwrap import fill

print(fill(res, width=80))

下面是微调模型的结果,整体效果相当不错。此处使用 textwrap 的 fill 方法将每行的最大字符数限制为 80,避免整段文本在同一行显示。

微调模型对问题的回答

微调模型对问题的回答

结论与后续步骤

本文提供了微调 FLAN-T5 模型的完整指南。首先帮助读者更好地理解模型及其潜在用例,然后逐步讲解了完整的技术实现流程:从在 Hugging Face 加载实验数据与模型,到在 GPU 上训练模型。根据 ROUGE 分数选择的最佳模型也在真实场景中进行了测试。

下一步该做什么?

我们的文章《12 个 GPT-4 开源替代方案》与《如何用 PyTorch 训练 LLM》可作为技能提升的良好下一步。第一篇文章强调了一些可提供相近性能、且需要更少计算资源即可运行的 GPT-4 开源替代方案。这些项目附带说明文档、源码、模型权重、数据集与聊天机器人界面。

第二篇文章帮助读者掌握使用 PyTorch 训练大型语言模型的全过程,从初始设置到最终实现。您还可以借助这一强大的深度学习框架提升技能。立即加入《用 PyTorch 进行深度学习》课程。

主题