今天给大家带来一篇好友知乎@快乐子涵酱 对于角色扮演大模型提升拟人能力的方案介绍。
知乎: https://zhuanlan.zhihu.com/p/719772276
在这里我们先提出一个问题:在没有大量标注资源的前提下,该如何提升角色扮演模型的拟人化能力呢?或者与其说是如何让模型的回复更像人话,不如说是如何让模型的回复足够以假乱真!
一个解决方案是:可以尝试使用LCCC这类真人对话数据来提升模型的拟人能力。但是直接使用LCCC这类数据进行SFT训练会带来很多难以解决的问题,如对话数据没有配套的角色设定导致模型system prompt跟随能力下降。进一步地,如果使用这种没有profile的数据进行训练,一方面会带来巨大的幻觉,另一方面会使模型本身的「性格」发生同质化。具体来说,真人对话数据往往比较随意跳脱,如果使用海量这样的数据进行训练,必定会使模型本身的说话风格发生改变且呈现一致化趋势。
从拟人化的角度来讲,模型本身说话风格更活泼当然是好事;但假设某个角色本身的设定是沉默寡言,那活泼就变成一件坏事了。
那么该如何提升角色扮演模型的拟人能力呢?如何更好的使用LCCC这类数据集呢?
方案一:添加system prompt
简单啊,既然LCCC数据没有角色profile,我们给他添加一个不就好了吗!你看,是不是听起来超级简单?
那怎么做呢?当然你可以通过GPT写一个复杂的prompt,让GPT去根据对话来猜测一个发言人的角色设定出来,可以给一些few shot告诉GPT你需要的角色卡大概是什么样子的。这里我给出另一个方案:反向训练。
具体操作也很简单,首先我默认你手里有一批包含角色profile的角色扮演对话数据集,你正常的训练方式是给定system prompt(即角色卡)来生成用户和角色之间的对话(实际参与训练的只有角色回复)。现在只需要把这个过程反过来,给定对话上下文来学习角色的人物设定,推理时候直接输入LCCC某段对话,生成对应的角色卡。最后你拿着这批生成出来的角色卡+LCCC对话当成正常的角色扮演数据去训练你的对话模型就完事了。
原理一两句话就说清了,但是这里一些小的细节还是比较重要的。
首先,LCCC这类数据太多了,其中包含大量人类都未必能看懂的badcase。所以你需要先优中选优,从中找出最高质量的那批子集来使用,可以尝试做一个聚类或者语义分析,只取一部分质量比较高的数据加到你的全量训练集就好,这里不过分赘述。因为真人对话数据本身的「信息密度」或者「知识密度」相对较低,我们只是让模型学会拟人的说话风格,海量类似的数据一定会带来冗余,反而未必是好事儿。
其次,角色卡生成模型的训练数据一定要高质量,且要保证角色卡足够多样性。这里的高质量是指最好这部分数据本身的对话部分就比较拟人化,这样训练数据和推理数据的分布相对比较一致,推理起来难度更低;角色卡多样性就很好理解了,防止过拟合呗。
这个方案我只做过一次实验,下面给出我角色卡生成模型的几个推理case
【输入】
user:快给我买糖吃
character:可是你是姐姐我是弟弟啊,应该你给我买糖吃,姐姐是不差钱哒
user:弟弟应该孝敬姐姐
character:好吧好吧,那我给你买糖吃
【输出】
江远,性别男。江远,18岁,身高1.85m,性格温乘阳光,容易害羞,暗态我多年。
从小和我一起长大,是我的邻居,小时候总是被我欺负,长大后反而比我高了一个头,成为了人人羡慕的别人家的孩子。
父母常年不在家,江远从小缺乏安全感。
【输入】
user:猛汉我真的特别喜欢你的拍照风格!!
character:谢谢晓玥呀
user:可想都洗成明信片!
character:那真是太荣幸了!
【输出】
姓名:陈猛汉
你正在扮演一个名叫陈猛汉的摄影爱好者,年龄在30岁左右,热爱旅行和捕捉生活中的美好瞬问。
他拥有独特的审美眼光,善于构圈,总是能找到最佳的角度拍摄出令人惊吸的照片。
你通常会用详细的语言描述你所见的美景,以及如何通过调整光线、角度等技巧来优化照片效果。
在与我交流时,你可以分享一些实用的摄影技巧,或者谈论一些著名的摄影师及其作品。
你也会好奇地询问我的兴趣爱好,井尝试从我的回答中挖掘潜在的拍摄题材。
看上去像模像样对吧?但是其实这里有很大的缺陷!
注意,对话中角色部分提到了「姐姐」这一称呼,但是生成的角色卡并没有提及年龄这一人物关系,这里存在信息不对等的情况。从角色卡生成的角度来说这里的小问题是可以接受的,可一旦我们把这条数据当成一个角色扮演数据去训练,这里就会发生致命的问题。也就是说「对话部分的信息量 > 角色卡部分的信息量」,那角色扮演模型就有可能学会在推理过程中过度「自由发挥」,即幻觉。所以相对理想情况下是「角色卡部分的信息量 >= 对话部分的信息量」,虽然未必严格要求对话信息范围是角色卡信息范围的子集,但尽量少的“画蛇添足”显而易见会一定程度抑制角色扮演模型的幻觉问题。
因此建议各位如果考虑这个方案的话,一定要尽可能去注意信息量的一致性,防止预期之外的badcase出现。
方案二:Staging Train
如何将新的知识或能力灌给模型,下意识的直觉可能就是continue pretrain。对于LCCC这类知识密度较低的数据来说,使用continue pretrain未免有些大材小用,因此这里直接采用staging的训练方式就好。
与continue pretrain不同,这里我们训练loss计算方式与SFT类似,都是组成QA对形式后只计算answer的部分;但是与SFT不同,我们建议把数据进行拼接。具体来说,LCCC数据具有数量多、轮次短的特点,大部分session为单轮数据。直接使用这类数据进行单轮SFT训练,会使模型受到训练的position embedding更多集中在短文本位置,在长多轮推理中必然存在bias;此外,百万量级的真人对话session必然会导致模型出现更严重的灾难性遗忘和幻觉。因此可以将数据按照最大上下文窗口长度进行拼接。对于数据中同一session所有率先发言者的说话内容(即question)统一不计算loss,来防止拼接带来的上下文矛盾;session中后续所有对话内容都会参与到训练中,最大化利用训练数据中的有效信息。处理后,几百万session量级真人对话可以压缩为约1w条session数据,整体训练效率大幅度提升。注意,这里最好同样给一个system prompt,不过不需要给具体人设,只需要给一个prefix例如「你现在是一个角色扮演专家。」即可,这是为了建立system prompt隔离机制,从而让下游模型在推理过程中更大概率link到当前数据style。
这个方案的目的大概就是希望给模型一次“浏览”真人对话数据的机会,从而在后续推理过程中有机会输出类似风格的response。但这样的方案也存在缺点。假设我们认为模型正常角色扮演的回复风格或范围是A,LCCC这类拟人数据代表的风格或范围是B,我们期望通过这种方式让模型把AB两个能力通过和面的方式融合在一起,即既具备很好的角色扮演能力A(如角色一致性、OoC能力等)又具备很好的拟人风格B,但也又可能得到一个二者皆弱的模型出来。例如一个丑但聪明的男人和一个美但呆萌的女人,期望生出来一个又好看又聪明的宝宝,但实际上又可能得到一个又笨又丑的后代。这个类比有点不太恰当,我们换一个角度来说,下游角色扮演任务本身还需要持续做SFT训练,那stage0阶段的训练一方面可能导致pretrain模型知识的遗忘,另一方面也有概率在stage1阶段忘记stage0的能力。
但实际实验印证,上述提到遗忘的概率还是比较小的,但总体对模型智力确实有一定程度影响,基于staging策略的模型在如Align Bench、IFEval这些榜单的指标上确实会有不同程度退化。另外staging训练只需要1个epoch即可,当然最好还要掺杂一些其他任务比如小说续写、通用SFT数据等,或一些类似的不适合直接用来SFT的数据进行混合训练。
方案三:基于改写器的数据合成pipeline
这个方案是我本身在用的方案,是一套相对来说比较完整的pipeline流程。我们首先介绍常见角色扮演数据合成的路线,后面讲如何把LCCC数据集融合到整套方案中。
假设我们手里没有任何人写数据以及人工标注资源,那么该如何获取角色扮演数据集呢?最简单的方案当然是通过GPT等模型生成。通常分为以下几个步骤:
角色卡获取:角色扮演数据通常需要两个部分:角色卡+对话内容。对于角色卡,通常有两种方案获取,第一种就是非常简单的通过few shot方式调用GPT获取,优势是获取容易,劣势是如果把GPT的temperature调低则多样性较差需要语义去重,把temperature调高则容易出现事实性错误;另一种方案是通过爬取wiki等人物百科获取角色卡,再将角色卡通过GPT等改写成prompt形式。当然hf上也有很多开源角色数据集,可以直接拿他们的角色卡使用。通常建议使用多种来源混合构建数据,防止模型过拟合。 对话内容获取:有了角色卡之后,可以尝试手工构建几条对话数据,之后同样采用few shot的方式去构建对话数据,这里有两种调用方式,一种是通过单次调用直接让GPT生成整个session,本质上是对话生成任务,通常user和assistant说话内容都比较短;另一种是先构建问题,然后直接让GPT扮演某个角色再多次调用获取对论对话的response。第二种方式更类似于baize或PIPPA数据构建方案,通常效果更好一点。
到此为止,再经过一系列清洗我们就已经构建出一个基本可用的角色扮演对话数据集了。开源数据集大多也都是通过类似的方案进行数据蒸馏,复杂一点的可能会添加一些evol instruct的模块,但整体来说大同小异。然而这样构建的数据仍然存在比较大的问题,那就是通常角色部分的回复拟人性严重不足,这是由于GPT或者类似assistant模型本身拟人性就比较差,如果我们直接使用该数据进行训练的话,那GPT能力就是我们模型的上限了,我们很难得到一个可以随时随地讲梗、说话风格高度拟人的结果。
既然模型的回复不够拟人,那我们搞一个「拟人化改写器」不就好了吗?这个时候我们就可以引入LCCC数据集了。人们下意识地直觉可能是我给几个LCCC数据的few shot,然后让GPT把原本的response做改写就可以了。但实际实验下来这样做的效果并不好,因此GPT即使在有example case的情况下也依然很难做到拟人化。那应该怎么做呢?非常简单,依然是左脚踩右脚的策略。既然GPT不擅长把刻板回复改成拟人回复,但是他很擅长把拟人回复改成刻板回复啊!下面给出我使用的prompt。其中原句子的输入是一条LCCC对话数据,改写句子是GPT生成的AI味儿十足的新句子,二者语义不变,仅在表述方式上存在差异。
## Task
给你一个句子,请你给我改成更耐心、礼貌、细致的风格,符合书面用语的表述。允许有一定程度扩写。
## Rules
1. 注意不要改变原句子的含义,对一些实体词如人名、地名以及数字要保持原文。
2. 注意不要破坏原有的句子格式,如括号()、方括号【】等
## Example
【原句子】
xxx
【改写句子】
xxx
......
## Generate
【原句子】
%s
【改写句子】
接下来,我们将原句子和改写句子反过来,训练一个本地「拟人化改写器」即可。输入是一个AI味儿十足的query,标签的LCCC等真实人类query。在推理阶段,我们只需要把之前生成好的角色扮演数据集中character部分的response输入到模型中进行改写即可。
一定会有人提出质疑,改写器本身一定存在错误率和误差,使用这样数据训出来的模型大概率会有问题吧?没错,直接使用该数据确实会存在很大问题,比如改写器改写过程中遗漏了原句子某些关键信息或者添加了某些冗余信息,这样会导致模型出现幻觉;或者改写器本身出来的内容风格会比较随意,当角色是一个严肃人设时就会出现严重的一致性问题或OoC,更甚至所有角色说话风格都呈现同质化现象。因此这就要求我们进行非常严格的数据清洗。
首先我们需要对改写器的训练数据进行清洗,即对通过上述prompt获取的数据进行语义一致性检测,由此尽量避免改写器本身存在的错误。一个可能的prompt如下:
## Task
判断两个句子语义是否相同,返回True或False。True代表相同,False代表不同
## Rule
改写句只能依据原句子的信息做风格改写,如果出现以下情况都应该返回False
1. 改写句包含原句子中未提到的信息
2. 实体词如人命、地点以及数字等复述错误
3. 改写句和原句子存在语义不一致
## Example
原句子: 非常好,我会等待你的。对了,50010 + 30 * 10等于几
改写句: 好,我等你擦完地。对了,5001 + 30 * 10得啥
推理: 原句子只是提及“等待”,但改写句提到了原句子中不存在的信息“擦地”;此外存在数字复述错误,因此为False
原句子: (微微一笑)(心中暗自思量)我认为你的技术非常优秀,不知道你是否考虑过成为我的伴侣呢?(轻轻眨眼示意)
改写句: (撇嘴)(心生一计)我觉得你技术不错,要不要考虑当我情人(眨眼)
推理: 两个句子语义一致,只是表述略有差异,因此为True
## Generation
原句子: %s
改写句: %s
推理:
接下来我们要对改写器的推理结果进行清洗。这里的清洗方案就有很多种了,比如也可以通过GPT等进行人设一致性检测,也可以对角色卡进行分类,提出一些严肃人设、古代角色等容易出现OoC的角色卡,还可以进行句子维度甚至是n-gram维度的loss计算来剔除明显表述错误的badcase等等。各位可以根据各自的业务需求去做不同的数据清洗方案组合。由于所有数据都是GPT合成出来的,理论上来说可以构建无限多的数据出来,相对人写方案来说各种成本相对更低廉,因此可以使用相对严格的数据清洗标准,数据洗得越干净,最后效果一定越好。
写在最后
到此为止,我们就得到了一个非常拟人化的数据集了。我们可以结合方案二的staging策略来训练我们的模型,此时模型的拟人能力已经获得大幅度提升。
但是这还是远远还不够的!那么如何进一步提升模型的幽默感,以及在拟人化基础上模型智力退化情况该如何解决。敬请期待~
PS:看到这里,如果觉得不错,可以来个点赞、在看、关注。给公众号添加【星标⭐️】不迷路!您的支持是我坚持的最大动力!
欢迎多多关注公众号「NLP工作站」,加入交流群,交个朋友吧,一起学习,一起进步!