趋近智
全参数 (parameter)微调 (fine-tuning)涉及理解其工作原理、超参数 (hyperparameter)考量、正则化 (regularization)需求和资源要求。这个动手练习将指导您完成对一个相对较小的预训练 (pre-training)语言模型进行全参数微调,以适应特定下游任务的过程。即使在LLM的背景下其规模较小,这个过程仍需计算资源,突出了资源管理的重要性。
我们将使用Hugging Face的transformers和datasets库,这些库为许多常见的自然语言处理(NLP)任务和模型提供了便捷的抽象。本例主要关注文本分类任务,这是微调的一个常见应用。
首先,请确保已安装必要的库。您主要需要transformers、datasets、torch(或tensorflow)以及accelerate来实现高效训练。
pip install transformers datasets torch accelerate scikit-learn
我们假设您在能使用GPU的环境中工作,因为即使对于较小的模型,全参数 (parameter)微调 (fine-tuning)在CPU上也会非常慢。accelerate库有助于自动管理设备(CPU/GPU)分配。
对于本次实践练习,我们将使用distilbert-base-uncased,它是BERT的一个蒸馏版本,在保持显著性能的同时,规模更小、速度更快。对于任务,我们将使用imdb数据集,它是二元情感分类(正面/负面电影评论)的标准基准。
# 定义模型和数据集名称
model_checkpoint = "distilbert-base-uncased"
dataset_name = "imdb"
datasets库使加载标准数据集变得简单。
from datasets import load_dataset
# 加载数据集
raw_datasets = load_dataset(dataset_name)
# 显示数据集结构(可选)
print(raw_datasets)
# DatasetDict({
# train: Dataset({
# features: ['text', 'label'],
# num_rows: 25000
# })
# test: Dataset({
# features: ['text', 'label'],
# num_rows: 25000
# })
# unsupervised: Dataset({
# features: ['text', 'label'],
# num_rows: 50000
# })
# })
接下来,我们需要对文本数据进行分词 (tokenization),以便模型能够理解它。我们使用与我们选择的预训练 (pre-training)模型对应的分词器 (tokenizer)。
from transformers import AutoTokenizer
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
# 定义分词函数
def tokenize_function(examples):
# 截断长度超过模型最大输入尺寸的序列
return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=512)
# 对整个数据集应用分词(为提高效率,进行批量处理)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
# 移除原始的'text'列,因为它不再需要
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
# 将'label'重命名为'labels',这是Trainer所期望的
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
# 将格式设置为PyTorch张量
tokenized_datasets.set_format("torch")
# 创建较小的子集以加快演示(可选)
# 对于完整运行,请移除或调整这些行
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
print("\n分词后的训练数据样本:")
print(small_train_dataset[0])
这个过程将原始文本转换为输入ID、注意力掩码,并包含监督学习 (supervised learning)所需的标签。
我们加载配置用于序列分类的预训练 (pre-training)DistilBERT模型。AutoModelForSequenceClassification会自动在基础DistilBERT模型之上添加一个分类头。标签数量从数据集中推断(在本例中为2:正面/负面)。
from transformers import AutoModelForSequenceClassification
# 加载用于序列分类的模型
# num_labels=2 表示二元分类(正面/负面)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)
此时,model包含预训练权重 (weight)。在全参数 (parameter)微调 (fine-tuning)期间,所有这些权重,包括基础模型和新添加的分类头,都将被更新。
TrainingArguments类包含了训练过程的所有超参数 (hyperparameter)和设置。这包括我们之前讨论过的参数,如学习率、批量大小、训练轮数和正则化 (regularization)(权重 (weight)衰减)。
from transformers import TrainingArguments
# 定义检查点和日志的输出目录
output_dir = "./results/distilbert-imdb-full"
training_args = TrainingArguments(
output_dir=output_dir,
evaluation_strategy="epoch", # 在每个 epoch 结束时评估性能
save_strategy="epoch", # 在每个 epoch 结束时保存检查点
num_train_epochs=3, # 训练 epoch 数量(根据需要调整)
per_device_train_batch_size=8, # 每个 GPU 的批量大小
per_device_eval_batch_size=8, # 评估时的批量大小
learning_rate=2e-5, # 初始学习率(微调的常用值)
weight_decay=0.01, # 应用权重衰减进行正则化
logging_dir='./logs', # 存储日志的目录
logging_steps=100, # 每 100 步记录训练损失
load_best_model_at_end=True, # 在结束时加载性能最好的模型检查点
metric_for_best_model="accuracy", # 用于确定“最佳”模型的指标
# 使用 push_to_hub=True 将结果上传到 Hugging Face Hub(需要登录)
# push_to_hub=False,
)
这些参数直接控制了前面章节理论上讨论的微调 (fine-tuning)过程。选择合适的值(如学习率和训练轮数)通常需要通过实验来确定。
为了在训练期间监测性能,我们需要一个根据模型预测和真实标签计算指标的函数。我们将为此分类任务使用标准准确率。
import numpy as np
from datasets import load_metric
# 加载准确率指标
metric = load_metric("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
Trainer类简化了训练循环。它根据提供的模型、参数 (parameter)、数据集、分词 (tokenization)器 (tokenizer)和指标函数,协调数据加载、模型正向/反向传播 (backpropagation)、优化、评估和检查点保存。
from transformers import Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=small_train_dataset, # 使用完整数据集进行实际训练
eval_dataset=small_eval_dataset, # 使用完整测试集进行正确评估
tokenizer=tokenizer, # 对于填充整理很有用
compute_metrics=compute_metrics,
)
一切设置就绪后,启动全参数 (parameter)微调过程只需一个命令:
# 启动训练过程
train_result = trainer.train()
# (可选)保存训练指标
trainer.log_metrics("train", train_result.metrics)
trainer.save_metrics("train", train_result.metrics)
# 保存最终微调好的模型和分词器
trainer.save_model(output_dir) # 由于 load_best_model_at_end=True,将保存最佳模型
trainer.save_state() # 保存 Trainer 状态,包括随机数生成器(RNG)状态
在训练期间,您将看到输出日志,显示训练损失逐渐减小,并且评估指标(准确率)在每个epoch后可能有所提升。这直接对应于模型参数 通过梯度下降 (gradient descent),基于在imdb数据集上计算的损失而被更新。
训练完成后,您可以在测试集(或指定的eval_dataset)上明确运行评估。
# 评估最终模型
eval_results = trainer.evaluate()
# 打印评估结果
print(f"评估结果: {eval_results}")
trainer.log_metrics("eval", eval_results)
trainer.save_metrics("eval", eval_results)
输出将显示您的微调模型在保留评估数据上的性能表现。
即使使用DistilBERT和部分数据,您可能也注意到训练需要相当多的时间和GPU内存。将此扩展到GPT-3变体或Llama模型等更大规模的模型,将需要更多资源,通常涉及多个高端GPU和分布式训练策略,正如第7章中所述。内存占用量大致随序列长度呈平方增长,并随更新参数 (parameter)的数量呈线性(或更高,取决于架构)增长。
这个动手练习展示了使用标准工具进行全参数微调 (fine-tuning)的完整流程。您加载了数据,进行了准备,配置了训练,执行了微调循环,并评估了结果。这个过程虽然有效,但强调了更新每个模型参数所涉及的计算成本。这种成本促使人们研究参数高效微调(PEFT)方法,我们将在下一章中介绍这些方法,目的是在大幅降低计算需求的情况下实现相似的适应结果。
简洁的语法。内置调试功能。从第一天起就可投入生产。
为 ApX 背后的 AI 系统而构建
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•