一、前言
目前大模型的微调方法有很多,且大多可以在消费级显卡上进行,开发者完全可以在本地环境中微调自己的大模型。然而在实际操作中,我们常面临一个核心问题:数据集的构建。网络上虽然有许多开源数据集,但往往难以直接满足特定业务场景的需求。我们更希望使用某本书、某个作者的作品、内部聊天记录或特定角色的对话来微调模型。
本文详细介绍如何利用大语言模型自动化构建垂直领域数据集,并结合 PEFT 技术对 Phi-3 等开源模型进行低成本微调。内容涵盖从原始文本到 Alpaca 格式的数据转换流程、LangChain 链式调用实现、训练参数配置及推理部署步骤。文章重点解决了数据集生成中的 Prompt 设计、数据清洗、LoRA 微调参数调优以及推理性能优化等关键问题,旨在帮助开发者在有限算力下快速构建专属大模型应用。

目前大模型的微调方法有很多,且大多可以在消费级显卡上进行,开发者完全可以在本地环境中微调自己的大模型。然而在实际操作中,我们常面临一个核心问题:数据集的构建。网络上虽然有许多开源数据集,但往往难以直接满足特定业务场景的需求。我们更希望使用某本书、某个作者的作品、内部聊天记录或特定角色的对话来微调模型。
用于微调的高质量数据通常是成千上万的问答对(Instruction-Input-Output)。如果完全依靠人工搜集和标注,将耗费大量时间成本。本文将介绍一种高效的方式:利用大语言模型自动生成高质量数据集,并使用该数据集对大模型进行微调。
微调数据集通常采用问答对形式,例如 Alpaca 数据集的标准结构如下:
{
"instruction": "保持健康的三个提示。",
"input": "",
"output": "以下是保持健康的三个提示:\n\n1. 保持身体活动。每天做适当的身体运动..."
}
但在实际场景中,我们拥有的原始数据往往是一大段非结构化的文本,例如书籍内容或文档片段:
小时候,那时我还只有6岁,看到一本描写原始森林壮观景象的书,名叫真实的故事。书里有一幅很精彩的插画,画的是一条大蟒蛇正在吞食一只动物...
我们的目标是将这种大段文本转换为标准的 Alpaca 格式。过去这只能依赖人工,现在我们可以借助大模型的能力,通过 Prompt 工程让模型根据上下文提取对话和问答内容。
在系统提示词中,我们需要明确指示模型根据上下文提取问答对。示例如下:
QA_PAIRS_SYSTEM_PROMPT = """
<Context></Context> 标记中是一段文本,请学习和分析它,并整理学习成果:
- 提出问题并给出每个问题的答案。
- 答案需详细完整,尽可能保留原文描述。
- 答案可以包含普通文字、链接、代码、表格等 Markdown 元素。
- 最多提出 30 个问题。
"""
为了便于程序解析,我们需要规定严格的输出格式(JSON 数组):
QA_PAIRS_HUMAN_PROMPT = """
请按以下格式整理学习成果:
<Context>
{text}
</Context>
[
{{"question": "问题 1", "answer": "答案 1"}},
{{"question": "问题 2", "answer": "答案 2"}}
]
------
我们开始吧!
"""
首先导入必要的模块,包括文件加载、文本分割及 LangChain 组件:
import json
from typing import List
from tqdm import tqdm
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_openai import AzureChatOpenAI
from langchain_community.document_loaders import UnstructuredFileLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
我们需要将长文档切分为适合模型处理的片段:
def split_document(filepath):
loader = UnstructuredFileLoader(filepath)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2048,
chunk_overlap=128
)
documents = loader.load_and_split(text_splitter)
return documents
构建生成数据集的 Chain,包含 Prompt、LLM 和 Output Parser 三部分。
prompt = ChatPromptTemplate.from_messages([
("system", QA_PAIRS_SYSTEM_PROMPT),
("human", QA_PAIRS_HUMAN_PROMPT)
])
建议选择长上下文能力强于待微调模型的基座。此处以 Azure OpenAI 为例:
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
定义 Pydantic 模型以确保输出符合 JSON Schema:
from langchain_core.pydantic_v1 import BaseModel, Field
class QaPair(BaseModel):
question: str = Field(description='问题内容')
answer: str = Field(description='问题的回答')
class QaPairs(BaseModel):
qas: List[QaPair] = Field(description='问答对列表')
parser = JsonOutputParser(pydantic_object=QaPairs)
连接 Chain 并封装为函数:
def create_chain():
prompt = ChatPromptTemplate.from_messages([
("system", QA_PAIRS_SYSTEM_PROMPT),
("human", QA_PAIRS_HUMAN_PROMPT)
])
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
parser = JsonOutputParser(pydantic_object=QaPairs)
chain = prompt | llm | parser
return chain
主流程执行逻辑:
def main():
chain = create_chain()
documents = split_document('The.Little.Prince.txt')
datas = []
bar = tqdm(total=len(documents))
for idx, doc in enumerate(documents):
bar.update(1)
try:
out = chain.invoke({'text': doc.page_content})
if isinstance(out, dict) and 'qas' in out:
datas.extend(out['qas'])
except Exception as e:
print(f"Error processing doc {idx}: {e}")
with open('dataset.json', 'w', encoding='utf-8') as f:
json.dump(datas, f, ensure_ascii=False, indent=2)
print(f"Total pairs generated: {len(datas)}")
if __name__ == '__main__':
main()
准备好数据集后,可以使用 PEFT 库进行高效微调。这里以 Hugging Face Transformers 和 LoRA 技术为例。
LoRA(Low-Rank Adaptation)通过在旁支网络注入可训练参数,大幅降低显存需求。
from peft import LoraConfig, TaskType, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
# 配置 LoRA 参数
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=8, # 低秩矩阵维度
lora_alpha=32, # 缩放系数
lora_dropout=0.1
)
# 加载基座模型
model = AutoModelForCausalLM.from_pretrained(
"microsoft/Phi-3-mini-4k-instruct",
trust_remote_code=True,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
# 应用 LoRA
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
需要将自定义数据集转换为模型可接受的输入格式。
from datasets import load_dataset
def tokenize_function(example):
encoded = tokenizer(
example['question'],
truncation=True,
padding='max_length',
max_length=512
)
# 标签设为答案部分
labels = tokenizer(
example["answer"],
truncation=True,
padding="max_length",
max_length=512
)["input_ids"]
encoded["labels"] = [-100] * len(encoded["input_ids"]) # 默认忽略
# 仅将答案部分的 token 设为标签
input_len = len(encoded["input_ids"])
label_start = input_len - len(labels)
encoded["labels"][label_start:] = labels
return encoded
# 加载本地数据集
dataset = load_dataset('json', data_files={'train': 'dataset.json'})
tokenized_dataset = dataset.map(tokenize_function, batched=True)
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="outputs",
learning_rate=2e-4,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
num_train_epochs=3,
weight_decay=0.01,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
fp16=True,
logging_steps=10
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset["train"],
eval_dataset=tokenized_dataset["validation"],
tokenizer=tokenizer,
)
trainer.train()
model.save_pretrained("outputs")
微调完成后,需要加载适配器进行推理。LoRA 允许我们在不修改基座权重的情况下切换不同任务。
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
"microsoft/Phi-3-mini-4k-instruct",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
model = model.to("cuda")
model.load_adapter('outputs', adapter_name='lora01')
model.set_adapter("lora01")
model.eval()
inputs = tokenizer("作者小时候看了一本关于什么的书?", return_tensors="pt")
outputs = model.generate(
input_ids=inputs["input_ids"].to("cuda"),
max_new_tokens=100,
temperature=0.7
)
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0])
在使用大模型生成数据集时,可能会遇到幻觉或格式错误。建议在生成后进行简单的过滤:
fp16 或 bf16 可显著减少显存占用并加速训练。本文详细介绍了如何利用大语言模型自动化构建垂直领域数据集,并结合 PEFT 技术对 Phi-3 等开源模型进行低成本微调。通过这一流程,开发者可以快速构建专属知识库,无需依赖昂贵的 API 服务。未来可进一步探索多模态数据融合及分布式训练方案,以适应更大规模的应用场景。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online