chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:59:13 +08:00
commit f2d31077b9
1276 changed files with 194291 additions and 0 deletions
+367
View File
@@ -0,0 +1,367 @@
# AMchat 高等数学大模型
## 📖 简介
AM (Advanced Mathematics) chat 是一个集成了数学知识和高等数学习题及其解答的大语言模型。该模型使用 Math 和高等数学习题及其解析融合的数据集,基于 InternLM2-Math-7B 模型,通过 xtuner 微调,专门设计用于解答高等数学问题。
你在这里可以学到 **收集数据**、**制作数据集**、**模型微调**和 **部署模型** 的全流程,帮助你更好地理解和掌握大模型应用开发的核心技术。
> *AMchat模型: Modelscope 地址:[*Link*](https://www.modelscope.cn/models/yondong/AMchat/summary)* *OpenXLab 地址:[*Link*](https://openxlab.org.cn/models/detail/youngdon/AMchat)*HuggingFace 地址:[*Link*](https://huggingface.co/axyzdong/AMchat)\
> *AMchat 项目地址:*[*Link*](https://github.com/AXYZdong/AMchat)\
> *AMchat 应用地址:*[*Link*](https://openxlab.org.cn/apps/detail/youngdon/AMchat)\
> *AMchat 视频介绍:*[*Link*](https://www.bilibili.com/video/BV14v421i7So/)
## 🛠️ 实现微调模型
接下来,我们将演示如何基于 Xtuner QLoRA 框架,快速实现一个用于高等数学的微调模型。 本教程将详细讲解针对 internLM2 进行数学微调的全流程,涵盖数据准备、微调执行以模型部署等环节。
本次微调示例选用 internlm2-math-7b 模型。你需要准备一台配备 24GB 显存的机器进行微调(例如 NVIDIA GeForce RTX 3090)。
微调模型的第一步是准备高质量的训练数据。对于数学大模型而言,你需要收集数学题目,数据来源可以是公开的数学数据集或自行收集的数据。
在准备完成数据来源以及获取到对应数据后,你需要使用该数据制作成结构化的数据集,以便于模型进行微调训练。
### Step 1: 数据准备
我们可以用调用api的方式让一些大模型自动生成数学题目,然后再将生成的数学题目进行答案的标注。
另外,我们也可以自己从一些考研数学题库中通过OCR的方式获取数学题目,然后再进行答案的标注。
对与每一条数据,需要整理成如下格式:
```json
{
"conversation": [
{
"input": "设 a_{n}=\\frac{3}{2} \\int_{0}^{\\frac{n}{n+1}} x^{n-1} \\sqrt{1+x^{n}} \\mathrm{~d} x, 则 \\lim _{n \\rightarrow \\infty} n a_{n}=\\\\",
"output": "\\left(1+\\mathrm{e}^{-1}\\right)^{\\frac{3}{2}}-1 \\\\ 解\\\\\\\\\\begin{aligned}\\\\a_{n} & =\\frac{3}{2} \\int_{0}^{\\frac{n}{n+1}} x^{n-1} \\sqrt{1+x^{n}} \\mathrm{~d} x=\\frac{3}{2} \\cdot \\frac{1}{n} \\int_{0}^{\\frac{n}{n+1}}\\left(1+x^{n}\\right)^{\\frac{1}{2}} \\mathrm{~d}\\left(1+x^{n}\\right) \\\\\\\\& =\\left.\\frac{3}{2} \\cdot \\frac{1}{n} \\cdot \\frac{2}{3}\\left(1+x^{n}\\right)^{\\frac{3}{2}}\\right|_{0} ^{\\frac{n}{n+1}}=\\frac{1}{n}\\left\\{\\left[1+\\left(\\frac{n}{n+1}\\right)^{n}\\right]^{\\frac{3}{2}}-1\\right\\} .\\\\\\end{aligned}\\\\\\\\\\\\由 \\lim _{n \\rightarrow \\infty}\\left(\\frac{n+1}{n}\\right)^{n}=\\mathrm{e}, 知 \\lim _{n \\rightarrow \\infty}\\left(\\frac{n}{n+1}\\right)^{n}=\\frac{1}{\\mathrm{e}}, 故\\\\\\\\\\lim _{n \\rightarrow \\infty} n a_{n}=\\lim _{n \\rightarrow \\infty}\\left\\{\\left[1+\\left(\\frac{n}{n+1}\\right)^{n}\\right]^{\\frac{3}{2}}-1\\right\\}=\\left(1+\\mathrm{e}^{-1}\\right)^{\\frac{3}{2}}-1 .\\\\"
}
]
}
```
每个 "conversation" 字段包含一个对话,对话中包含一个输入和一个输出。输入是数学题目,输出是数学题目的答案。
> 小规模开源数据集:[AMchat_dataset](https://github.com/AXYZdong/AMchat/tree/main/dataset)
### Step 2: 环境准备
1. clone 项目
```bash
git clone https://github.com/AXYZdong/AMchat.git
cd AMchat
```
2. 创建虚拟环境
```bash
conda env create -f environment.yml
conda activate AMchat
pip install xtuner
```
### Step 3: 模型微调
1. 基座模型下载
```bash
mkdir -p /root/math/model
```
`download.py`
```python
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('Shanghai_AI_Laboratory/internlm2-math-7b', cache_dir='/root/math/model')
```
2. 准备配置文件
```bash
# 列出所有内置配置
xtuner list-cfg
mkdir -p /root/math/data
mkdir /root/math/config && cd /root/math/config
xtuner copy-cfg internlm2_chat_7b_qlora_oasst1_e3 .
```
3. 修改配置文件
> 仓库中 `config` 文件夹下已经提供了一个微调的配置文件,可以参考 `internlm_chat_7b_qlora_oasst1_e3_copy.py`。
> 可以直接使用,注意修改 `pretrained_model_name_or_path` 和 `data_path` 的路径。
配置文件代码如下:
```python
import torch
from datasets import load_dataset
from mmengine.dataset import DefaultSampler
from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook,
LoggerHook, ParamSchedulerHook)
from mmengine.optim import AmpOptimWrapper, CosineAnnealingLR, LinearLR
from peft import LoraConfig
from torch.optim import AdamW
from transformers import (AutoModelForCausalLM, AutoTokenizer,
BitsAndBytesConfig)
from xtuner.dataset import process_hf_dataset
from xtuner.dataset.collate_fns import default_collate_fn
from xtuner.dataset.map_fns import oasst1_map_fn, template_map_fn_factory
from xtuner.engine import DatasetInfoHook, EvaluateChatHook
from xtuner.model import SupervisedFinetune
from xtuner.utils import PROMPT_TEMPLATE
#######################################################################
# PART 1 Settings #
#######################################################################
# Model
pretrained_model_name_or_path = '/root/math/model/Shanghai_AI_Laboratory/internlm2-math-7b'
# Data
data_path = '../dataset/AMchat_dataset.json'
prompt_template = PROMPT_TEMPLATE.internlm2_chat
max_length = 2048
pack_to_max_length = True
# Scheduler & Optimizer
batch_size = 2 # per_device
accumulative_counts = 1
dataloader_num_workers = 0
max_epochs = 3
optim_type = AdamW
lr = 2e-4
betas = (0.9, 0.999)
weight_decay = 0
max_norm = 1 # grad clip
warmup_ratio = 0.03
# Evaluate the generation performance during the training
evaluation_freq = 500
SYSTEM = "You're a professor of mathematics."
evaluation_inputs = [
'2x^2+3x+1=10,求x', '求积分 $\int_{0}^{1} x dx$'
]
#######################################################################
# PART 2 Model & Tokenizer #
#######################################################################
tokenizer = dict(
type=AutoTokenizer.from_pretrained,
pretrained_model_name_or_path=pretrained_model_name_or_path,
trust_remote_code=True,
padding_side='right')
model = dict(
type=SupervisedFinetune,
llm=dict(
type=AutoModelForCausalLM.from_pretrained,
pretrained_model_name_or_path=pretrained_model_name_or_path,
trust_remote_code=True,
torch_dtype=torch.float16,
quantization_config=dict(
type=BitsAndBytesConfig,
load_in_4bit=True,
load_in_8bit=False,
llm_int8_threshold=6.0,
llm_int8_has_fp16_weight=False,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type='nf4')),
lora=dict(
type=LoraConfig,
r=64,
lora_alpha=16,
lora_dropout=0.1,
bias='none',
task_type='CAUSAL_LM'))
#######################################################################
# PART 3 Dataset & Dataloader #
#######################################################################
train_dataset = dict(
type=process_hf_dataset,
dataset=dict(type=load_dataset, path='json', data_files=dict(train=data_path)),
tokenizer=tokenizer,
max_length=max_length,
dataset_map_fn=None,
template_map_fn=dict(
type=template_map_fn_factory, template=prompt_template),
remove_unused_columns=True,
shuffle_before_pack=True,
pack_to_max_length=pack_to_max_length)
train_dataloader = dict(
batch_size=batch_size,
num_workers=dataloader_num_workers,
dataset=train_dataset,
sampler=dict(type=DefaultSampler, shuffle=True),
collate_fn=dict(type=default_collate_fn))
#######################################################################
# PART 4 Scheduler & Optimizer #
#######################################################################
# optimizer
optim_wrapper = dict(
type=AmpOptimWrapper,
optimizer=dict(
type=optim_type, lr=lr, betas=betas, weight_decay=weight_decay),
clip_grad=dict(max_norm=max_norm, error_if_nonfinite=False),
accumulative_counts=accumulative_counts,
loss_scale='dynamic',
dtype='float16')
# learning policy
# More information: https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/param_scheduler.md # noqa: E501
param_scheduler = [
dict(
type=LinearLR,
start_factor=1e-5,
by_epoch=True,
begin=0,
end=warmup_ratio * max_epochs,
convert_to_iter_based=True),
dict(
type=CosineAnnealingLR,
eta_min=0.0,
by_epoch=True,
begin=warmup_ratio * max_epochs,
T_max=max_epochs,
convert_to_iter_based=True)
]
# train, val, test setting
train_cfg = dict(by_epoch=True, max_epochs=max_epochs, val_interval=1)
#######################################################################
# PART 5 Runtime #
#######################################################################
# Log the dialogue periodically during the training process, optional
custom_hooks = [
dict(type=DatasetInfoHook, tokenizer=tokenizer),
dict(
type=EvaluateChatHook,
tokenizer=tokenizer,
every_n_iters=evaluation_freq,
evaluation_inputs=evaluation_inputs,
system=SYSTEM,
prompt_template=prompt_template)
]
# configure default hooks
default_hooks = dict(
# record the time of every iteration.
timer=dict(type=IterTimerHook),
# print log every 100 iterations.
logger=dict(type=LoggerHook, interval=10),
# enable the parameter scheduler.
param_scheduler=dict(type=ParamSchedulerHook),
# save checkpoint per epoch.
checkpoint=dict(type=CheckpointHook, interval=1),
# set sampler seed in distributed evrionment.
sampler_seed=dict(type=DistSamplerSeedHook),
)
# configure environment
env_cfg = dict(
# whether to enable cudnn benchmark
cudnn_benchmark=False,
# set multi process parameters
mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
# set distributed parameters
dist_cfg=dict(backend='nccl'),
)
# set visualizer
visualizer = None
# set log level
log_level = 'INFO'
# load from which checkpoint
load_from = None
# whether to resume training from the loaded checkpoint
resume = False
# Defaults to use random seed and disable `deterministic`
randomness = dict(seed=None, deterministic=False)
```
也可以在命令行中直接修改配置文件。
```bash
cd /root/math/config
vim internlm_chat_7b_qlora_oasst1_e3_copy.py
```
```python
# 修改模型为本地路径
- pretrained_model_name_or_path = 'internlm/internlm-chat-7b'
+ pretrained_model_name_or_path = './internlm2-math-7b'
# 修改训练数据集为本地路径
- data_path = '../dataset/AMchat_dataset.json'
+ data_path = './data'
```
4. 开始微调
```bash
xtuner train /root/math/config/internlm2_chat_7b_qlora_oasst1_e3_copy.py
```
5. PTH 模型转换为 HuggingFace 模型
```bash
mkdir hf
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
xtuner convert pth_to_hf ./internlm2_chat_7b_qlora_oasst1_e3_copy.py \
./work_dirs/internlm2_chat_7b_qlora_oasst1_e3_copy/epoch_3.pth \
./hf
```
6. HuggingFace 模型合并到大语言模型
```bash
# 原始模型参数存放的位置
export NAME_OR_PATH_TO_LLM=/root/math/model/Shanghai_AI_Laboratory/internlm2-math-7b
# Hugging Face格式参数存放的位置
export NAME_OR_PATH_TO_ADAPTER=/root/math/config/hf
# 最终Merge后的参数存放的位置
mkdir /root/math/config/work_dirs/hf_merge
export SAVE_PATH=/root/math/config/work_dirs/hf_merge
# 执行参数Merge
xtuner convert merge \
$NAME_OR_PATH_TO_LLM \
$NAME_OR_PATH_TO_ADAPTER \
$SAVE_PATH \
--max-shard-size 2GB
```
7. Demo
```bash
streamlit run web_demo.py --server.address=0.0.0.0 --server.port 7860
```
### 致谢每一位贡献者
核心贡献者:
- [张友东](https://github.com/AXYZdong) Datawhale成员-东南大学)
- [宋志学](https://github.com/KMnO4-zx)Datawhale成员-中国矿业大学(北京))
- [肖鸿儒](https://github.com/Hongru0306)Datawhale成员-同济大学)
贡献者目录:
https://github.com/AXYZdong/AMchat/graphs/contributors
+221
View File
@@ -0,0 +1,221 @@
# Chat-嬛嬛 是如何炼成的
**Chat-甄嬛**是利用《甄嬛传》剧本中所有关于甄嬛的台词和语句,基于大模型进行**LoRA微调**得到的模仿甄嬛语气的聊天语言模型。
> 甄嬛,小说《后宫·甄嬛传》和电视剧《甄嬛传》中的女一号,核心女主角。原名甄玉嬛,嫌玉字俗气而改名甄嬛,为汉人甄远道之女,后被雍正赐姓钮祜禄氏,抬旗为满洲上三旗,获名“钮祜禄·甄嬛”。同沈眉庄、安陵容参加选秀,因容貌酷似纯元皇后而被选中。入宫后面对华妃的步步紧逼,沈眉庄被冤、安陵容变心,从偏安一隅的青涩少女变成了能引起血雨腥风的宫斗老手。雍正发现年氏一族的野心后令其父甄远道剪除,甄嬛也于后宫中用她的连环巧计帮皇帝解决政敌,故而深得雍正爱待。几经周折,终于斗垮了嚣张跋扈的华妃。甄嬛封妃时遭皇后宜修暗算,被皇上嫌弃,生下女儿胧月后心灰意冷,自请出宫为尼。然得果郡王爱慕,二人相爱,得知果郡王死讯后立刻设计与雍正再遇,风光回宫。此后甄父冤案平反、甄氏复起,她也生下双生子,在滴血验亲等各种阴谋中躲过宜修的暗害,最后以牺牲自己亲生胎儿的方式扳倒了幕后黑手的皇后。但雍正又逼甄嬛毒杀允礼,以测试甄嬛真心,并让已经生产过孩子的甄嬛去准格尔和亲。甄嬛遂视皇帝为最该毁灭的对象,大结局道尽“人类的一切争斗,皆因统治者的不公不义而起”,并毒杀雍正。四阿哥弘历登基为乾隆,甄嬛被尊为圣母皇太后,权倾朝野,在如懿传中安度晚年。
Chat-甄嬛,实现了以《甄嬛传》为切入点,打造一套基于小说、剧本的**个性化 AI** 微调大模型完整流程,通过提供任一小说、剧本,指定人物角色,运行本项目完整流程,让每一位用户都基于心仪的小说、剧本打造一个属于自己的、契合角色人设、具备高度智能的个性化 AI。
> *Chat-嬛嬛模型累计下载量 15.6kModelscope 地址:*[*Link*](https://www.modelscope.cn/models/kmno4zx/huanhuan-chat-internlm2)
> *Chat-嬛嬛累计获得 500 starhuahuan-chat 项目地址:*[*Link*](https://github.com/KMnO4-zx/huanhuan-chat.git)xlab-huanhuan-chat 项目地址:[*Link*](https://github.com/KMnO4-zx/xlab-huanhuan.git)
***OK,那接下来我将会带领大家亲自动手,一步步实现 Chat-甄嬛 的训练过程,让我们一起来体验一下吧~***
## Step 1: 环境准备
本文基础环境如下:
```
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
```
> 本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。
首先 `pip` 换源加速下载并安装依赖包
```shell
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.16.1
pip install transformers==4.43.1
pip install accelerate==0.32.1
pip install peft==0.11.1
pip install datasets==2.20.0
```
## Step 2: 数据准备
首先,我们需要准备《甄嬛传》剧本数据,这里我们使用了《甄嬛传》剧本数据,我们可以查看一下原始数据的格式。
```text
第2幕
(退朝,百官散去)
官员甲:咱们皇上可真是器重年将军和隆科多大人。
官员乙:隆科多大人,恭喜恭喜啊!您可是国家的大功臣啊!
官员丙:年大将军,皇上对你可是垂青有加呀!
官员丁:年大人,您可是皇上的股肱之臣哪!
苏培盛(追上年羹尧):年大将军请留步。大将军——
年羹尧:苏公公,有何指教?
苏培盛:不敢。皇上惦记大将军您的臂伤,特让奴才将这秘制的金创药膏交给大人,叫您使用。
年羹尧(遥向金銮殿拱手):臣年羹尧恭谢皇上圣恩!敢问苏公公,小妹今日在宫中可好啊?
苏培盛:华妃娘娘凤仪万千、宠冠六宫啊,大将军您放心好了。
年羹尧:那就有劳苏公公了。(转身离去)
苏培盛:应该的。
```
每一句都有人物及对应的台词,所以就可以很简单的将这些数据处理成对话的形式,如下:
```
[
{"role":"官员甲", "content":"咱们皇上可真是器重年将军和隆科多大人。"},
{"role":"官员乙", "content":"隆科多大人,恭喜恭喜啊!您可是国家的大功臣啊!"},
{"role":"官员丙", "content":"年大将军,皇上对你可是垂青有加呀!"},
{"role":"官员丁", "content":"年大人,您可是皇上的股肱之臣哪!"},
{"role":"苏培盛", "content":"年大将军请留步。大将军——"},
...
]
```
然后再将我们关注的角色的对话提取出来,形成 QA 问答对。对于这样的数据,我们可以使用正则表达式或者其他方法进行快速的提取,并抽取出我们关注的角色的对话。
然后很多情况下,我们并没有这样优秀的台词格式数据。所以我们可能就需要从一大段文本中抽取角色的对话数据,然后将其转换成我们需要的格式。
比如《西游记白话文》,我们可以看到他的文本是这样的。对于这样的文本,那我们就需要借助大模型的能力,从文本中提取出角色和角色对应的对话。然后再筛选出我们需要的角色对话。
> 可以借助一个小工具:[*extract-dialogue*](https://github.com/KMnO4-zx/extract-dialogue.git) 从文本中提取对话。
```
......
原来孙悟空走了以后,有一个混世魔王独占了水帘洞,并且抢走了许多猴子猴孙。孙悟空听到这些以后,气得咬牙跺脚。他问清了混世魔王的住处,决定找混世魔王报仇,便驾着筋斗云,朝北方飞去。
不一会儿,孙悟空就来到混世魔王的水脏洞前,对门前的小妖喊到∶“你家那个狗屁魔王,多次欺负我们猴子。我今天来,要和那魔王比比高低!
”小妖跑进洞里,报告魔王。魔王急忙穿上铁甲,提着大刀,在小妖们的簇拥下走出洞门。
孙悟空赤手空拳,夺过了混世魔王的大刀,把他劈成了两半。然后,拔下一把毫毛咬碎喷了出去,毫毛变成许多小猴子,直杀进洞里,把所有的妖精全杀死,然后救出被抢走的小猴子,放了一把火烧了水脏洞。
......
```
> chat-甄嬛 的原始数据:[*甄嬛传*](https://github.com/KMnO4-zx/huanhuan-chat/tree/master/dataset/input/huanhuan)
> 西游记白话文原始数据:[*西游记*](https://github.com/KMnO4-zx/huanhuan-chat/blob/master/dataset/input/wukong/%E8%A5%BF%E6%B8%B8%E8%AE%B0%E7%99%BD%E8%AF%9D%E6%96%87.txt)
最后再将其整理成 `json` 格式的数据,如下:
```
[
{
"instruction": "小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——",
"input": "",
"output": "嘘——都说许愿说破是不灵的。"
},
{
"instruction": "这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。",
"input": "",
"output": "你们俩话太多了,我该和温太医要一剂药,好好治治你们。"
},
{
"instruction": "嬛妹妹,刚刚我去府上请脉,听甄伯母说你来这里进香了。",
"input": "",
"output": "出来走走,也是散心。"
}
]
```
> Chat-嬛嬛 的数据:[*chat-甄嬛*](https://github.com/datawhalechina/self-llm/blob/master/dataset/huanhuan.json)
所以,在这一步处理数据的大致思路就是:
***1. 从原始数据中提取出角色和对话  2. 筛选出我们关注的角色的对话  3. 将对话转换成我们需要的格式***
> *这一步也可以增加数据增强的环节,比如利用两到三条数据作为 example 丢给LLM,让其生成风格类似的数据。再或者也可以找一部分日常对话的数据集,使用 RAG 生成一些固定角色风格的对话数据。这里大家可以完全放开的大胆去尝试!*
## Step 3: 模型训练
那这一步,大家可能再熟悉不过了。在`self-llm`的每一个模型中,都会有一个 `Lora` 微调模块,我们只需要将数据处理成我们需要的格式,然后再调用我们的训练脚本即可。
此处选择我们选择 `LLaMA3_1-8B-Instruct` 模型进行微调,首先还是要下载模型,创建一个`model_download.py`文件,输入以下内容:
```python
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('LLM-Research/Meta-Llama-3.1-8B-Instruct', cache_dir='/root/autodl-tmp', revision='master')
```
> 注意:记得修改 `cache_dir` 为你的模型下载路径哦~
其次,准备训练代码。对于熟悉 `self-llm` 的同学来说,这一步可能再简单不过了,在此处我会在当前目录下放置`train.py`,大家修改其中的数据集路径和模型路径即可。
> *当然也可以使用 `self-llm` 中的 `lora` 微调教程。教程地址:[Link](https://github.com/datawhalechina/self-llm/blob/master/models/LLaMA3/04-LLaMA3-8B-Instruct%20Lora%20%E5%BE%AE%E8%B0%83.md)*
在命令行运行以下指令:
```shell
python train.py
```
> *注意:记得修改 `train.py` 中的数据集路径和模型路径哦~*
训练大概会需要 *20 ~ 30* 分钟的时间,训练完成之后会在`output`目录下生成`lora`模型。可以使用以下代码进行测试:
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
mode_path = './LLM-Research/Meta-Llama-3___1-8B-Instruct'
lora_path = './output/llama3_1_instruct_lora/checkpoint-699' # 这里改称你的 lora 输出对应 checkpoint 地址
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16, trust_remote_code=True).eval()
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path)
prompt = "嬛嬛你怎么了,朕替你打抱不平!"
messages = [
{"role": "system", "content": "假设你是皇帝身边的女人--甄嬛。"},
{"role": "user", "content": prompt}
]
input_ids = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
# print(input_ids)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print('皇上:', prompt)
print('嬛嬛:',response)
```
```
皇上: 嬛嬛你怎么了,朕替你打抱不平!
嬛嬛: 皇上,臣妾不是故意的。
```
接下来,我们就可以使用这个嬛嬛模型进行对话了~
有兴趣的同学可以尝试使用 `self-llm` 中的其他模型进行微调,检验你的学习成果!
## 写在最后
*Chat-嬛嬛是在去年LLM刚火起来的时候,我们觉得如果不做点什么的话,可能会错过很多有趣的事情。于是就和几个小伙伴一起,花了很多时间,做了这个项目。在这个项目中,我们学到了很多,也遇到了很多问题,但是我们都一一解决了。并且Chat-嬛嬛也获得了奖项,让项目得到了很多人的关注。所以,我觉得这个项目是非常有意义的,也是非常有趣的。*
- *2023 讯飞星火杯人认知大模型场景创新赛 Top50*
- *2024 书生·浦语大模型挑战赛(春季赛)创意应用奖 Top12*
### Chat-嬛嬛贡献者
- [宋志学](https://github.com/KMnO4-zx)Datawhale成员-中国矿业大学(北京))
- [邹雨衡](https://github.com/logan-zou)Datawhale成员-对外经济贸易大学)
- [王熠明](https://github.com/Bald0Wang)Datawhale成员-宁夏大学)
- [邓宇文](https://github.com/GKDGKD)Datawhale成员-广州大学)
- [杜森](https://github.com/coderdeepstudy)Datawhale成员-南阳理工学院)
- [肖鸿儒](https://github.com/Hongru0306)Datawhale成员-同济大学)
+83
View File
@@ -0,0 +1,83 @@
from datasets import Dataset
import pandas as pd
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig
from peft import LoraConfig, TaskType, get_peft_model
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\nToday Date: 26 Jul 2024\n\n现在你要扮演皇帝身边的女人--甄嬛<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{example['instruction'] + example['input']}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
response = tokenizer(f"{example['output']}<|eot_id|>", add_special_tokens=False)
input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为eos token咱们也是要关注的所以 补充为1
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
if len(input_ids) > MAX_LENGTH: # 做一个截断
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
if __name__ == "__main__":
model = AutoModelForCausalLM.from_pretrained('./LLM-Research/Meta-Llama-3___1-8B-Instruct', device_map="auto",torch_dtype=torch.bfloat16)
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
tokenizer = AutoTokenizer.from_pretrained('./LLM-Research/Meta-Llama-3___1-8B-Instruct', use_fast=False, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
# 将JSON文件转换为CSV文件
df = pd.read_json('huanhuan.json')
ds = Dataset.from_pandas(df)
tokenized_id = ds.map(process_func, remove_columns=ds.column_names)
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
inference_mode=False, # 训练模式
r=8, # Lora 秩
lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
lora_dropout=0.1# Dropout 比例
)
model = get_peft_model(model, config)
model.print_trainable_parameters() # 打印总训练参数
args = TrainingArguments(
output_dir="./output/llama3_1_instruct_lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=100, # 为了快速演示,这里设置10,建议你设置成100
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train() # 开始训练
# 在训练参数中设置了自动保存策略此处并不需要手动保存。
+725
View File
@@ -0,0 +1,725 @@
# 天机简介
想从零学会大语言模型应用?想直接复制粘贴代码?天机满足你的一切需求;甚至,你还能学到一点意想不到的东西。
中国文化注重人情世故,涉及到复杂的社交规则、礼仪和人际关系。Social 团队通过深入理解中文语境、文化特点和丰富的语料,提供了基于各类中国式文化的场景下的应对方法、对话案例与情景模拟。
目前天机已经支持的应用场景包括:如何送**祝福、敬酒、请客、送礼、人际交流、化解尴尬以及应对矛盾**等等,还有更多社区小伙伴们建议的 LLM 提高情商方案正在制作中~
除了把这个项目当成一个好玩的社交大模型外,你还可以把它当作完整的全栈大语言模型应用入门学习仓库! 你可以在里面学到**提示词工程 、智能体制作、 模型微调、RAG 数据清洗与使用,以及代码规范**...等等你所需要的大语言模型应用知识,你可以在通过学习该项目后,  快速改造出属于自己的新的大语言模型项目,我们期待你成为下一个大模型应用开发高手!
> *Tianji 开源链接:*[*Link*](https://github.com/SocialAI-tianji/Tianji)
> *Tianji 官方网站:*[*Link*](https://socialai-tianji.github.io/socialai-web/)
> *Tianji 知识库版应用地址:*[*Link*](http://120.76.130.14:6006/knowledges/)
> *Tianji prompt版应用地址:*[*Link*](http://120.76.130.14:6006/prompt/)
> *Tianji 视频介绍:*[*Link*](https://www.bilibili.com/video/BV1cvbyefEfp)
由于内容众多,时间关系,本篇教程只会带你简单看看如何快速运行天机的“送祝福”微调模型,更多有趣的内容,等你来天机项目和文档亲自探索!
## 实现送祝福微调模型
接下来,让我们快速实现一个属于自己的送祝福微调模型。我们将基于 Xtuner Qlora,详细讲解如何对 internLM2 进行天机送祝福模块的微调,全流程包括数据制造、推理微调等所有环节。
在本次微调示范中,我们选用的是 internlm2-chat-7b 模型,你需要准备一台24G显存的机器用于微调(3090即可)。
微调一个模型的第一步是准备高质量的训练数据。对于一个送祝福模型,你需要收集各种祝福语的数据,数据来源可以是公开的祝福语数据集、社交媒体、电子书籍或者任何包含丰富祝福语的文本。
在准备完成数据来源以及获取到对应数据后,你需要使用该数据文本进行数据制造(比如下面演示的few shot,但这只是最小的例子,真正意义的数据制造你需要用一个数据“知识”切块去生成对应的QA对,这是才是我们最后期望得到的数据。
所以,理论上最好的数据是利用这些现有知识,通过更聪明的大模型基于这些知识得到高精度的回复QA对数据,也有的人是通过大模型抽取小说文本对话的方式来实现格式抽取,但总之你需要的是一个最好无限火力的聪明大模型来帮助你进行文本数据清洗。
当你成功打通微调后,你会发现**真正复杂的工作都是在清洗数据、处理、生成数据、归类数据**上,这些才是影响最后效果的**最大难点问题**。
这里推荐你使用本地的llm去进行数据清洗(除非你财大气粗),否则api key很容易分分钟用完,你可以通过部署本地 llama3-chinese 或者 qwen 进行数据制造工作。
接下来我们来看看如何进行数据制造:
### 数据处理
#### 数据制造
在清洗数据前,请确保你已经安装对应SDK如zhipuai以及openai SDK,安装后直接运行即可。
```python
from zhipuai import ZhipuAI
import time
import json
import random
import datetime
# zhipuai
# 此处填写您自己的APIKey
# zhipu_api_key = ""
# client = ZhipuAI(api_key=zhipu_api_key)
# def get_data_zhipu(content):
# response = client.chat.completions.create(
# model="glm-4", # 填写需要调用的模型名称
# messages=[
# {"role": "system", "content": "你现在是一个精通言语表达、热爱他人、尊重长辈、富有文采的送祝福大师,请你编辑一条文本,表示对应场景的祝福语"},
# {"role": "user",
# "content": content,
# "temperature": 1} # 多样化输出
# ],
# )
# res = response.choices[0].message.content
# return res
# deepseek
from openai import OpenAI
deepseek_key = "" #此处填写deepseek的key
client = OpenAI(api_key=deepseek_key, base_url="https://api.deepseek.com/v1")
def get_data_ds(content):
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你现在是一个精通言语表达、热爱他人、尊重长辈、富有文采的送祝福大师,请你编辑一条文本,表示对应场景的祝福语"},
{"role": "user",
"content": content,
"temperature": 1} # 多样化输出
]
)
res = response.choices[0].message.content
return res
# 可利用大模型补充不同对象 当前28种
name_list = ['赵老师', '大舅', '大伯', '李总', '邻居赵大妈', '母亲', '姐姐', '妹妹', '哥哥', '弟弟', '爷爷', '奶奶', '外公',
'外婆', '伯母', '叔叔', '阿姨', '堂兄', '堂妹', '表哥', '表妹', '导师', '同学', '同事', '领导',
'邻居', '老板', '医生', ]
# 可利用大模型补充对应场景 当前18种
scenes = ['生日', '春节', '元宵节', '端午节', '七夕节', '中秋节',
'重阳节', '除夕', '腊八节','谈判顺利','乔迁新居', '周年纪念' ,'新婚快乐' ,'家庭和睦', '比赛取得好成绩' ,'发财','工作升职 ','康复', ]
# 可利用大模型补充不同风格,加入更多 fewshot 造出更好的数据
styles = {
"小红书":
{
"style_temple":"小红书风格,每条加入1-2个emoji表情包来增加趣味性。\n### 注意,你要参考下列句子的艺术风格进行祝福语撰写(注意!只看造句风格),祝福语结尾都带上语气助词词,参考句子为:{} ###",
"if_example":True,
"examples":
[
'默念你的名,祝你前途云蒸霞蔚,灿若星河。愿你度过的叫吉时,得到的叫如愿!',
'希望你岁末将至,敬颂冬绥,平安喜乐,万事胜意。',
'希望你不用奔赴大海,也能看到春暖花开;不用颠沛流离,也能遇到一生所伴!',
'祝我们好在春夏秋冬,祝你阔谈,祝你烂漫,祝你和自己相约在风里,此后只剩欢愉。',
'希望你可以明确地爱,直接的厌恶,真诚的喜欢,站在太阳下的坦荡,大声无愧地称赞自己,学会爱自己!',
'前方荣光万丈,身后温暖一方,凡是过往,皆为序章。',
'愿所念之人 平安喜乐。愿所想之事 顺心如意!',
]
},
"正常":
{
"style_temple":"正常风格,有礼貌即可",
"if_example":False,
"examples":[]
},
"严肃":
{
"style_temple":"商业严肃风格,要求用在职场或长辈祝福上,显得有礼貌、干练,句子可以长一些",
"if_example":False,
"examples":[]
}
}
random_finalprompt_sentence = [
'', #默认情况
'回答中可以不出现对象称谓和场景信息,也不用出现“愿你”“祝你”(对自己的长辈需要出现对象称谓和祝你),',
'回答中可以不出现对象称谓和场景信息,',
'回答中不用出现“愿你”“祝你”',
]
final_prompt = """
该祝福语字数小于 {} 字。 \n
请根据对象称谓及场景,写出符合对象的身份和场景气氛的祝福文案。要求的风格是:{} \n,注意不要有标题混在其中,对象称谓是:{},祝福场景是:{}\n
{} 根据不同对象用不同的语气(尊敬、诙谐搞笑、亲近),请直接返回祝福文本,不要说任何其他话:
"""
if __name__ == "__main__":
##### 此处配置 #####
roop_count = 2
now_count = 0
stylename = "小红书" # 小红书、正常、严肃
output_number_limit = 50 # 限制回答输出长度,严肃的100,普通的小于20
##### 此处配置 #####
for roop in range(roop_count):
conversations = []
for name in name_list:
for scene in scenes:
try:
if styles[stylename]['if_example']:
style_prompt = styles[stylename]['style_temple'].format(random.choice(styles[stylename]['examples']))
else:
style_prompt = styles[stylename]['style_temple']
input_prompt = final_prompt.format(output_number_limit, style_prompt, name, scene,random.choice(random_finalprompt_sentence))
response = get_data_ds(input_prompt)
now_count += 1
if '\n' in str(response):
response = str(response).split('\n')[0]
print(name,scene,'response:',response)
print("当前生成数目:", now_count)
if stylename == '正常':
# 默认不加风格指定
_input_prompt = f"{name}{scene}"
else:
_input_prompt = f"{name}{scene},{stylename}风格"
print("input:",_input_prompt)
conversation = {
"conversation": [
{
"system": "你现在是一个送祝福大师,帮我针对不同人和事情、节日送对应的祝福",
"src_input":input_prompt,
"style_name":stylename,
"input": _input_prompt,
"output": str(response).replace('\"','')
}
]
}
# 将对话加入到列表中
conversations.append(conversation)
except Exception as e:
print(e)
continue
now_time = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
file_path = f"./wishes_{stylename}_{now_time}.json"
with open(file_path, "w", encoding='utf8') as f:
json.dump(conversations, f, ensure_ascii=False, indent=4)
```
**注意**,这里为什么需要把 input 替换成 `f"祝{name}{scene}"` 的格式——是因为这里的input需要尽可能模拟人的输入,而不能是制造数据时候的输入。此外,我们在此设定了三种风格:正常、小红书、严肃;期待当加上风格名触发后可以有预期的输出。
这只是最简单的示例,如何根据祝福语的长短不同而生成后的语法风格要对应变化、如何更接近真人的风格化;这些都需要高质量的数据 + 良好的数据制造方式才可获得。
举个例子,如果在这里我们想要控制祝福语长短和语法风格,首先前者我们就要在之前的制造条件中(比如之前的对象、场景是一个条件)加入新的祝福语长短控制条件(比如我这里的小红书和正常风),而且此时few shot参考的句子也应当有所不同,这样才能保证我们造数据的llm可以返回预期长度的结果。如果是控制语法风格,我们就需要爬取大量文艺书籍、小红书等真正人类写的文案进行清洗,然后利用这些作为few shot得到严格的返回,需要用严格的提示词让模型写出类似的语句或者单独微调一个属于该文艺范畴的模型版本用于制造对应数据(有时候few shot指令跟随不那么有用)。
💡代码中有 `random_xxxxxxx_sentence` 的部分表明这是一个随机性注入列表,我们可以维护一些语句用于提高随机性(比如附加条件的修改),让大模型返回的结果更具特色。
若成功运行,你将看到类似如下输出结果,等待片刻后将得到属于本地的json文件 `wishes_0501_5000.json`
```
同学 家庭和睦 response: "烟火年年,暖意洋洋,🏡❤️家是心之所向。"
当前生成数目: 914
同学 比赛取得好成绩 response: "灿若星河,前程似锦🌟,所得皆所愿!"
当前生成数目: 915
同学 发财 response: "春风得意马蹄疾,财源滚滚至君前🎉💰"
当前生成数目: 916
同学 工作升职 response: "升职之光,照亮星河,未来灿烂如霞。🌟🌈"
当前生成数目: 917
同学 康复祝福 response: "挥别病痛,如花开坚强。🌱✨愿你前程,云蒸霞蔚,身心俱灿。"
当前生成数目: 918
```
💡注意,这里只是粗暴的进行所有角色和场景的遍历,但**并非所有角色都适配所有场景**(很多是不合适的),这里为了改进应该做一个heatmap进行映射,若不合适生产该数据,将直接跳过;又或者是在得到数据后做一个匹配,如果同时满足不合适的角色+场景就去除该数据QA对。
#### 数据合并
因为我们之前的数据都是跑完一轮存一次(以防前功尽弃),所以可能你有多个json需要组合,这里提供了一个脚本合并一个文件夹中的所有json,并把json格式清洗成和训练脚本一致适配的格式:
```bash
import os
import json
def extract_and_merge_conversations(folder_path, output_file):
all_conversations = []
# 遍历指定文件夹
for filename in os.listdir(folder_path):
if filename.endswith('.json'):
file_path = os.path.join(folder_path, filename)
# 打开并读取JSON文件
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
# 提取需要的字段
for item in data:
for conversation in item['conversation']:
extracted = {
'system': conversation['system'],
'input': conversation['input'],
'output': conversation['output']
}
# 将每个对话包装在一个 'conversation' 键中,并作为独立对象加入列表
all_conversations.append({'conversation': [extracted]})
# 将合并后的所有对话数据写入一个新的JSON文件
with open(output_file, 'w', encoding='utf-8') as file:
json.dump(all_conversations, file, ensure_ascii=False, indent=4)
# 使用示例
folder_path = 'tianji_wishes_datasets' # 要扫描的文件夹路径
output_file = 'tianji-wishes-chinese-v0.1.json' # 输出文件的名称和路径
extract_and_merge_conversations(folder_path, output_file)
```
合并后就是我们所需要的微调数据集。
#### 二次清洗
得到最初数据后,可能还存在一些奇怪的东西,比如句子长度返回错误,不是回答而是很短的一句 `当前祝福语如下` ,加入语气助词后容易出现 `!啦~` `。哦!` 如此在前面出现标点的奇怪现象,故我们需要利用清洗脚本对数据进行一定的筛选,由于比较冗长(还没有优雅的迭代),清洗脚本放在 [https://github.com/SocialAI-tianji/Tianji](https://github.com/SocialAI-tianji/Tianji),可以自行查看
当然,如果你有时间和精力,最重要的还是可以用人工进行清洗。当然,**最重要的还是最好在数据制造的时候就记录比较完好的信息和做出比较好的output,能大大节约二次清洗的时间。**
#### 直接下载
为了方便大家使用,这里提供了已制造数据的下载地址,大家可以自行从huggingface上获取:
[https://huggingface.co/datasets/sanbu/tianji-wishes-chinese/blob/main/tianji-wishes-chinese-v0.1.json](https://huggingface.co/datasets/sanbu/tianji-wishes-chinese/blob/main/tianji-wishes-chinese-v0.1.json)
镜像站下载:
[https://hf-mirror.com/datasets/sanbu/tianji-wishes-chinese](https://hf-mirror.com/datasets/sanbu/tianji-wishes-chinese)
### 环境准备
接下来我们准备微调的环境,由于时间关系(标准流程是一样的),这里只做省略快速操作,详细操作请参考 xtuner的官方教程 [https://github.com/InternLM/Tutorial/tree/main/xtuner](https://github.com/InternLM/Tutorial/tree/main/xtuner),或者是self-llm 项目中关于Xtuner Qlora的部分:[https://github.com/datawhalechina/self-llm/blob/master/InternLM2/04-InternLM2-7B-chat Xtuner Qlora 微调.md](https://github.com/datawhalechina/self-llm/blob/master/InternLM2/04-InternLM2-7B-chat%20Xtuner%20Qlora%20%E5%BE%AE%E8%B0%83.md)
⚠ 以下基于 **python 3.10** 构建全过程,请注意版本
首先创建一个虚拟环境,随后安装如下依赖
```python
python -m pip install --upgrade pip
pip install modelscope==1.9.5
pip install transformers==4.36.2
pip install streamlit==1.39.0
pip install sentencepiece==0.1.99
pip install accelerate==0.24.1
pip install transformers_stream_generator==0.0.4
pip install einops ujson
pip install protobuf
```
安装xtuner
```
git clone -b v0.1.18 https://github.com/InternLM/xtuner
cd xtuner && pip install -e '.[all]'
# 验证成功
xtuner version
```
模型下载
找地方执行下列python文件
```python
from modelscope import snapshot_download
model_dir = snapshot_download('Shanghai_AI_Laboratory/internlm2-chat-7b', cache_dir='./model_temp', revision='master')
```
完成上述过程后就可以正式进入微调阶段。
### 正式微调
为了微调自己的数据集配置,我们需要修改xtuner的config,首先查看有哪些配置:
```python
xtuner list-cfg | grep internlm2
internlm2_7b_full_finetune_custom_dataset_e1
internlm2_7b_full_finetune_custom_dataset_e1_sequence_parallel_4
internlm2_7b_qlora_alpaca_e3
internlm2_7b_qlora_arxiv_gentitle_e3
internlm2_7b_qlora_code_alpaca_e3
internlm2_7b_qlora_colorist_e5
internlm2_7b_qlora_json_e3
internlm2_7b_qlora_lawyer_e3
internlm2_7b_qlora_msagent_react_e3_gpu8
internlm2_7b_qlora_oasst1_512_e3
internlm2_7b_qlora_oasst1_e3
internlm2_7b_qlora_sql_e3
```
```python
# 新建一个用于微调工作的文件夹
mkdir /home/finetune
# 复制配置文件
cd /home/finetune && xtuner copy-cfg internlm2_chat_7b_qlora_oasst1_e3 ./
```
接下来我们需要修改配置文件,简单来说你要做这几处修改:
```python
# 修改模型为本地路径
- pretrained_model_name_or_path = 'internlm2/internlm2-chat-7b'
+ pretrained_model_name_or_path = '/home/model_temp/Shanghai_AI_Laboratory/internlm2-chat-7b'
# 修改训练数据集为本地路径
- data_path = 'timdettmers/openassistant-guanaco'
+ data_path = '/home/merged_data.json'
# 修改Evaluate
-
evaluation_freq = 500
SYSTEM = ''
evaluation_inputs = [
'请给我介绍五个上海的景点', 'Please tell me five scenic spots in Shanghai'
]
+
evaluation_freq = 50
SYSTEM = '你现在是一个送祝福大师,帮我针对不同人和事情、节日送对应的祝福'
evaluation_inputs = [
'祝姐姐生日快乐', '祝妹妹谈判顺利','祝大家元宵节快乐'
]
# 修改数据集加载
- dataset=dict(type=load_dataset, path=data_path),
+ dataset=dict(type=load_dataset, path='json', data_files=dict(train=data_path)),
```
以下是修改后的结果,你可以直接复制(只要修改模型路径和训练集路径,以及Evaluate的input,就可以把他变为你自己的配置文件开始训练。)
或者,你也可以在 tianji 的主仓库中获得所有使用 xtuner 的微调配置 `https://github.com/SocialAI-tianji/Tianji/tree/main/tianji/finetune/xtuner`
```python
# Copyright (c) OpenMMLab. All rights reserved.
import torch
from datasets import load_dataset
from mmengine.dataset import DefaultSampler
from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook,
LoggerHook, ParamSchedulerHook)
from mmengine.optim import AmpOptimWrapper, CosineAnnealingLR, LinearLR
from peft import LoraConfig
from torch.optim import AdamW
from transformers import (AutoModelForCausalLM, AutoTokenizer,
BitsAndBytesConfig)
from xtuner.dataset import process_hf_dataset
from xtuner.dataset.collate_fns import default_collate_fn
from xtuner.dataset.map_fns import oasst1_map_fn, template_map_fn_factory
from xtuner.engine.hooks import (DatasetInfoHook, EvaluateChatHook,
VarlenAttnArgsToMessageHubHook)
from xtuner.engine.runner import TrainLoop
from xtuner.model import SupervisedFinetune
from xtuner.utils import PROMPT_TEMPLATE
#######################################################################
# PART 1 Settings #
#######################################################################
# Model
pretrained_model_name_or_path = '/home/model_temp/Shanghai_AI_Laboratory/internlm2-chat-7b'
use_varlen_attn = False
# Data
data_path = '/home/tianji-wishes-test_0502.json'
prompt_template = PROMPT_TEMPLATE.internlm2_chat
max_length = 2048
pack_to_max_length = True
# Scheduler & Optimizer
batch_size = 1 # per_device
accumulative_counts = 16
dataloader_num_workers = 0
max_epochs = 3
optim_type = AdamW
lr = 2e-4
betas = (0.9, 0.999)
weight_decay = 0
max_norm = 1 # grad clip
warmup_ratio = 0.03
# Save
save_steps = 50
save_total_limit = 10 # Maximum checkpoints to keep (-1 means unlimited)
# Evaluate the generation performance during the training
evaluation_freq = 50
SYSTEM = '你现在是一个送祝福大师,帮我针对不同人和事情、节日送对应的祝福'
evaluation_inputs = [
'祝姐姐生日快乐','祝姐姐生日快乐,严肃风格','祝姐姐生日快乐,小红书风格', '祝妹妹谈判顺利,小红书风格','祝大家元宵节快乐','祝领导春节快乐,严肃风格'
]
#######################################################################
# PART 2 Model & Tokenizer #
#######################################################################
tokenizer = dict(
type=AutoTokenizer.from_pretrained,
pretrained_model_name_or_path=pretrained_model_name_or_path,
trust_remote_code=True,
padding_side='right')
model = dict(
type=SupervisedFinetune,
use_varlen_attn=use_varlen_attn,
llm=dict(
type=AutoModelForCausalLM.from_pretrained,
pretrained_model_name_or_path=pretrained_model_name_or_path,
trust_remote_code=True,
torch_dtype=torch.float16,
quantization_config=dict(
type=BitsAndBytesConfig,
load_in_4bit=True,
load_in_8bit=False,
llm_int8_threshold=6.0,
llm_int8_has_fp16_weight=False,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type='nf4')),
lora=dict(
type=LoraConfig,
r=64,
lora_alpha=16,
lora_dropout=0.1,
bias='none',
task_type='CAUSAL_LM'))
#######################################################################
# PART 3 Dataset & Dataloader #
#######################################################################
train_dataset = dict(
type=process_hf_dataset,
dataset=dict(type=load_dataset, path='json', data_files=dict(train=data_path)),
tokenizer=tokenizer,
max_length=max_length,
dataset_map_fn=None,
template_map_fn=dict(
type=template_map_fn_factory, template=prompt_template),
remove_unused_columns=True,
shuffle_before_pack=True,
pack_to_max_length=pack_to_max_length,
use_varlen_attn=use_varlen_attn)
train_dataloader = dict(
batch_size=batch_size,
num_workers=dataloader_num_workers,
dataset=train_dataset,
sampler=dict(type=DefaultSampler, shuffle=True),
collate_fn=dict(type=default_collate_fn, use_varlen_attn=use_varlen_attn))
#######################################################################
# PART 4 Scheduler & Optimizer #
#######################################################################
# optimizer
optim_wrapper = dict(
type=AmpOptimWrapper,
optimizer=dict(
type=optim_type, lr=lr, betas=betas, weight_decay=weight_decay),
clip_grad=dict(max_norm=max_norm, error_if_nonfinite=False),
accumulative_counts=accumulative_counts,
loss_scale='dynamic',
dtype='float16')
# learning policy
# More information: https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/param_scheduler.md # noqa: E501
param_scheduler = [
dict(
type=LinearLR,
start_factor=1e-5,
by_epoch=True,
begin=0,
end=warmup_ratio * max_epochs,
convert_to_iter_based=True),
dict(
type=CosineAnnealingLR,
eta_min=0.0,
by_epoch=True,
begin=warmup_ratio * max_epochs,
end=max_epochs,
convert_to_iter_based=True)
]
# train, val, test setting
train_cfg = dict(type=TrainLoop, max_epochs=max_epochs)
#######################################################################
# PART 5 Runtime #
#######################################################################
# Log the dialogue periodically during the training process, optional
custom_hooks = [
dict(type=DatasetInfoHook, tokenizer=tokenizer),
dict(
type=EvaluateChatHook,
tokenizer=tokenizer,
every_n_iters=evaluation_freq,
evaluation_inputs=evaluation_inputs,
system=SYSTEM,
prompt_template=prompt_template)
]
if use_varlen_attn:
custom_hooks += [dict(type=VarlenAttnArgsToMessageHubHook)]
# configure default hooks
default_hooks = dict(
# record the time of every iteration.
timer=dict(type=IterTimerHook),
# print log every 10 iterations.
logger=dict(type=LoggerHook, log_metric_by_epoch=False, interval=10),
# enable the parameter scheduler.
param_scheduler=dict(type=ParamSchedulerHook),
# save checkpoint per `save_steps`.
checkpoint=dict(
type=CheckpointHook,
by_epoch=False,
interval=save_steps,
max_keep_ckpts=save_total_limit),
# set sampler seed in distributed evrionment.
sampler_seed=dict(type=DistSamplerSeedHook),
)
# configure environment
env_cfg = dict(
# whether to enable cudnn benchmark
cudnn_benchmark=False,
# set multi process parameters
mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
# set distributed parameters
dist_cfg=dict(backend='nccl'),
)
# set visualizer
visualizer = None
# set log level
log_level = 'INFO'
# load from which checkpoint
load_from = None
# whether to resume training from the loaded checkpoint
resume = False
# Defaults to use random seed and disable `deterministic`
randomness = dict(seed=None, deterministic=False)
# set log processor
log_processor = dict(by_epoch=False)
```
接下来对新的配置直接开始训练(如果你显存不够,可以切换成 `--deepspeed deepspeed_zero3` )
```bash
xtuner train ./internlm2_chat_7b_qlora_oasst1_e3_copy.py --deepspeed deepspeed_zero2
```
训练结束后,所有权重文件放置在训练目录下的work_dirs中,目录大致为:
```python
drwxr-xr-x 3 root root 4096 May 2 12:23 20240502_122337/
-rw-r--r-- 1 root root 6413 May 2 12:24 internlm2_chat_7b_qlora_oasst1_e3_copy.py
-rw-r--r-- 1 root root 1886589762 May 2 12:43 iter_500.pth
-rw-r--r-- 1 root root 1886601474 May 2 12:50 iter_657.pth
-rw-r--r-- 1 root root 76 May 2 12:50 last_checkpoint
```
值得注意的是,这里通常只需要微调一轮就好,原因是llm通常是过目不忘(有很多相关研究)容易过拟合。
- 如果你想查看更多超参数对结果带来的变动,请参考:
LoRA和QLoRA微调语言大模型:数百次实验后的见解 - OneFlow的文章 - 知乎
[https://zhuanlan.zhihu.com/p/664912829](https://zhuanlan.zhihu.com/p/664912829)
- 如果你想使用全量微调,对于 internlm2-7B 请至少准备 2xA100 80G 的显卡使用下列命令启用微调。(建议你使用“万”级别的数据再进行全量微调,目前天机相关数据还不足以支持好的全量微调)推荐混入更多正常对话数据来确保全量微调效果正常。NPROC_PER_NODE 的值表示使用几张显卡进行微调,此时双卡每张卡显存占用接近 79G。
```bash
NPROC_PER_NODE=2 xtuner train ./全量微调配置.py --deepspeed deepspeed_zero3
```
### 效果验证
首先我们需要转换格式为hf,再与原模型合并,这里提供了统一脚本:
```bash
HF_OUTPUT_DIR="./hf" # lora转为hf格式后的输出地址
MERGE_OUTPUT_DIR="./merge" # 与原模型合并后的输出地址
SCRIPT_PATH="./internlm2_chat_7b_qlora_oasst1_e3_copy.py" # 训练配置文件
SRC_MODEL_PATH="/home/model_temp/Shanghai_AI_Laboratory/internlm2-chat-7b" # 原模型地址
WEIGHTS_PATH="/home/finetune/work_dirs/internlm2_chat_7b_qlora_oasst1_e3_copy/iter_150.pth" # lora权重地址
rm -rf $HF_OUTPUT_DIR
rm -rf $MERGE_OUTPUT_DIR
mkdir -p $HF_OUTPUT_DIR
mkdir -p $MERGE_OUTPUT_DIR
xtuner convert pth_to_hf "${SCRIPT_PATH}" "${WEIGHTS_PATH}" "${HF_OUTPUT_DIR}"
xtuner convert merge \
"${SRC_MODEL_PATH}" \
"${HF_OUTPUT_DIR}" \
"${MERGE_OUTPUT_DIR}" \
--max-shard-size "2GB"
```
如果这步报错,请检查 WEIGHTS_PATH 是否正确。
当然,你也可以不合并(合并后就可以上传权重),而是转换后直接加载lora,对应脚本如下:
```bash
HF_OUTPUT_DIR="./hf" # lora转为hf格式后的输出地址
SCRIPT_PATH="./internlm2_chat_7b_qlora_oasst1_e3_copy.py" # 训练配置文件
SRC_MODEL_PATH="/home/model_temp/Shanghai_AI_Laboratory/internlm2-chat-7b"
WEIGHTS_PATH="/home/finetune/work_dirs/internlm2_chat_7b_qlora_oasst1_e3_copy/iter_150.pth"
rm -rf $HF_OUTPUT_DIR
rm -rf $MERGE_OUTPUT_DIR
mkdir -p $HF_OUTPUT_DIR
xtuner convert pth_to_hf "${SCRIPT_PATH}" "${WEIGHTS_PATH}" "${HF_OUTPUT_DIR}"
xtuner chat "${SRC_MODEL_PATH}" --adapter "${HF_OUTPUT_DIR}" --prompt-template internlm2_chat --system "你现在是一个送祝福大师,帮我针对不同人和事情、节日送对应的祝福" --temperature 0.7
```
启动对话:
```python
# 如果想要多样性更强加上 --temperature 1
xtuner chat ./merge --prompt-template internlm2_chat --system "你现在是一个送祝福大师,帮我针对不同人和事情、节日送对应的祝福" --temperature 0.7
```
此时你会看到如下显示,你只需要恰当的输入之前的prompt和触发风格即可验证。这里比较不方便的是为了去除上下文影响,我们需要反复输入RESET进行重置。
```bash
double enter to end input (EXIT: exit chat, RESET: reset history) >>> 祝弟弟工作升职 ,小红书风格
弟弟呀,你的才华如同春日里的花朵,绽放出绚烂的光彩。工作如鱼得水,升职之路顺顺利利,愿你继续闪耀,成为职场的明星!加油哦!🌟🌈<|im_end|>
double enter to end input (EXIT: exit chat, RESET: reset history) >>> RESET
Log: History responses have been removed!
double enter to end input (EXIT: exit chat, RESET: reset history) >>> 我想送哥哥中秋节祝福,小红书风格
🌕🎉 哥,中秋月圆人团圆,愿你事业如月圆,家庭幸福似团圆,快乐满怀,幸福满满!🌟<|im_end|>
double enter to end input (EXIT: exit chat, RESET: reset history) >>> RESET
Log: History responses have been removed!
double enter to end input (EXIT: exit chat, RESET: reset history) >>> 我想送赵老师生日祝福,严肃风格
尊敬的赵老师,值此佳辰,恭祝您福寿安康,事业蒸蒸日上。愿您在未来的岁月里,如同春日之花,绽放出更加绚烂的光彩,为莘莘学子树立起崇高的榜样。愿您的生活如同诗篇般美好,每一天都充满着智慧与喜悦。愿您的工作如同乐章般动听,每一个音符都承载着对教育事业的热爱与执着。在此,衷心祝愿您生日快乐,万事胜意!<|im_end|>
```
### 网页demo
为了方便测试,我们还可以使用streamlit的demo,使用方法如下:
`pip install streamlit==1.39.0`
```
# 找地方clone仓库
git clone https://github.com/InternLM/InternLM.git
```
将 `/root/code/InternLM/web_demo.py` 中 29 行和 33 行的模型路径更换为Merge后存放参数的路径 `/home/finetune/merge`
按照如下命令运行 `/root/personal_assistant/code/InternLM` 目录下的 `web_demo.py` 文件即可。
```python
streamlit run /root/personal_assistant/code/InternLM/web_demo.py --server.address 127.0.0.1 --server.port 6006
```
通过以上步骤,你就可以成功地微调出最初版本的天机送祝福模块并成功运行推理。
但由于制造数据的质量仍有改善空间、回答随机性注入、输入input的随机性加强仍有很多方式可以实现,送祝福完全可以变得更好、更智能、更通用,从而真正更接近中文下的使用场景,让llm对话更富有“人情味”。
## 感谢每一位贡献者
由于贡献者人数众多无法一一列举,我们由衷的感谢每一位为天机项目做出贡献的小伙伴,是他们让这个项目变得更好。我们也期待你的加入。相信我,你也可以变成光!
https://github.com/SocialAI-tianji/Tianji/blob/main/docs/contributor.md
+54
View File
@@ -0,0 +1,54 @@
# Examples
在学习者完成了基础知识的学习之后,我们将提供一系列精选的实际案例,帮助学习者更深入地理解和掌握大模型应用开发的核心技术。通过这些案例,学习者可以基于实际应用类型,探讨和实践如何有效地利用大模型来解决实际问题。我们的目标是,通过提供这些优秀的大模型应用项目示范(Demo),使学习者能够在完成我们的示例后,更全面地掌握大模型应用开发的技术要点,并具备独立进行大模型应用的二次开发或自主开发的能力。
## Examples 目录
### [Chat-嬛嬛](./Chat-嬛嬛/readme.md)
**Chat-甄嬛**是利用《甄嬛传》剧本中所有关于甄嬛的台词和语句,基于LLM进行**LoRA微调**得到的模仿甄嬛语气的聊天语言模型。
> 甄嬛,小说《后宫·甄嬛传》和电视剧《甄嬛传》中的女一号,核心女主角。原名甄玉嬛,嫌玉字俗气而改名甄嬛,为汉人甄远道之女,后被雍正赐姓钮祜禄氏,抬旗为满洲上三旗,获名“钮祜禄·甄嬛”。同沈眉庄、安陵容参加选秀,因容貌酷似纯元皇后而被选中。入宫后面对华妃的步步紧逼,沈眉庄被冤、安陵容变心,从偏安一隅的青涩少女变成了能引起血雨腥风的宫斗老手。雍正发现年氏一族的野心后令其父甄远道剪除,甄嬛也于后宫中用她的连环巧计帮皇帝解决政敌,故而深得雍正爱待。几经周折,终于斗垮了嚣张跋扈的华妃。甄嬛封妃时遭皇后宜修暗算,被皇上嫌弃,生下女儿胧月后心灰意冷,自请出宫为尼。然得果郡王爱慕,二人相爱,得知果郡王死讯后立刻设计与雍正再遇,风光回宫。此后甄父冤案平反、甄氏复起,她也生下双生子,在滴血验亲等各种阴谋中躲过宜修的暗害,最后以牺牲自己亲生胎儿的方式扳倒了幕后黑手的皇后。但雍正又逼甄嬛毒杀允礼,以测试甄嬛真心,并让已经生产过孩子的甄嬛去准格尔和亲。甄嬛遂视皇帝为最该毁灭的对象,大结局道尽“人类的一切争斗,皆因统治者的不公不义而起”,并毒杀雍正。四阿哥弘历登基为乾隆,甄嬛被尊为圣母皇太后,权倾朝野,在如懿传中安度晚年。
Chat-甄嬛,实现了以《甄嬛传》为切入点,打造一套基于小说、剧本的**个性化 AI** 微调大模型完整流程,通过提供任一小说、剧本,指定人物角色,运行本项目完整流程,让每一位用户都基于心仪的小说、剧本打造一个属于自己的、契合角色人设、具备高度智能的个性化 AI。
> *Chat-嬛嬛模型累计下载量 15.6k+Modelscope 地址:*[*Link*](https://www.modelscope.cn/models/kmno4zx/huanhuan-chat-internlm2)
> *Chat-嬛嬛累计获得 500 starhuahuan-chat 项目地址:*[*Link*](https://github.com/KMnO4-zx/huanhuan-chat.git)xlab-huanhuan-chat 项目地址:[*Link*](https://github.com/KMnO4-zx/xlab-huanhuan.git)
- *2023 讯飞星火杯人认知大模型场景创新赛 Top50*
- *2024 书生·浦语大模型挑战赛(春季赛)创意应用奖 Top12*
### [天机](./Tianji-天机/readme.md)
想从零学会大语言模型应用?想直接复制粘贴代码?天机满足你的一切需求;甚至,你还能学到一点意想不到的东西。
中国文化注重人情世故,涉及到复杂的社交规则、礼仪和人际关系。Social 团队通过深入理解中文语境、文化特点和丰富的语料,提供了基于各类中国式文化的场景下的应对方法、对话案例与情景模拟。
目前天机已经支持的应用场景包括:如何送**祝福、敬酒、请客、送礼、人际交流、化解尴尬以及应对矛盾**等等,还有更多社区小伙伴们建议的 LLM 提高情商方案正在制作中~
除了把这个项目当成一个好玩的社交大模型外,你还可以把它当作完整的全栈大语言模型应用入门学习仓库! 你可以在里面学到**提示词工程 、智能体制作、 模型微调、RAG 数据清洗与使用,以及代码规范**...等等你所需要的大语言模型应用知识,你可以在通过学习该项目后,  快速改造出属于自己的新的大语言模型项目,我们期待你成为下一个大模型应用开发高手!
> *Tianji 开源链接:*[*Link*](https://github.com/SocialAI-tianji/Tianji)
> *Tianji 官方网站:*[*Link*](https://socialai-tianji.github.io/socialai-web/)
> *Tianji 官方文档:*[*Link*](https://tianji.readthedocs.io/en/latest/)
> *Tianji 知识库版应用地址:*[*Link*](http://120.76.130.14:6006/knowledges/)
> *Tianji prompt版应用地址:*[*Link*](http://120.76.130.14:6006/prompt/)
> *Tianji 视频介绍:*[*Link*](https://www.bilibili.com/video/BV1cvbyefEfp)
- *2024 书生·浦语社区优秀大模型应用*
### [AMchat-高等数学](./AMchat-高等数学/readme.md)
AM (Advanced Mathematics) chat 是一个集成了数学知识和高等数学习题及其解答的大语言模型。该模型使用 Math 和高等数学习题及其解析融合的数据集,基于 InternLM2-Math-7B 模型,通过 xtuner 微调,专门设计用于解答高等数学问题。
> *AMchat 项目地址:*[*Link*](https://github.com/AXYZdong/AMchat)
> *AMchat模型: Modelscope 地址:[*Link*](https://www.modelscope.cn/models/yondong/AMchat/summary)* *OpenXLab 地址:[*Link*](https://openxlab.org.cn/models/detail/youngdon/AMchat)*HuggingFace 地址:[*Link*](https://huggingface.co/axyzdong/AMchat)
> *AMchat GGFU模型地址:[Link](https://huggingface.co/axyzdong/AMchat-GGUF)*
> *AMchat 应用地址:*[*Link*](https://openxlab.org.cn/apps/detail/youngdon/AMchat)
> *AMchat 视频介绍:*[*Link*](https://www.bilibili.com/video/BV14v421i7So/)
- *2024 书生·浦语大模型挑战赛(春季赛)创意应用奖 Top12*
### [数字生命](./数字生命/readme.md)
本项目将以我为原型,利用特制的数据集对大语言模型进行微调,致力于创造一个能够真正反映我的个性特征的AI数字人——包括但不限于我的语气、表达方式和思维模式等等,因此无论是日常聊天还是分享心情,它都以一种既熟悉又舒适的方式交流,仿佛我在他们身边一样。整个流程是可迁移复制的,亮点是数据集的制作。
> *MBTI数据集github地址([[https://huggingface.co/datasets/pandalla/Machine_Mindset_MBTI_dataset`]]
- *2024 大模型微调挑战赛(冬季赛)最佳创意奖 Top3*
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+251
View File
@@ -0,0 +1,251 @@
# 如何练就一个我
本项目将以我为原型,利用特制的数据集对大语言模型进行微调,致力于创造一个能够真正反映我的个性特征的AI数字人——包括但不限于我的语气、表达方式和思维模式等等,因此无论是日常聊天还是分享心情,它都以一种既熟悉又舒适的方式交流,仿佛我在他们身边一样。整个流程是可迁移复制的,亮点是数据集的制作。
> 项目背景详情
>
> 由于现下生活节奏加快,许多人因工作繁忙或地理距离而难以时常陪伴家人与朋友,导致情感上的疏离感加剧。在这个数字化快速发展的时代,人们越来越渴望通过技术手段弥补时间和空间上的隔阂。因而AI情感陪伴类产品应运而生,然而,尽管市场需求巨大,现有的AI情感陪伴服务仍存在诸多不足。一个是定制化服务成本高昂,难以普及,一个是信息安全难以保障,还有一个是人格化缺失,容易让人出戏。综合考量下,我们认为在角色扮演上微调不失为一个可行的路子。因为微调是在预训练模型基础上再学习,模型本身已经有较强的文本理解能力、逻辑能力、泛化能力等,只需要一些特定人物个性风格的数据加成就能很好的扮演我们想要的角色了。
*好的~接下来就让我们开始沉浸式体验一下一个暖心AI的完整训练流程吧~*
---
注意,本次演示,是基于github开源项目[“留痕”](https://github.com/LC044/WeChatMsg)、 [讯飞星辰MaaS平台](https://training.xfyun.cn/modelSquare)
## 数据集的制作
首先我们需要获取原始数据,即微信聊天记录的JSON格式。先在电脑端登录微信,同步聊天信息,接着我们进github项目页把“留痕”相关的文件下载到电脑本地,然后运行MemoTrace.exe文件,把应用激活。选择和某个朋友的聊天记录,将聊天内容导出为JSON格式保存至电脑(由于时间考虑,我就不赘述了,大家可以按照项目自述文档操作),结束可以在VSCode中打开查看,内容包括conversations、role和content三个字段,为了便于模型更好地学习,我们需要再“精修”一下,可参考以下数据处理的大致思路:
```
# -*- coding: utf-8 -*-
import json
from copy import deepcopy
#标准化角色命名   
def convert_to_sharegpt_format(original_data,new_system_value=None):
    sharegpt_data = []
    for conversation in original_data:
        new_conversation = {
            "conversations": [],
            "system":  new_system_value,
            "tools": "[]"  # 如果没有工具调用,可以留空或设置为空列表
        }      
        system_message = None
        for msg in conversation["conversations"]:
            # if msg["role"] == "system":
            #     system_message = msg["content"]
            if msg["role"] == "user":
                new_conversation["conversations"].append({
                    "from": "human",
                    "value": clean_content(msg["content"])
                })
            elif msg["role"] == "assistant":
                new_conversation["conversations"].append({
                    "from": "gpt",
                    "value": clean_content(msg["content"])
                })
               
#如果原始数据中已经存在"role": "system"的消息,那么这个消息的内容会被优先用于设置新对话的"system"字段。因此,即使你在调用convert_to_sharegpt_format时传递了new_system_value参数,一旦遇到原始数据中定义的系统消息,它就会覆盖你传入的新值。     
        # # 将系统消息设置为system字段
        # if system_message:
        #     new_conversation["system"] = system_message
        sharegpt_data.append(new_conversation)
    return sharegpt_data
# 读取原始JSON数据
with open('z.json', 'r', encoding='utf-8') as f:
    original_data = json.load(f)
# 转换为ShareGPT格式
# 批量修改"system"的值为"New System Value"
#添加优化系统提示词,可以理解为人设前提
new_system_value = "你是(替换为主角名字,人设),对你来说,他人的需求感受在自己之前。此外,你很喜欢倾听......"
sharegpt_formatted_data = convert_to_sharegpt_format(original_data, new_system_value)
# 写入新的JSON文件
with open('sharegpt_formatted_data.json', 'w', encoding='utf-8') as f:
    json.dump(sharegpt_formatted_data, f, ensure_ascii=False, indent=2)
print("数据转换完成并保存为 sharegpt_formatted_data.json")
```
这里我们用ShareGPT的格式主要是它适用于多轮对话的模型训练,相较于别的格式更适用于角色扮演的场景,其次就是规范了角色命名,便于模型的理解,一定程度避免混淆幻觉,还有一个就是人设的完善补充,还有最重要的一点,一定一定要记得对数据集进行敏感信息脱敏(包括电话,密码,地址等)!!!!!~~
记得改系统提示词的值(system),文件保存位置等!!!
*对于这个数据集,有两个特别的点,大家可参考保留优化,一个就是我保留所有的颜文字、表情包转的文字(如[委屈]等),因为经过实验证明,这有助于大模型理解话语的情感色彩特征,并能模仿到个性化的表达方式,我们发现微调后它能根据语境适时也配上颜文字和表情包返回给用户。一个就是我改写了MBTI的数据集,将我自己对应的那个人格的思维数据集按照聊天信息的形式格式改编,把一些比较有代表性的问题场景改成对话加到数据集里,这将更便于模型捕捉角色思维模式的特征。*
MBTI数据集github地址([[https://huggingface.co/datasets/pandalla/Machine_Mindset_MBTI_dataset`]]
再有就是要注意对对话片段内容不完整的进行筛选处理,包括但不限于补充对话、删除一些话题跳跃的片段等。我们大概有3000条数据,考虑到我很多时候话题都比较跳跃,没连着回答问题,所以我是人为再清洗处理。有些话题跳跃严重的片段给模型带来较大的理解难度,一些人为去除,对于某些存在跳跃但有价值的对话,我们尝试添加适当的背景信息,帮助模型建立正确的语境。
当然对于精修,大家可以继续探索,比如接入对话情绪色彩判断的API,添加情感标签、描述角色的性格特点、职业背景、兴趣爱好等的特征标签,都有利于AI数字人的人性化、个性化,示例如下。
```
{ "from": "gpt", "value": "学会了吗[笑]", "character_traits": { "personality": "聪明、热情、善良", "occupation": "程序员", "interests": ["编程", "阅读", "旅行"] } }
```
> [!NOTE] 数据集说明
> 为什么我们选择聊天记录来做数据集,原因无他,我们日常的交流就能在脑海中渐渐描摹出一个人的模样,慈眉善目的,或是天真可爱的,都通过我们和朋友家人的日常交流中的很多细节不断在显现,那让模型学一个人模仿一个人,不就是在这些细节里磨炼吗,这无疑是造就一个灵魂分身最好的模版,所以我们选择在聊天场景、内容中下功夫~~
好了,那数据集就先告一段落,接下来就到讯飞星辰MaaS平台进行微调.
## 模型微调训练
这一步,我们使用的是[讯飞星辰MaaS平台](https://training.xfyun.cn/modelSquare),大家可以根据自己的需要自行选择,其实主要就是需要开源的通用大模型,然后把数据集给它进行训练学习(微调),这里使用教程可以看看Datawhale的官方教程[零基础定制你的专属大模型](https://www.datawhale.cn/activity/110?subactivity=21)作为参考,这里就不赘述了~
不过要注意一点,按照我们上面的步骤,我们的数据集属于ShareGPT格式,别选错了~
>
> 首先是对一些参数的设置提一些建议以供参考:学习率(Learning Rate)和训练次数(Epochs)是两个关键的超参数,它们对最终模型性能有着重要影响。选择合适的学习率和训练次数需要结合具体任务、数据集特征以及计算资源来综合考虑。一个过大的学习率可能会导致模型无法收敛,而太小的学习率则可能导致训练过程缓慢甚至陷入局部最优解。因此,在微调阶段通常使用较小的学习率,以避免破坏已经学到的有用信息 。可以将这个参数理解为它依据学习内容去改变它自己认知的幅度的大小,建议是从小开始,这样会较大程度保证损失函数曲线的平缓,不太震荡。训练次数即整个训练集被遍历的次数。微调通常不需要太多的训练周期,因为预训练的模型已经具备了一定的知识基础。还有,如果数据集本身不大(少于500条),过多的训练周期可能会导致过拟合,即模型在训练集上表现很好,但在未见过的数据上表现不佳。对于这两个参数,可以结合训练的Loss曲线进行不断地调整,目的就是让曲线不要过于震荡,也不能太平,太平可能过拟合~然后就是温度系数,我的实验结果显示0.9为佳,大家也可以根据回应的效果多调试。
> 其次就是微调方式的选择,主要考虑Lora和全量精调,那为什么我最终选择Lora,而不是全量精调,我们综合考虑时间和资源成本,我们采用LoRa微调,当数据集规模较小时,实际上没有必要对所有参数进行全面调整,因为大部分预训练模型已经具备了良好的初始化和特征提取能力。在这种情况下,采用全量精调不仅增加了不必要的计算负担,还可能导致训练过程变得冗长且低效。当然数据集很大的话另说(几万条)。
比较好的微调效果大概会像下面这样:
![](/images/图片1.png)
![](/images/图片2.png)
(里面的原型就是我,我回答喜欢颜文字和各种表情包,会让对方感觉很温馨,我也比较在意一些标点符号的情感表达,可以看到我是对感叹号波浪号等非常情有独钟的哈哈哈哈,整体来说,效果是比较不错的~)
## 前端页面展示
这一步主要是需要调用你微调好的模型的API,我们做了一个比较美观的聊天界面展示,大家可以模仿探索一下,还是比较有意思的,考验你的审美了。这里主要展示一下API调用的相关代码,我们的API调用是通过*WebSocket连接*到训练好的模型的API:
```
            async function handleSendMessage() {
                const message = userInput.value.trim();
                if (!message) return;
                addMessage('用户', message, 'user-message');
                userInput.value = '';
#这里是在讯飞平台里微调好并发布的模型的地址,改成你自己的!!
                const ws = new WebSocket('wss://maas-api.cn-huabei-XX');
#以下几行为API调用,在“服务管控”页的右下角“信息调用”处
                ws.onopen = () => {
                    const requestData = {
#这要改成你自己的相关API!!
                        header: {
                            app_id: "XXXXXXXX",
                            uid: "XXXXX",
                            patch_id: ["XXXXXXXXXXXXXXXXXXX"]
                        },
                        parameter: {
                            chat: {
#这里我们选用的事星火13b的模型进行微调,如果不是用的这个,记得看讯飞平台的API调用文档说明,对应的改!!
                                domain: "xspark13b6k",
#这是温度系数
                                temperature: 0.9
                            }
                        },
                        payload: {
                            message: {
                                text: [
                                    { "role": "user", "content": message }
                                ]
                            }
                        }
                    };
                    console.log(message)
                    ws.send(JSON.stringify(requestData));
                };
                let fullResponse = ''; // 用于存储完整的AI响应
                ws.onmessage = (event) => {
                    const response = JSON.parse(event.data);
                    if (response.header.code === 0) {
                        // 拼接每次接收到的内容
                        const aiResponsePart = response.payload.choices.text.map(choice => choice.content).join('');
                        fullResponse += aiResponsePart;
                        // 检查是否是最后一次响应
                        if (response.payload.choices.status === 2) {
                            addMessage('AI', fullResponse, 'ai-message');
                        }
                    } else {
                        console.error('Error:', response.header.message);
                        addMessage('AI', '抱歉,服务器出现错误,请稍后再试。', 'ai-message');
                    }
                };
                ws.onerror = (error) => {
                    console.error('WebSocket Error:', error);
                    addMessage('AI', '抱歉,连接出现错误,请稍后再试。', 'ai-message');
                };
                ws.onclose = () => {
                    console.log('WebSocket connection closed');
                };
            }
```
这段代码定义了一个名为`handleSendMessage`的异步函数,它在用户点击发送按钮时被调用。函数首先获取用户输入的消息,然后通过 ___WebSocket连接___ 到指定的API端点。在连接成功后,它发送一个包含用户消息的JSON请求数据。接着,它监听WebSocket的`onmessage`事件,接收并处理API返回的响应,将响应内容拼接起来,并在接收到完整响应后将其显示在聊天界面中。如果发生错误或连接关闭,它会相应地处理这些情况并在聊天界面中显示错误信息~~
成功后,效果大概会是下面这样:
![](屏幕截图 2025-01-23 010103.png)
那现在你就可以日常没事逗逗他/她啦~
好啦,如果你到这一步结束了,恭喜你!!!!已经拥有了你梦寐以求的那个人的灵魂分身了,它应该可以让你或者别的用户沉浸式体验有温度的陪伴啦!!!当然,这个还可以拓展,主要是这个思路,还能继续迁移到别的应用场景,比如你喜欢的世界上可能不存在的动漫角色,比如早已离你而去的亲人,都可以,只要是情感陪伴相关的,其实都可以尝试一下,*我们的终极目标就是降低成本,让更多的人可以享受到这种形式的暖心陪伴,然后探索怎么在这个过程中不让人出戏,想想,要是在你向它倾诉时,出于一些敏感词的规避设置,它回你一句对不起,我只是一个语言大模型,那真是让人心都凉了呢~*
后续,我还会探索语音功能的加成,有兴趣的朋友欢迎交流鸭~~