前段时间刷Arixv的时候,清华大学开放了大型中文闲谈语料库LCCC。 从开源文件来看,这可能是目前开源数量最大、质量最好的闲谈语料库。 而且,还包括部分多次对话聊天,总的来说,好玩性相当强。 笔者也被它吸引了,尝试训练了闲谈对话的模式,结果看起来很好,所以在这里分享自己的经验。
论文名称: 《A Large-Scale Chinese Short-Text Conversation Dataset》
论文链接: https://arxiv.org/abs/2008.03946
项目地址: https://github.com/thu-coai/c拨号- GPT
1资料介绍
这里简要介绍一个叫做LCCC的数据集(大规模集成电路)。 具体详情请在Github上查看。 下载链接也在上面。 廉价航空公司分为base和large两个版本,base主要来源于微博对话,large是在base的基础上融合了其他开源对话资料,据作者介绍,LCCC经过了严格的清洗过程,所以整体质量不错
为了简化任务,所有的样本都会处理成两个人的对话。 下面是一些样品。
A:等新年买兔子头来吃火锅吧
B:太原没有看到好吃的兔子头
从A:虹桥带回来的那天,看到了地道的
B:最爱你
A:那是必须的
A:嗯,再等等! 你现在在上海吧? 上海好像比南京风还大呢。 控制外出吧
B:是啊。 在家。 没关系。 小心啊!
A:我去年也转了一圈,见到了以前的体育老师,拍了照片
B:哈哈我又去找了高一时侯的英语老师,但是找不到女朋友正好有事不在学校~
A:你也真的在找回忆哦
B:哈哈毕业了。 没去过。 我想去看看呢
2模型设计
知道了数据的长度之后,接下来设计模型。 很明显,我们要做的就是训练模型,预测接下来应该回复什么。 既然词汇中包含多个对话,我们就要求这个模型支持多个对话。 思考对话历史最简单的方法是将当前句子之前的所有历史对话与单句文本联系起来,作为模型的输入信息。
给出一些输入,预测一个输出,从形式上看我们应该使用Seq2Seq模型。 直接使用Seq2Seq其实也没什么问题,但标准的Seq2Seq一般用于形式比较固定的输入输出。 例如,输入的文本的长度应该集中在某个范围内,不应该变化太大,但考虑到多次对话,理论上不知道前面有多少次对话,所以原则上输入的文本的长度没有限制。 Seq2Seq还存在训练效率低的问题。 我们每次只能训练一个回复。 如果一个以上的对话有n个回复的话,必须分成n个样本进行训练。
因此,我们需要一个长度可以相当自由地变化,同时可以预测整个多次对话的模型。 实现这一需求的比较合适的选择是单向语言模型(LM、GPT )。 方法如下图所示。
如图所示,选择目前主流的Transformer模型,按照BERT的通常输入格式,用[SEP]将各对话连接起来,训练从左到右的单向语言模型。 为了区别不同的说话者,我们对不同的说话者用不同的Segment Id进行区别。 另外,BERT和GPT都使用绝对位置代码,可以处理的文本的长度有上限,考虑到对话的圈数理论上是无限的,这里采用相对位置代码的NEZHA作为模型的初始化权重,是NEZHA的事前训练权重
简言之,就是在NEZHA中加入下三角形式的Attention Mask,使之成为一个语言模型。 相关介绍请参照《从语言模型到Seq2Seq:Transformer如戏,全靠Mask》 [1]。
3培训详细信息
首先,给出笔者的参考实现和训练的模型:
359 github.com/bojone/nezha _ GPT _ dialog
代码基于bert4keras实现,基本结构为NEZHA Base。 为了使训练更加“平民化”,笔者用一张卡(22G的RTX )配合梯度累积进行训练,明显内存不足的读者可以缩小batch_size。 开源模型是
的学习率,用0.01的权重衰减率的Adam优化器训练的,训练了1000万步(20多天),一步batch_size为16,累计梯度步数为16,所以在256的batch_size上训练了62.5万步
ken的交叉熵)。我们来测试一下我们的训练结果。
>>> print(chatbot.response([u'我最近天天吃火锅']))
火锅好吃啊
>>> print(chatbot.response([u'我最近天天吃火锅']))
我也是
>>> print(chatbot.response([u'我最近天天吃火锅']))
哈哈哈哈哈哈我最近一周吃了两顿火锅
>>> print(chatbot.response([u'我最近天天吃火锅']))
我也是,每次都吃的撑
>>> print(chatbot.response([u'我最近天天吃火锅']))
吃火锅不长胖
>>> print(chatbot.response([u'我最近天天吃火锅']))
火锅好啊
>>> print(chatbot.response([u'我最近天天吃火锅']))
你们那边有这种火锅?
>>> print(chatbot.response([u'我最近天天吃火锅']))
火锅好吃吗?我也想吃
>>> print(chatbot.response([u'我最近天天吃火锅']))
吃吧,吃了就开心了
>>> print(chatbot.response([u'我最近天天吃火锅']))
我也想天天吃火锅!
>>> print(chatbot.response([u'我最近天天吃火锅']))
火锅吃不了多久的。
>>> print(chatbot.response([u'你那天气好吗']))
还好,不热,有点冷
>>> print(chatbot.response([u'你那天气好吗', u'还好,不热,有点冷', u'那你得穿多厚的衣服']))
我穿了两件
4 对比分析
CDial-GPT也开源了自己训练的预训练模型,笔者也将它转换为bert4keras能加载的格式了,CDial-GPT-tf[2],读者也可以测试比对一下。从训练上来看,CDial-GPT使用pytorch实现的模型,基本结构是GPT Base,使用了4张2080Ti,总batch_size为32,累积梯度64步,论文说训练了30个epoch,总步数约2100万步(笔者的两倍),因此大概相当于batch_size为2048训练了33万步。
在输入设计上,CDial-GPT也有所不同,如下图:
如图所示,CDial-GPT跟我们前述设计的主要不同是多轮对话之间的拼接方式,我们之前是直接用[SEP]连接,它是用[speaker1]、[speaker2](图中简记为S1、S2)这样的角色标记来连接,最后才用一个[SEP]表示回复结束。这样一来,由于预测部分的格式跟历史的格式不一样,因此每次只能训练一句回复,多轮对话要拆分为多个样本来训练,理论上是增加了训练复杂性的(要训练多步才能把一个多轮对话样本训练完)。
至于效果上,个人测试的感觉是两者没什么明显差别。有兴趣的读者也可以自行比较测试。
5 文章总结
本文主要分享了一次对话模型实践,基于开源的LCCC闲聊语料库,利用语言模型(GPT)对多轮对话进行生成式建模,得到了一个相对通用的闲聊对话模型,最后将本文的思路与CDial-GPT本身开源的模型进行了比较。
参考文献:
[1]《从语言模型到Seq2Seq:Transformer如戏,全靠Mask》: https://kexue.fm/archives/6933
[2] CDial-GPT-tf:https://github.com/bojone/CDial-GPT-tf