ChatGLM-6B 中文对话模型复现、调用模块、微调及部署实现(更新中)

ChatGLM-6B-PT

一、前言

近期,清华开源了其中文对话大模型的小参数量版本 ChatGLM-6B(GitHub地址:https://github.com/THUDM/ChatGLM-6B)。其不仅可以单卡部署在个人电脑上,甚至 INT4 量化还可以最低部署到 6G 显存的电脑上,当然 CPU 也可以。
随着大语言模型的通用对话热潮展开,庞大的参数量也使得这些模型只能在大公司自己平台上在线部署或者提供 api 接口。所以 ChatGLM-6B 的开源和部署在个人电脑上,都具有重要的意义。

二、下载

在这里插入图片描述

本仓库实现了对于 ChatGLM-6B 模型基于 P-Tuning v2 的微调。P-Tuning v2 将需要微调的参数量减少到原来的 0.1%,再通过模型量化、Gradient Checkpoint 等方法,最低只需要 7GB 显存即可运行。
在这里插入图片描述

下面以 ADGEN (广告生成) 数据集为例介绍代码的使用方法。

软件依赖

运行微调需要4.27.1版本的transformers。除 ChatGLM-6B 的依赖之外,还需要安装以下依赖

pip install rouge_chinese nltk jieba datasets

使用方法
下载数据集

ADGEN 数据集任务为根据输入(content)生成一段广告词(summary)。

{
    "content": "类型#上衣*版型#宽松*版型#显瘦*图案#线条*衣样式#衬衫*衣袖型#泡泡袖*衣款式#抽绳",
    "summary": "这件衬衫的款式非常的宽松,利落的线条可以很好的隐藏身材上的小缺点,穿在身上有着很好的显瘦效果。领口装饰了一个可爱的抽绳,漂亮的绳结展现出了十足的个性,配合时尚的泡泡袖型,尽显女性甜美可爱的气息。"
}

从 Google Drive 或者 Tsinghua Cloud 下载处理好的 ADGEN 数据集,将解压后的 AdvertiseGen 目录放到本目录下。

Google Drive:https://drive.google.com/file/d/13_vf0xRTQsyneRKdD1bZIr93vBGOczrk/view
Tsinghua Cloud:https://cloud.tsinghua.edu.cn/f/b3f119a008264b1cabd1/?dl=1

三. 训练

运行以下指令进行训练:

bash train.sh

train.sh 中的 PRE_SEQ_LEN 和 LR 分别是 soft prompt 长度和训练的学习率,可以进行调节以取得最佳的效果。P-Tuning-v2 方法会冻结全部的模型参数,可通过调整 quantization_bit 来被原始模型的量化等级,不加此选项则为 FP16 精度加载。

在默认配置 quantization_bit=4、per_device_train_batch_size=1、gradient_accumulation_steps=16 下,INT4 的模型参数被冻结,一次训练迭代会以 1 的批处理大小进行 16 次累加的前后向传播,等效为 16 的总批处理大小,此时最低只需 6.7G 显存。若想在同等批处理大小下提升训练效率,可在二者乘积不变的情况下,加大 per_device_train_batch_size 的值,但也会带来更多的显存消耗,请根据实际情况酌情调整。

四. 推理

将 evaluate.sh 中的 CHECKPOINT 更改为训练时保存的 checkpoint 名称,运行以下指令进行模型推理和评测:

bash evaluate.sh

评测指标为中文 Rouge score 和 BLEU-4。生成的结果保存在:./output/adgen-chatglm-6b-pt-8-1e-2/generated_predictions.txt

  1. 例子

示例1

Input: 类型#上衣*材质#牛仔布*颜色#白色*风格#简约*图案#刺绣*衣样式#外套*衣款式#破洞

Label: 简约而不简单的牛仔外套,白色的衣身十分百搭。衣身多处有做旧破洞设计,打破单调乏味,增加一丝造型看点。衣身后背处有趣味刺绣装饰,丰富层次感,彰显别样时尚。

Output[微调前]:这件上衣的材质是牛仔布,颜色是白色,风格是简约,图案是刺绣,衣样式是外套,衣款式是破洞。

Output[微调后]: 这是一款简约的牛仔外套,破洞设计,将牛仔布破洞,带来一种随意与个性。破洞的牛仔外套,展现出时尚气息,带来一种休闲感。同时,刺绣图案,让整件外套更加立体。

示例2

Input: 类型#裙*版型#显瘦*风格#文艺*风格#简约*图案#印花*图案#撞色*裙下摆#压褶*裙长#连衣裙*裙领型#圆领

Label: 文艺个性的印花连衣裙,藏青色底蕴,低调又大气,撞色太阳花分布整个裙身,绚丽而美好,带来时尚减龄的气质。基础款的舒适圆领,简约不失大方,勾勒精致脸庞。领后是一粒包布扣固定,穿脱十分方便。前片立体的打褶设计,搭配后片压褶的做工,增添层次和空间感,显瘦又有型。

Output[微调前]: 类型#裙版型#显瘦风格#文艺风格#简约图案#印花图案#撞色裙下摆#压褶裙长#连衣裙裙领型#圆领 1. 连衣裙:简约风格,裙长为膝盖以上,裙领型为圆领。2. 裙下摆:压褶设计,使裙摆呈现出流畅的褶皱效果。3. 裙领型:裙领型为圆领,使穿上连衣裙后更加有型。4. 版型:采用显瘦设计,让连衣裙看起来更加苗条。5. 风格:文艺风格,让连衣裙更加有内涵和品味。6. 图案:印花设计,在连衣裙上印有独特的图案。7. 撞色:采用撞色设计,让连衣裙在色彩上更加鲜明、富有层次感。

Output[微调后]: 这是一款文艺范的连衣裙,以印花为元素,采用简约的印花,既能够突出文艺气质,又能够展现简约风。在印花的同时又有领子和裙摆的压褶设计,更加凸显文艺气质。简约而不会过于单调,搭配出街,穿着十分舒适。

五 复现过程

AI对话

执行 python 文件即可,可以在命令行终端输入:

python cli_demo.py

即可启动 demo,开始使用了!
在这里插入图片描述自己写代码调用模型来运行

用如下几行代码,就能启动模型运行,并输出结果。

from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True).half().quantize(4).cuda()
model = model.eval()

ques = '''
下面这段代码的功能是什么?
# QSort
nus = [4, 5, 1, 2, 3, 5, 4, 1]
 
# left,right分别为子数组中第一个元素和最后一个元素在原数组中的位置
def QSort(left, right):
    # 边界条件               
    if left >= right:                
        return
    # 初始化左右指针的初始值
    l, r, key = left, right, nus[left] 
    # 调整元素的位置 
    while l < r:                     
        while l < r and nus[r] >= key:
            r -= 1
        nus[l] = nus[r]
        while l < r and nus[l] <= key:
            l += 1
        nus[r] = nus[l]
    # 把基准值赋给左右指针共同指向的位置
    nus[r] = key 
    # 对左侧数组排序                   
    QSort(left, l-1)  
    # 对右侧数组排序              
    QSort(l+1, right)               
QSort(0, len(nus) - 1)
print(nus)

'''

response, history = model.chat(tokenizer, ques, history=[])
print(response)

效果图
在这里插入图片描述

微调

微调步骤大同小异,据代码:tatsu-lab/stanford_alpaca · GitHub,可得微调的步骤如下

  1. 导入所需的库:包括torch,transformers等。

  2. 定义一些全局变量,如特殊字符、提示模板等。

  3. 定义用于处理模型、数据和训练参数的数据类。

  4. 定义辅助函数,如:

    safe_save_model_for_hf_trainer:安全地保存训练器中的模型;
    smart_tokenizer_and_embedding_resize:调整分词器和词嵌入大小;
    _tokenize_fn:将字符串序列进行分词;
    preprocess:预处理数据,对源数据和目标数据进行分词。

  5. 定义SupervisedDataset类,用于加载数据、格式化输入、进行分词等操作。

  6. 定义DataCollatorForSupervisedDataset类,用于将数据集的实例整理为批次。

  7. 定义make_supervised_data_module函数,用于创建监督学习任务的数据集和整理器。

  8. 定义train函数,用于执行以下操作:

    a. 解析命令行参数:使用transformers.HfArgumentParser解析命令行参数,将它们分为模型参数、数据参数和训练参数
    b. 加载预训练模型:使用transformers.AutoModelForCausalLM.from_pretrained从预训练的模型检查点加载一个用于因果语言建模的模型
    c. 加载分词器:使用transformers.AutoTokenizer.from_pretrained从预训练的模型检查点加载分词器
    d. 为分词器添加特殊字符:根据需要,将特殊字符添加到分词器中
    e. 创建数据集和整理器:使用make_supervised_data_module函数为监督学习任务创建数据集和整理器
    f. 实例化Trainer类:实例化transformers.Trainer类,并传入模型、分词器、训练参数以及数据集。Trainer类负责管理训练过程
    g. 训练模型:调用Trainer类的train()方法对模型进行微调,相当于链路就是:transformers库 \rightarrow Trainer类 \rightarrow train函数
    h. 保存模型状态:在训练完成后,调用Trainer.save_state()方法保存模型的状态
    i. 将训练器的模型安全地保存到磁盘:使用safe_save_model_for_hf_trainer函数将训练器中的模型安全地保存到磁盘

  9. 如果这个脚本是主程序,则调用train函数以开始训练过程
    中的损失计算、梯度下降、参数更新呢,实际上这三步的具体实现都封装在了Hugging face社区实现的鼎鼎大名的transformers的Trainer类中:transformers/trainer.py at main · huggingface/transformers · GitHub

这个 transformers/trainer.py 文件的主要部分如下

    导入:文件首先导入了一些必要的Python库,如os、sys、logging以及其他一些库。它还导入了Hugging Face库中的一些相关模块,如datasets、transformers等
    TrainerState:这个类用于保存训练器的状态,包括当前的epoch、迭代步数、最佳指标值等

    TrainOutput:这个类用于返回训练过程的结果,包括训练损失、训练步数等

   TrainerControl:这个类提供了一种用于控制训练循环的机制,例如,当用户想要在某个特定的迭代步数时停止训练

   Trainer:这是文件中的主要类,用于训练和评估Transformers模型,它包含许多方法,如train、evaluate、predict等

更具体的,Trainer类包括如下关键方法:

init:初始化方法,用于创建训练器对象。它接收模型、训练参数、数据集等作为输入,并设置相关属性

def __init__(
    self,
    model: PreTrainedModel,
    args: TrainingArguments,
    train_dataset: Optional[Dataset] = None,
    eval_dataset: Optional[Dataset] = None,
    tokenizer: Optional[PreTrainedTokenizerBase] = None,
    data_collator: Optional[DataCollator] = None,
    train_iterator: Optional[DataLoader] = None,
    eval_iterator: Optional[DataLoader] = None,
    ...
):

train:这个方法负责整个训练过程,它包括遍历数据集、计算损失、计算梯度、更新模型参数以及日志记录等

遍历数据集:train方法通过使用dataloader来遍历训练数据集

for step, inputs in enumerate(epoch_iterator):

计算损失:损失计算在training_step方法中,接收输入数据并产生预测输出,然后,这个预测输出会与真实输出(标签)进行比较,以计算损失

outputs = model(**inputs)

上述代码行使用model(已经加载了预训练模型)和inputs(包含输入数据的字典)计算模型的预测输出。这个outputs变量包含模型预测的结果
接下来,我们从outputs中获取预测结果,并与真实标签(即labels)进行比较,以计算损失

loss = outputs.loss

outputs.loss是模型预测输出和真实输出(标签)之间的损失。这个损失值将用于计算梯度并更新模型参数
计算梯度:loss.backward()这行代码计算模型参数关于损失的梯度

loss.backward()

梯度累积:当gradient_accumulation_steps大于1时,梯度会被累积,而不是立即更新模型参数

if (step + 1) % self.args.gradient_accumulation_steps == 0:

更新模型参数:optimizer.step()这行代码根据计算出的梯度来更新模型参数

self.optimizer.step()

学习率调整:lr_scheduler.step()根据预定义的学习率调度策略更新学习率

self.lr_scheduler.step()

日志记录:log方法用于记录训练过程中的一些关键指标,例如损失、学习率等

evaluate:这个方法用于评估模型在验证数据集上的性能,返回评估结果

def evaluate(
    self, eval_dataset: Optional[Dataset] = None, ignore_keys: Optional[List[str]] = None
) -> Dict[str, float]:

predict:这个方法用于在给定的数据集上进行预测,返回预测结果

def predict(
    self, test_dataset: Dataset, ignore_keys: Optional[List[str]] = None
) -> PredictionOutput:

save_model:这个方法用于将训练好的模型保存到指定的目录

def save_model(self, output_dir: Optional[str] = None):

运行训练截图,数据集huggingface 下载
在这里插入图片描述微调前的效果
在这里插入图片描述
微调后的效果
在这里插入图片描述

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值