微调的效果是否优于少量提示?
如果问一个年轻的儿科医生和一位资深内科医生,谁更擅长治疗婴儿咳嗽?虽然二者都具备治疗儿童咳嗽的资质,但显然儿科医生会更专长于婴儿病症的诊断。
这就是微调对小模型的影响:它使较小、相对弱小的模型能在特定任务上胜过那些通用的大模型。
最近,我就遇到了这种选择的场景。
我在开发一个查询路由机器人,用于将用户的查询转发到正确的部门,之后由该部门的人工代理继续对话。背后其实是一个简单的文本分类任务。
GPT-4o 及其迷你版本在此任务上表现优秀,但同时它们既昂贵又僵化。而且它是一个封闭模型,无法在本地环境中进行微调。虽然 OpenAI 提供了微调服务,但费用不菲。
在 OpenAI 平台上训练 GPT-4o 每 100 万个 Token 成本高达 25 美元。我的训练数据很快达到了数百万 Token。更进一步,微调后的模型使用成本比原始模型高出约 50%。
如此高的成本对我的小型应用来说难以承受,我的项目资金有限,必须考虑更具性价比的替代方案。
备选方案是使用开源模型。开源模型在分类任务中表现不错,但训练所需的大量 GPU 资源也是一笔不小的开销。
我最终决定尝试小型模型。
小型语言模型是微调的理想对象,能够在成本上取得平衡并实现良好效果。小模型不仅可以在更廉价的硬件上运行,还可以使用更经济的 GPU 进行微调。此外,它们的训练和推理速度要比大型语言模型快得多。
候选模型包括 Phi3.5、DistillBERT 和 GPT-Neo 等。我最终选择了 Meta 的 Llama 3.2 的 1B 模型,也许是受到了最近的讨论和推广的影响。
本文将对比微调的 Llama 3.2–1B 模型与少量提示的 GPT-4o 在任务中的表现。
这是我的训练流程和成果。
微调 Llama 3.2 1B(无费用)
微调的成本较高,除非选择适当策略。你可以重新训练全部参数、迁移学习或参数高效微调。
- 全参数训练是最贵的选择,会重新训练模型中的所有 10 亿参数,费时费钱。此外,全参数微调可能会导致“灾难性遗忘”,即模型可能会遗失预训练阶段学到的一些知识。
- 迁移学习是个不错的选择,但稍显复杂。
- **参数高效微调(PEFT)既便宜又高效。这个策略只微调部分参数,其中低秩适应(LORA)**被认为是最佳选择。LORA 仅微调特定层中的一些参数,既能快速训练又能保持效果。
为了减少训练资源需求,我采用了量化。通过量化,可以将模型参数存储为 float16 或更小的数据类型,内存占用更小,计算速度更快,不过可能会略微影响准确性。
既然决定采用 LORA 进行微调,那么如何获取免费的训练资源呢?
Colab 或 Kaggle 笔记本提供免费的 GPU,通常足以用于小模型的微调。
微调的 Llama-3.2 对比少量提示的 OpenAI GPT-4o
LORA 微调已相当流行,这里不赘述教程。
可以参考 Unsloth 的 Colab 笔记本,它提供了详细的分步指导,我的微调任务也是基于该笔记本完成的。
以下是我的调整:
笔记本原本微调的是 Llama-3.2 3B 参数模型。如果这个模型适合你,可以保持不变,否则可以在多个可用模型中选择。我将其更改为 Llama-3.2–1B-Instruct,想测试小模型是否足以完成我的任务。
其次,需将数据集格式转换为微调所需的格式。我使用了自己的微调数据集,如下所示:
# 原代码
from unsloth.chat_templates import standardize_sharegpt
dataset = standardize_sharegpt(dataset)
dataset = dataset.map(formatting_prompts_func, batched=True,)
# 修改后
from datasets import Dataset
dataset = Dataset.from_json("/content/insurance_training_data.json")
dataset = dataset.map(formatting_prompts_func, batched=True,)
数据集结构可以简单定义为以下格式:
{
"conversations": [
{"role": "user", "content": "<user_query>"},
{"role": "assistant", "content": "<department>"}
]
}
评估微调模型
LLM 评估是一个广泛且实用的领域。这里我采用经典的混淆矩阵方式进行评估。
在笔记本末尾添加以下代码:
from langchain.prompts import FewShotPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel
# 1. 使用微调模型生成响应
def generate_response(user_query):
# 启用更快推理
FastLanguageModel.for_inference(model)
# 定义消息模板
messages = [
{"role": "system", "content": "You are a helpful assistant who can route the following query to the relevant department."},
{"role": "user", "content": user_query},
]
tokenized_input = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
).to("cuda")
generated_output = model.generate(
input_ids=tokenized_input, max_new_tokens=64, use_cache=True, temperature=1.5, min_p=0.1
)
decoded_response = tokenizer.batch_decode(generated_output, skip_special_tokens=True)[0]
assistant_response = decoded_response.split("\n\n")[-1]
return assistant_response
# 2. 使用 OpenAI GPT-4o 生成响应
class Department(BaseModel):
department: str
def predict_department(user_query):
structured_llm = llm.with_structured_output(Department)
prediction_chain = few_shot_prompt_template | structured_llm
result = prediction_chain.invoke(user_query)
return result.department
# 3. 读取评估数据集并预测部门
import json
with open("/content/insurance_bot_evaluation_data.json", "r") as f:
eval_data = json.load(f)
for ix, item in enumerate(eval_data):
print(f"{ix+1} of {len(eval_data)}")
item['open_ai_response'] = generate_response(item['user_query'])
item['llama_response'] = item['open_ai_response']
# 4. 计算预测的精度、召回率、准确率和 F1 分数。
# 4.1 使用 Open AI
from sklearn.metrics import precision_score, recall_score, accuracy_score, f1_score```yaml
OpenAI 响应评分:
精确度:0.9
召回率:0.75
准确率:0.75
F1 分数:0.818
Llama 响应评分:
精确度:0.88
召回率:0.73
准确率:0.79
F1 分数:0.798
结果显示微调后的 Llama-3.2 与 GPT-4o 在表现上非常接近,对于只有 10 亿参数的小模型来说,这是非常令人满意的成绩。
当然,GPT-4o 表现略胜一筹,但差距并不大。
我们可以在少量提示中提供更多示例以提升 GPT-4o 的效果,但这会增加成本,因为 OpenAI 按输入标记收费。
总结
我逐渐倾向于使用小型语言模型。它们不仅速度快、成本低,还能满足大多数实际需求,尤其是在微调后。
本文中,我展示了如何微调 Llama 3.2 1B 模型
,这是一个可在低成本硬件上运行的小型模型,非常适合文本分类任务。虽然小模型无法与 GPT-4o 或 Meta Llama 的 80 亿、110 亿和 900 亿参数模型的能力媲美,但对于不涉及多语言理解、视觉指引等复杂任务的应用场景,小模型已经足够。
如果这些高级功能并非必需,为何不尝试一个轻量且高效的小型大语言模型呢?