一、测试大模型

1.1 新建管理环境

首先,在Anaconda Prompt输入命令,新建MedChinese 环境。

conda create -n MedChinese python=3.10

然后,进入该环境。

activate MedChinese 

1.2 安装包

在MedChinese 环境中输入以下命令安装必要的包。

pip install transformers==4.55.4
pip install torch==2.8.0
pip install accelerate==1.10.0

1.3 下载大模型

在魔塔社区官网https://modelscope.cn/home,下载模型Qwen1.5-0.5B-Chat-GGUF,存放在Qwen目录中。

1.4 测试大模型

新建程序test.py,用于测试大模型。

# 导入Hugging Face Transformers相关库
from transformers import AutoModelForCausalLM, AutoTokenizer

# 指定模型名称
MODEL = "Qwen/Qwen1.5-0.5B-Chat-GGUF"
# 加载训练好的模型和分词器# tokenizer和model要一一对应
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MODEL, trust_remote_code=True, device_map='auto')
# 模型设为评估状态
model.eval()
# 定义测试示例
examples = [ {"instruction": "使用中医知识正确回答适合这个病例的中成药。", "input": "我这段时间感觉身体不太对劲,有腹泻的迹象,面黄肌瘦,吃点什么中成药能改善?" },
             {"instruction": "使用中医知识正确回答适合这个病例的中成药。", "input": "我昨天开始咳嗽,感觉喉咙痛,痰又稠又黄,还感觉有点发热。" }]
# 测试模型生成结果
for example in examples:
    context = f"Instruction: {example['instruction']}\nInput: {example['input']}\nAnswer: "

    # 使用tokenizer对context进行处理,并将结果转换为PyTorch张量格式
    inputs = tokenizer(context, return_tensors="pt")
    # 模型生成回复
    outputs = model.generate(inputs.input_ids.to(model.device), max_length=512, num_return_sequences=1, no_repeat_ngram_size=2)
    # 对回复内容进行解码
    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(f"Output: \n{answer}\n")

二、预训练大模型

新建程序finetune.py,用于预训练大模型。

2.1 本地安装与设置wandb

(1)本地安装

wandb(Weights&Biases, W&B):用于跟踪、可视化和协作机器学习实验的工具,支持在线和离线。它提供了一个简单的 Python API,可以轻松地将实验数据发送到云端,并通过 Web 应用程序进行访问和可视化。利用docker在本地安装wandb。打开系统CMD命令窗口,输入命令:

docker run --rm -d -v wandb:/vol -p 5001:8080 --name wandb-local wandb/local:0.9.41

(2)本地设置

在谷歌浏览器通过网址http://localhost: 5001访问wandb。首先输入邮箱、用户名和密码创建用户。最后,通过setting选项,查询API keys信息。

(3)程序添加必要字段

import os
import wandb
os.environ["WANDB_BASE_URL"] = "http://localhost:5001"
wandb.login(key="local-xxx")   # 此处设置API keys的值。

2.2 自定义中药数据集

from torch.utils.data import Dataset
import json
class MedicineDataset(Dataset):
    def __init__(self, data_path, tokenizer, device):
        self.data = json.load(open(data_path,'r',encoding='utf8'))
        self.tokenizer = tokenizer
        self.device = device

    def __len__(self):
        # 返回data的长度
        return len(self.data)

    def __getitem__(self, idx):
        # 从data中获取指定索引的示例
        example = self.data[idx]
        # 对示例进行格式化
        formatted_example = self.format_example(example)
        # 使用tokenizer处理context部分,并设置最大长度为512,截断超出部分,使用最大长度进行填充,并返回PyTorch张量
        inputs = self.tokenizer(formatted_example["context"], max_length=512, truncation=True, padding="max_length", return_tensors="pt" )
        # 使用tokenizer处理target部分,设置与inputs相同的参数
        labels = self.tokenizer(formatted_example["target"], max_length=512, truncation=True, padding="max_length",  return_tensors="pt" )
        # 将labels的input_ids添加到inputs中,作为预测目标
        inputs["labels"] = labels["input_ids"]
        # 确保所有张量在同一个设备上,并移除不必要的维度
        return {key: val.squeeze().to(self.device) for key, val in inputs.items()}

    def format_example(self, example: dict) -> dict:
        # 构建context字符串,包含指令和输入(如果有)
        context = f"Instruction: {example['instruction']}\n"
        if example.get("input"):
            context += f"Input: {example['input']}\n"
            context += "Answer: "
            target = example["output"]
        # 返回包含context和target的字典
        return {"context": context, "target": target}

2.3 对大模型进行设置

# 导入微调模型所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
# 指定模型
MODEL = "Qwen/Qwen1.5-0.5B-Chat-GGUF"
# 判断设备类型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(MODEL, trust_remote_code=True, use_cache=False)
# 把模型移动到设备上
model = model.to(device)
# 节省内存的一些配置
model.supports_gradient_checkpointing = True
# 支持梯度检查点功能,减少显存使用
model.gradient_checkpointing_enable()
# 启用梯度检查点功能,减少训练时的显存占用
model.enable_input_require_grads()
# 允许模型输入的张量需要梯度,支持更灵活的梯度计算
model.is_parallelizable = True
# 指定模型可以并行化处理
model.model_parallel = True

2.4 LoRA配置

(1)安装peft库

PEFT(参数高效微调)是一个库,用于有效地将大型预训练模型适配到各种下游应用,无需微调模型的所有参数,因为这成本过高。PEFT 方法仅微调少量(额外)模型参数,从而显著降低计算和存储成本,同时获得与完全微调模型相当的性能。这使得在消费级硬件上训练和存储大型语言模型 (LLM) 更加容易。在MedChinese 环境中输入以下命令。

pip install peft==0.5.0

(2)配置相关参数

# 导入peft库
from peft import get_peft_model, LoraConfig, TaskType
# 配置LoRA相关参数
peft_config = LoraConfig(task_type=TaskType.CAUSAL_LM, # 任务类型: 因果语言模型(Causal LM)
                         inference_mode=False, # 设置推理模式为False,表示当前配置用于训练模式,而非推理模式
                         r=8, # 设置低秩分解的秩: Rank=8。r越小,表示模型的参数量和内存占用越少,压缩程度越高
                         lora_alpha=16, # 设置缩放因子:lora_alpha=16
                         lora_dropout=0.1, # 设置dropout概率:lora_dropout=0.1,表示有10%的神经元会被丢弃,此操作有助于防止模型过拟合,提升模型的泛化能力
                         target_modules=["q_proj", "v_proj"] # 查询投影和值投影模块target_modules
                         )
# 在模型上应用LoRA配置
model = get_peft_model(model, peft_config)

2.5 微调模型

(1)新建文件夹

创建文件夹data、results、logs。

(2)下载数据集。

在魔塔社区官网https://modelscope.cn/home,下载数据集cpmi_dataset.json,存放在data目录。

(3)开始微调

# 创建训练数据集
train_dataset = MedicineDataset("data/cpmi_dataset.json", tokenizer, device)

# 导入训练相关的库
from transformers import TrainingArguments
from transformers.trainer import Trainer

# 定义训练参数
training_args = TrainingArguments(output_dir="./results", # 训练结果保存的目录
                                  num_train_epochs=10, # 训练的总轮数
                                  per_device_train_batch_size=32, # 每个设备上的训练批次大小
                                  gradient_accumulation_steps=8, # 梯度累积步数,在进行反向传播前累积多少步
                                  eval_strategy="no", # 评估策略,这里设置为不评估
                                  save_strategy="epoch", # 保存策略,每个 epoch 保存一次模型
                                  learning_rate=5e-5, # 学习率
                                  fp16=True, # 启用 16 位浮点数训练,提高训练速度并减少显存使用
                                  logging_dir="./logs", # 日志保存目录
                                  dataloader_pin_memory=False # 禁用pin_memory以节省内存
                                  )
# 自定义CustomTrainer
class CustomTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
        labels = inputs.pop("labels")
        # 从输入中取出标签
        outputs = model(**inputs)
        # 获取模型输出
        logits = outputs.logits
        # 获取模型输出的logits
        shift_logits = logits[..., :-1, :].contiguous()
        # 对logits进行偏移,准备计算交叉熵损失
        shift_labels = labels[..., 1:].contiguous()
        # 对标签进行偏移,准备计算交叉熵损失
        loss_fct = torch.nn.CrossEntropyLoss()
        # 定义交叉熵损失函数
        loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
        # 计算损失
        return (loss, outputs) if return_outputs else loss
        # 根据参数返回损失和输出

# 定义Trainer
trainer = CustomTrainer(model=model, # 训练的模型
                        args=training_args, # 训练参数
                        train_dataset=train_dataset # 训练数据集
                        )
# 开始微调
print("开始微调")
trainer.train()
print("微调完成")

2.6 保存微调模型

新建文件夹fine-tuning-Qwen1.5-0.5B-Chat,用于保存微调后的模型参数。

# 微调后模型的保存路径
FINE_TUNING_DIR = "./fine-tuning-Qwen1.5-0.5B-Chat"
# 保存训练后的模型和配置文件
model.save_pretrained(FINE_TUNING_DIR)
tokenizer.save_pretrained(FINE_TUNING_DIR)
# 将配置文件下载到模型目录中
config = model.config.to_dict()
config_path = os.path.join(FINE_TUNING_DIR, 'config.json')
with open(config_path, 'w') as f:
    json.dump(config, f, ensure_ascii=False, indent=4)
print(f"微调后的模型已经成功保存到: {FINE_TUNING_DIR}")

三、大模型评估

微调完成之后,最后一步就是大模型评估。

# 导入所需要的库
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载训练好的模型和分词器
FINE_TUNING_DIR = "./fine-tuning-Qwen1.5-0.5B-Chat"
tokenizer = AutoTokenizer.from_pretrained(FINE_TUNING_DIR, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(FINE_TUNING_DIR, trust_remote_code=True, device_map='auto')
# 模型设为评估状态
model.eval()
# 定义测试示例
questions = [{"instruction": "使用中医知识正确回答适合这个病例的中成药。","input": "我这段时间感觉身体不太对劲,有腹泻的迹象,面黄肌瘦,吃点什么中成药能改善?" },
             {"instruction": "使用中医知识正确回答适合这个病例的中成药。", "input": "我昨天开始咳嗽,感觉喉咙痛,痰又稠又黄,还感觉有点发热。" }]
# 生成回复
for question in questions:
    context = f"Instruction: {question['instruction']}\nInput: {question['input']}\nAnswer: "
    # 对输入文本进行编码
    inputs = tokenizer(context, return_tensors="pt")
    # 模型生成回复
    outputs = model.generate(inputs.input_ids.to(model.device), max_length=512, num_return_sequences=1, no_repeat_ngram_size=2)
    # 对回复内容进行解码
    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(f"Input: {question['input']}")
    print(f"Output: {answer}\n")

四、小结

本项目基于 LoRA 技术与 peft 框架,对 Qwen 模型进行了微调,使其在中医领域的问答任务上取得了较好的效果。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐