创新项目实训记录(三)

在使用rag对原本的通用模型进行改造后,我们发现整合后,模型的效果并不是很好。

下面介绍我们分析的原因和进行的两点改进。

一、改进rag中检索和返回的内容

我们的数据集是对话(问答)形式的数据集,即每条数据分为问题和回答两部分,在对原始数据集进行切割时也保持这样的结构,切割后的每条文本数据结构为“问题+一到多条回答的拼接”。

参考一些rag策略,发现在很多问答rag的策略中,对每条数据整体做嵌入和索引建立,但返回给模型的是top-k文本中的回答(即不返回问题)。认为该策略具有一定合理性,一是因为使用的大语言模型有token限制,删掉问题,减少语言模型的分析压力;二是检索到的问题可能与query并不完全一致,返回整个问答对可能会对模型产生误导,降低结果的准确性。

为了实现不返回文本中的问题的功能,遍历原始数据集,重新进行一遍数据切割,在切割的同时统计每个文本

下面展示的是切割部分的关键代码,可以看到每条切割文本生成后,都会将生成文本的问题长度保存到mask数组中。

        answers_info = item['answers_info']
        i=0
        while i<len(answers_info):#进行一个问题和多个回答的拼接
            answerForOne=''
            combination=question       
            count=0#记录一个当前chunk中存储了多少个answer
            while i<len(answers_info):
                answer=answers_info[i]
                answer_content = answer['content'].replace('\r', '').replace('\n', '')
                ...#一些对长度的逻辑判断
                       
            combination+=answerForOne   
            mask.append(len(question))               
            combinationList.append(combination)    

完成所有原始文本的切割后,将所有mask写入文本文件,每行对应一条切割问答对的问题长度。

在检索后,因为检索索引中的向量索引与mask的索引一致,可以直接用检索到的top-k下标到mask中读取问题长度,对rag_answer,只拼接每个切割问答对的问题部分。

        I = I[0]
        for index in I:
            bef = mask[index]
            rag_answer += text_documents[index][bef:] + '\n'

二、加上微调技术

1.背景

使用rag增强后的psyLLM作为我们网站的使用的模型,在进行网站使用测试时发现,由于知识库中数据的限制,rag有时无法发挥很好的性能,甚至会误导原本LLM的回答。

分析原因,认为是我们使用的数据集具有局限性:我们的数据集多集中于情感问题,对于生活中的其他问题,知识库中可能不存在与query相关的文本,但检索算法一定会返回最近的k个向量的下标(即使它们与query向量的相似度很低)。

我们可以选择对返回的top-k的相似度进行约束,search方法返回两个元素,I是top-k向量的下标,D是top-k向量与query向量的距离。可以判断每个距离是否超过阈值,舍弃与query向量距离较大的向量,不将其对应的原始文本拼接到rag_answer中。但这样就与我们使用rag的目的不完全一致了。

        D, I = loaded_index.search(xq, k)  # actual search
        count=0
        for distance in D:
            if distance>threshold:
                break
            count+=1

考虑使用微调技术:微调后query与用于训练的数据是否相关的问题就交给了模型来判断,将知识库直接嵌入到模型的参数中,比rag更为直接。

2.环境配置

仍然选择我们之前使用的chatGLM进行部署和微调。因为微调需要较大的显存(>=24G),因此租用一张4090的显卡进行训练。此外,运行实例要求python>=3.10

选择占用显存最少的微调方式——LORA 微调: 1张显卡,占用 14082MiB 显存。

1.安装chatGLM3

下载模型到本地

git clone https://github.com/THUDM/ChatGLM3.git

进入模型目录,安装模型

pip install -r requirements.txt
pip install nltk
pip install datasets
pip install jieba
pip install rouge_chinese

如果直接整个安装requirements.txt无法成功,可以删除requirements.txt中报错的包,先把能安装的部分安装掉。之后解决无法安装的包的问题。

当时我无法一次性安装mpi4py包,参考了以下博客:

mpi4py包安装报错_mpi4py安装失败-CSDN博客

2.从魔搭社区下载模型

因为chatGLM是国内团队开发,所以魔搭社区上也提供了模型参数的下载,相比huggingface,下载速度更快一些,成功率也更高。

直接在ChatGLM3/chatglm3-6b文件夹下clone包即可,如果服务器上下载不稳定,可以先下载到本地,再从本地上传到服务器。

git clone https://www.modelscope.cn/ZhipuAI/chatglm3-6b.git

当时在配置环境时,先在hugging-face上下载模型,完成后发现所有的文件都是齐全的,但一直报错。最后在网上检索bug的过程中发现一篇博客中提到,由于网络问题可能会发生chatglm3-6b下应有的文件名齐全,但每个文件没有下完整的情况。

使用ls -l打印文件夹下的文件大小后发现本来应该有10G+的文件只有240K,没能在hugging-face上下全。改用在魔搭社区中下载,修改basic_demo/cli_demo.py中的模型路径为本地路径,之后运行demo命令,成功跑通本地部署的chatGLM3。

python basic_demo/cli_demo.py 

这里贴一张当时成功跑通demo的图片

可以看出在中文情景下chatglm3-6b还是非常自然通顺的。

3.数据处理

当时找到了两个数据集,使用另一个数据集用来做微调,另一个数据集同样是问答形式的数据集。

ChatGLM3的github官网上给出了不同应用场景下的微调数据的格式,选择我们网站需要的对话格式的数据,根据以下格式对数据集进行整合。

对数据集中的问题和回答进行拼接和切分,同时记录最长问题和最长回答的大小,用作后面的微调配置参数,之后将每个问答对组合成不同角色回答内容的格式。

for i in range(len(questions)):
        new_data = {
             "conversations": [
            {
                "role": "user",
                "content": questions[i]
            },
            {
                "role": "assistant",
                "content": answers[i]
            }
        ]
        }

由于微调的需求,将调整格式后的数据集划分成训练集、验证集和测试集,之后分别写入json文件。

if count%10==0:
            val_data.append(new_data)
        elif count%85==0:
            test_data.append(new_data)
        else:
            train_data.append(new_data)
        count+=1
    
with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(train_data, f, ensure_ascii=False, indent=4)
        ...

调整后的数据样式如下:

将三个数据集放在服务器上的同一个文件夹下。

4.配置文件

修改ChatGLM文件夹下的lora.yaml文件(我们使用lora微调方式),修改max_input/output_length为上面我们统计的数值,修改各个数据集的文件路径,指定output_dir,max_steps和save_steps等参数。

对于微调来说,max_steps>50才有效果。

  1. lora.yaml / ptuning.yaml / sft.yaml: 模型不同方式的配置文件,包括模型参数、优化器参数、训练参数等。 部分重要参数解释如下:
    • data_config 部分
      • train_file: 训练数据集的文件路径。
      • val_file: 验证数据集的文件路径。
      • test_file: 测试数据集的文件路径。
      • num_proc: 在加载数据时使用的进程数量。
    • max_input_length: 输入序列的最大长度。
    • max_output_length: 输出序列的最大长度。
    • training_args 部分
      • output_dir: 用于保存模型和其他输出的目录。
      • max_steps: 训练的最大步数。
      • per_device_train_batch_size: 每个设备(如 GPU)的训练批次大小。
      • dataloader_num_workers: 加载数据时使用的工作线程数量。
      • remove_unused_columns: 是否移除数据中未使用的列。
      • save_strategy: 模型保存策略(例如,每隔多少步保存一次)。
      • save_steps: 每隔多少步保存一次模型。
      • log_level: 日志级别(如 info)。
      • logging_strategy: 日志记录策略。
      • logging_steps: 每隔多少步记录一次日志。
      • per_device_eval_batch_size: 每个设备的评估批次大小。
      • evaluation_strategy: 评估策略(例如,每隔多少步进行一次评估)。
      • eval_steps: 每隔多少步进行一次评估。
      • predict_with_generate: 是否使用生成模式进行预测。
    • generation_config 部分
      • max_new_tokens: 生成的最大新 token 数量。
    • peft_config 部分
      • peft_type: 使用的参数有效调整类型(如 LORA)。
      • task_type: 任务类型,这里是因果语言模型(CAUSAL_LM)。
    • Lora 参数:
      • r: LoRA 的秩。
      • lora_alpha: LoRA 的缩放因子。
      • lora_dropout: 在 LoRA 层使用的 dropout 概率
    • P-TuningV2 参数:
      • num_virtual_tokens: 虚拟 token 的数量。

运行下面的命令进行微调(单机单卡模式)

finetune_hf.py  数据集文件夹路径  本地模型路径  configs/lora.yaml

结束后会在output_dir下生成检查点文件,使用最后一个生成的即可。

5.微调效果展示

运行

python inference_hf.py 微调后生成的检查点文件路径 --prompt your prompt

即可使用微调后的模型。

简单的效果展示如下:

6.参考

ChatGML3安装与微调2_哔哩哔哩_bilibili

ChatGLM3/finetune_demo at main · THUDM/ChatGLM3 · GitHub

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值