大语言模型实战

在上一章对Qwen-1.8B模型进行裸测时,我们发现模型对指令遵循的效果不是很理想,在生成结果时,虽然结果以JSON格式返回,但输出内容格式并不完全正确,并且对话要素抽取效果也有待提升。那么如何对本地模型进行定制优化来提高模型的效果呢?本节通过构建一个基于wen-1.8B的对话要素抽取模型,让读者更加深入地了解任务的原理、流程,以及如何利用真实场景数据来进行大型语言模型的调优。

 

1. 项目介绍


本项目是基于Qwen-1.8B模型的对话要素抽取实战。通过Qwen-1.8B模型在医疗对话数据集中抽取药品名称、药物类别、医疗检查、医疗操作、现病史、辅助检查、诊断结果和医疗建议等相关内容,并对Qwen-1.8B模型进行模型训练及测试,让读者更加深入地了解大型语言模型在真实场景中如何进行微调。代码见GitHub中的DiaEleExtraProj项目。项目主要结构如下。

·data:存放数据的文件夹。

■all.json:医疗对话要素抽取数据集。

·qwen1_8:Qwen-1.8B模型相关文件夹。

■modeling_qwen.py:Qwen-1.8B模型文件。

■tokenization_qwen.py:Qwen-1.8B模型分词器文件。

■configuration_qwen.py:Qwen-1.8B模型配置文件。

■qwen_generation_utils.py:Qwen-1.8B模型工具文件。

·data_helper.py:数据处理文件。

·train.py:模型训练文件。

·predict.py:模型预测文件。

·merge_params.py:模型参数合并文件。

·utils.py:模型工具文件,包括模型数据类、参数打印函数、模型保存函数、模型验证函数、线性层查找函数等。

本项目会从数据预处理、模型微调、模型预测几个部分入手,手把手地带领大家一 起完成一个大型语言模型微调任务。

 

 

2. 数据预处理


智能对话诊疗数据集:https://github.com/lemuria-wchen/imcs21-cblue。

本项目的医疗对话数据是由复旦大学大数据学院在复旦大学医学院专家的指导下构建的智能对话诊疗数据集 。该数据收集了真实的在线医患对话,并进行了命名实体、对话意图、症状标签、医疗报告等标注。由于本项目主要进行对话要素抽取,因此对数据进行重新构造,抽取药品名称、药物类别、医疗检查、医疗操作、现病史、辅助检查、诊断结果和医疗建议等8个相关内容。医疗对话数据示例如下。

 

 

其中,药品名称、药物类别、医疗检查和医疗操作4个标签中内容严格来自于对话内容,药品名称表示医疗对话中提到的具体的药物名称;药物类别表示医疗对话中提到的根据药物功能进行划分的药物种类内容;医疗检查表示医疗对话中提到的医学检验内容;医疗操作表示医疗对话中提到的相关的医疗操作。在医疗对话中,药品名称、药物类别、医疗检查和医疗操作4个标签会对应多个抽取内容。而现病史、辅助检查、诊断结果和医疗建议4个标签中的内容需要从对话中总结提炼,现病史表示医疗对话中病人患病后的全过程总结;辅助检查表示医疗对话中涉及的医疗检查结果;诊断结果表示医疗对话中医生对病人的诊断结果;医疗建议表示医疗对话中医生对病人的治疗建议。在医疗对话中,现病史、辅助检查、诊断结果和医疗建议4个标签仅对应一个内容。

本项目中的医疗对话数据共包含2472个样本,并随机选取50个样本作为测试集,其他所有样本作为训练集。同时,将数据集转换为标准大型语言模型微调格式,包含提示内容、输入内容和输出内容。数据处理代码见data_helper.py文件,具体流程如下。

步骤1:利用json.load读取原始文件内容。

步骤2:遍历每一条样本。

步骤3:创建提示内容,根据角色、任务、详细要求三个方面编写提示内容。

步骤4:将数据按照instruction、input和output形式进行保存,并将输出结果修改为markdown形式,方便后面解析。

步骤5:将数据随机打乱,并遍历数据,将其分别保存到训练文件和测试文件中。

 

 

设置原始数据路径和训练集、测试集保存路径,运行得到最终数据结果,具体如下。

 

单个样本示例如下。

 

对于模型微调,需要构建模型所需要的数据类,加载训练数据和测试数据,将文本数据转化成模型训练可用的索引ID数据,详细代码在utils.py文件中,数据构造过程具体如下。

步骤1:通过数据路径、分词器、模型训练最大长度、输入最大长度、是否去除不符合标准数据等参数,初始化数据类所需的变量。

步骤2:遍历数据文件中的每一个样本,利用json.load进行数据加载。

步骤3:利用分词器构造千问模型所需的系统指令内容。

步骤4:利用分词器对提示模板和源文本进行分词,构建输入内容及标签,并根据输入最大长度对输入内容进行裁剪。

步骤5:利用分词器对目标文本进行分词,构建输出内容及标签,并根据输出最大长度对输出内容进行裁剪。

步骤6:将系统指令、用户输入、模型输出进行拼接,构建完整的模型训练所需数据。

步骤7:根据参数进行判断,是否丢弃不符合长度标准的数据。

步骤8:将每个样本进行保存,用于后续训练使用。

 


3. 模型微调


该项目的大型语言模型微调主要采用QLoRA方法,其详细原理可参见2.3.7节。模型微调文件为train.py,主要包括模型训练参数设置函数和模型训练函数,主要涉及以下步骤。

步骤1:设置模型训练参数。如果没有输入参数,则使用默认参数。

步骤2:通过判断单卡训练还是多卡训练,设置并获取显卡信息,用于模型训练。并设置随机种子,方便模型复现。

步骤3:实例化QWenTokenizer分词器和QWenLMHeadModel模型,并在模型初始化过程中采用INT4初始化。

步骤4:找到模型中所有的全连接层,并初始化LoRA模型。

步骤5:加载模型训练所需要的训练数据和测试数据,如果是多卡训练需要分布式加载数据。

步骤6:加载DeepSpeed配置文件,并通过训练配置参数修改optimizer、scheduler等配置。

步骤7:利用DeepSpeed对原始模型进行初始化。

步骤8:遍历训练数据集,进行模型训练。

步骤9:获取每个训练批次的损失值,并进行梯度回传、模型调优。

步骤10:当训练步数整除累计步数时,记录训练损失值;当步数达到模型保存步数时,用测试数据对模型进行验证计算困惑度指标及模型保存。

 

在模型在训练时,可以在文件中修改相关配置信息,也可以通过命令行运行train.py文件指定相关配置信息。其中,一般进行设置的配置信息如表7-1所示。

 

 

模型单卡训练命令如下。

 

模型四卡训练命令如下。

 

在模型训练过程中,通过CUDA_VISIBLE_DEVICES参数控制具体哪块或哪几块显卡进行训练,如果不加该参数,表示使用运行机器上所有卡进行训练。模型训练过程所需的显存大小为24GB,训练参数占总参数的比例为1.0794%。如果没有足够大的显卡,可以减小模型最大长度和训练批次大小。

运行状态如图7-8所示。

 

 

模型训练完成后,可以根据使用Tensorboard查看训练损失下降情况以及测试集困惑度变化情况,如图7-9所示。

 

 

4. 模型预测


为了保证模型在保存时,所存储变量尽可能小,以节约模型存储时间,在7.3.2节中模型存储时,仅保存了训练的参数,即外挂的LoRA参数。因此在模型预测前,需要进行参数融合,即将外挂参数合并到原来模型的参数中,形成一个新的模型。这样在模型进行预测的过程中,不会增加额外的推理时间。参数融合代码见merge_params.py文件,具体步骤如下。

步骤1:设置模型融合参数。

步骤2:加载千问原始模型参数。

步骤3:加载LoRA方法训练的增量参数。

步骤4:调用merge_and_unload函数,将外挂参数合并到原始参数中。

步骤5:将合并后的模型参数进行保存。

 

完成模型参数融合后,可以进行单条测试。测试文件为predict.py,主要涉及以下步骤。

步骤1:设置预测的配置参数。

步骤2:加载融合LoRA参数后的模型以及Tokenizer。

步骤3:内置对话要素抽取的提示词内容。

步骤4:输入对话文本,进行对话要素抽取。

 

其中,单样本预测的具体步骤如下。

步骤1:获取解码的配置参数,涉及生成内容最大长度、TopP解码的Top-P概率、温度、重复惩罚因子等。

步骤2:根据文本内容,融合提示词和输入对话内容,构建模型输入所需要的input_ids。

步骤3:进行模型预测,输出结果。

步骤4:对输出内容进行截取,仅截取生成内容,并将其转化为字符串。

 

在模型预测时,可以在文件中修改相关配置信息,也可以通过命令行运行

predict.py文件指定相关配置信息。模型预测配置信息如表7-2所示。

 

 

模型单条预测命令如下,运行后如图7-10所示。

 

 

对要素抽取模型进行预测测试,针对每个医疗对话内容进行要素抽取,测试样例详细如下。

 

通过对测试结果分析,可以看出经过微调的模型,输出内容格式和要素抽取结果均有明显提高。输出结果格式为定义的JSON格式,对药品名称、药物类别、医疗检查和医疗操作的抽取内容均来自于原对话,并且没有重复内容。对于现病史、辅助检查、诊断结果和医疗建议的要素抽取内容均通过总结提炼得出,符合任务原本预期。