Press "Enter" to skip to content

大型语言模型微调的全面指南

介绍

在过去几年中,自然语言处理(NLP)领域发生了一场令人瞩目的变革,这完全归功于大型语言模型的出现。这些复杂的模型为各种应用打开了大门,从语言翻译到情感分析,甚至智能聊天机器人的创建。

但它们的多功能性使得这些模型与众不同;将它们微调以应对特定任务和领域已经成为标准做法,释放出它们的真正潜力,将其性能提升到新的高度。在这本全面的指南中,我们将深入探讨大型语言模型的微调世界,涵盖从基础知识到高级知识的一切。

学习目标

  • 了解微调的概念和将大型语言模型调整适应特定任务的重要性。
  • 探索多任务、指令微调和参数高效微调等高级微调技术。
  • 获得实际应用的实用知识,微调的语言模型在其中革新行业。
  • 了解大型语言模型微调的逐步过程。
  • 实施完善的微调机制。
  • 了解标准微调和指令微调之间的区别。

本文作为数据科学博文的一部分发表。

理解预训练语言模型

预训练语言模型是在互联网上获取的大量文本数据上进行训练的大型神经网络。训练过程包括预测给定句子或序列中缺失的单词或令牌,从而使模型对语法、上下文和语义有深刻的理解。通过处理数十亿个句子,这些模型可以把握语言的复杂性,有效捕捉其细微差别。

流行的预训练语言模型示例包括BERT(双向编码器表示转换)、GPT-3(生成式预训练转换器3)、RoBERTa(经过优化的鲁棒BERT预训练方法)等等。这些模型以其出色的性能在文本生成、情感分类和语言理解等任务上表现出色。

让我们详细讨论其中一个语言模型。

GPT-3

GPT-3(生成式预训练转换器3)是一种突破性的语言模型架构,改变了自然语言生成和理解。Transformer模型是GPT-3架构的基础,它包含了多个参数,以产生出色的性能。

GPT-3的架构

GPT-3由一系列Transformer编码器层组成。每个层由多头自注意力机制和前馈神经网络组成。前馈网络处理和转换编码表示,注意力机制使模型能够识别单词之间的依赖关系和关联。

GPT-3的主要创新是其巨大的规模,它拥有令人惊叹的1750亿个参数,使其能够捕捉到大量的语言知识。

大型语言模型微调的全面指南 四海 第1张

代码实现

您可以使用OpenAI API与GPT-3模型进行交互。以下是使用GPT-3进行文本生成的示例。

import openai

# 设置OpenAI API凭据
openai.api_key = 'YOUR_API_KEY'

# 定义文本生成的提示
prompt = "A quick brown fox jumps"

# 向GPT-3发送文本生成的请求
response = openai.Completion.create(
  engine="text-davinci-003",
  prompt=prompt,
  max_tokens=100,
  temperature=0.6
)

# 从API响应中获取生成的文本
generated_text = response.choices[0].text

# 打印生成的文本
print(generated_text)

微调:定制模型以满足我们的需求

这里有一个转折:虽然预训练语言模型非常强大,但它们本质上并不擅长任何特定任务。它们可能对语言有着令人难以置信的理解,但它们需要在情感分析、语言翻译或回答特定领域的问题等任务中进行微调。

微调就像给这些多才多艺的模型提供最后的修饰。想象一下有一个多才多艺的朋友,擅长各个领域,但你需要他们掌握一项特定的技能来应对特殊场合。你会给他们一些特定领域的训练,对吗?这正是我们在微调过程中对预训练的语言模型所做的。

大型语言模型微调的全面指南 四海 第2张

微调涉及在较小的任务特定数据集上对预训练模型进行训练。这个新的数据集带有与目标任务相关的示例标签。通过向模型展示这些带标签的示例,它可以调整参数和内部表示,以便更适合目标任务。

微调的必要性

虽然预训练的语言模型非常出色,但它们默认情况下并不是任务特定的。微调是将这些通用模型调整到更准确、更高效地执行专门任务的过程。当我们遇到特定的自然语言处理任务,比如针对客户评论的情感分析或特定领域的问答,我们需要对预训练模型进行微调,以理解该特定任务和领域的细微差别。

微调的好处多多。首先,它利用了预训练期间学到的知识,节省了从头开始训练模型所需的大量时间和计算资源。其次,微调使我们能够在特定任务上表现更好,因为模型现在已经适应了微调所用领域的复杂性和细微差别。

微调过程:一步一步的指南

微调过程通常涉及将任务特定的数据集提供给预训练模型,并通过反向传播调整其参数。目标是最小化损失函数,该函数衡量模型的预测与数据集中的真实标签之间的差异。这个微调过程更新了模型的参数,使其更适用于你的目标任务。

在这里,我们将介绍一个用于情感分析的大型语言模型的微调过程。我们将使用Hugging Face Transformers库,该库提供了易于访问的预训练模型和微调工具。

第1步:加载预训练语言模型和分词器

第一步是加载预训练语言模型及其对应的分词器。在本例中,我们将使用“distillery-base-uncased”模型,这是BERT的一个较轻版本。

from transformers import DistilBertTokenizer, DistilBertForSequenceClassification

# 加载预训练的分词器
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')

# 加载预训练的序列分类模型
model = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased')

第2步:准备情感分析数据集

我们需要一个带有文本样本和相应情感标签的标记数据集进行情感分析。为了说明目的,让我们创建一个小的数据集:

texts = ["我喜欢这部电影。太棒了!",
         "这食物太糟糕了。",
         "天气还可以。"]
sentiments = ["积极", "消极", "中性"]

接下来,我们将使用分词器将文本样本转换为标记ID和模型所需的注意力掩码。

# 对文本样本进行分词
encoded_texts = tokenizer(texts, padding=True, truncation=True, return_tensors='pt')

# 提取输入ID和注意力掩码
input_ids = encoded_texts['input_ids']
attention_mask = encoded_texts['attention_mask']

# 将情感标签转换为数字形式
sentiment_labels = [sentiments.index(sentiment) for sentiment in sentiments]

第3步:添加自定义分类头

预训练语言模型本身不包含分类头。我们必须为模型添加一个分类头来执行情感分析。在这种情况下,我们将添加一个简单的线性层。

import torch.nn as nn

# 在预训练模型之上添加自定义分类头
num_classes = len(set(sentiment_labels))
classification_head = nn.Linear(model.config.hidden_size, num_classes)

# 用我们的自定义头替换预训练模型的分类头
model.classifier = classification_head

步骤4:微调模型

在自定义的分类头部就位后,我们现在可以在情感分析数据集上对模型进行微调。我们将使用AdamW优化器和CrossEntropyLoss作为损失函数。

import torch.optim as optim

# 定义优化器和损失函数
optimizer = optim.AdamW(model.parameters(), lr=2e-5)
criterion = nn.CrossEntropyLoss()

# 微调模型
num_epochs = 3
for epoch in range(num_epochs):
    optimizer.zero_grad()
    outputs = model(input_ids, attention_mask=attention_mask, labels=torch.tensor(sentiment_labels))
    loss = outputs.loss
    loss.backward()
    optimizer.step()

什么是指令微调?

指令微调是一种专门的技术,用于根据明确的指令调整大型语言模型以执行特定任务。传统的微调方法涉及在特定任务的数据上训练模型,而指令微调则更进一步,通过整合高级指令或演示来引导模型的行为。

大型语言模型微调的全面指南 四海 第3张

这种方法允许开发人员指定期望的输出结果,鼓励特定行为,或者实现对模型响应的更好控制。在本详细指南中,我们将逐步探讨指令微调的概念及其实现方法。

指令微调流程

如果我们能超越传统的微调方法,为模型行为提供明确的指令,会怎么样呢?指令微调通过提供新的控制和精确度水平,实现了这一点。在这里,我们将探索使用指令微调大型语言模型进行情感分析的流程。

步骤1:加载预训练语言模型和分词器

首先,让我们加载预训练的语言模型及其分词器。在本示例中,我们将使用GPT-3,一种最先进的语言模型。

from transformers import GPT2Tokenizer, GPT2ForSequenceClassification

# 加载预训练的分词器
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

# 加载用于序列分类的预训练模型
model = GPT2ForSequenceClassification.from_pretrained('gpt2')

步骤2:准备指令数据和情感分析数据集

对于指令微调,我们需要使用明确的指令增强情感分析数据集。让我们创建一个用于演示的小型数据集:

texts = ["我喜欢这部电影。太棒了!",
         "这个食物太糟糕了。",
         "天气还可以。"]
sentiments = ["积极", "消极", "中性"]
instructions = ["分析文本的情感,判断其是否为积极的。",
                "分析文本的情感,判断其是否为消极的。",
                "分析文本的情感,判断其是否为中性的。"]

接下来,让我们使用分词器对文本、情感和指令进行分词:

# 对文本、情感和指令进行分词
encoded_texts = tokenizer(texts, padding=True, truncation=True, return_tensors='pt')
encoded_instructions = tokenizer(instructions, padding=True, truncation=True, return_tensors='pt')

# 提取输入ID、注意力掩码和指令ID
input_ids = encoded_texts['input_ids']
attention_mask = encoded_texts['attention_mask']
instruction_ids = encoded_instructions['input_ids']

步骤3:使用指令自定义模型架构

为了在微调过程中引入指令,我们需要自定义模型架构。我们可以通过将指令ID与输入ID连接起来来实现这一点:

import torch

# 将指令ID与输入ID连接起来并调整注意力掩码
input_ids = torch.cat([instruction_ids, input_ids], dim=1)
attention_mask = torch.cat([torch.ones_like(instruction_ids), attention_mask], dim=1)

步骤4:使用指令微调模型

在引入指令后,我们现在可以在增强的数据集上对GPT-3模型进行微调。在微调过程中,指令将引导模型的情感分析行为。

import torch.optim as optim

# 定义优化器和损失函数
optimizer = optim.AdamW(model.parameters(), lr=2e-5)
criterion = torch.nn.CrossEntropyLoss()

# 微调模型
num_epochs = 3
for epoch in range(num_epochs):
    optimizer.zero_grad()
    outputs = model(input_ids, attention_mask=attention_mask, labels=torch.tensor(sentiments))
    loss = outputs.loss
    loss.backward()
    optimizer.step()

指导微调将传统微调的能力提升到一个新的水平,使我们能够精确控制大型语言模型的行为。通过提供明确的指示,我们可以引导模型的输出,实现更准确和定制化的结果。

两种方法的关键区别

标准微调涉及在标记数据集上训练模型,以有效地执行特定任务。但是,如果我们想提供明确的指示来引导模型的行为,则需要使用指导微调来实现对模型行为的无与伦比的控制和适应性。

以下是指导微调和标准微调之间的关键区别。

  • 数据要求:标准微调依赖于大量标记数据以执行特定任务,而指导微调通过明确的指示提供的指导使其能够在有限的标记数据的情况下更具适应性。
  • 控制和精确性:指导微调允许开发人员指定期望的输出,鼓励特定的行为或更好地控制模型的响应。标准微调可能无法提供这种级别的控制。
  • 从指示中学习:指导微调需要将指示合并到模型的架构中,而标准微调则不需要。

引入灾难性遗忘:一项危险的挑战

当我们进入微调的世界时,我们会遇到灾难性遗忘的危险挑战。这种现象发生在模型对新任务进行微调时,它会删除或“遗忘”在预训练过程中获得的知识。模型在专注于新任务时失去了对更广泛的语言结构的理解。

想象一下我们的语言模型就像一艘船的货舱,里面装满了各种不同的知识容器,每个容器代表了不同的语言细微差别。在预训练期间,这些容器被细心地填满了语言理解。当我们面临新任务并开始微调时,船员会重新安排这些容器。他们会清空一些容器来为新的任务特定知识腾出空间。不幸的是,一些原始知识会丢失,导致灾难性遗忘。

减轻灾难性遗忘:保护知识

为了应对灾难性遗忘的挑战,我们需要采取策略来保护在预训练过程中捕获的宝贵知识。有两种可能的方法。

多任务微调:渐进学习

在这里,我们逐步向模型引入新任务。最初,模型专注于预训练知识,并逐渐融入新的任务数据,以最小化灾难性遗忘的风险。

多任务指导微调采用了一种新的范式,通过同时训练语言模型来处理多个任务。我们为每个任务提供明确的指示,在微调过程中引导模型的行为。

大型语言模型微调的全面指南 四海 第4张

多任务指导微调的好处

  • 知识传递:通过在多个任务上训练,模型从不同领域获得洞察和知识,增强了整体语言理解能力。
  • 共享表示:多任务指导微调允许模型在任务之间共享表示。这种知识共享提高了模型的泛化能力。
  • 效率:与逐个微调每个任务相比,同时在多个任务上进行训练可以减少计算成本和时间。

参数高效微调:迁移学习

在这里,我们在微调过程中冻结模型的某些层。通过冻结负责基本语言理解的早期层,我们保留了核心知识,仅对特定任务微调后期层。

理解 PEFT

内存是完全微调所必需的,用于存储模型和其他与训练相关的参数。您必须能够在整个训练过程中为优化器状态、梯度、前向激活和临时内存分配内存,即使您的计算机可以容纳最大模型的数百千兆字节的权重。这些额外的部分可能比模型大得多,并迅速超出了消费者硬件的能力。

大型语言模型微调的全面指南 四海 第5张

参数高效微调技术仅更新一小部分参数,而不是完全微调,后者在监督学习期间更新每个模型权重。一些路径技术专注于微调现有模型参数的一部分,例如特定的层或组件,同时冻结大部分模型权重。其他方法添加一些新的参数或层,仅微调新的组件;它们不会影响原始模型的权重。使用PEFT,大多数甚至全部LLM权重都被冻结。因此,与原始LLM相比,训练参数显著减少。

为什么选择PEFT?

PEFT使参数高效模型具有令人印象深刻的性能,彻底改变了自然语言处理领域的格局。以下是我们使用PEFT的几个原因。

  • 降低计算成本:PEFT需要较少的GPU和GPU时间,使其更适用于训练大型语言模型的成本效益更高。
  • 更快的训练时间:使用PEFT,模型的训练速度更快,能够快速迭代并更快地在实际应用中部署。
  • 较低的硬件要求:PEFT可以与较小的GPU高效配合使用,并且需要更少的内存,使其在资源受限的环境中可行。
  • 改进的建模性能:PEFT通过减少过拟合,为各种任务生成更稳健和准确的模型。
  • 高效的存储空间利用:通过在任务之间共享权重,PEFT最小化存储需求,优化模型的部署和管理。

使用PEFT进行微调

在冻结大部分预训练LLM的同时,PEFT仅微调少量模型参数,大大降低了计算和存储成本。这也解决了在LLM的完全微调过程中出现的灾难性遗忘问题。

在数据较少的情况下,PEFT方法还被证明优于微调,并能更好地泛化到域外场景。

加载模型

让我们在这里加载opt-6.7b模型;它在Hub上的权重大约为13GB,以半精度(float16)加载它们将需要约7GB的内存。

import os
os.environ["CUDA_VISIBLE_DEVICES"]="0"
import torch
import torch.nn as nn
import bitsandbytes as bnb
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    "facebook/opt-6.7b", 
    load_in_8bit=True, 
    device_map='auto',
)

tokenizer = AutoTokenizer.from_pretrained("facebook/opt-6.7b")

模型后处理

在对8位模型应用一些后处理之前,让我们冻结所有层并将层归一化为float32以提高稳定性。出于相同的原因,我们还将最终层的输出转换为float32。

for param in model.parameters():
  param.requires_grad = False  # 冻结模型 - 以后训练适配器
  if param.ndim == 1:
    param.data = param.data.to(torch.float32)

model.gradient_checkpointing_enable()  # 减少存储的激活数量
model.enable_input_require_grads()

class CastOutputToFloat(nn.Sequential):
  def forward(self, x): return super().forward(x).to(torch.float32)
model.lm_head = CastOutputToFloat(model.lm_head)

使用LoRA

加载PeftModel,我们将使用低秩适配器(LoRA),使用Peft的get_peft_model实用函数。

该函数计算并打印给定模型的可训练参数总数和所有参数。同时提供可训练参数的百分比,以提供模型复杂性和训练资源需求的概述。

def print_trainable_parameters(model):
 
    # 打印模型中可训练参数的数量。
   
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    print(
        f"trainable params: {trainable_params} || all params: {all_param} || 
          trainable%: {100 * trainable_params / all_param}"
    )
    

这使用了Peft库来创建一个具有特定配置设置的LoRA模型,包括dropout、bias和task类型。然后,它获取模型的可训练参数并打印可训练参数的总数和所有参数,以及可训练参数的百分比。

from peft import LoraConfig, get_peft_model 

config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, config)
print_trainable_parameters(model)

训练模型

这使用了Hugging Face Transformers和Datasets库来在给定数据集上训练语言模型。它利用了’transformers.Trainer’类来定义训练设置,包括批量大小、学习率和其他与训练相关的配置,然后在指定的数据集上训练模型。

import transformers
from datasets import load_dataset
data = load_dataset("Abirate/english_quotes")
data = data.map(lambda samples: tokenizer(samples['quote']), batched=True)

trainer = transformers.Trainer(
    model=model, 
    train_dataset=data['train'],
    args=transformers.TrainingArguments(
        per_device_train_batch_size=4, 
        gradient_accumulation_steps=4,
        warmup_steps=100, 
        max_steps=200, 
        learning_rate=2e-4, 
        fp16=True,
        logging_steps=1, 
        output_dir='outputs'
    ),
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
model.config.use_cache = False  # silence the warnings. Please re-enable for inference!
trainer.train()

精调LLM的实际应用

我们将更详细地了解精调大型语言模型的一些令人兴奋的实际应用案例,其中自然语言处理的进展正在改变行业并赋予创新解决方案能力。

  • 情感分析:通过对情感分析进行语言模型的精调,企业可以分析客户反馈、产品评论和社交媒体情感,以了解公众的看法并做出数据驱动的决策。
  • 命名实体识别(NER):通过对NER进行模型的精调,可以自动从文本中提取出名称、日期和地点等实体,实现信息检索和文档分类等应用。
  • 语言翻译:精调模型可用于机器翻译,打破语言障碍,实现不同语言之间的无缝沟通。
  • 聊天机器人和虚拟助手:通过对语言模型进行精调,聊天机器人和虚拟助手可以提供更准确和上下文相关的回答,提升用户体验。
  • 医学文本分析:精调模型可以帮助分析医疗文件、电子健康记录和医学文献,协助医疗专业人员进行诊断和研究。
  • 金融分析:精调语言模型可用于金融情感分析、预测市场趋势和从大量数据集生成财务报告。
  • 法律文件分析:精调模型可以帮助进行法律文件分析、合同审查和自动文档摘要,为法律专业人员节省时间和精力。

在现实世界中,精调大型语言模型已经在各个行业中找到了应用,赋予企业和研究人员利用自然语言处理能力进行各种任务的能力,提高效率、改善决策和丰富用户体验。

结论

精调大型语言模型已经成为一种强大的技术,可以使这些预训练模型适应特定的任务和领域。随着自然语言处理领域的发展,精调将继续成为开发尖端语言模型和应用的关键。

这份全面的指南带领我们进入了精调大型语言模型的世界。我们首先了解了精调的重要性,它使预训练和精调相辅相成,使语言模型在特定任务上表现出色。选择合适的预训练模型至关重要,我们探索了流行的模型。我们深入探讨了多任务精调、参数高效精调和指导精调等高级技术,这些技术推动了自然语言处理中的效率和控制的界限。此外,我们还探讨了实际应用,见证了精调模型如何革新情感分析、语言翻译、虚拟助手、医疗分析、金融预测等领域。

要点

  • 微调是对预训练的补充,使语言模型能够针对特定任务,对于尖端应用至关重要。
  • 多任务、参数高效和指令微调等高级技术推动了自然语言处理的边界,提高了模型的性能和适应性。
  • 接受微调革命可以改变现实世界的应用,改变我们对文本数据的理解,从情感分析到虚拟助手。

借助微调的力量,我们能够准确、创造性地驾驭语言的浩瀚海洋,改变我们与文本世界的互动和理解方式。因此,接受微调的可能性,释放语言模型的全部潜力,每个微调模型都塑造着自然语言处理的未来。

常见问题

本文中显示的媒体不归 Analytics Vidhya 所有,由作者自行决定使用。

Leave a Reply

Your email address will not be published. Required fields are marked *