Datawhale 2025冬令营第二弹!!😊自己微调模型!
Datawhale AI冬令营(第一期) 垂类大模型构建
定制垂类大模型 = 优质数据集 + 开源大模型 |
整理者:博客园-岁月月宝贝!!!
姓名:HeYang
特别鸣谢Datawhale!!!:https://www.datawhale.cn/activity/110/21/82?rankingPage=1
经过上面一条与“嬛嬛”的切磋,我们可能发现:
eg.“为什么微调后一些基本问题好像都回答不上,感觉理解能力似乎没有增强?”
答:这里有两种情况:(1)问的问题在数据集分布内,这种情况就是①数据集质量不高;②并未实现有效微调.(2)问题在数据集分布外,这里是因为“微调效果提升,仅针对给定的数据集所涉及的领域提升”
下面附两张搞笑🤡图片:
续上文:
通用大模型≈高中生(更能说,更愿意表达)
微调用的数据集≈对世界的知识偏好
垂类大模型≈职业选手(在某一领域专精)
——这也是导致大家表示嬛嬛“过拟合”或者“乱说话”的原因😀
过拟合: 官方描述版:模型学习数据时,学到了数据中的所有细节,包括噪声(偶然出现的、不重要的东西),而非纯粹实现理解数据背后规律的目标。此时它在处理新数据时可能就不会表现得很好。 拟人化版:高考前的一次模考考后提供了带着答案的样卷,小白不仅记住了所有的题目和答案,还把试卷上的小污点、铅笔划痕都记下来了。接着小白每次做这份卷都是满分。但高考时,考的是题目类型差不多的一张新的试卷,但因为小白之前记忆了很多不必要的细节,不知道如何正确回答新卷上的问题了😔。 |
假如我们拿到了微调用的数据集,我们微调的流程又是怎样的呢?→
为了达到我们“垂类大模型”的目标,我们要不断评估、分析模型效果(分析效果不佳的原因),迭代数据集或微调方法~
接着,有的模型涉及到落地,比如前面的“网络小说家”,搭建一个大家都可以用的,可以设计身份背景与起承转合的“AI+小说写作” 应用系统岂不更好?
——思绪回到现实,我们先从构建一个“网络小说家”/“嬛嬛”这样一个微调好的模型开始!
总共有四大步:数据收集
、数据构建
、模型微调
、效果评价
我们先从上一篇讲到的“嬛嬛”(项目链接:GitHub - KMnO4-zx/huanhuan-chat)开始,学习基于小说、剧本微调出个性化聊天大模型!
微调流程图2:
人格化LLM-Chat-嬛嬛构建
1.1收集数据集素材
先了解下“大模型人格化”:通过一系列技术手段,使大模型在语言表达、行为模式、情感反应等方面更接近人类👩🍳,从而为用户带来更自然、更亲切、更个性化的交互体验 。
小例子:“语言表达风格的人格化”
- 模仿人类在不同场景下的语气,如正式、亲切、幽默等。
- 根据不同的应用场景和用户需求,展现出不同的语言风格(比如扮演npy,扮演风流的诗人)。
“嬛嬛”就是大模型人格化的一种哦!
其他人格化的论文&项目🌟
- Human Simulacra: Benchmarking the Personification of Large Language Models
- ChatHaruhi: Reviving Anime Character in Reality via Large Language Model
- Chat凉宫春日
为了构造“嬛嬛”,也就是构造一个可以“在对话中,能按照一问一答的方式,回复出对应人物风格的话语”的AI🤖,我们会不会直接想到,直接把嬛嬛说过的所有话喂给通用大于语言模型呀?
所以,不管我们想训练得到什么具体段位的嬛嬛,第一步就是----搜----剧----本!!!
PS:我们甚至还可以思考,究竟训练什么具体段位的嬛嬛
一般情况下,最简单的数据集获取方案是去相关开源平台搜索并下载,如:github(https://github.com/)、hugging face(https://huggingface.co/)、魔搭(https://modelscope.cn/datasets)等等平台 |
魔搭平台
但是你想,微调LLM可以用Word/PDF嘛?显然不行,所以----就到了下一步----
1.2构建数据集
1.2.1了解数据集格式
对于语言模型来说,微调数据必须是文本。所以,如果我们的数据源是WORD、PDF等格式,需要使用专门的解析工具(eg.python的docx、PyPDF2包),提取其中的文本。
接着我们再想想对话的构成,一句话不仅是一句话,它是被谁说的,上一句是什么,是不是都需要在喂给LLM时告它一声呀?
so,微调数据除了当前对话的输入和输出之外,还有一些其他的属性,如想要大模型扮演的角色、对话历史等等。
为了能和LLM更好地沟通,我们需要对数据结构化+将数据以json或者jsonl的格式进行存储。
下面是Datawhale为大家贴心准备的小介绍😖(本宝整理!)
首先是数据的存储格式:
JSON格式 VS JSONL格式
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,采用键值对的形式表示数据。JSON文件通常包含一个根对象,可以嵌套多个数组和基本数据类型,适合存储结构化数据如配置文件和API响应。
eg.包含个人信息的JSON对象:
{ "name": "张三", "age": 30, "isStudent": false, "courses": ["语文", "数学", "英语"], "address": { "city": "北京", "street": "朝阳路" } }
JSONL(JSON Lines)是一种文本文件格式,每行包含一个独立的JSON对象。这种格式适用于逐行处理大型数据集,每行之间没有分隔符,便于流式读取和处理,节省内存。
eg.包含多个个人信息的JSONL文件 :
{"name": "John", "age": 30, "city": "New York"} {"name": "Alice", "age": 25, "city": "Los Angeles"} {"name": "Bob", "age": 35, "city": "Chicago"}
load方式:json为可以直接load的格式。jsonl是一行一个json,分别进行load。
接着我们来了解数据集的格式!
目前主流的数据集有两种格式: Alpaca 格式和 ShareGPT 格式,其主要特点如下表所示。
格式 | 特点 | 结构 | 应用场景 |
---|---|---|---|
Alpaca格式 | 主要用于指令驱动的任务,如文本生成、摘要、翻译等。 包含清晰的指令、输入和输出字段,便于模型理解和执行。 |
数据集通常包括 instruction(人类指令)、input(人类输入,可选)、 output(模型回答,必填)、 system(系统提示词,可选)以及 history(历史消息列表) |
适用于指令监督微调、预训练、偏好训练、KTO(LLM与人类反馈对齐)和多模态任务。 |
ShareGPT格式 | 侧重于多轮对话,适用于对话系统的训练,模拟用户与AI的交互。支持更多角色种类,如human、gpt、observation、function等,这些角色以对象列表 形式出现在conversations列中。 |
数据集包含人类指令、模型回答、系统提示词(可选)和工具列表(可选)。 偏好数据集需包含更优和更差的回答,以及系统提示词(可选)和工具列表(可选)。 |
适用于需要模拟自然对话流的场景,例如聊天机器人、客服问答、陪伴式AI助手等。 |
小洋补充1:人类提示和人类输入有什么区别?
在实际应用中,"人类提示"可能用于设定对话的基调或目标,而"人类输入"则是对话过程中用户的具体参与内容。例如,在一个问答系统中,"人类提示"可能是一个问题,而"人类输入"可能是用户在对话过程中提供的额外信息或澄清。
小洋补充2:对于sharegpt,工具列表是什么?
在ShareGPT格式中,"工具列表"(tools)是一个可选字段,用于描述可用的外部工具或功能的信息。这些工具可能被模型用来执行某些任务或获取更多信息,例如调用外部API来获取天气信息(它允许模型在对话中利用这些工具来提供更加丰富和准确的回答)。在数据集的结构中,"tools"字段以对象列表的形式出现,每个对象包含工具的名称、描述以及可能需要的参数等信息。
下面的Alpaca格式要求+ShareGPT 格式要求源自讯飞用户手册描述的格式要求
Alpaca 格式要求
- instruction列对应的内容会与input列对应的内容拼接后作为模型输入,即为instruction\input。
- output列对应的内容为模型回答。
- 如果指定,system列对应的内容将被作为系统提示词。
- history列是由多个字符串二元组构成的列表,分别代表历史消息中每轮对话的指令和回答。
[ { "instruction": "人类指令(必填)", "input": "人类输入(选填)", "output": "模型回答(必填)", "system": "系统提示词(选填)", "history": [ ["第一轮指令(选填)", "第一轮回答(选填)"], ["第二轮指令(选填)", "第二轮回答(选填)"] ] } ]
Alpaca格式数据具体示例如下:
{ "instruction": "描述一个可以从人工智能技术中受益的医疗应用。", "input": "", "output": "用于帮助医生诊断疾病的人工智能辅助诊断系统可以被视为一个可以从人工智能技术中受益的医疗应用。这种系统通过使用深度学习算法来分析患者的病史、临床数据和医学影像资料,能够为医生提供准确的诊断建议。\n\n例如,人工智能技术可以用来分析X光片、 CT扫描和MRI扫描图像,帮助医生诊断肺炎,肿瘤,中风等疾病。此外,通过分析大量患者数据,人工智能系统能够找出患病风险较高的人群并预测潜在健康问题,从而为预防性医疗提供有力支持。\n\n这类人工智能系统不仅能够提高诊断准确性,还能帮助医生节约时间,让他们能够更好地关注患者的治疗。因此,人工智能辅助诊断系统是一个可以从人工智能技术中受益的医疗应用,具有广阔的发展前景。", "system": "", "history": "" }
💡突然想到,我们能否利用GPT自动补全这个“幻觉”功能,去越狱GPT呢?
ShareGPT 格式要求
相比Alpaca格式的数据集,ShareGPT格式支持更多的角色种类,例如human、gpt、observation、function等。它们构成一个对象列表呈现在conversations列中。
注: 其中human和observation必须出现在奇数位置,gpt和function必须出现在偶数位置。
[ { "conversations": [ { "from": "human", "value": "人类指令" }, { "from": "function_call", "value": "工具参数" }, { "from": "observation", "value": "工具结果" }, { "from": "gpt", "value": "模型回答" } ](必填), "system": "系统提示词(选填)", "tools": "工具描述(选填)" } ]
ShareGPT格式数据具体示例如下:
{ "conversations": [ { "from": "human", "value": "你好,我出生于1990年5月15日。你能告诉我我今天几岁了吗?" }, { "from": "function_call", "value": "{\"name\": \"calculate_age\", \"arguments\": {\"birthdate\": \"1990-05-15\"}}" }, { "from": "observation", "value": "{\"age\": 31}" }, { "from": "gpt", "value": "根据我的计算,你今天31岁了。" } ], "tools": "[{\"name\": \"calculate_age\", \"description\": \"根据出生日期计算年龄\", \"parameters\": {\"type\": \"object\", \"properties\": {\"birthdate\": {\"type\": \"string\", \"description\": \"出生日期以YYYY-MM-DD格式表示\"}}, \"required\": [\"birthdate\"]}}]" }
上图太模糊了,小洋把括号标清楚啦!😊
不用谢~~
Alpaca格式更简单 ,能够满足绝大多数场景。而ShareGPT的功能更强大,可以支持更复杂的任务需求。对于小白,建议选择Alpaca格式!
1.2.2构建数据集
构建人格化数据的大致思路是: 1.从原始数据中提取出角色和对话 2.筛选出我们关注的角色的对话 3.将对话转换成我们需要的格式 |
以 Chat-嬛嬛团队 从网络渠道所搜集到的剧本文档为例,原始的剧本数据——
第十一集
第203幕(续)
甄嬛:兔子急了也会咬人的。
眉庄:可是连苏培盛都棘手的事,陵容却能解决得这么干脆利落,实在是让我太意外了。
甄嬛:虽然意外,可陵容毕竟也是为了咱们。
眉庄:话是如此,可是我总还觉得这不像我日日面对的那个陵容。
(陵容在屏风外听得对话,默然离去。)
第204幕
(翊坤宫)
华妃:死绝了?
颂芝:死得透透的了,拉去乱葬岗的时候,有人亲眼瞧见。下手可真够狠的,脖子都勒断了半根,可吓人了。
华妃:那些没根儿的东西,做事倒是挺利落的。也难怪呀,她连皇上身边的人都敢得罪,活该她有今天。她没说漏了嘴吧?
颂芝:一句都没有。
华妃:算她识相。从前小看了那个病歪歪的甄嬛,以后走着瞧吧。(雷声)且闷了这么些天,也该下场大雨了。
这里,每一行都是人物及对应的台词,所以就可以通过简单的逐行读取,将这些数据处理成对话的形式:
我们将用如下代码处理:
def extract_dialogues(file_path):
result = []
current_act_dialogues = []
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
for line in lines:
line = line.strip()
if line.startswith("("): # 过滤旁白行
continue
if line.startswith("第") and line.endswith("幕"): # 解析新的一幕
if current_act_dialogues:
result.append(current_act_dialogues)
current_act_dialogues = []
elif ":" in line: # 按:切分角色和对话内容
role, content = line.split(":", 1)
role = role.strip()
content = content.strip()
current_act_dialogues.append({"role": role, "content": content})
if current_act_dialogues: # 保存最后一幕
result.append(current_act_dialogues)
return result
file_path = '甄嬛传剧本11-20.txt'
dialogues = extract_dialogues(file_path)
print(dialogues)
代码处理结果为:
[
{"role": "甄嬛", "content": "兔子急了也会咬人的。"},
{"role": "眉庄", "content": "可是连苏培盛都棘手的事,陵容却能解决得这么干脆利落,实在是让我太意外了。"},
{"role": "甄嬛", "content": "虽然意外,可陵容毕竟也是为了咱们。"},
{"role": "眉庄", "content": "话是如此,可是我总还觉得这不像我日日面对的那个陵容。"}
],
[
{"role": "华妃", "content": "死绝了?"},
{"role": "颂芝", "content": "死得透透的了,拉去乱葬岗的时候,有人亲眼瞧见。下手可真够狠的,脖子都勒断了半根,可吓人了。"},
{"role": "华妃", "content": "那些没根儿的东西,做事倒是挺利落的。也难怪呀,她连皇上身边的人都敢得罪,活该她有今天。她没说漏了嘴吧?"},
{"role": "颂芝", "content": "一句都没有。"},
{"role": "华妃", "content": "算她识相。从前小看了那个病歪歪的甄嬛,以后走着瞧吧。(雷声)且闷了这么些天,也该下场大雨了。"}
]
然后将我们关注的角色的对话提取出来,形成 QA 问答对。
对于这样格式比较规整的数据,我们可以使用正则表达式或者其他方法进行快速的提取,并抽取出我们关注的角色的对话。
def extract_zhenhuan_dialogues(dialogues):
result = []
zhenhuan_dialogues = []
for act in dialogues:
for i, dialogue in enumerate(act):
if "甄嬛" in dialogue["role"] and i > 0:#如果本句是甄嬛,就把上一句也放进去
zhenhuan_dialogues.append(act[i-1])
zhenhuan_dialogues.append(dialogue)
result.append(zhenhuan_dialogues)
zhenhuan_dialogues = []
return result
zhenhuan_dialogues = extract_zhenhuan_dialogues(dialogues)
print(zhenhuan_dialogues)
得到的大致效果为:
[
{"role": "眉庄", "content": "可是连苏培盛都棘手的事,陵容却能解决得这么干脆利落,实在是让我太意外了。"},
{"role": "甄嬛", "content": "虽然意外,可陵容毕竟也是为了咱们。”}
]
其中“眉庄”的content为预计的Question,甄嬛的content为预计的Answer
😭Datawhale甚至提供了处理剧本的0代码思路QwQ:
没有优秀的台词格式处理方法:
这时就需要从一大段文本中抽取角色的对话数据,然后将其转换成我们需要的格式。
比如《西游记白话文》,他的文本是这样的。
...... 原来孙悟空走了以后,有一个混世魔王独占了水帘洞,并且抢走了许多猴子猴孙。孙悟空听到这些以后,气得咬牙跺脚。他问清了混世魔王的住处,决定找混世魔王报仇,便驾着筋斗云,朝北方飞去。 不一会儿,孙悟空就来到混世魔王的水脏洞前,对门前的小妖喊到∶“你家那个狗屁魔王,多次欺负我们猴子。我今天来,要和那魔王比比高低! ”小妖跑进洞里,报告魔王。魔王急忙穿上铁甲,提着大刀,在小妖们的簇拥下走出洞门。 孙悟空赤手空拳,夺过了混世魔王的大刀,把他劈成了两半。然后,拔下一把毫毛咬碎喷了出去,毫毛变成许多小猴子,直杀进洞里,把所有的妖精全杀死,然后救出被抢走的小猴子,放了一把火烧了水帘洞。 ......
对于这种数据,推荐通过 讯飞星辰MaaS平台的问答对抽取功能,直接进行数据构建。
然后点击创建任务,上传西游记原始的文本数据,点击确定~
任务运行成功,点击右侧的查看~
就可以看到抽取出来的问答对数据啦~
注:问答对抽取需要一定的时间,建议先尝试少量数据,验证抽取效果没有问题后,再提交全量数据任务~
有代码基础的小伙伴也可以借助一个小工具: extract-dialogue 从文本中提取对话(PS:需要调用大模型来批量处理~)。
对话抽取完成后,再将其整理成 json 格式的数据,就大功告成了!
转化格式的代码:
整理为JSON文件:
import json def build_json_data(dialogues, json_file): result = [] for act in dialogues: sample = { "instruction": act[0]["content"], "input": "", "output": act[1]["content"] } result.append(sample) with open(json_file, 'w', encoding='utf-8') as f: json.dump(result, f, ensure_ascii=False, indent=4) build_json_data(zhenhuan_dialogues, 'dataset.json')
整理为JSONL文件:😔代码未给出
最后的数据集效果:
[ { "instruction": "可是连苏培盛都棘手的事,陵容却能解决得这么干脆利落,实在是让我太意外了。", "input": "", "output": "虽然意外,可陵容毕竟也是为了咱们。" } ]
这一步也可以增加 数据增强 的环节,比如
- 利用两到三条数据作为 example 丢给LLM,让其生成风格类似的数据。
- 也可以找一部分日常对话的数据集,使用 RAG 生成一些固定角色风格的对话数据。
小洋补充:RAG是什么?
RAG,全称为"Retrieval-Augmented Generation",即检索增强生成,是一种结合了信息检索和语言生成模型的人工智能技术。它通过从外部知识库中检索相关信息,并将其作为上下文信息用于生成模型,以提高生成文本的准确性和相关性。RAG特别适用于需要结合大量信息源来生成回答的场景,如问答系统、文档生成等。
1.3 配置微调参数
在完成训练集构建后,就可以上传数据,配置模型参数啦!
在讯飞星辰MaaS平台,可支持配置的微调参数可以分成下面三个大类。
参数大类 | 参数名称 | 说明 | 备注 |
---|---|---|---|
😻深度学习 | 学习率 | 控制在每次迭代中更新模型参数的步幅。建议值:1e-5~1e-4(1e-5=0.00001,1e-4=0.0001)。较小的学习率使更新更加稳定,但可能需要更多的迭代才能达到收敛。较大的学习率可以加快收敛速度,但可能会导致不稳定的训练过程甚至发散 | 建议使用默认 |
😻深度学习 | 训练次数 | 训练数据的次数,建议值为1-20,训练轮数过多可能会导致过拟合,出现通用能力的遗忘;训练轮数过少可能无法充分训练模型。训练的数据集较小可以设置大一点的epoch,让模型充分收敛。100条左右的数据集,建议epochs为10-20;1000条左右的数据集,建议epochs为5-10;10000条左右的数据集,建议epochs为2-5 | 复杂任务可适当增大 |
😻深度学习 | 输入序列分词后的最大长度 | 模型每次处理的文本序列的最大长度限制 (通常以分词后的token数量计算)。建议值为512-2048,较长的序列允许模型处理更多的上下文信息,能够捕捉到更复杂的依赖关系,但会增加计算开销和内存占用。较短的序列可以提高计算效率,但可能丢失部分上下文信息 | 长序列任务可适当增大 |
🐇LoRA | LoRA秩 | LoRA秩指在微调过程中使用的低秩矩阵的维度,决定适配器的复杂度和更新参数的数量。较低的秩可以减少内存和计算需求,提高微调效率,但可能会影响模型表现;较高的秩则能保留更多信息,适合对精度要求较高的任务,但过高的秩可能导致过拟合。建议值为4-64 | 复杂任务可适当增大 |
🐇LoRA | LoRA随机丢弃 | LoRA随机丢弃是一种正则化技术,用于在微调过程中随机丢弃低秩适配器中的部分权重,以防止模型过拟合,能在不显著增加计算复杂度的前提下提高模型的整体表现。基于此,模型能够在小数据集上提升泛化能力。LoRA的dropout应设置较小,以确保性能的稳定性,建议值为0-0.02 | 建议使用默认 |
🐇LoRA | LoRA作用模块 | LoRA作用模块主要集中在大型语言模型的自注意力机制和前馈神经网络上。通过引入低秩适配器, LoRA可以减少这些层在微调过程中的计算和内存需求,同时保留模型的性能 | 目前只支持all,即全部引入 |
🐇LoRA | LoRA缩放系数 | LoRA缩放系数(LoRA Alpha)用于调整低秩适配器对模型权重更新的影响,帮助控制适配器在微调过程中的贡献。较大的缩放系数可以增强适配器的作用,而较小的系数则保留更多原始模型的特征。建议值为16-32,合适的LoRA缩放系数可以平衡模型性能和适配器的有效性,确保在微调过程中模型的稳定性与适配器的表现都得到优化 | 建议使用默认 |
🐎训练加速 | Galore特性 | Galore(Gradient Low-Rank Projection)即梯度低秩投影,能够通过投影技术降低优化器状态的内存占用,大幅减少训练大模型时的内存需求,结合8位精度优化器进一步优化内存使用,同时保持模型的准确性。此外,Galore具备动态子空间切换和分层更新机制,使得模型训练更加灵活高效 | 建议使用默认 |
🐎训练加速 | BAdam特性 | Badam(Block coordinate method with Adam as an inner solver)是一种基于块坐标优化的内存高效优化器。通过将模型参数划分为多个块,每次只更新一个块的参数,大大减少了内存使用量。其核心特性包括块坐标优化框架、动态块切换机制以及结合Adam优化器,从而在有限的硬件资源上实现完整参数的高效微调 | 建议使用默认 |
1.4 评价微调效果
模型训练完成后,可以像速通手册一样,将模型发布为服务 ,使用简单数据进行效果验证。
如果效果符合预期,可以采用类似构建训练集的方式,构建测试集,进行批量推理 。
最后,如果希望将模型集成在要开发的应用中,可以在模型服务列表,查看调用信息 ,进行api调用。
生成能力plus-网文小说大模型构建
谢谢Datawhale!!!💘更多代码细节可见: 动手学小说创作模型微调
网文小说大模型,就是根据创作者设定的主题、人物、情节大纲等要素,自动生成故事框架、章节内容,为创作者提供创作灵感和基础文本。
🍭可以改善的部分:创作效率与质量,读者阅读体验优化,推动网文行业向智能化、个性化方向发展。
1.1 数据集素材构建
eg.已无版权的经典小说、AI写作的小说等
1.2 数据集格式构建
此数据集格式:同上的Alpaca格式
为了微调出一个网文小说大模型,我们需要的数据输入为主题、人物等要素,输出为小说章节内容。
期待的结果:
用户的输入:
你是一个熟读各类小说的专家,请你根据要求写一段800字左右的小说。 \n 现代励志故事,一个失业青年如何克服生活困境,终于实现自我突破,成为行业翘楚的心路历程
LLM的输出:
以此为主题的小说……
为了构建这样的微调数据,我们可以基于已有的小说章节,利用 通用大模型 的总结能力,让其生成输入的所有章节的情节概要,从而得到成对的输入和输出。
这里的数据集因为文本较长,外加我们现在是只有输出,未知输入的状态,所以整体构造数据集有3步~
1.2.1 数据分块
也就是拆分整篇小说的段落(为什么拆?我感觉可能是上下文输入框的限制,或?)
《呼啸山庄》的字数为: 215931 字
一年。我刚刚拜访过我的房东回来——就是那个将要给我惹麻烦的孤独的邻居。这儿可真是一个美丽的乡间!在整个英格兰境内,我不相信我竟能找到这样一个能与尘世的喧嚣完全隔绝的地方,一个厌世者的理想的天堂。而希刺克厉夫和我正是分享这儿荒凉景色的如此合适的一对。一个绝妙的人!在我骑着马走上前去时,看见他的黑眼睛缩在眉毛下猜忌地瞅着我。而在我通报自己姓名时.他把手指更深地藏到背心袋里,完全是一副不信任我的神气。刹那间,我对他产生了亲切之感,而他却根本未察觉到。\n“希刺克厉夫先生吗?”我说。\n回答是点一下头。\n“先生,我是洛克乌德,您的新房客。我一到这儿就尽可能马上来向您表示敬意,希望我坚持要租画眉田庄没什么使您不方便。昨天我听说您想——”。\n“画眉田庄是我自己的,先生。”他打断了我的话,闪避着。“只要是我能够阻止,我总是不允许任何人给我什么不方便的。
这里,用句号作为切分符,将句子合并成800字一段的段落。
# 拆分《呼啸山庄》的文本为 800 字一段的段落
sentences = []
for sentence in data.split('。'): # 使用句号作为切分符
sentences.append(sentence)
# 将句子合并成800字一段的段落
paragraphs = []
current_paragraph = ''
for sentence in sentences:
if len(current_paragraph) + len(sentence) <= 800:
current_paragraph += sentence+'。'
else:
paragraphs.append(current_paragraph.strip())
current_paragraph = sentence#这里另起一段的动作
# 将最后一段加入到段落列表中
if current_paragraph:
paragraphs.append(current_paragraph.strip())
# 打印切分后的段落
for idx, paragraph in enumerate(paragraphs):
print(f'段落 {idx + 1}: {paragraph}')
下面是拆分效果:
段落 1: 一年。我刚刚拜访过我的房东回来——就是那个将要给我惹麻烦的孤独的邻居。这儿可真是一个美丽的乡间!在整个英格兰境内,我不相信我竟能找到这样一个能与尘世的喧嚣完全隔绝的地方,一个厌世者的理想的天堂。而希刺克厉夫和我正是分享这儿荒凉景色的如此合适的一对。一个绝妙的人!在我骑着马走上前去时,看见他的黑眼睛缩在眉毛下猜忌地瞅着我。而在我通报自己姓名时.他把手指更深地藏到背心袋里,完全是一副不信任我的神气。刹那间,我对他产生了亲切之感,而他却根本未察觉到。
“希刺克厉夫先生吗?”我说。
回答是点一下头。....
段落 2: 的确,他们这儿一定是随时都流通着振奋精神的纯洁空气从房屋那头有几棵矮小的枞树过度倾斜,还有那一排瘦削的荆棘都向着一个方向伸展枝条,仿佛在向太阳乞讨温暖,就可以猜想到北风吹过的威力了。幸亏建筑师有先见把房子盖得很结实:窄小的窗子深深地嵌在墙里,墙角有大块的凸出的石头防护着。
在跨进门槛之前,我停步观赏房屋前面大量的稀奇古怪的雕刻,特别是正门附近,那上面除了许多残破的怪兽和不知羞的小男孩外,我还发现“一五”年代和“哈里顿·恩萧”的名字。我本想说一两句话,向这倨傲无礼的主人请教这地方的简短历史,但是从他站在门口的姿势看来,是要我赶快进去,要不就干脆离开,而我在参观内部之前也并不想增加他的不耐烦。
不用经过任何穿堂过道,我们径直进了这家的起坐间:他们颇有见地索性把这里叫作“屋子”。一般所谓屋子是把厨房和大厅都包括在内的;但是我认为在呼啸山庄里,厨房是被迫撤退到另一个角落里去了;至少我辨别出在顶里面有喋喋的说话声和厨房用具的磕碰声;而且在大壁炉里我并没看出烧煮或烘烤食物的痕迹,墙上也没有铜锅和锡滤锅之类在闪闪发光。倒是在屋子的一头,在一个大橡木橱柜上摆着一叠叠的白镴盘子;以及一些银壶和银杯散置着,一排排,垒得高高的直到屋顶,的确它们射出的光线和热气映照得灿烂夺目。橱柜从未上过漆;它的整个构造任凭人去研究。只是有一处,被摆满了麦饼、牛羊腿和火腿之类的木架遮盖住了。壁炉台上有杂七杂八的老式难看的枪,还有一对马枪;并且,为了装饰起见,还有三个画得俗气的茶叶罐靠边排列着。地是平滑的白石铺砌的;椅子是高背的,老式的结构,涂着绿色;一两把笨重的黑椅子藏在暗处。橱柜下面的圆拱里,躺着一条好大的、猪肝色的母猎狗,一窝唧唧叫着的小狗围着它,还有些狗在别的空地走动。
要是这屋子和家具属于一个质朴的北方农民,他有着顽强的面貌,以及穿短裤和绑腿套挺方便的粗壮的腿,那倒没有什么稀奇。
段落 3: 这样的人,坐在他的扶手椅上,一大杯啤酒在面前的圆桌上冒着白沫,只要你在饭后适当的时间,在这山中方圆五六英里区域内走一趟,总可以看得到的但是希刺克厉夫先生和他的住宅,以及生活方式,却形成一种古怪的对比。在外貌上他像一个黑皮肤的吉普赛人,在衣着和风度上他又像个绅士——也就是,像乡绅那样的绅士:也许有点邋遢,可是懒拖拖的并不难看,因为他有一个挺拔、漂亮的身材;而且有点郁郁不乐的样子。可能有人会怀疑,他因某种程度的缺乏教养而傲慢无礼;我内心深处却产生了同情之感,认为他并不是这类人。我直觉地知道他的冷淡是由于对矫揉造作——对互相表示亲热感到厌恶。他把爱和恨都掩盖起来,至于被人爱或恨,他又认为是一种鲁莽的事。不,我这样下判断可太早了:我把自己的特性慷慨地施与他了。希刺克厉夫先生遇见一个算是熟人时,便把手藏起来,也许另有和我所想的完全不同的原因。但愿我这天性可称得上是特别的吧。我亲爱的母亲总说我永远不会有个舒服的家。直到去年夏天我自己才证实了真是完全不配有那样一个家。
我正在海边享受着一个月的好天气的当儿,一下子认识了一个迷人的人儿——在她还没注意到我的时候,在我眼中她就是一个真正的女神。我从来没有把我的爱情说出口;可是,如果神色可以传情的话,连傻子也猜得出我在没命地爱她。后来她懂得我的意思了,就回送我一个秋波——一切可以想象得到的顾盼中最甜蜜的秋波。我怎么办呢?我羞愧地忏悔了——冷冰冰地退缩,像个蜗牛似的;她越看我,我就缩得越冷越远。直到最后这可怜的天真的孩子不得不怀疑她自己的感觉,她自以为猜错了,感到非常惶惑,便说服她母亲撤营而去。由于我古怪的举止,我得了个冷酷无情的名声;多么冤枉啊,那只有我自己才能体会。
我在炉边的椅子上坐下,我的房东就去坐对面的一把。为了消磨这一刻的沉默,我想去摩弄那只母狗。它才离开那窝崽子,正在凶狠地偷偷溜到我的腿后面,呲牙咧嘴地,白牙上馋涎欲滴。
...
1.2.2 数据收集
然后,通过编写Prompt,然后调用 通用大模型 ,例如 Spark Max,完成数据收集。例如:
总结下面的内容。用一句不超过50字的话总结这段小说的情节。仅回答总结,不需要添加其他内容。
这里可以进行 数据增强 ,优化Prompt。
然后点击 推理 ,查看Prompt优化前后的的效果。
这个过程可以重复多次,直到符合预期。
最后,通过 批量推理 ,获取训练集。(我想通俗易懂的就是每一个回答都需要一个梗概,后文会把它作为问题)
1.2.3 数据处理
完成数据收集后,就可以将得到的数据处理成Alpaca格式的训练集,并保存成jsonl。
具体来说,需要将:
- 推理的结果作为输入
- 分段的小说作为输出
- 额外编写一个训练的指令
def build_dataset(novel, data):
instruction = "你是一个熟读各类小说的专家,请你根据要求写一段800字左右的小说。"
dataset = [] # 初始化dataset列表
for summary, text in tqdm(data, desc=f"Processing {novel}", total=len(data)):
dataset.append({
"instruction": instruction,
"input": summary,
"output": text
})
with open(f"./data/{novel}.jsonl", "w", encoding='utf-8') as f: # 确保文件以utf-8编码写入
json.dump(dataset, f, ensure_ascii=False, indent=4)
1.3 配置微调参数
模型微调参数使用默认即可,参数说明详见Chat-嬛嬛。
1.4 微调效果评价
网文小说大模型的输入中,既包含instruction,也包含input(这个与嬛嬛不同),在体验时,需要将二者使用\n
进行拼接。样例:
你是一个熟读各类小说的专家,请你根据要求写一段800字左右的小说。
\n
现代励志故事,一个小洋洋如何克服生活困境,终于实现自我突破,成为行业翘楚的心路历程
我的尝试
这里我选择了下载量4.0K的“沐雪”
数据集看起来是这样哒!
好可爱哈哈哈哈
很快就微调好啦!
这里是任务信息:
参数配置单独展示:
下面是loss曲线:
(这个曲线看起来比嬛嬛还要好!)
上面是第一次对话~我感觉它有待提升,所以又有了下面的版本(主要增加了迭代次数):
参数设置如下:
参数+1
这里是loss曲线:
具体版:
接着,我更新了服务:
结果比原来好太多啦!😀
(沐雪上一个版本会承认自己是AI!这次纠正啦!)
再来一张!
(上图选效果评测可以看到!!!)
微调后的沐雪比原来的QWen_v2.5_7b对数据集中的话响应更快,更加“沐雪化”!
时间有限,我先去交作业啦!期待下次更多的尝试!!!!❤