Chapter 55
6.微调语言模型 Finetuning a language model
一. 微调语言模型的重要性

从头开始训练语言模型需要耗费大量时间和资源,而对这些模型进行评估同样需要复杂且资源密集的工作。因此,务必密切留意训练过程,并利用 checkpoint 来妥善应对可能出现的意外问题。仪表板(dashboard)是一个宝贵的工具,可以展示训练的进度和指标,并且在需要时提供 checkpoint。这样可以帮助您及时了解模型的性能,并保证训练过程的顺利进行。
微调语言模型是一种更经济高效的优化方式,特别是在计算资源有限的情况下。然而,在评估过程中仍然需要保持谨慎。根据使用语言模型的目标,可以制定出适合的评估策略,以确保模型达到所需的性能水平。
二. 使用 Hugging Face 微调语言模型
在本课程中,我们将展示如何使用 Hugging Face 微调语言模型。为了在 CPU 上高效地进行这项工作,我们将使用一个名为 TinyStories 的小型语言模型,它拥有 3300 万个参数。我们将在《龙与地下城》游戏世界的角色背景故事数据集上微调这个轻量级模型。
from transformers import AutoTokenizer
from datasets import load_dataset
from transformers import AutoModelForCausalLM
from transformers import Trainer, TrainingArguments
import transformers
transformers.set_seed(42)
import wandbwandb.login(anonymous="allow")Output
[34m[1mwandb[0m: Currently logged in as: [33manony-moose-980007700204230807[0m. Use [1m`wandb login --relogin`[0m to force relogin
True
AutoClasses 从提供给 from_pretrained() 方法的预训练模型的名称或路径中猜测要使用的架构。
AutoConfig、AutoModel 和 AutoTokenizer 可以根据名称/路径自动检索相关模型。
- 远程:huggingface.co 的仓库中根级别的表示,如 bert-base-uncased,或用户或组织名称下的命名空间,如 roneneldan/TinyStories-33M
- 本地:用 save_pretrained() 方法保存的目录
model_checkpoint = "roneneldan/TinyStories-33M"2.1 数据处理
首先,我们将从 Huggingface 加载一个包含《龙与地下城》角色背景故事的数据集。
ds = load_dataset('MohamedRashad/characters_backstories')# 让我们来看一个例子
ds["train"][400]Output
{'text': 'Generate Backstory based on following information\nCharacter Name: Dewin \nCharacter Race: Halfling\nCharacter Class: Sorcerer bard\n\nOutput:\n',
'target': 'Dewin thought he was a wizard, but it turned out it was the draconic blood in his veins that brought him eldritch power. Music classes in wizarding college taught him yet another use for his power, and when he was expelled he took up adventuring'}这个数据集包含两列:一列是文本,要求模型生成一个背景故事;另一列是目标,保存了角色的背景故事。
我们将对数据集进行分割,以便创建一个验证集。
# 由于该数据集没有预先分割的验证集,我们需要自行创建
ds = ds["train"].train_test_split(test_size=0.2, seed=42)在训练模型之前,我们需要将 text(角色信息) 和 target(背景故事) 进行拼接,并确保它们经过正确的分词和填充处理。
在这一过程中,Hugging Face 框架会自动为每个输入标记分配相应的正确 label,并将其用于模型的训练。由于模型需要预测序列中的下一个标记,Hugging Face 会自动将原始内容作为 label,并将这些 label 向右移动一个位置,以便模型能够正确地预测序列中的下一个标记。
AutoTokenizer 将会帮助我们获得预训练模型对应的 tokenizer,使模型处理预料与训练时保持一致。
# 我们将从模型 checkpoint 创建一个 tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, use_fast=False)
# 我们需要对样本进行 padding,以便在批次中使用相同长度的序列
tokenizer.pad_token = tokenizer.eos_token
# 将文本和目标首先进行拼接。然后使用 tokenizer 对拼接后的字符串进行分词
def tokenize_function(example):
merged = example["text"] + " " + example["target"]
batch = tokenizer(merged, padding='max_length', truncation=True, max_length=128)
batch["labels"] = batch["input_ids"].copy()
return batch
# 将其应用于我们的数据集,并删除文本列
tokenized_datasets = ds.map(tokenize_function, remove_columns=["text", "target"])Output
Map: 0%| | 0/465 [00:00<?, ? examples/s]
您可能会在这里收到一些警告,这没关系
在开始模型训练前,我们会先验证一下生成样本的质量,以确保一切正常。当我们进行解码输出时,您将首先看到一些指令,然后是生成的背景故事。如果一切看起来都很好,我们就可以继续了。
# 让我们看看一个准备好的例子
print(tokenizer.decode(tokenized_datasets["train"][900]['input_ids']))Output
Generate Backstory based on following information Character Name: Mr. Gale Character Race: Half-orc Character Class: Cleric Output: Growing up the only half-orc in a small rural town was rough. His mother didn't survive childbirth and so was raised in a church in a high mountain pass, his attention was always drawn by airships passing through, and dreams of an escape. Leaving to strike out on his own as early as he could he made a living for most of his life as an airship sailor, and occasionally a pirate. A single storm visits him throughout his life, marking every major
2.2 模型训练
我们将使用 Hugging Face 的 Transformers,及其 wandb 集成在数据集上微调预训练的语言模型。
我们创建的模型用于因果语言建模,这意味着它是一个自回归语言模型,类似于 GPT。它的任务是预测序列中的下一个词。我们将开始一个新的 Weights & Biases 运行,工作类型设置为训练。接下来,我们需要定义一些训练参数,比如训练周期数、学习率、权重衰减,并且非常重要的是,我们将设置 wandb 作为报告机制。这意味着您的所有结果将汇集到同一个集中仪表板中。这是您所需要做的一切,以确保指标的流动展示。
AutoModelForCausalLM 会从 checkpoint 自动载入对应的因果语言模型。
# 我们将根据预训练的检查点来训练因果(自回归)语言模型
model = AutoModelForCausalLM.from_pretrained(model_checkpoint);# 启动新的 wandb 运行
run = wandb.init(project='dlai_lm_tuning', job_type="training", anonymous="allow")Output
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
/content/wandb/run-20230813_134429-8o77rmiz<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
<IPython.core.display.HTML object>
# 定义训练参数
model_name = model_checkpoint.split("/")[-1]
training_args = TrainingArguments(
f"{model_name}-finetuned-characters-backstories",
report_to="wandb", # 我们需要一行来跟踪 wandb 中的实验
num_train_epochs=1,
logging_steps=1,
evaluation_strategy = "epoch",
learning_rate=1e-4,
weight_decay=0.01,
no_cuda=True, # 强制使用 CPU,将改为 use_cpu
)# 我们将使用 HF Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
)# 开始训练
trainer.train()Output
/usr/local/lib/python3.10/dist-packages/transformers/optimization.py:411: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning warnings.warn(
<IPython.core.display.HTML object>
| Epoch | Training Loss | Validation Loss |
|---|---|---|
| 1 | 2.804500 | 3.350718 |
TrainOutput(global_step=233, training_loss=3.7419421836541957, metrics={'train_runtime': 1912.6777, 'train_samples_per_second': 0.971, 'train_steps_per_second': 0.122, 'total_flos': 40423258718208.0, 'train_loss': 3.7419421836541957, 'epoch': 1.0})三. 模型训练结果的实时查看和分析
在模型训练过程中,我们可以通过点击链接来实时查看结果。随着时间的推移,我们可以观察到各种指标的变化。其中,我们最关注的指标是训练 loss,我们会持续关注它的变化趋势。在调试模型训练运行时,检查 loss 是否持续下降是非常有用的。因此,您希望看到这个曲线向下并向右移动。
对于一些非常大的语言模型,训练可能需要几天甚至几周的时间。因此,拥有一个可以远程查看图表的功能非常有帮助。这样可以确保我们的模型持续改进,并且有效利用 GPU 资源,避免浪费。

| loss0 | loss1 |
|---|---|
![]() | ![]() |
在完成模型训练后,我们将使用该模型生成样本。我们定义了一些 prompt,并使用它们来为我们的角色生成背景故事。接下来,我们创建一个新的表格,用于记录生成的结果。在每个 prompt 上调用 model.generate 来生成对应的文本。我们可以在此处传递各种参数,例如 top_p 或温度系数(temperature),以引导模型生成。生成的结果被添加到表格中,并最终记录在 wandb 中。
transformers.logging.set_verbosity_error() # 抑制 tokenizer 警告
prefix = "Generate Backstory based on following information Character Name: "
prompts = [
"Frogger Character Race: Aarakocra Character Class: Ranger Output: ",
"Smarty Character Race: Aasimar Character Class: Cleric Output: ",
"Volcano Character Race: Android Character Class: Paladin Output: ",
]
table = wandb.Table(columns=["prompt", "generation"])
# 在每个 prompt 上调用"model.generate"来生成对应的文本。
for prompt in prompts:
input_ids = tokenizer.encode(prefix + prompt, return_tensors="pt")
output = model.generate(input_ids, do_sample=True, max_new_tokens=50, top_p=0.3)
output_text = tokenizer.decode(output[0], skip_special_tokens=True)
table.add_data(prefix + prompt, output_text)
wandb.log({'tiny_generations': table})Output
[34m[1mwandb[0m: [33mWARNING[0m Artifacts logged anonymously cannot be claimed and expire after 7 days.
注意:LLM 并不总是生成相同的结果。您生成的角色和背景故事可能与视频不同。
wandb.finish()Output
<IPython.core.display.HTML object>
VBox(children=(Label(value='0.003 MB of 0.004 MB uploaded (0.000 MB deduped)\r'), FloatProgress(value=0.655475…
<IPython.core.display.HTML object>
Run history:
| eval/loss | ▁ |
| eval/runtime | ▁ |
| eval/samples_per_second | ▁ |
| eval/steps_per_second | ▁ |
| train/epoch | ▁▁▁▁▂▂▂▂▂▃▃▃▃▃▃▄▄▄▄▄▅▅▅▅▅▅▆▆▆▆▆▇▇▇▇▇████ |
| train/global_step | ▁▁▁▂▂▂▂▂▂▃▃▃▃▃▄▄▄▄▄▄▅▅▅▅▅▆▆▆▆▆▆▇▇▇▇▇████ |
| train/learning_rate | ████▇▇▇▇▇▆▆▆▆▆▆▅▅▅▅▅▄▄▄▄▄▄▃▃▃▃▃▂▂▂▂▂▂▁▁▁ |
| train/loss | █▅▃▂▃▃▂▃▃▃▂▂▃▃▂▃▂▂▃▃▂▃▂▂▂▂▃▃▂▃▂▂▂▂▁▂▂▁▂▂ |
| train/total_flos | ▁ |
| train/train_loss | ▁ |
| train/train_runtime | ▁ |
| train/train_samples_per_second | ▁ |
| train/train_steps_per_second | ▁ |
Run summary:
| eval/loss | 3.35072 |
| eval/runtime | 158.3652 |
| eval/samples_per_second | 2.936 |
| eval/steps_per_second | 0.373 |
| train/epoch | 1.0 |
| train/global_step | 233 |
| train/learning_rate | 0.0 |
| train/loss | 2.8045 |
| train/total_flos | 40423258718208.0 |
| train/train_loss | 3.74194 |
| train/train_runtime | 1912.6777 |
| train/train_samples_per_second | 0.971 |
| train/train_steps_per_second | 0.122 |
<IPython.core.display.HTML object>
Synced 5 W&B file(s), 1 media file(s), 1 artifact file(s) and 0 other file(s)
<IPython.core.display.HTML object>
./wandb/run-20230813_134429-8o77rmiz/logs我们可以在仪表板中查看 prompt 以及生成的样本。从中可以观察到,这个小模型存在一些问题。这是可以理解的,因为我们在优化速度而不是性能方面进行了调整。从您所提供的消息和输出中,您可以判断模型的表现是否良好。我们鼓励您提出可能与您特定用例相关的指标,并进行实现和记录。例如,您可以测量唯一单词的数量。在第三个 prompt 生成的样本中,我们可以看到它只使用了三个单词,即 the tribe of。这可能不是一个很好的输出。
因此,在下次训练或微调模型时,我们希望您能够利用这些工具更快地获得更好的结果。



