Qwen微调医疗模型(学习笔记)

项目介绍:

会基于ShareGPT数据,使⽤QLoRA微调Qwen2 7B模型,并且在微调过程中使⽤Flash Attention技术。 最终实现⼀个简单的对⻬后模型。

QLoRA微调是什么?
Flash Attention技术是什么?

一,如何为⼀个⼤模型微调项⽬制定解决⽅案

需求分析

  1. 了解的是下游任务所属的领域,从⽽为微调任务选择合适的基座模型
  2. 了解模型所要具备的能⼒,确定微调技术方案。
  3. 收集微调数据,确定应该使⽤什么类型的评测数据。

模型选择

  1. 语言:中文还是英文。如果需要应对的是英⽂任务,可以考虑选择llama系列的模型。如果需要考虑中⽂场景,则可以考虑通义千问系列。或者直接⽆脑选择Qwen。
  2. base模型还是chat模型 :选择base模型还是chat模型主要由你⼿头的数据量所决定。
    • 如果微调阶段可以获取的数据量较⼤(如10B以上的token),或者有很多专业知识要注⼊,那么可以考虑使⽤base模型,通过⼆阶段预训练先注⼊相关知识,然后使⽤微调数据激发模型的指令微调能⼒。
    • 如果⼿头上只有较少的指令微调数据,那么chat模型是⼀个不错的选择。

数据准备

  1. 准备数据:包括寻找开源数据,构造领域专有数据。开源数据:Huggingface,modelscope,github等
  2. 数据清洗
  3. 数据预处理

模型训练和评估

  1. 模型微调的方法:大模型微调(学习笔记一)
  2. 模型评估方法(待补充)

使⽤QLoRA⽅法和Flash Attention微调Qwen系列模型

python 复制代码
#用pytorch实现了一个self-attention函数
import torch

def torch_attention(q, k, v, mask=None):
    '''
    PyTorch implementation of the scaled dot-product attention mechanism.
    Parameters:
        q: query tensor, [batch_size, n_heads, seq_len, hidden_size]
        k: key tensor, [batch_size, n_heads, seq_len, hidden_size]
        v: value tensor, [batch_size, n_heads, seq_len, hidden_size]
        mask: mask tensor, [batch_size, n_heads, seq_len, seq_len]
    Returns:
        attention output: output tensor, [batch_size, n_heads, seq_len, hidden_size]
    '''
    hidden_size = q.size(-1)
    scores = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(hidden_size, dtype=torch.float16)) # [batch_size, n_heads, seq_len, seq_len]
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)
    weights = torch.nn.functional.softmax(scores, dim=-1)
    return torch.matmul(weights, v) # [batch_size, n_heads, seq_len, hidden_size]
#设置随机变量
import torch

batch_size = 4
n_heads = 2
seq_len = [2, 3, 8, 4]
hidden_size = 32

q = torch.randn(batch_size, n_heads, max(seq_len), hidden_size, dtype=torch.float16, device='cuda')
k = torch.randn(batch_size, n_heads, max(seq_len), hidden_size, dtype=torch.float16, device='cuda')
v = torch.randn(batch_size, n_heads, max(seq_len), hidden_size, dtype=torch.float16, device='cuda')

#分别使用pytorch实现和`flash_attn`库中所提供的attention实现计算注意力机制的输出。并且比较两个输出的结果
import torch
from flash_attn import flash_attn_func

torch_attention_output = torch_attention(q, k, v)
flash_attn_output = flash_attn_func(
    q.transpose(1, 2), 
    k.transpose(1, 2), 
    v.transpose(1, 2)).transpose(1, 2)

def compare_tensors(t1, t2):
    return torch.allclose(t1, t2, atol=1e-3)

print(compare_tensors(torch_attention_output, flash_attn_output))
print(torch_attention_output[0][0][0])
print(flash_attn_output[0][0][0])

二、代码实现流程

1.下载模型

可以在在Huggingface的模型仓库或者在ModelScope下载Qwen模型(Qwen2.5-3B-Instruct)

AutoDL可以开启学术加速

source /etc/network_turbo

取消学术加速

unset http_proxy && unset https_proxy

方式,优点,缺点,推荐场景

ModelScope,国内速度最快,中文支持好,模型库相对 HF 略小,国内微调首选

Hugging Face,模型最全,更新最快,国内网络受限,需配置镜像,找最新/冷门医疗模型

Git LFS,纯命令行工具,不依赖 Python,无法断点续传,易报错,仅需简单拉取少量代码

方式 优点 缺点
ModelScope 国内速度最快,中文支持好 模型库相对 HF 略小
Hugging Face 模型最全,更新最快 国内网络受限,需配置镜像
Git 纯命令行工具,不依赖 Python 无法断点续传,易报错

方法1:

  1. 安装huggingface_hub: pip install -U "huggingface_hub"
  2. 安装hf-cli (huggingface-cli改名为hf-cli): pip install hf-cli
  3. 输入:huggingface-cli login。连接到自己huggingface,输入自己的token
  4. 下载模型:hf download Qwen/Qwen2.5-3B-Instruct --local-dir ./Qwen2.5-3B-Instruct

方法2:

  1. 安装包:pip install modelscope
  2. 下载模型

modelscope download --model 'qwen/Qwen2.5-3B-Instruct' --local_dir './Qwen2.5-3B-Instruct'

下载后的文件目录结构:

2. 配置环境

pip install transformers=='4.45.2' peft=='0.13.1' accelerate=='1.0.0'

tiktoken=='0.8.0' bitsandbytes=='0.44.1'

  1. transformers: 这个库提供了常⻅预训练模型的实现,并且基本上已经成为了⾃然语⾔处理任务的标准库。被⼴泛地应⽤在各类NLP相关应⽤中。
  2. peft: 这个库和transformers⼀样,都是由huggingface这个组织所维护和开发的。它的主要功能是提供了各类低参数量微调算法的实现,⽐如Lora等。
  3. bitsandbytes: 这个库提供了很多⾼效的位操作和字节级别的处理能⼒,⾮常适合深度学习任务中对于性能要求⽐较⾼的场景,并且集成了各类低⽐特量化算法的实现。
  4. accelerate: 这个库也是由huggingface这个组织所维护和开发的,是huggingface全家桶中的重要⼀员。它提供了多种并⾏训练功能,并且⽀持多GPU训练。这个库已经被深度集成在huggingface全家桶中。

3. 数据集准备

医疗对话数据集

微调自我认知数据集

4. 配置模型

python 复制代码
compute_dtype = getattr(torch, "float16") 
quant_config = BitsAndBytesConfig( 
    load_in_4bit=True, 
    bnb_4bit_quant_type="nf4", 
    bnb_4bit_compute_dtype=compute_dtype, 
    bnb_4bit_use_double_quant=True, 
)

5.模型微调

完整代码

python 复制代码
#导入需要使用的各个库
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"    # 这里设置使用哪块GPU
from torch.utils.data import Dataset        # 用于自定义数据集
from transformers import (
    AutoModelForCausalLM,                   # 用于加载预训练模型
    AutoTokenizer,                          # 用于加载分词器
    BitsAndBytesConfig,                     # 用于加载配置文件
    TrainingArguments,                      # 用于加载训练参数
    Trainer,                                # 用于训练
    DataCollatorForLanguageModeling,        # 用于处理数据
)
import torch
import time
import json
import random

#加载数据集
import csv
import pprint
def load_dataset(filename):
    data_list = []
    # read csv
    with open(filename, "rb") as f:
        raw = f.read(50000)

    print(chardet.detect(raw))
    with open(filename, "r", encoding="utf-8") as f:    # 这里使用utf-8编码,你可以更换编码
        reader = csv.DictReader(f)                        # 使用csv.DictReader读取csv文件
        for row in reader:
            data_list.append(
                {
                    'department': row['department'],
                    'input': row['ask'],
                    'output': row['answer']
                }
            )

    return data_list                  
                       
dataset = load_dataset("数据_utf8.csv")                  # 读取数据
print('len(dataset):', len(dataset))
pprint.pprint(dataset[0])

#准备可以用来模型训练的数据集,包括医疗对话数据和自我认知数据
def prepare_message(data_list):
    new_list = []
    for i, data in enumerate(data_list):
        _id = f"identity_{i}"
        new_list.append(
        {
            "id": _id,
            "conversations": [       # 将所输入的对话转换成conversations的形式
                {
                    "from": "user",
                    "value": data["input"]
                },
                {
                    "from": "assistant",
                    "value": data["output"]
                }
            ]
        })
    return new_list   


def replace_name(s):
    s = s.replace('<NAME>', '智能医生客服机器人小D')
    s = s.replace('<AUTHOR>', 'Greedy AI')
    return s


def load_self_cong_data(filename):
    data_list = []
    id = 0
    for d in json.load(open(filename, "r", encoding="utf-8")):
        d["instruction"] = replace_name(d["instruction"])     # 将instruction中的<NAME>和<AUTHOR>替换成智能医生客服机器人小D和Greedy AI
        d["output"] = replace_name(d["output"])
        data_list.append({
            "id": id,
            "conversations": [
                {
                    "from": "user",
                    "value": d["instruction"]
                },
                {
                    "from": "assistant",
                    "value": d["output"]
                }
            ]
        })
        id += 1
    return data_list

self_cong_data = load_self_cong_data("self_cognition.json")       # 读取自我认知数据
format_data_list = prepare_message(dataset[:1000])                # 将数据转换成conversations的形式
format_data_list = self_cong_data + format_data_list
random.shuffle(format_data_list)                                  # 打乱数据
print(format_data_list[0])

#设置LoRA参数和量化参数
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

output_dir = 'checkpoints_self_cong/'
# 设置LoRA参数
config = LoraConfig(
    r=32,                                  # LoRA所使用的Rank,r 越大:可学习能力越强,参数越多;r 越小:更省显存,但可能欠拟合
    lora_alpha=16,                         # LoRA的Alpha 缩放因子
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],    # LoRA所作用的模块,告诉 LoRA:只在注意力层改动。
    bias="none",                           # LoRA的Bias,这里设置为none,表示不使用Bias
    lora_dropout=0.05,                     # LoRA的Dropout,这里设置为0.05
    task_type="CAUSAL_LM",                 #告诉 PEFT:这是一个"自回归语言模型"任务
)
compute_dtype = getattr(torch, "float16")

quant_config = BitsAndBytesConfig(
    load_in_4bit=True,                       # 加载4bit量化模型 基座模型用 4-bit 加载
    bnb_4bit_quant_type="nf4",               # 量化类型,这里使用nf4
    bnb_4bit_compute_dtype=compute_dtype,    # 计算精度,这里使用float16
    bnb_4bit_use_double_quant=True,          # 使用双量化
)

peft_training_args = TrainingArguments(
    output_dir = output_dir,                 # 输出目录
    warmup_steps=1,                          # warmup步数,一般在训练模型时,都会将学习率从0逐渐增加到一个较大的值,这个过程就是warmup
    per_device_train_batch_size=1,           # 每个设备的训练batch_size
    gradient_accumulation_steps=1,           # 梯度累积步数
    learning_rate=2e-4,                      # 学习率
    optim="paged_adamw_8bit",                # 优化器,这里使用paged_adamw_8bit
    logging_steps=100,                       # 多少步打印一次日志
    logging_dir="./logs",                    # 日志目录
    save_strategy="steps",                   # 保存策略,按照步数保存
    max_steps=1000,                          # 要训练多少步
    save_steps=500,                          # 多少步保存一次
    gradient_checkpointing=True,             # 是否使用gradient_checkpointing功能,这个功能可以节省显存
    report_to="none",                        # 不输出报告,这里可以设置成向tensorboard和wandb输出报告
    overwrite_output_dir = True,           # 是否覆盖输出目录
    group_by_length=True,                    # 是否根据长度分组,这个参数可以加速训练,其原理是将长度相近的数据放在一起
)

#加载模型并且开始训练
# 加载预训练模型
original_model = AutoModelForCausalLM.from_pretrained(
    model_path,                                # 预训练模型所存放的路径
    quantization_config=quant_config,          # 使用什么样的量化配置
    attn_implementation="flash_attention_2",   # 是否要使用flash attention,这里的设置是使用flash attention
)

# 1 - Enabling gradient checkpointing to reduce memory usage during fine-tuning
original_model.gradient_checkpointing_enable()
# 2 - Using the prepare_model_for_kbit_training method from PEFT
original_model = prepare_model_for_kbit_training(original_model)
peft_model = get_peft_model(original_model, config)   # 基于LoRA的配置获取PEFT模型

peft_model.config.use_cache = False
peft_trainer = Trainer(                   # 定义Trainer
    model=peft_model,
    train_dataset=train_dataset,
    args=peft_training_args,
    data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)

torch.cuda.empty_cache()

start_time = time.time()
peft_trainer.train()                      # 开始训练
end_time = time.time()

print(f"Training time: {end_time - start_time} seconds")

补充

  1. 在 qlora 环境里注册 Jupyter kernel,并立即可在 Notebook 使用:

激活 qlora 环境

conda activate qlora

安装 ipykernel(如果尚未安装)

pip install ipykernel

在 Jupyter 中注册 kernel

python -m ipykernel install --user --name qlora --display-name "Python (qlora)"

确认 kernel 已注册

jupyter kernelspec list

相关推荐
小明和大树2 小时前
JAVA 学习笔记 三
java·笔记·学习
糕......2 小时前
Java IO流:数据传输的艺术与机制
java·开发语言·网络·学习
栗少2 小时前
the-science-of-storytelling
学习
好奇龙猫2 小时前
【人工智能学习-AI-MIT公开课-第6.博弈,极小化极大化,α-β】
人工智能·学习
djxbbdjedhb2 小时前
Linux应用学习-读取jpg显示在LCD上
linux·学习
名誉寒冰2 小时前
AI大模型-Prompt工程参考学习
人工智能·学习·大模型·prompt
狮子雨恋2 小时前
Python 多维数组学习示例
python·学习·numpy
老王熬夜敲代码2 小时前
C++模版元编程1
数据结构·c++·笔记
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 36--二次封装MySQL数据库连接操作
python·学习·测试工具·pytest