趋近智
大师班
为了在监督微调(SFT)过程中让模型有效学习,高质量的指令-回应数据集需要进行适当的格式化。SFT的目的是在给定特定输入(提示词)时,教会模型生成期望的输出(回应)。适当的格式化可确保模型理解任务结构,并将其学习重心放在生成正确的回应上。
最简单地说,每个SFT示例包含两部分:
模型在给定提示词后,会按顺序预测回应的标记。以一个直接的问答示例来说:
训练期间,模型会处理提示词并学习将其与目标回应关联起来。用于提示词的具体文本会因任务和期望的交互风格而有很大不同。它可能包含明确的指令、示例(在少量样本情况下),或对话历史。
结构需要根据你希望模型学习的具体行为来调整。
对于模型应遵循明确命令的任务,提示词会清楚地说明指令,通常后跟输入数据。
# 示例 1:摘要
提示词: "总结以下文章:\n\n[文章文本在此...]\n\n总结:"
回应: "[文章的简明摘要]"
# 示例 2:代码生成
提示词: "编写一个计算数字阶乘的Python函数。\n```python\n"
回应: "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n-1)\n```"
为了训练对话代理,格式必须呈现对话的往复特性。这通常涉及使用特殊标记或分隔符来区分用户和助手的发言。
# 使用特殊角色标记的对话示例格式
提示词: "<|USER|> 你好,能解释一下光合作用吗?\n<|ASSISTANT|>"
回应: " 光合作用是植物、藻类和蓝细菌将光能转化为化学能的过程..."
# 多轮对话示例格式
提示词: "<|USER|> 伦敦天气怎么样?\n<|ASSISTANT|> 伦敦现在多云,15°C。\n<|USER|> 明天呢?\n<|ASSISTANT|>"
回应: " 伦敦明天的预报是局部多云,最高气温18°C。"
使用<|USER|>和<|ASSISTANT|>等不同标记有助于模型学习对话结构并识别轮到谁发言。
对于需要推理的任务,回应本身可能包含得出最终答案的中间步骤。这会教导模型如何得到答案。
提示词: "问题:约翰有5个苹果。他又买了3箱,每箱有4个苹果。他总共有多少个苹果?\n答案:"
回应: " 约翰最初有5个苹果。他买了3箱 * 4个苹果/箱 = 12个苹果。总共,他有 5 + 12 = 17个苹果。最终答案是17。"
为了帮助模型在连接的输入序列中清楚地区分提示词和回应,通常会使用特殊标记。这些可以是模型分词器中预定义的标记(如 [SEP]、</s>、<|endoftext|>),也可以是专门为SFT添加的自定义标记(如 <|PROMPT|>、<|COMPLETION|>、<|END_OF_TURN|>)。
考虑一个使用通用标记的简化示例:
import torch
from transformers import AutoTokenizer
# 假设分词器已加载
tokenizer = AutoTokenizer.from_pretrained("gpt2") # 示例分词器
# 如果需要,添加特殊标记(首先检查它们是否存在)
special_tokens_dict = {'sep_token': '<|SEP|>', 'pad_token': '<|PAD|>'}
num_added_toks = tokenizer.add_special_tokens(special_tokens_dict)
# 如果添加了新标记,请记住调整模型嵌入层的大小
prompt = "Translate to French: Hello world"
completion = " Bonjour le monde"
# 使用分隔符进行简单格式化
formatted_text = (
f"{prompt}{tokenizer.sep_token}"
f"{completion}{tokenizer.eos_token}"
)
# 对格式化文本进行分词
tokenized_input = tokenizer(formatted_text, return_tensors="pt")
print("Formatted Text:", formatted_text)
print("Token IDs:", tokenized_input['input_ids'])
# 输出可能如下所示(标记ID取决于具体分词器):
# Formatted Text: Translate to French: Hello world<|SEP|>
# Bonjour le monde<|endoftext|>
# Token IDs: tensor([[ 14685, 284, 10607, 35, 995, 11858, 50257,
# 40195, 259, 813, 50256]])
分隔符的选择会影响模型在训练和推理时如何划分输入。在整个数据集中一致地应用这些标记是很重要的。
SFT训练的一个重要方面是确保损失只在回应标记上计算。模型应学习预测期望的输出,而不是预测它已作为输入收到的提示词本身。这通常通过使用标签掩码或修改注意力掩码来实现。
为模型准备批次数据时,输入ID将包含连接的提示词和回应标记。标签(损失函数的目标)通常是输入ID的右移版本。我们需要告知损失函数(例如CrossEntropyLoss)忽略为提示词标记计算的损失。
import torch
import torch.nn.functional as F
# 假设 tokenized_input 包含提示词 + 分隔符 + 回应 + eos
# input_ids = tokenized_input['input_ids'] # 形状:[batch_size, sequence_length]
# 示例:单个序列的ID
# 提示词: "Q: Why sky blue? <|SEP|>" -> ID [10, 20, 30, 40, 50] (长度 5)
# 回应: " Scattering <|EOS|>" -> ID [60, 70, 80] (长度 3)
# 连接后: [10, 20, 30, 40, 50, 60, 70, 80] (长度 8)
input_ids = torch.tensor([[10, 20, 30, 40, 50, 60, 70, 80]])
# 标签通常是 input_ids 向右移动的版本
# 模型在每个位置预测*下一个*标记
# 标签 = [10, 20, 30, 40, 50, 60, 70, 80] -> 移动后
# -> [20, 30, 40, 50, 60, 70, 80, <PAD>]
# 或更常见的是: [-100, -100, -100, -100, -100, 60, 70, 80]
# 其中 -100 是 ignore_index
labels = torch.tensor([[-100, -100, -100, -100, -100, 60, 70, 80]]) # 掩盖提示词标记
# 假设 model_output 的形状为 [batch_size, sequence_length, vocab_size]
# 示例虚拟输出 logits
vocab_size = 100
sequence_length = input_ids.shape[1]
model_output_logits = torch.randn(1, sequence_length, vocab_size)
# 计算损失
# 为 CrossEntropyLoss 重塑:需要 (N, C) 和 (N)
loss_fct = torch.nn.CrossEntropyLoss(ignore_index=-100)
# -100 是默认的忽略索引
loss = loss_fct(
model_output_logits.view(-1, vocab_size),
labels.view(-1)
)
print("Calculated Loss (only on completion tokens):", loss.item())
在此片段中,将与提示词标记对应的标签值设置为-100(PyTorch的CrossEntropyLoss的默认ignore_index)可确保这些位置不参与损失计算或梯度更新。只有模型对回应标记([60, 70, 80])的预测会受到惩罚。
通常,提示词和回应会连接成一个单一序列,并输入给模型,通常像前面所示那样由一个特殊标记分隔。模型随后会处理整个序列。
SFT输入序列的常见结构。提示词和回应被连接起来,通常带有分隔符和序列结束标记。损失通常只在回应部分计算。
在SFT数据集中所有示例中保持格式的绝对一致性是重要的。不一致地使用空格、换行符或特殊标记会使模型感到困惑,并阻碍其学习期望的模式。选择一种格式并统一应用。
SFT数据集通常以JSON Lines (.jsonl) 等格式存储,其中每行是一个代表一个示例的JSON对象。
{
"prompt": "对情感进行分类:'这部电影太棒了!'\n情感:",
"completion": " 积极"
}
{
"prompt": "写一首关于月亮的短诗。\n诗歌:",
"completion": " 银盘悬于夜幕,\n轻柔投下暗影,柔和而明亮"
}
{
"prompt": "<|USER|> 水的沸点是多少摄氏度?\n<|ASSISTANT|>",
"completion": " 水的沸点是100摄氏度"
}
或者,结构化格式可能会分开指令、输入和输出:
{
"instruction": "将以下英文文本翻译成西班牙语。",
"input": "今天天气很好。",
"output": " Hace buen tiempo hoy."
}
所选择的结构应清晰地映射到分词和训练时使用的提示词-回应格式。
一个实际的考量是模型支持的最大序列长度。如果连接的提示词和回应超过此限制,你需要一个截断策略。常见的方法包括:
最佳策略取决于具体任务以及提示词与回应内容的相对重要性。
通过仔细格式化你的SFT数据,清晰区分提示词和回应,并确保一致性,你为模型提供了它所需的结构化输入,以便有效学习指令遵循和有益对话等对齐行为。
这部分内容有帮助吗?
trl库中SFTTrainer的官方文档,提供了监督微调的数据准备、特殊标记和损失掩码的实用指导。© 2026 ApX Machine Learning用心打造