官方项目地址:https://github.com/THUDM/ChatGLM-6B
参考本人之前的博客下载ChatGLM-6B模型:
https://blog.csdn.net/Acecai01/article/details/131221676

设备

一张3090Ti, 24G显卡

实验目的

通过微调的方法,训练ChatGLM-6B模型,使得模型能够根据输入的片段小说(斗破苍穹)文本,续写两行后面的故事,同时要是能够具备记住小说的内容的能力的话就最好了【续写中可以体现记忆,问答中也可以测试记忆】

P tuning v2

官方说明:https://github.com/THUDM/ChatGLM-6B/blob/main/ptuning/README.md
后面P tuning v2会被简写为ptv2

ptv2训练数据

仿照官方模板数据,每条训练数据包括"content"和"summary","content"为大约600字的前文(小于600字就加1行,稍微超一点也没关系),"summary"为前文后面两行完整的文字:

{"content": "“斗之力,三段!”\n望着测..痛…\n“..\n", "summary": "“这些人,都如此刻薄势力吗?或许是因为三年前他们曾经在自己面前露出过最谦卑的笑容,所以,..,孤单的身影,与周围的世界,有些格格不入。\n“下一个,萧媚!”\n"}
{"content": "周围..急促。\n少年..苦涩了。\n“这些..不入。\n..\n", "summary": "“耶!”听着测验员所喊出的成绩,少女脸颊扬起了得意的笑容…\n“啧啧,七段斗之气,真了不起,按这进度,恐怕顶多只需要三年时间,她就能成为一名真正的斗者了吧…”\n"}

为了让训练样本之间有联系,下一条样本的开头两行和上一条样本的结尾两行是相同的。

训练过程参照官方说明即可,其中train.sh的配置内容如下:

PRE_SEQ_LEN=128
LR=2e-2

CUDA_VISIBLE_DEVICES=2 /home/ml/anaconda3/envs/py_chatglm/bin/python3 main.py \
    --do_train \
    --train_file DouPo/doupo_train.json \
    --validation_file DouPo/doupo_dev.json \
    --prompt_column content \
    --response_column summary \
    --overwrite_cache \
    --model_name_or_path /data/ml/cxz/chatglm-6b \
    --output_dir output/adgen-chatglm-6b-pt-$PRE_SEQ_LEN-$LR \
    --overwrite_output_dir \
    --max_source_length 1024 \
    --max_target_length 1024 \
    --per_device_train_batch_size 1 \
    --per_device_eval_batch_size 1 \
    --gradient_accumulation_steps 16 \
    --predict_with_generate \
    --max_steps 3000 \
    --logging_steps 10 \
    --save_steps 1000 \
    --learning_rate $LR \
    --pre_seq_len $PRE_SEQ_LEN \
    --quantization_bit 4

模型微调训练完后,修改并执行目录ChatGLM-6B/ptuning/中的web_demo.sh:

PRE_SEQ_LEN=128

CUDA_VISIBLE_DEVICES=2 /home/ml/anaconda3/envs/py_chatglm/bin/python3 web_demo.py \
    --model_name_or_path /data/ml/cxz/chatglm-6b \
    --ptuning_checkpoint output/adgen-chatglm-6b-pt-128-2e-2/checkpoint-3000 \
    --pre_seq_len $PRE_SEQ_LEN

访问web页面,进行一个续写能力测试和问答测试,本人的测试问题可以参考后面的lora的结果,后面有两种微调结果的对比展示。

Lora微调

微调步骤参考博客:https://zhuanlan.zhihu.com/p/621793987?utm_id=0
以上博客的微调代码:https://github.com/mymusise/ChatGLM-Tuning
主要步骤参考以上博客,部分本人实验不一样的地方在下面的内容会有体现。

制作json训练数据如下:
在这里插入图片描述
针对本人的训练数据,修改数据转换代码cover_alpaca2jsonl.py,如下:

import argparse
import json
from tqdm import tqdm

def crl_format_example(example: dict) -> dict:
    context = f"指令: {example['指令']}\n"
    if example.get("输入"):
        context += f"输入: {example['输入']}\n"
    context += "输出: "
    target = example["输出"]
    return {"context": context, "target": target}
    
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--data_path", type=str, default="data/alpaca_data.json")
    parser.add_argument("--save_path", type=str, default="data/alpaca_data.jsonl")

    args = parser.parse_args()
    with open(args.data_path) as f:
        examples = json.load(f)

    with open(args.save_path, 'w') as f:
        for example in tqdm(examples, desc="formatting.."):
            f.write(json.dumps(crl_format_example(example), ensure_ascii=False) + '\n') # ensure_ascii=False才能输入中文,否则是Unicode字符


if __name__ == "__main__":
    main()

执行以上代码转换训练json数据为jsonl数据:

python3 cover_alpaca2jsonl.py \
\--data_path /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/doupo_train.json \
\--save_path /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/doupo_train.jsonl

数据转换后每条数据如下形式:

{"context": "指令: 请根据输入文本输出后续的故事\n输入: “斗之力,三段!”\n望。。。的疼痛…\n。。。加苦涩了。\n\n输出: ", "target": "“这些人,都如此刻薄势力吗?或许是因为三年前他们曾经在自己面前露出过最谦卑的笑容,所以,如今想要讨还回去吧…”苦涩的一笑,萧炎落寞的转身,安静的回到了队伍的最后一排,孤单的身影,与周围的世界,有些格格不入。\n“下一个,萧媚!”\n"}

修改tokenize_dataset_rows.py文件:

model_name = "/data/ml/cxz/chatglm-6b"  # 设置为本地模型路径

创建分词输出目录save_path(参考如下命令),执行以上文件进行训练文本分词:

python3 tokenize_dataset_rows.py \
\--jsonl_path  /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/doupo_train.jsonl  \
\--save_path /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/alpaca_tokenize \
\--max_seq_length 1024 \
\--skip_overlength True

同样修改finetune.py文件,将模型路径改为本地路径:

tokenizer = AutoTokenizer.from_pretrained("/data/ml/cxz/chatglm-6b", trust_remote_code=True)
...
    model = AutoModel.from_pretrained(
        "/data/ml/cxz/chatglm-6b", load_in_8bit=True, trust_remote_code=True, device_map="auto"
    )

创建模型输出目录output_dir(参考如下命令),执行finetune.py,进行模型lora微调:

CUDA_VISIBLE_DEVICES=2 /home/ml/anaconda3/envs/py_chatglm/bin/python3 finetune.py \
    --dataset_path /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/alpaca_tokenize \
    --lora_rank 4 \
    --per_device_train_batch_size 4 \
    --gradient_accumulation_steps 2 \
    --num_train_epochs 15 \
    --save_steps 1000 \
    --save_total_limit 2 \
    --learning_rate 1e-4 \
    --fp16 \
    --remove_unused_columns false \
    --logging_steps 50 \
    --output_dir /data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/chatglm-6b-lora

模型训练完后,编写模型推理代码inference.py:

# -*-coding:utf-8-*-
from transformers import AutoModel,AutoTokenizer
import torch
from peft import PeftModel
import json

device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu")

model = AutoModel.from_pretrained("/data/ml/cxz/chatglm-6b", trust_remote_code=True, load_in_8bit=True, device_map='auto', revision="")
tokenizer = AutoTokenizer.from_pretrained("/data/ml/cxz/chatglm-6b", trust_remote_code=True,  revision="")

model = PeftModel.from_pretrained(model, "/data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/chatglm-6b-lora")

with torch.no_grad():
    with open("/data/ml/crl/ChatGLM-Tuning/data/DouPoCangQiong/doupo_dev2.jsonl", "r") as f:
        idx = 1
        for line in f.readlines():
            instructions = json.loads(line)
            print('\n\n--------------------------------------------------------')
            input_text = instructions['context']
            ids = tokenizer.encode(input_text)
            input_ids = torch.LongTensor([ids])
            input_ids = input_ids.to(device)
            out = model.generate(
                input_ids=input_ids,
                max_length=1024,
                do_sample=False,
                temperature=0
            )
            out_text = tokenizer.decode(out[0])

            # 由于input_txt中的?!等标点符号和out_text中的不同,所以无法直接从out_text用replace的方法剥离出input_txt
            # 故只能通过计算input_txt的行数,从out_text剔除掉这部分行,就可以剥离出纯答案
            cc = len(input_text.strip().split('\n'))
            answer = '\n'.join(out_text.strip().split('\n')[cc:])

            print('Input: \n', input_text)
            print("True Answer:\n", instructions["target"])
            print('Model Answer: \n', answer)
            # print('Model Answer: \n', out_text)

            idx = idx + 1

上述代码中的测试数据doupo_dev2.jsonl需要自己仿照训练数据制作,这里不再详述。
执行推理代码即可看到每条测试数据的结果被打印出来,本人的测试数据不到10条,所以直接一一print。

CUDA_VISIBLE_DEVICES=2  python3 inference.py

接下来对比看一下原模型、lora和ptv2的测试情况。

续写能力对比


Input:
“萧炎学长,这次有多亏您了。”那位叫做阿泰的青年,笑着走上来,对着萧炎恭声感谢道。“大家同为新生,自然是需要共同协助,只是没想到我们顺利通过了火能猎捕赛,竟然让的我们惹了这些麻烦。”萧炎摇了摇头,有些无奈的道。
“哎,是啊,这些年很少出现这种事情,而大多数的老生当年在进入内院时,都受过这种待遇,我想,这次借助着萧炎学长的名头将这些家伙震退,指不定以后会不会又来一些。”阿泰叹了一口气道。
萧炎微微皱眉,低声喃喃道:“看来这也不是长久的办法啊。。。”
两人距离相近,萧炎的喃喃声自然是没有逃过阿泰的耳朵,当下眼睛微亮,略微迟疑下,斟酌着言辞道:“萧炎学长,不知道。。。您是否有在内院创建一方自保势力的打算?”
“哦?”眉头微微一挑,萧炎亚宁紧盯着面前这皮肤有些黝黑,可却先得颇为精干的青年。
“呵呵,萧炎学长或许也该知道,在这内院中,大小势力纵错分布,数量颇为不少,而且内院的风气,也并不制止这些拉帮结派,反而是有种鼓励的态势。。。”阿泰沉吟道。
“内院里,一般来说,除非一个人势力极强,不然的话,单独混得话,会有些难以避免的麻烦。。。别的不说,就说进入天焚炼气塔修炼的事,这是每个内院学生快速提升实力的关键,每个人都极为看重,而在那塔中,每一层都被内院划分出了高中低三种等级的修炼之所,在高级地所修炼,修炼速度以及好处,无疑将会大大超过后者。”

True Answer:
“本来按照常理,只要谁运气好先进入高级地所,那么他便是能过在这里修炼,可是。。。内院并未规定天焚炼气塔内,不准私斗,因此,一般来说,就算你好运,想要在天焚炼气塔内得到最好的修炼条件,那么便是必须拥有强悍的实力以及团队。。。”阿泰目光也是停在萧炎脸庞上,沉声道。
萧炎微微点头,眼神闪烁着,不知道在想什么。

Model Answer(未微调的模型):
阿泰思考了一下,然后继续道:“所以,我认为,在进入内院之后,如果想快速提升自己的实力,创建一方自保势力可能是一个不错的选择。当然,这需要足够的资源和势力支持,但如果能够在内院中建立起一定的关系网,那么也许就会更容易一些。”
萧炎听了阿泰的话,心中有些动摇。他虽然不喜欢拉帮结派,但是保护自己的学生并维护内院的声誉,也是必要的。他开始考虑是否应该考虑这个问题,并考虑如何建立一方自保势力。
“嗯,你说的有道理。我会考虑一下的。”萧炎点头道。
“好的,那就由我们来帮助你建立势力吧。我们可以一起组织一些活动,让更多的人了解你的实力和背景。”阿泰说道,然后转身离开了房间。
萧炎看着阿泰的背影,心中也生出了一股想法。他希望能够在这个内院中建立起自己的势力,保护自己的学生,同时也能够为内院带来更多的利益。他需要好好思考,如何才能够实现这个目标。

lora-Model Answer:
“所以, 想要在内院混得话,势力是必不可少的,而萧炎学长,似乎实力颇为强横,所以,我想,或许萧炎学长能够为我们新生提供一个相对安全地庇护。”阿泰沉吟道。
闻言,萧炎眉头微皱,他可没想到,这看似看似毫末地势力,竟然会如此重要。

ptv2-Model Answer:
“那如果萧炎学长想进入天焚炼气塔修炼,需要做什么准备?”
“进入天焚炼气塔修炼,需要一个人拥有强横的魂兽,而且那魂兽得能与塔中修炼之人相配合,否则的话,进入塔中修炼,那修炼速度,将会极为缓慢。。。而萧炎学长应该能具备这种条件吧?”

未微调的模型答案显然更多行,没有固定的答案行数,回答略有些废话,微调后的lora和p tuning v2的答案都限制在2行,因为训练数据的结果也都是2行,另外可见lora的结果相比ptv2更通顺


Input:

True Answer:
既然不能与之接触,仅仅是施展斗气攻击的话,怕对于这犹如乌龟壳一般的盔甲,也是难以取得多大的效果吧,这从刚才白程施展出如此强横的“血地八裂”都未能使得盔甲出现多大损伤上便是能够瞧出,这东西,有着令人目瞪口呆的防御力。
毫不客气的说,如今有了这个既不能直接碰触,防御力又恐怖的青火盔甲之后,萧炎的战力立刻飙升了不少,因为,面对着这种坚固防御,就是柳擎等人,也是短时间内想不出应付之法。

lora-Model Answer:
“这青火盔甲,是萧炎所创?”柳翎目光死死的盯着那青莲地心火盔甲,忽然低声喃喃道,他自然清楚这盔甲的防御力,若是萧炎能够成功领悟火焰凝甲,那这盔甲的防御力,恐怕将会达到一个恐怖的地步。
“这青莲地心火盔甲,究竟是何物?竟然能够凝聚出如此恐怖的火焰盔甲?”萧炎目光死死的盯着那青莲地心火盔甲,心中也是略有些疑惑,这青莲地心火,虽然凶悍,可也并没有传说中的那种恐怖火焰盔甲吧?

ptv2-Model Answer:
场中,萧炎望着场中那团巨大火焰,脸色也是变得凝重了许多,他能够感应到,那团火焰之中所蕴含的恐怖火焰能量,绝对是比那“火能”还要凶猛许多。
“这小家伙,看来真得有些本事啊,竟然能够依靠火焰来形成盔甲,看来想突破六星斗师,若是要依靠这种神秘力量。”心中这样想,萧炎心中也是略微有些幸灾乐祸的感怀了起来,虽然这种神秘力量对于他却是极为不利,可若是能依靠它,或许也能成为一位真正的斗师吧。

lora结果中出现前些章节内容的人物名字,但是输入文本中没有这个名字,可见模型有一定的历史记忆能力


Input:
“若是要清除云岚宗,或许得先解决一人。”海波东脸色凝重的道。
“谁?”萧炎微微皱眉,道。
“丹王古河!”
听得这个名字,萧炎也是一愣,嘴中缓缓的念叨了一片,也是想起了那在三年之前便是名震整个加玛帝国的第一炼药师。
“你应该也清楚古河六品炼药师在这个帝国拥有着何等的号召力,若是他要帮云岚宗的话,恐怕光是帝国十大强者中,他便是能找来五人以上,所以,在对云岚宗出手前,你得把他的问题解决。”海波东郑重的道。
萧炎微微点头,他倒是差点把这个重要人物给忘记了,古河也是一名六品炼药师,对于这个阶别的炼药师所拥有的能量,萧炎最为清楚不过,当初韩枫在黑角域所号召的强者。连内院都是颇感头疼,虽说古河的炼药术或许不及韩枫,可不管如何说,他也是一名货真价实的六品炼药师
“不过要如何才能解决他,也是个大麻烦啊,古河可不是省油的灯,虽然这些年他似乎也与云岚宗的关系不似以往那般好,可他始终都是云岚宗的供奉长老…”海波东略有些头疼的道。
萧炎虚眯着眼睛,手指轻点,片刻后,突然道:“海老能否联系到古河?”
闻言,海波东一愣,沉吟了半晌,缓缓的道:“古河在炼药师公会也挂着一个长老的虚衔,或许能托法犸找到他。”

True Answer:
“既然如此,那便请法犸会长出面找一下古河吧,最好能让我与他见一面,我会想办法让他保持中立…”萧炎点了点头,道。
“好吧,我会让法犸帮帮忙”见到萧炎将这事揽在自己身上,海波东也松了一口气,只要将古河解决掉,那么便是能够令得云岚宗少去一个绝大的助力。

lora-Model Answer:
“那便请法犸吧,他可是加玛帝国炼药师公会的会长,在云岚宗被攻击时,他一定会出手相助。”萧炎微微点头,道。
“好,法犸,你便去联系古河吧,我则去联系云岚宗,若是他们能够联合起来,那便能够彻底将云岚宗除名,日后,云岚宗若是再次被攻击,我们便是不会袖手旁观。”海波东沉声道。

ptv2-Model Answer:
萧炎点了点头,从纳戒中取出一枚药鼎,然后对着炼药师公会的炼药师公会地址处行去。
望着萧炎的行动,海波东略微有些惊讶,不过也能明白,这位萧炎,对云岚宗的关系,自然是极为清楚。

lora结果中出现了一个很惊人的记忆,就是还记得法犸是加玛帝国炼药师公会的会长,说明经过微调后模型初步具备事实记忆能力

问答能力对比


Input:
萧炎与纳兰嫣然决斗时的段位是什么?

True Answer:
大斗师

lora-Model Answer:
抱歉,作为一个人工智能,我的知识库只到2021年,所以无法回答这个问题。不过,纳兰嫣然与萧炎决斗时,应该还没有成为斗皇,所以,他们的段位应该还在斗师以下。

ptv2-Model Answer:
“火系斗气修炼法”的段位,在炼药师界中,一般是处于第三阶左右,第三阶的炼药师,实力略微强点,不过依然不如第一阶与第二阶的炼药师强。


Input:
萧炎的父亲叫什么名字?

True Answer:
萧战

lora-Model Answer:
萧炎的父亲叫什么名字? 萧炎的父亲是药老,是斗气大陆上著名的炼药师,也是一名药鼎师,曾经炼制过无数种炼药术,并且炼制出了一些极其珍贵的丹药,因此被誉为药老,是斗气大陆上最为著名的炼药师之一。

ptv2-Model Answer:
没有明确答案。

在萧炎成长的过程中,他经历了许多事,从刚开始的孤身一人,到渐渐进入炼药师界,再到今天的炼丹能力达到二级,经历了许多磨练,才成为了今日的萧炎。

以上两个问题,lora和p tuning v2都没有回答正确,但是很惊讶的是两微调模型都能够正确识别到当前输入是要回答问题,而不是被微调的任务–续写故事

总结

1、原模型能够根据输入文本自动续写故事,这个能力很让人惊讶!居然能够自主判断或猜测出用户意图是需要续写故事;
2、原模型续写没有固定字数和行数,毕竟没有受过微调;
3、ptv2、lora微调模型能够按照固定行数续写故事;
4、lora微调后的模型表现出记忆历史事实的能力,虽说目前效果不太好,这可能只是因为训练轮数不够,或者r参数需要调整;
5、ptv2、lora微调的模型理论上讲只会做续写任务,但是惊奇发现,问其问题,会自动识别为问答任务,而不会傻傻的续写。。这个不知是模型本身的能力,还是chat模块先对输入进行了判断,之后指定任务种类要求模型进行输出?
6、ptv2、lora微调的模型虽然能够识别除了续写之外的问答任务,但是几乎无法回答关于小说的内容,ptv2微调后的模型甚至会乱回答问题。

Logo

更多推荐