在本次动手环节中,你将把全参数微调的原则应用到一个小型生成模型上。我们的目标是选用一个预训练模型,并使其适应特定的问答风格。本次练习将引导你完成整个流程,从加载数据到使用你新训练的模型生成文本。我们将使用 Qwen/Qwen2.5-0.5B 模型,它是 Qwen 的一个更小、更易于管理的版本,这使得它非常适合在单个 GPU 上运行此示例,例如 Google Colab 或 Kaggle Kernels 中提供的那些。数据集将是 squad 的一个小型子集,这是一个包含问题和基于阅读段落的回答的数据集。微调流程在编写代码之前,我们先概述一下将要采取的步骤。整个过程遵循一个结构化流程,这在机器学习项目中是一种常见模式。digraph G { rankdir=TB; node [shape=box, style="rounded,filled", fillcolor="#e9ecef", fontname="sans-serif"]; edge [fontname="sans-serif"]; subgraph cluster_prep { label = "准备"; style=filled; color="#f8f9fa"; ds [label="加载数据集\n(squad 子集)", fillcolor="#a5d8ff"]; model [label="加载基础模型和分词器\n(Qwen/Qwen2.5-0.5B)", fillcolor="#a5d8ff"]; tok [label="格式化和分词数据", fillcolor="#bac8ff"]; ds -> tok; model -> tok; } subgraph cluster_train { label = "训练"; style=filled; color="#f8f9fa"; args [label="配置训练参数", fillcolor="#ffec99"]; trainer [label="初始化训练器", fillcolor="#ffd8a8"]; train_loop [label="运行 trainer.train()", shape=ellipse, fillcolor="#ffc9c9"]; args -> trainer; train_loop -> trainer [dir=back]; } subgraph cluster_deploy { label = "使用"; style=filled; color="#f8f9fa"; save [label="保存微调模型", fillcolor="#b2f2bb"]; infer [label="进行推理", fillcolor="#96f2d7"]; save -> infer; } tok -> trainer; trainer -> save [lhead=cluster_deploy]; }整个过程始于数据和模型的准备,然后进入训练环节,在此环节中模型的权重会得到更新,最后以保存模型以便将来使用而结束。第一步:环境配置首先,请确保你已安装所需的库。transformers 库提供模型和 Trainer API,datasets 帮助我们管理数据,而 accelerate 则优化 PyTorch 的训练代码。pip install datasets tokenizers huggingface-hub transformers accelerate evaluate torch vllm如果我们想在线保存模型检查点,还需要登录 Hugging Face Hub。此步骤是可选的,但这是一个好的习惯。from huggingface_hub import notebook_login notebook_login()第二步:加载和准备数据集我们将使用 squad 数据集的一小部分以缩短训练时间。我们的目标是将问答对格式化成模型将学习生成的一个单一字符串。让我们加载数据集并创建一个简单的格式化函数。我们将把每个示例格式化为 question: [QUESTION] answer: [ANSWER]。这种结构会教会模型在看到“question:”前缀时生成一个回答。from datasets import load_dataset # 加载训练集的一小部分 train_dataset = load_dataset("squad", split="train[:5000]") # 将5000个示例分割为训练集和验证集 train_test_split = train_dataset.train_test_split(test_size=0.1) train_dataset = train_test_split["train"] eval_dataset = train_test_split["test"] # 定义格式化函数 def format_qa(example): # SQuAD 数据集有答案列表,取第一个 question = example["question"] answer = example["answers"]["text"][0] return f"question: {question} answer: {answer}"数据格式化即任务定义 你组织数据的方式是基础性的。通过将数据格式化为 question: ... answer: ...,我们就在无形中教会模型一个特定任务:即给定一个以 question: 开头的字符串,用一个相关的 answer: 来补全它。现在,我们需要加载分词器并将其应用到我们格式化的数据集上。分词器将我们的文本字符串转换为模型能理解的整数 ID。我们还将设置 pad_token 来处理不同长度的输入。from transformers import AutoTokenizer # 为 Qwen/Qwen2.5-0.5B 加载分词器 model_name = "Qwen/Qwen2.5-0.5B" tokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.pad_token = tokenizer.eos_token # 设置填充标记 def tokenize_function(examples): formatted_examples = [ format_qa({"question": q, "answers": a}) for q, a in zip(examples["question"], examples["answers"]) ] tokenized = tokenizer(formatted_examples, truncation=True, padding="max_length", max_length=128) tokenized["labels"] = tokenized["input_ids"].copy() # Add labels for training return tokenized # 对数据集应用分词 tokenized_train_dataset = train_dataset.map(tokenize_function, batched=True, remove_columns=train_dataset.column_names) tokenized_eval_dataset = eval_dataset.map(tokenize_function, batched=True, remove_columns=eval_dataset.column_names)第三步:配置和初始化模型数据准备就绪后,我们就可以加载 Qwen/Qwen2.5-0.5B 模型了。我们使用 AutoModelForCausalLM 是因为我们的任务是文本生成,也称因果语言建模。from transformers import AutoModelForCausalLM, TrainingArguments, Trainer # 加载预训练模型 model = AutoModelForCausalLM.from_pretrained(model_name)接下来,我们定义 TrainingArguments。此对象包含训练运行所需的所有超参数,例如学习率、训练轮数和批量大小。这些设置直接控制本章开头讨论的梯度下降更新过程。training_args = TrainingArguments( output_dir="qwen2.5-0.5b-squad-finetuned", num_train_epochs=3, per_device_train_batch_size=8, per_device_eval_batch_size=8, learning_rate=2e-5, weight_decay=0.01, eval_strategy="epoch", save_strategy="epoch", logging_steps=100, load_best_model_at_end=True, push_to_hub=False, # 如果已登录并想推送,请设置为 True )第四步:启动微调过程现在,我们已准备好将所有部分整合起来,放入 Trainer 对象中。它需要模型、训练参数、数据集和分词器。trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_train_dataset, eval_dataset=tokenized_eval_dataset, tokenizer=tokenizer, ) # 开始微调 trainer.train()当你运行 trainer.train() 时,你将看到一个进度条,显示训练损失和其他指标。此输出是你监控过程的主要工具,正如“监控训练”部分所述。持续下降的损失表明模型正在从你的数据中学习。/opt/conda/lib/python3.10/site-packages/transformers/trainer.py:... ***** Running training ***** Num examples = 4500 Num Epochs = 3 Instantaneous batch size per device = 8 ... Step | Training Loss | Validation Loss 100 | 1.503200 | N/A 200 | 1.354100 | N/A ... 563 | 1.298700 | 1.251142 ...第五步:评估和保存模型训练完成后,Trainer 将在每个训练轮次结束时自动评估模型在验证集上的表现。你也可以手动触发一次最终评估。import math eval_results = trainer.evaluate() print(f"困惑度: {math.exp(eval_results['eval_loss']):.2f}")困惑度是语言模型的一种常用评估指标;它衡量模型预测一段文本样本的准确程度。困惑度得分越低,性能越好。Trainer 会将最佳模型检查点保存到 TrainingArguments 中指定的 output_dir 目录下。你也可以手动将其保存到其他位置。# 保存模型和分词器 trainer.save_model("my_finetuned_qwen2.5-0.5b") tokenizer.save_pretrained("my_finetuned_qwen2.5-0.5b")第六步:测试你的微调模型最后一步是使用你的模型进行推理。让我们看看它是否已学会以期望的格式回答问题。我们可以使用 pipeline 工具进行一个简单的测试。from transformers import pipeline # 加载微调后的模型进行推理 finetuned_model = AutoModelForCausalLM.from_pretrained("my_finetuned_qwen2.5-0.5b") finetuned_tokenizer = AutoTokenizer.from_pretrained("my_finetuned_qwen2.5-0.5b") # 创建文本生成流程 generator = pipeline("text-generation", model=finetuned_model, tokenizer=finetuned_tokenizer) # 一个符合我们模型期望格式的新问题 prompt = "question: What is the main purpose of the immune system?" # 生成一个回答 result = generator(prompt, max_length=100, num_return_sequences=1) print(result[0]['generated_text'])你应该会看到一个以你的提示开始,并接着生成回答的输出,遵循 squad 数据集的风格。模型已成功调整其行为,从一个通用文本生成器变成一个更专业的问答器。本次动手练习展现了全参数微调的端到端过程。虽然有效,但更新每一个权重计算成本较高。下一章中,我们将介绍更高效的技术,这些技术能以一小部分计算成本达到类似的效果。