大模型项目:基于InternLM大模型的中医诊断助手

前言

这里主要是借鉴了https://aistudio.baidu.com/projectdetail/7337156?channelType=0&channel=0项目的数据,在这里表示感谢该大佬的辛苦的数据整理!

本项目基于InternLM大模型来进行数据集的制作,模型微调,还有模型部署,都是使用的本课程学习的工具链,并且最后实现一个可以问诊的效果。

本人基础有限,这是第一次尝试大模型,如若有问题还请海涵。

XTuner格式数据集制作

数据集大家可以在我开头的链接中下载,下载之后我们可以看到2054行数据在这里插入图片描述
当然我们不可能直接使用这个来微调,要进行数据集的制作,我们这里是使用XTuner进行微调,那就要先明白XTuner数据格式。

XTuner数据格式介绍

在大模型问答的时候是存在三个角色的:system,input,output,所以我们要将我们的数据转换一下,下面是目标格式

[{
    "conversation":[
        {
            "system": "xxx",
            "input": "xxx",
            "output": "xxx"
        }
    ]
},
{
    "conversation":[
        {
            "system": "xxx",
            "input": "xxx",
            "output": "xxx"
        }
    ]
}]

我们的数据有两个标签,一个是case可以理解为症状,另一个是diagnosis可以理解为诊断,那么对应上面的就是前者为input,后者为output,system就设置为阿森的私人医生。

我举个例子,先把数据复制一下,看看:

[{
    "conversation":[
        {
            "system": "阿森的私人医生",
            "input": "患者新冠感染后出现头痛,微热,小便不利等症状。随着病情的发展,患者逐渐出现烦渴欲饮,饮水即吐,咳嗽等症状。患者自觉神疲乏力,舌苔白,脉浮数",
            "output": "诊断:太阳伤寒蓄水。建议处方:五苓散。建议中成药:五苓胶囊"
        }
    ]
}

下面我们就写代码实现上述的功能。

将原数据转换成XTuner数据格式

在这里我直接把代码放出来了,然后大家可以运行一下

import json

# 读取JSON文件
file_path = "RecentColdMedicalCase.json"  # 将 "your_file_path.json" 替换为实际的文件路径
with open(file_path, "r", encoding="utf-8") as json_file:
    data = json.load(json_file)

# 将每个病例转换成目标格式
formatted_data_list = []
for case_data in data:
    formatted_data = {
        "conversation": [
            {
                "system": "阿森的私人医生",
                "input": case_data["case"],
                "output": case_data["diagnosis"]
            }
        ]
    }
    formatted_data_list.append(formatted_data)

# 打印或保存结果
for i, formatted_data in enumerate(formatted_data_list, 1):
    print(f"病例 {i}:")
    print(json.dumps(formatted_data, ensure_ascii=False, indent=4))
    print()

# 保存到一个新的JSON文件中
output_file_path = "formatted_cases.json"
with open(output_file_path, "w", encoding="utf-8") as output_file:
    json.dump(formatted_data_list, output_file, ensure_ascii=False, indent=4)

然后我们看一下效果
在这里插入图片描述

XTuner模型微调

我本来打算直接从以前的作业上弄得,后来想想还是从零开始吧。

这里我们先创建开发机并打开(你自己的环境也行)。在这里插入图片描述

环境准备

conda create --name mydemo python=3.10 -y
# 在开发机平台可运行下面的语句
# /root/share/install_conda_env_internlm_base.sh mydemo

conda activate mydemo
mkdir xtuner && cd xtuner

# 拉取 0.1.9 的版本源码
git clone -b v0.1.9 https://gitee.com/Internlm/xtuner
# 进入源码目录
cd xtuner

# 从源码安装 XTuner
pip install -e '.[all]'

准备模型和数据集

# 创建一个微调 oasst1 数据集的工作路径,进入
mkdir ~/ft-demo && cd ~/ft-demo

cd ~/ft-demo

# 准备模型文件
cp -r /root/share/temp/model_repos/internlm-chat-7b .

# 准备数据集文件
cp ~/formatted_cases.json .

准备配置文件

cd ~/ft-demo 
# 复制配置文件到当前目录
xtuner copy-cfg internlm_chat_7b_qlora_oasst1_e3 .
# 改个文件名
mv internlm_chat_7b_qlora_oasst1_e3_copy.py internlm_chat_7b_qlora_formatted_cases_e3.py

在这里插入图片描述

之后修改配置文件,直接使用下面代码进行覆盖即可

# Copyright (c) OpenMMLab. All rights reserved.
import torch
from bitsandbytes.optim import PagedAdamW32bit
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
from peft import LoraConfig
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 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 = './internlm-chat-7b'

# Data
data_path = 'formatted_cases.json'
prompt_template = PROMPT_TEMPLATE.internlm_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 = PagedAdamW32bit
lr = 2e-4
betas = (0.9, 0.999)
weight_decay = 0
max_norm = 1  # grad clip

# Evaluate the generation performance during the training
evaluation_freq = 500
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,
    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=CosineAnnealingLR,
    eta_min=0.0,
    by_epoch=True,
    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)

微调启动

xtuner train internlm_chat_7b_qlora_formatted_cases_e3.py --deepspeed deepspeed_zero2

现在开始训练啦,需要1个小时左右,这里我们等待一下。
在这里插入图片描述
最后发现有效果,但是并不理想,如下在这里插入图片描述

调参

打算训练25个epoch试一下,我在配置文件里把3换成25,并且将batchsize=2(4的话,有机会会爆)

然后我们这里也防止断联,用一下工具

apt update -y

apt install tmux -y

这个tmux的作用就是在ssh断联的时候也可以继续训练。首先创建一个session

tmux new -s finetune

这时候要退出的话要使用ctrl + b,点按 d,之后再退出之后再回去的话就是要去使用

tmux attach -t finetune

在这里插入图片描述
再次训练起来,6个小时后看看调参后的效果:
在这里插入图片描述
训练了15轮,就先停下来了,然后我们试试效果在这里插入图片描述

合并

因为我们使用的是LoRA算法,,下一步就是将微调的与原来的合并一下,第一步将得到的 PTH 模型转换为 HuggingFace 模型:

mkdir hf
export MKL_SERVICE_FORCE_INTEL=1

xtuner convert pth_to_hf ./internlm_chat_7b_qlora_formatted_cases_e3.py ./work_dirs/internlm_chat_7b_qlora_formatted_cases_e3/epoch_15.pth ./hf

在这里插入图片描述
第二步进行合并:将 HuggingFace adapter 合并到大语言模型

xtuner convert merge ./internlm-chat-7b ./hf ./merged --max-shard-size 2GB

在这里插入图片描述

对话

我们现在来对话测试一下,这里全加载回答很慢,可以选择量化加载:

export MKL_SERVICE_FORCE_INTEL=1
xtuner chat ./merged --bits 4 --prompt-template internlm_chat

在这里插入图片描述
成功了!!!!下一步使用lmdeploy进行模型部署。

lmdeploy部署实践

lmdeploy安装

pip install packaging
pip install /root/share/wheels/flash_attn-2.4.2+cu118torch2.0cxx11abiTRUE-cp310-cp310-linux_x86_64.whl

pip install 'lmdeploy[all]==v0.1.0'

在这里插入图片描述

服务部署

下图是服务部署的框架流程,分为三个模块模型推理,api,前端
在这里插入图片描述

模型转换

使用 TurboMind 推理模型需要先将模型转化为 TurboMind 的格式,之后可以看到一个workspace的文件夹。

lmdeploy convert internlm-chat-7b  ~/ft-demo/merged

在这里插入图片描述

TurboMind 推理作为后端+Gradio做为前端

# Gradio+Turbomind(local)
lmdeploy serve gradio ./workspace

这时会出现一个报错,如下图:
在这里插入图片描述
我们使用下面的命令进行解决

pip install --upgrade httpx

之后就可以发现运行成功了
在这里插入图片描述
这里我们也需要把端口映射一下,在本地运行

ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p 35340

在这里插入图片描述
之后我们点击上上图的链接,之后进行对话,就看到了这个效果啦!!!
在这里插入图片描述

将模型文件上传到OpenXlab

概述

我们先来介绍一下OpenXLab 平台:https://openxlab.org.cn
在这里插入图片描述
参考:https://zhuanlan.zhihu.com/p/677728408

安装openxlab库

python -m pip install -U openxlab

在这里插入图片描述
我们想要上传这个模型,有两步,第一步是根据模型来修改/创建元文件,然后根据元文件创建模型仓库。

元文件编辑

就需要编写一个元文件,里面包括模型仓库的基础信息,还有这个文件夹的每个文件信息。这里我们可以可以直接复制官方的元文件(毕竟我们是在官方的模型进行微调的)。
在这里插入图片描述
我们先创建一个Diagnostic_Assistant.yaml:
在这里插入图片描述
把上面的元文件信息复制过去
在这里插入图片描述
下一步就是为每个文件补充键值对信息,有一种很方便的方法就是将该yaml复制到模型文件夹里去,这时模型名字就是文件夹里的文件名。

Collections:
- Name: "InternLM-chat-7b"
  License: "Apache-2.0"
  Framework: "[]"
  Paper: {}
  Code:
    URL: "https://github.com/InternLM/InternLM"
Models:
- Name: "config.json"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "configuration_internlm.py"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "generation_config.json"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "modeling_internlm.py"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00001-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00002-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00003-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00004-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00005-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00006-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00007-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model-00008-of-00008.bin"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "pytorch_model.bin.index.json"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "special_tokens_map.json"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "tokenization_internlm.py"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "tokenizer.model"
  Results:
  - Task: "Question Answering"
    Dataset: "none"
- Name: "tokenizer_config.json"
  Results:
  - Task: "Question Answering"
    Dataset: "none"

我们可以运行下面的脚本直接改写,先安装一下包

python -m pip install ruamel.yaml
import sys
import ruamel.yaml

yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
yaml.default_flow_style = False
file_path = 'Diagnostic_Assistant.yaml'
# 读取YAML文件内容
with open(file_path, 'r') as file:
 data = yaml.load(file)
# 遍历模型列表
for model in data.get('Models', []):
 # 为每个模型添加Weights键值对,确保名称被正确引用
 model['Weights'] = model['Name']

# 将修改后的数据写回文件
with open(file_path, 'w') as file:
 yaml.dump(data, file)

print("Modifications saved to the file.")

在这里插入图片描述

模型仓库创建并上传

我们先在官网创建一个创建密钥https://sso.openxlab.org.cn/usercenter?lang=zh-CN
在这里插入图片描述
之后添加配置,运行下面的命令

openxlab login

创建

cp Diagnostic_Assistant.yaml merged   #  一定是拷贝! 一定是拷贝! 一定是拷贝! 重要的事情说三遍
cd  merged
openxlab model create --model-repo='ZhaoHongSen0726/Diagnostic_Assistant'  -s  ./Diagnostic_Assistant.yaml

等待上传
在这里插入图片描述
这个需要花费很长的时间!

终于上传成功了呀!
在这里插入图片描述
然后我们在网站上找一下

在这里插入图片描述
也是为社区尽了一份力量!!!

结语

首先很感谢这次课程的制作吧,并且还给我们配了很多的gpu资源,我原先也没有什么基础,也跟了下来,虽然不可能全都吸收,但是至少了解了大模型开发的流程,也是大大激发了我的兴趣,打算以后要花费更多的时间去拥抱新时代的到来。

致谢!

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值