一、前言
目前大模型的微调方法有很多,而且大多可以在消费级显卡上进行,每个人都可以在自己的电脑上微调自己的大模型。
但是在微调时我们时常面对一个问题,就是数据集问题。网络上有许多开源数据集,但是很多时候我们并不想用这些数据集微调模型,我们更希望使用某本书、某个作者的作品、我们自己的聊天记录、某个角色的对话来微调模型。
用于微调的数据通常是成千上万的问答对,如果手工搜集,需要花费大量时间。
文本将介绍一种方式,利用大模型来构造自己的数据集,并使用我们构造的数据集来微调大模型。
二、构造数据集
2.1 目的
数据集通常是问答对形式,比如alpaca数据集的形式如下:
{
"instruction": "保持健康的三个提示。",
"input": "",
"output": "以下是保持健康的三个提示:\n\n1. 保持身体活动。每天做适当的身体运动,如散步、跑步或游泳,能促进心血管健康,增强肌肉力量,并有助于减少体重。\n\n2. 均衡饮食。每天食用新鲜的蔬菜、水果、全谷物和脂肪含量低的蛋白质食物,避免高糖、高脂肪和加工食品,以保持健康的饮食习惯。\n\n3. 睡眠充足。睡眠对人体健康至关重要,成年人每天应保证 7-8 小时的睡眠。良好的睡眠有助于减轻压力,促进身体恢复,并提高注意力和记忆力。"
}
但是实际上我们能拿到的数据通常是一大段文本的形式,比如:
小时候,那时我还只有6岁,看到一本描写原始森林壮观景象的书,名叫真实的故事。书里有一幅很精彩的插画,画的是一条大蟒蛇正在吞食一只动物,下面就是那幅插画的复制品。
这本书上说:“大蟒蛇把它们的猎物不加咀嚼地整个吞下去,之后,就再也不动了,然后通过长达六个月的睡眠来消化掉这些食物。”
...
现在我们要做的就是把大段文本形式的数据转换成alpaca的形式。
在以往我们只能通过人工的方式完成,而现在我们可以借助大模型的能力。大致思路就是让大模型根据文本,总结出对话、问答内容。这点可以通过Prompt工程实现。
2.2 Prompt设计
在系统Prompt中,我们需要强调根据上下文内容,让模型提取对话、问答等内容。比如:
QA_PAIRS_SYSTEM_PROMPT = """
<Context></Context> 标记中是一段文本,学习和分析它,并整理学习成果:
- 提出问题并给出每个问题的答案。
- 答案需详细完整,尽可能保留原文描述。
- 答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 Markdown 元素。
- 最多提出 30 个问题。
"""
这样就可以让模型自己提问,自己回答。然后我们需要规定输出的格式,我们希望得到字典数组,所以用户Prompt可以设置成:
QA_PAIRS_HUMAN_PROMPT = """
请按以下格式整理学习成果:
<Context>
文本
</Context>
[
{{"question": "问题1","answer":"答案1"}},
{{"question": "问题2","answer":"答案2"}},
]
------
我们开始吧!
<Context>
{text}
<Context/>
"""
根据问题不同,可以对上面的内容进行一些调整。下面可以开始编写代码。
2.3 处理文档
首先导入需要用到的模块:
import json
from typing import List
from tqdm import tqdm
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
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
在构建chain前,我们先完成文档处理的操作。我们希望传入的内容是文本数据,这里可以是txt等文件形式。我们这里以txt为例:
def split_document(filepath):
loader = UnstructuredFileLoader(filepath)
text_spliter = RecursiveCharacterTextSplitter(
chunk_size=2048,
chunk_overlap=128
)
documents = loader.load_and_split(text_spliter)
return documents
使用上面的函数,可以返回大段的文本片段。
2.4 构建chain
下面就是构建用于生成数据集的chain,包括Prompt、LLM、Outputparser三个部分内容分别如下:
2.4.1 Prompt
我们使用ChatPromptTemplate将上面的Prompt整合起来,代码如下:
QA_PAIRS_SYSTEM_PROMPT = "..."
QA_PAIRS_HUMAN_PROMPT = "..."
prompt = ChatPromptTemplate.from_messages([
("system", QA_PAIRS_SYSTEM_PROMPT),
("human", QA_PAIRS_HUMAN_PROMPT)
])
在QA_PAIRS_HUMAN_PROMPT中我们添加了{text}占位,invoke时需要传入{“text”: “xxx”}。
2.4.2 LLM
大模型的选择非常多,一般的建议是选择长上下文、且能力比你要微调的模型强的模型。这里使用GPT-3.5-16k,代码如下:
llm = AzureChatOpenAI(
azure_endpoint=endpoint,
deployment_name=deployment_name,
openai_api_key=api_key,
openai_api_version="2024-02-01",
)
2.4.3 OutputParser
最后是提取出结果,我们定义结果的Model:
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 = prompt | llm | parser
我们把构建chain的操作写成create_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')
with open(f'dataset.json', 'w', encoding='utf-8') as f:
datas = []
bar = tqdm(total=len(documents))
for idx, doc in enumerate(documents):
bar.update(idx + 1)
out = chain.invoke({'text': doc.page_content})
datas += out
f.write(json.dumps(datas, ensure_ascii=False))
if __name__ == '__main__':
main()
我使用小王子的书作为测试,下面是生成的部分数据集:
[ { "question": "作者小时候看了一本关于什么的书?", "answer": "描写原始森林壮观景象的书" }, { "question": "这本书上说大蟒蛇通过什么方式来消化食物?", "answer": "通过长达六个月的睡眠来消化食物" } ...]
我们可以收集同一作者的大量书籍,使用上面的方式构建数据集。在构建过程中,每次执行后,结果可能不一样,因此可以通过多次构建的方式生成更多样本。
三、微调模型
在准备好数据集后,我们就可以进行微调了,我们可以使用已有的项目进行微调,比如LLaMA-Factory就是一个不错的选择。
具体的微调方式可以参考项目文档。
本文选择使用peft模块实现微调操作,其实其它项目也是使用这个项目来完成。先导入必要的模块:
from peft import LoraConfig, TaskType
from transformers import Trainer
from datasets import load_dataset
from transformers import AutoModelForCausalLM, TrainingArguments, AutoTokenizer
from peft import get_peft_model
3.1 加载模型和配置LoRA
首先需要加载模型以及配置微调模型,我们选择使用LoRA进行微调:
# 配置参数
peft_config = LoraConfig(task_type=TaskType.SEQ_2_SEQ_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
)
tokenizer = AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
3.2 加载数据集
接下来加载我们创建的数据集:
def tokenize_function(example):
encoded = tokenizer(
example['question'],
truncation=True,
padding='max_length',
max_length=128
)
encoded["labels"] = tokenizer(
example["answer"],
truncation=True,
padding="max_length",
max_length=128
)["input_ids"]
return encoded
# 加载数据集
data_files = {"train": "train.json", "validation": "train.json"}
dataset = load_dataset('./dataset', data_files=data_files)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
3.3 配置训练参数并训练
接下来配置训练参数开始训练:
training_args = TrainingArguments(
output_dir="outputs",
learning_rate=1e-3,
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
num_train_epochs=2,
weight_decay=0.01,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
)
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")
我们可以根据硬件情况调整per_device_train_batch_size和per_device_eval_batch_size。现在只需要运行代码,等待片刻即可训练完成。
四、推理
接下来我们要做的就是推理了。LoRA是一个旁支网络,我们需要在原有的模型上,添加LoRA,添加方式如下:
model.load_adapter('outputs', adapter_name='lora01')
model.set_adapter("lora01")
调用上面代码后,model的推理操作就是添加LoRA后的推理。推理的完整代码如下:
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
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=50)
print(tokenizer.batch_decode(outputs.detach().cpu().numpy(), skip_special_tokens=True)[0])
最后我们可以和使用正常的AutoModelForCausalLM模型一样使用微调后的模型。
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓