大模型微调:Hugging Face Transformers全流程实战
原创 精选 作者: 崔皓 本文描述了模型微调技术,通过调整预训练模型的参数,使其适应特定任务,从而提升模型在特定领域的表现。文中以 Yelp 数据集为例,详细介绍了如何使用 Hugging Face Transformers 框架对 BERT 模型进行微调,实现评论星级分类任务。随着大模型在人工智能领域的兴起,如何将其应用于垂直领域成为关键问题。本文描述了模型微调技术,通过调整预训练模型的参数,使其适应特定任务,从而提升模型在特定领域的表现。文中以 Yelp 数据集为例,详细介绍了如何使用 Hugging Face Transformers 框架对 BERT 模型进行微调,实现评论星级分类任务。文档涵盖了微调的背景、主流手段、架构和工具,并深入讲解了微调的思路和实践步骤,包括数据预处理、模型选择、超参数设置、训练评估和结果验证等。
大模型风头正盛,微调展现威力
近年来,大型预训练模型如GPT系列、Llama系列、通义千问在各大科技巨头的推动下,成为了人工智能领域的明星。然而,除了这些通用模型外,越来越多的垂直领域企业也在利用这些大模型,通过模型微调,打造符合行业需求的定制化解决方案。
以一家科技公司为例,公司需要频繁参与技术类项目的投标,每次都要根据具体项目的要求编写详细的技术方案。如果直接使用预训练好的通用语言模型来生成技术方案,虽然模型在理解语言和生成文本方面表现不错,但对于科技行业特有的技术术语、项目需求以及公司的业务特点,它的输出质量可能并不理想。通过模型微调,公司可以使用少量历史投标文件和行业相关的标记数据,对模型进行再训练,使其生成的技术方案更加贴合公司的技术风格和项目要求。这样,微调后的模型不仅能显著提高方案生成的准确性,还能帮助公司减少手工编写时间,提高效率和投标成功率。
关于模型微调,我们还可以举一个例子:假设你已经学会了画画,能画出非常漂亮的风景画。现在,你想画一只特定品种的小鸟,虽然你已经有了基础的画画技巧,但为了画好这只小鸟,你还需要学习一些额外的技巧,比如观察它的羽毛颜色和翅膀形状。模型微调就像是这样一个过程:AI模型已经掌握了很多基础技能,但要完成特定任务,还需要通过微调来“特训”,以提高它在某个领域的表现。
从理论上看,模型微调是针对已经经过大规模数据预训练的通用模型进行小规模的再次训练,使其在特定任务中表现更好。这种方法不仅节省了从零开始训练的资源和时间,还通过迁移学习的方式,充分利用了预训练模型中的丰富知识。
在论文BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding (Devlin et al., 2018)中就指出通过预训练和微调的结合,显著提升了模型在多项任务中的表现。在Transfer Learning for NLP: A Comprehensive Survey (Ruder, 2019) 中也提到迁移学习与微调技术的演变,以及它们在自然语言处理中的关键作用。这些研究表明,微调不仅能让通用模型更好地适应具体任务,还能为各行业提供高效且专业化的解决方案。这就是为什么在现代AI领域,微调成为了一项不可或缺的技术。
主流的微调手段
既然大模型微调解决了企业在垂直领域的大模型使用问题,那么市面上有哪些微调的手段呢?微调(Fine-tuning)作为将大型预训练模型应用于特定任务的重要手段,依赖于对模型参数的灵活调整。不同任务的需求、数据量的大小以及计算资源的限制,决定了选择哪种微调方式。以下是几种主流的微调策略:
微调的架构和工具
前面我们提到了,在不同的应用场景需要使用不同的微调手段,那么有哪些大模型微调的工具和框架可以帮助开发者快速上手微调呢?在业内, Hugging Face Transformers 和 DeepSpeed 是两个备受瞩目的工具和架构,它们的出现使得微调变得更加高效和可扩展。
1. Hugging Face Transformers
Hugging Face 的 Transformers 框架是目前自然语言处理(NLP)领域中最受欢迎的工具之一。它的优势在于简化了从预训练模型加载到微调整个过程的复杂性:
2. DeepSpeed
DeepSpeed 是微软推出的一款针对大规模深度学习模型的优化工具,它的主要优势在于极大地提高了训练效率,尤其适合处理像 GPT-3 这样的超大规模模型。
参数高效的训练:通过对训练过程的优化,如梯度累积、张量并行等技术,DeepSpeed 可以在降低内存需求的同时提高训练速度。
微调思路
虽然两个调优工具都非常优秀,但由于本文篇幅有限,所以我们选取HuggingfaceTransformers框架来为大家进行大模型调优的实践。
这里通过一张图来介绍大模型微调的思路,如下图所示,展示了从文本预处理到模型训练、评估、验证和保存等全过程。对于本次微调,我们希望解决“分类问题”,让模型理解“评论内容”与“评分星级”之间的关系,这里我们会提供大量的评论数据,每条数据会打上评分星级。在微调之后,给模型输入任何的评论内容,它都会返回该评论对应的星级。接下来的过程将分为几个主要步骤:
1. 确认问题
首先,要确认本次微调要解决的问题:理解“评论内容”与“评分星级”之间的对应关系。通过微调模型,我们希望模型能够根据评论的文本内容自动预测其星级评分(1-5星)。微调后的模型能够在新的评论输入时自动给出星级评定。
2. 装载数据集
本次微调会使用Yelp 数据集,它是一个公开的评论数据集,包含了大量用户对商家的评价和评分,广泛应用于自然语言处理任务。数据集的特点包括:
3. 分词器处理文本
对评论文本进行分词,将每条评论分解为词或词组。这里我们使用 BERT 自带的分词器(Tokenizer),它能够将文本转化为模型所需的输入格式(如 token IDs、attention masks 等)。通过分词,我们能够将每个词或词组转化为模型理解的向量表示,并保留文本的结构信息。
4. 生成小数据集
为了加快实验和测试过程,我们从原始的 Yelp 数据集中提取一个小样本数据集。小数据集可以帮助我们快速迭代模型的训练、调参和验证。经过分词器处理后,我们可以将这些小样本输入到模型中进行训练和评估。
5. 选择微调模型
我们将使用预训练的 BERT 模型,并根据 Yelp 数据集的需求调整模型的输出层。BERT 本身经过大规模语料库的预训练,具备强大的语言理解能力,通过微调,可以将其适应特定的分类任务。
6. 设置超参数
超参数包括学习率、训练轮次、batch size 等。合理的超参数设置能够确保模型的有效训练,避免过拟合或欠拟合问题。在训练过程中,我们将定期监控模型的性能,确保其学习到评论文本中的有效信息。
7. 训练评估
在训练阶段,我们会定期对模型进行评估。这通常在每个训练轮次结束后进行,通过验证集上的数据来评估模型的性能。通过这样的方式,能够实时了解模型的学习效果,并根据评估结果适时调整,避免过拟合等问题。
8. 计算准确性计算
通过计算模型的预测准确性来衡量其表现。在每次评估时,模型会对验证集中的数据进行预测,然后将这些预测结果与实际标签进行比较。通过计算准确性,能够判断模型在该分类任务中的成功率,即模型在多大程度上能够正确预测评论的星级。
9. 执行训练
开始对模型进行微调训练。
10.保存模型
在训练和验证完成后,将微调后的模型进行保存。保存的模型可以用于后续的预测和推理任务。通过调用保存的模型,我们可以在给定新评论的情况下快速获得预测的星级评分。
11. 验证结果
将设计对比实验:让未经过微调的模型和经过微调的模型分别对 Yelp 数据集中的评论进行星级预测,查看评分结果的差异。这样可以展示微调后的模型是否能够更好地理解评论文本与评分之间的关系,并给出更加准确的预测。
通过这些步骤,我们将详细探讨如何使用 Hugging Face 的工具对 BERT 模型进行微调,并结合 Yelp 数据集,逐步完成从数据预处理、模型训练到结果验证的整个流程。
实现微调
介绍完整个微调思路之后,我们会通过HuggingFace的Transformers模型框架编写微调的代码。
由于微调模型需要算力资源,一般而言都需要多张高显存的显卡支持。通过上面的微调思路大家可以知道,为了演示微调过程,我们将数据集进行了缩减的操作,利用更小的数据集来完成微调。在实际应用场景如果需要微调更大的数据集就需要多张高阶显卡。如下图所示,我们租用AutoDL上的虚拟服务器,使用3080ti显卡进行微调,实际测试的效果还不错,在小数据微调的情况下10-20分钟可以完成。
接着,需要安装必要的组件库,如下:
!pip install transformers>
上述库支持自然语言处理任务,尤其是涉及到模型微调、评估和数据处理的任务。下面是对每个库的简要解释:
1. transformers
这个库是由 Hugging Face 提供的,它包含了许多预训练的自然语言处理模型,支持包括文本分类、生成、翻译等任务。
2.>该库也是由 Hugging Face 提供的,专门用于处理和管理各种机器学习数据集。它可以加载、预处理和操作不同格式的数据集,支持直接加载许多公开数据集如 Yelp、IMDB 等。
3. evaluate
用于评估模型的性能。它提供了各种常用的评估指标(如准确率、精确率、召回率等),方便在模型训练和验证时计算这些指标,帮助衡量模型的表现。
4. accelerate
用于加速模型训练的库,支持分布式训练和加速设备的无缝切换,比如在CPU和GPU之间转换,或者使用多个GPU进行训练。
5. scikit-learn
非常流行的机器学习库,提供了各种统计学习工具、数据预处理和模型评估方法。在自然语言处理项目中,它常用于进行数据处理和分类算法的基础操作,如分词、向量化、模型评估等。
装载数据集
这里我们使用了Yelp数据集,它是从 2 年的 Yelp 数据集挑战赛中提取的,包含大量的用户评论,主要用于情感分类和文本分类任务。通过分析这些评论文本,可以预测用户的情感倾向,也就是评分的星级。数据集中的评论主要以英文撰写,每条评论包含两个核心元素:评论的文本内容和对应的评分标签。评论内容中涉及的文本经过特殊处理,双引号和换行符都进行了转义。评分标签表示评论的星级,范围从 1 到 5 星不等。整个数据集被随机划分为训练集和测试集,每个星级类别各包含 130,000 条训练样本和 10,000 条测试样本,总共包含 650,000 条训练数据和 50,000 条测试数据。
数据集的格式大致如下:
{'label': 0,'text': 'I got \'new\' tires from them and within two weeks got a flat...'}
接着通过如下代码加载数据。
通过代码可以看出,load_dataset 函数可以直接加载 yelp_review_full 数据集,它来自 Hugging Face 的>当使用 load_dataset("yelp_review_full") 时,函数会通过数据集名称自动从 Hugging Face 的数据集库中找到对应的资源,并加载相应的数据集。这意味着你不需要手动下载或准备数据集,Hugging Face 已经在其平台上托管了这个数据集,并定义了它的结构和格式。因此,load_dataset 能够通过提供的名称直接访问并加载。
在加载完毕数据,可以通过dataset["train"][100],查看Yelp Review 数据集中提取第 100 条训练数据,确认数据已经加载完毕。
分词器文本处理
分词器的工作是将我们看得懂的语言文字转化为模型能理解的数字。无论是机器学习模型还是深度学习模型,它们都只能处理数字,而不是直接处理文本。我们需要一个方法来把文本“翻译”成模型能够理解的形式。分词器正是负责这一“翻译”工作的工具。通过将句子分解成“词”或“字符”,然后给每个词或字符分配一个数字(通常叫做输入ID),模型就可以开始处理这些数字了。
如下图所示,比如,“我喜欢苹果”这句话,分词器就会把“我”翻译成1,“喜欢”翻译成2,“苹果”翻译成3。而这些数字是根据词汇表(vocabulary)中的子词来索引的,可以理解为在模型内部有一个很大的词汇表,词汇表中维护每个词对应的ID,你输入ID模型就知道你要表达什么词(类似身份证与人之间的关系)。这个词汇表可以看作是模型用来理解语言的“字典”,其中包含了大量常见的词和子词(subwords)。每个子词在词汇表中都有一个对应的索引 ID,当我们输入一段文本时,分词器会将它切分成多个子词,并查找每个子词在词汇表中的索引。
为什么需要词汇表?因为语言的多样性非常大,而单词的数量是无限的。直接给每个完整的单词一个唯一的 ID 会导致词汇表非常庞大,这不仅会增加模型的复杂性,还容易出现很多未知的词(也就是词汇表中没有的词)。使用子词来代替整个单词能够减少词汇表的大小,同时确保即便遇到生僻词也能通过已知的子词来表达。例如,英文"unhappiness" 可以被拆分为 "un-" 、"happi-" 和“ness”三个子词,即便模型没有见过完整的“unhappiness”这个词,它仍然可以通过上述三个子词来理解。
如下图所示,简单理解,分词器的工作就是讲输入的文字参照词汇表,将其转化为子词ID的形式,然后输入到大模型中进行处理。
接着来看代码:
from transformers import AutoTokenizertokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")def tokenize_function(examples):return tokenizer(examples["text"], padding="max_length", truncation=True)tokenized_datasets =>
从 Hugging Face 预训练模型库中加载了google-bert/bert-base-cased版本分词器。
def tokenize_function(examples)用于处理数据集中的每一个数据点。
tokenizer(examples["text"], padding="max_length", truncation=True):这里使用分词器将输入的文本数据进行分词。分词器会将文本转换为标记(tokens),并将其转换为适合模型输入的数字化形式。padding="max_length":表示对文本进行填充,使所有文本的长度相同,达到预定义的最大长度。truncation=True:如果文本长度超过最大长度,自动截断多余部分。
tokenized_datasets =>在处理大型数据集时,训练模型和进行验证会耗费大量时间和计算资源。因此,我们通常会创建一个小数据集来进行快速的实验和调试。
加入如下代码:
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
tokenized_datasets["train"] 和 tokenized_datasets["test"]: 分别从已分词的数据集中获取训练集和测试集。
指定微调模型
微调数据集是基于一个预训练模型展开的,这里选择bert-base-cased 模型,用于一个有 5 个分类标签(1-5星的评价)的序列分类任务。
代码如下:
from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
AutoModelForSequenceClassification:是 Hugging Face 的 transformers 库中提供的模型,用于文本分类任务。它基于预训练的 Transformer 模型,并添加一个分类头(线性层)来处理分类任务。通过这个类中的from_pretrained("google-bert/bert-base-cased")方法加载预训练模型。
num_labels=5:定义了分类任务的标签数为 5,表示我们要对文本进行 5 类分类任务。
设置超参数
设置超参数帮助配置模型训练时的各种参数,比如训练输出的目录、批量大小、学习率、是否使用 GPU 等。
如下代码用于设置训练模型时的超参数,使用 Hugging Face transformers 库中的 TrainingArguments 类。
from transformers import TrainingArgumentstraining_args = TrainingArguments(output_dir="test_trainer")
为了简化演示过程这里只配置了output_dir="test_trainer"作为模型输出的路径。在训练过程中,模型的检查点(checkpoints)、日志文件、以及最终模型都会被保存在这个目录下。
此外,TrainingArguments 提供了许多其他参数,你可以根据具体的训练需求来设置,比如:
训练评估与计算预测的准确性
在模型的训练和评估过程中,特别是在分类任务中,衡量模型性能的一个关键指标就是准确性。训练好的模型可以在验证集上进行评估,查看模型在实际数据上的表现,以此来衡量其分类能力。我们会创建一个compute_metrics 函数,它用来计算预测结果和实际标签的差异来评估模型的准确性。同时会使用evaluate 库的 accuracy 指标得到模型在验证集上的表现。
代码如下:
import numpy as npimport evaluatemetric = evaluate.load("accuracy")def compute_metrics(eval_pred):logits, labels = eval_predpredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)
metric = evaluate.load("accuracy")加载了 Hugging Face 库中预定义的“准确率”评估指标。 compute_metrics(eval_pred) 函数是为训练/验证流程提供的评估函数,传入的参数 eval_pred 包含了两个部分:logits 和 labels。
logits:模型输出的原始预测结果,通常是未经过激活函数的值(在分类任务中通常是未经 softmax 的分数)。
为什么突然跳出一个logits来了?这里需要稍微解释一下,在深度学习的分类模型中,模型在最后一层输出的是各个类别的分数(logits)。这些分数用来表示模型对各个类别的预测概率。在很多情况下,模型会直接输出 logits,而不是最终的类别,因为 logits 还可以进一步处理(比如通过 softmax 转换为概率)。
大家还记得吗?在最开始处理文本的时候,我们需要针对输入文本进行分词(tokenization),接着每个词会被映射成嵌入(embedding)。嵌入作为输入经过模型的层层处理,最后在输出层产生 logits,用于预测文本的分类结果。因此,分词是处理输入数据的第一步,而 logits 是最终的输出分数,用来确定分类的结果。
来举个例子说说logits方便大家理解,假设我们有一个分类任务,模型需要把一句话分成 5 个类别之一(比如情感分类:0=负面, 1=中性, 2=正面, 3=疑问, 4=惊讶)。在模型处理输入之后,它会输出一个包含 5 个数值的向量(logits),表示模型对每个类别的信心。
示例:
输入句子:"I love this product!"
假设模型的输出 logits 是:
logits = [2.5, -1.3, 5.2, 0.8, -0.5]
这个向量表示模型对每个类别的信心。这里的5.2是 logits 中的最大值,表示模型认为这个句子最可能属于第 2 类(“正面”情感),尽管它对每个类别都有一定的信心值。
所以,这里的logits就是在对预测结果进行“打分”,哪个分类上分数越高,模型就会认为这个文字描述属于哪类。
而labels表示的是实际标签,它用于与模型的预测结果对比。
logits, labels = eval_pred:将 eval_pred 拆分为 logits 和 labels。
predictions = np.argmax(logits, axis=-1):使用 np.argmax() 函数从 logits 中找到最大值的索引,代表模型的最终预测类别。axis=-1 表示沿最后一个维度进行操作,对于每个输入,输出模型认为最可能的类别。
metric.compute(predictions=predictions, references=labels):计算模型预测的准确性。predictions 是模型的预测值,references 是真实标签。这个函数返回一个包含准确性(accuracy)指标的字典。
创建Trainer对象执行微调任务
微调前的基本工作已经完成了, 接下来我们需要创建一个Trainer类,用于简化训练和评估过程,该类包含了模型、训练参数、训练和验证数据集,以及评估指标计算方法。然后,执行trainer.train() 用于微调训练,模型的权重会根据训练数据逐步优化。最后,trainer.save_model() 会将训练好的模型保存,方便后续使用。代码如下:
trainer = Trainer(model=model,# 定义的模型( BERT 分类模型)args=training_args,# 训练的参数,如学习率、批次大小、训练周期等train_dataset=small_train_dataset,# 用于训练的小数据集eval_dataset=small_eval_dataset,# 用于评估的小数据集compute_metrics=compute_metrics,# 之前定义的,验证的准确性等指标的函数)trainer.train() # 开始训练模型trainer.save_model() # 保存训练后的模型
执行上述代码,得到如下运行截图:
从图中可以看出,验证集上的准确性(Accuracy)分别为:
尽管第三轮的验证损失增加了,准确率却依然提升了一点。这可能表明模型在某些类别上的预测更准确,但整体的损失仍然较大,表明模型存在过拟合或不平衡的表现。
同时得到如下运行信息:
TrainOutput(global_step=375, training_loss=0.49261214192708336, metrics={'train_runtime': 127.2273, 'train_samples_per_second': 23.58, 'train_steps_per_second': 2.947, 'total_flos': 789354427392000.0, 'train_loss': 0.49261214192708336, 'epoch': 3.0})
从信息可以看出,模型微调训练共进行了 3 个 Epoch,执行了 375 个训练步数,总运行时间为 127 秒。在训练过程中,模型每秒能够处理约 23.58 个样本,每秒执行 2.95 个训练步。最终,模型的平均训练损失为 0.49,表明模型在训练数据上的表现较为理想。此外,训练的总浮点运算量(FLOPs)为 789,354,427,392,000,展示了训练过程中涉及的大量计算量。
验证微调
前面我们通过获取数据集针对BERT模型进行微调,执行微调命令之后完成了微调的过程,并且通过trainer.save_model()语句将微调之后的模型保存到本地。接下来,通过对比微调前后模型对评论信息的星级反馈,来查看微调之后模型的能力是否提升。
先调用测试之前的模型输入评论内容,看模型对评论给出什么星级的评定。
如下图所示,测试的评论是2star(2星的星级评定)。
执行如下代码:
from transformers import AutoTokenizer, AutoModelForSequenceClassificationimport torchimport numpy as np# Yelp review 示例input_text = "My first time going to Barb's Country Junction and I really wanted to like this place, I really did...however,……."# 加载预训练模型和 tokenizertokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")model_pretrained = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)# 标记化输入文本inputs = tokenizer(input_text, padding=True, truncation=True, return_tensors="pt")# 使用预训练模型进行预测(没有经过微调)with torch.no_grad():outputs_pretrained = model_pretrained(**inputs)# 获取未微调模型的预测标签predicted_label_pretrained = np.argmax(outputs_pretrained.logits, axis=-1).item()print(f"Prediction before fine-tuning (without knowledge of Yelp reviews): {predicted_label_pretrained}")
得到如下结果。
Prediction before fine-tuning (without knowledge of Yelp reviews): 3
很明显,从结果上看模型预测的星级为“3”,与数据集标注的“2”存在差异。
接着利用微调之后模型进行推理,代码如下:
model = AutoModelForSequenceClassification.from_pretrained("test_trainer")tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")# 输入文本input_text = "My first time going to Barb's Country Junction and I really wanted to like this place, I really did...however, ……"# 标记化输入文本inputs = tokenizer(input_text, padding=True, truncation=True, return_tensors="pt")# 获取模型预测with torch.no_grad():# 关闭梯度计算outputs = model(**inputs)# 获取预测标签predicted_label = np.argmax(outputs.logits, axis=-1).item()# 输出结果print(f"Predicted label: {predicted_label}")
上述代码通过model = AutoModelForSequenceClassification.from_pretrained("test_trainer"),获取微调之后test_trainer目录下的模型进行推理。 执行结果如下:
Predicted label: 2
与预期的结果相同,说明微调之后的模型能够胜任评论星级分类的工作。
总结
大模型微调技术为将预训练模型应用于垂直领域提供了有效途径。通过微调,我们可以利用预训练模型的知识,并结合特定领域的任务需求,打造定制化解决方案。本文以 Yelp 数据集为例,演示了如何使用 Hugging Face Transformers 框架对 BERT 模型进行微调,实现评论星级分类任务。文档涵盖了微调的背景、主流手段、架构和工具,并深入讲解了微调的思路和实践步骤,包括数据预处理、模型选择、超参数设置、训练评估和结果验证等。相信通过本文的学习,读者能够更好地理解大模型微调技术,并将其应用于实际项目中。
作者介绍
崔皓,社区编辑,资深架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。
本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://www.jmbhsh.com/baobaofuzhuang/32489.html