【深度学习和自然语言处理】Seq2seq 中文文本生成

问题描述

基于 Seq2seq 模型来实现文本生成的模型,输入可以为一段已知的金庸小说段落,来生成新的段落并做分析。

引言

文本生成,旨在利用NLP技术,根据给定信息产生特定目标的文本序列,应用场景众多,并可以通过调整语料让相似的模型框架适应不同应用场景。

Seq2seq + Attention

Seq2seq

seq2seq是sequence to sequence的缩写。Seq2seq 是深度学习中最强大的概念之一,从翻译开始,后来发展到问答系统,音频转录等。顾名思义,它旨在将一个序列转换到另一个序列。前一个sequence称为编码器encoder,用于接收源序列source sequence。后一个sequence称为解码器decoder,用于输出预测的目标序列target sequence。

Seq2Seq

主要思想是它包含一个编码器RNN (LSTM)和一个解码器RNN。一个是“understand”输入序列,解码器解码“thought vector”,并构造一个输出序列。有如下步骤:

  • 将源序列输入encoder网络。
  • encoder将源序列的信息编码成一个定长的向量encoder vector。

  • encoder vector被送入decoder网络。

  • decoder根据输入的向量信息,输出预测的目标序列。

Auto-encoder

尽管Seq2seq模型备受青睐,但是把一整个文本序列通过encoder压缩到区区一个向量里,很难通过decoder进行完美地没有信息缺失的解码。

Seq2seq 机器翻译

此外,由于循环神经网络RNN的特性,源序列中越迟输入的文本,对encoder vector的影响也越大。换句话说,encoder vector里会包含更多的序列尾的文本信息,而忽略序列头的文本信息。所以在很多早期的论文中,会将文本序列进行倒序后再输入encoder(双向RNN),模型测评分数也会有一个显著地提高。

为了让decoder能够更好地提取源序列的信息,Bahdanau在2014年提出了注意力机制Attention Mechanism,Luong在2015年对Bahdanau Attention进行了改进。

Attention mechanism

怎样获取一个合适的语义向量,让decoder能够输出最优的文本。

为了获取这个语义向量

  • 首先找出encoder里几个最匹配的词语,

  • 然后把这几个词语的语义向量加权求和,

  • 最后对加权和做一些修正,得到最终的语义向量。

这也就是Attention机制的核心流程。

在NLP领域,通过decoder预测输出目标序列的时候,希望能够有一种机制,将目标序列当前step,和源序列某几个step的文本关联起来。

以翻译任务为例,将“我爱机器学习”翻译成“I love machine learning.”在decoder输出序列第一个step,我们希望关注输入序列中的“我”,并将“我”翻译成“I”;在第三个step,我们希望关注“机器”,并翻译成“machine”。

Attention

这个例子比较简单,我们就会产生一个初步的想法:是不是把源序列中的每个词语单独翻译成英文,再依次输出,就构成目标序列了呢?

但是,如果进一步思考下,我们就会发现两个问题:

  • 一词多义:源序列里的同一个词,在输出序列里,可能根据场景的不同,会有不同的输出。
  • 序列顺序:源序列和目标序列并不是顺序依次映射的,例如“你是谁?”翻译成“who are you?”,不同语言有不同的语法规则和顺序。

Attention 详情

这两个问题也就是Attention机制的关注重点。因为源序列太长了,导致早期的信息无法有效地被传递,所以需要一个额外的通道,把早期的信息送到解码器上。送的时候还不能只送一个词的信息,最好把上下文信息一起给送了。

实验

textgenrnn

本文中,使用 textgenrnn 进行金庸小说的文本生成实验。textgenrnn 是采用 RNN 的方式来实现文本生成的一个简洁高效的库,代码量非常少,又非常易于理解。其架构是采用了LSTM+Attention的方式来实现。如下图所示:

textgenrnn

textgenrnn 接受最多 40 个字符的输入,首先每个字符转换为 100 维的词(char)向量,并将这些向量输入到一个包含 128 个神经元的长短期记忆(LSTM)循环层中。其次,这些输出被传输至另一个包含 128 个神经元的 LSTM 中。以上所有三层都被输入到一个注意力层中,用来给最重要的时序特征赋权,并且将它们取平均(由于嵌入层和第一个 LSTM 层是通过跳跃连接与注意力层相连的,因此模型的更新可以更容易地向后传播并且防止梯度消失)。该输出被映射到最多 394 个不同字符的概率分布上,这些字符是序列中的下一个字符,包括大写字母、小写字母、标点符号和表情。而且关键是上述的参数都可以设置。

  • 使用注意力权重(attention-weighting)和跳跃嵌入(skip-embedding)等先进技术的现代神经网络架构,用于加速训练并提升模型质量。
  • 能够在字符层级和词层级上进行训练和预测。
  • 能够设置 RNN 的大小、层数,以及是否使用双向 RNN。
  • 能够对任何通用的输入文本文件进行训练。
  • 能够在 GPU 上训练模型,然后在 CPU 上使用这些模型。
  • 在 GPU 上训练时能够使用强大的 CuDNN 实现 RNN,这比标准的 LSTM 实现大大加速了训练时间。
  • 能够使用语境标签训练模型,能够更快地学习并在某些情况下产生更好的结果。

准备数据

本文采用金庸小说作为训练数据。部分数据示例:

1
当年郭靖、黄蓉参与华山论剑之後,由黄药师主持成婚,在桃花岛归隐。黄药师性情怪僻,不喜热闹,与女儿女婿同处数月,不觉厌烦起来,留下一封书信,说要另寻清静之地闲居,迳自飘然离岛。黄蓉知道父亲脾气,虽然不舍,却也无法可想。初时还道数月之内,父亲必有消息带来,那知一别经年,音讯杳然。黄蓉思念父亲和师父洪七公,和郭靖出去寻访,两人在江湖上行走数月,不得不重回桃花岛,原来黄蓉有了身孕。

数据详见:https://share.weiyun.com/5zGPyJX

训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def train():
    textgen = textgenrnn(name="novel") # 给模型起个名字

    textgen.reset() # 重置模型
    # 从数据文件训练模型
    textgen.train_from_file(file_path='./novel.txt', # 文件路径
                            new_model=True, # 训练新模型
                            batch_size=4,
                            rnn_bidirectional=True, # 是否使用Bi-LSTM
                            rnn_size=64,
                            word_level=False, # True:词级别,False:字级别
                            dim_embeddings=300,
                            num_epochs=50, # 训练轮数
                            max_length=25, # 一条数据的最大长度
                            verbose=1)

    print(textgen.model.summary())

还有其他的模型参数可以配置,主要包括以下几项:

1
config = { 'rnn_layers': 2, 'rnn_size': 128, 'rnn_bidirectional': False, 'max_length': 15, 'max_words': 10000, 'dim_embeddings': 100, 'word_level': False, 'single_text': False }

模型结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Model: "functional_5"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input (InputLayer)              [(None, 40)]         0                                            
__________________________________________________________________________________________________
embedding (Embedding)           (None, 40, 300)      1193400     input[0][0]                      
__________________________________________________________________________________________________
rnn_1 (Bidirectional)           (None, 40, 128)      186880      embedding[0][0]                  
__________________________________________________________________________________________________
rnn_2 (Bidirectional)           (None, 40, 128)      98816       rnn_1[0][0]                      
__________________________________________________________________________________________________
rnn_concat (Concatenate)        (None, 40, 556)      0           embedding[0][0]                  
                                                                 rnn_1[0][0]                      
                                                                 rnn_2[0][0]                      
__________________________________________________________________________________________________
attention (AttentionWeightedAve (None, 556)          556         rnn_concat[0][0]                 
__________________________________________________________________________________________________
output (Dense)                  (None, 3978)         2215746     attention[0][0]                  
==================================================================================================
Total params: 3,695,398
Trainable params: 3,695,398
Non-trainable params: 0
__________________________________________________________________________________________________
None

生成数据

1
2
3
4
5
6
def textgen():
    textgen_2 = textgenrnn(weights_path='novel_weights.hdf5',
                           vocab_path='novel_vocab.json',
                           config_path='novel_config.json')

    textgen_2.generate_samples()

生成数据样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
####################
Temperature: 0.2
####################
  杨过见她脸色苍白,眼见她泪盈眶水,滚夜滚开,滚来滚去,滚夜抖动,滚开绸带,绸带末端的拆解开来。绸带拆解,带著金铃击倒,甩开解开。李莫愁拂尘挥动,拂尘卷起土来,拂尘往她脸上一卷,宛如一块大树皮结唤,脸上似是一丝笑道:「你若是我爹爹知道?」

  杨过见她这般温柔诚恳,心中一动,道:「你说我是媳妇儿,我教你的心意,我要你做我的。」

  杨过道:「我不知道,你不是我的。」那少女道:「我跟你说,你是谁?」陆无双道:「你要我杀你,我也不会跟你说。」杨过道:「我不知道,你不是我。」

####################
Temperature: 0.5
####################
  杨过见到这少年,心中大喜,道:「你不用跟我说话,我跟你说,我在这里陪著你。」杨过道:「我不知道。」洪凌波道:「你瞧我,我就在这儿干麽。」

  那少年不见怪异,杨过正自沉吟,心想:「这姑娘是谁的?」说著站在天井之中,最後仰天便往地底。两个人说不过半枚,直天往北星扑上去。」

  杨过道:「你这般无礼,只是我义父师父,你就不能再好。」小龙女道:「我瞧著你的,是甚麽?」杨过道:「你就是我,我也不会。」顿了一顿,说道:「我不知道,你怕我们师父麽?」郭靖道:「为甚麽?」杨过道:「我在这里内功力深。」黄蓉道:「我知道?」黄蓉道:「他定然不知在这儿,重阳宫该当遭劫,若不是她不幸,误会,你若不会打过我,还会下面子山上,难道你就是一样。」

####################
Temperature: 1.0
####################
  那个嘴角带了来,轻著这一跃进了。杨过给她治病,刻身朝负著胆子;照料得到原期竟然不同小龙女。他想到一时所莫错杀,没能尽化解救之下,说道:「小王重阳临危,难道是万祸仙庄子!」郭靖听了郭靖此事说,黄蓉当杀他妻子之口,说道:「难道咱们上当真教。她傻蛋,你说这娃儿罢?」黄蓉拂尘一起,轻身黄蓉再度,见杨过站在她身後。郭靖呆出房门外,慢说:「九阴真经功夫。」

  杨过瞧她一时,难以与不成的小龙女并肩而来,柔声道:「唉,果然极是温柔,从姑娘生想取了这一天真下对敌人的手帕有来造诣。此时若是别多,温言反而便得到万事。表姊、傻是傻子聪明。郭靖本来就撞连几线、程陆逊去,陆家脱身双姊姊妹,时见疯疯疯癫,怕己傻疯却乘、狂以之势骇死。她心里傻笑,疯疯癫癫癫癫,或忘迎像像前洪老矣、双仞,靖、黄蓉上两、一灯、大师、一灯、周伯通等四周大遭大的关式都是攻的藏在周身前阁,身的一脚实已易躲,不敢再瞧到他们去见啦。

  小龙女见他过身摇幌,扑击过地,不似招术。玉女剑法自奇,那时石顺畅饮,凑眼巧落,突觉如此落人,手足向武修文身旁急道:「过儿,那位了姓龙的书生,你已经过他的那万不及,武功法测儿。」李莫愁对付诸流人,其後必定夹攻,自他二人也难练了,不过他们急忙。黄药师道:「我……你一听说的。」店小二女说过饭菜,又问受甚麽连公孙止了?一人叫道:「朱伯伯,我师父有两位和甚师兄为难相似,拉住他头发便深入了寸风。」杨过笑道:「郭伯伯,我武家哥哥武功不弱,此时还得虽有没能让他们胡子人力,我们却有了何师我忍气?因之师父丝毫不剩下气,又何必为争?」脸色苍苍老迈,那老丐如何黯然不然足力老衰,又何以之又变招法精降专研心的武功?


Process finished with exit code 0

从生成的小说数据来看,大部分看上去还是比较正常的,但是也有一些不合理的数据,比如:

1
小龙女道:「你们就是打扮?」杨过道:「我打扮过儿,又是要你的一个儿子,你来瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧瞧

总结

textgenrnn 是建立在 Keras 和 TensorFlow 之上的,可用于生成字级别和词级别文本。网络体系结构使用注意力加权来加速训练过程并提高质量,并允许调整大量超参数,如RNN模型大小、RNN层和双向RNN。Seq2seq 模型在自然语言处理中取得了较好的结果,与经典RNN结构不同的是,Seq2Seq结构不再要求输入和输出序列有相同的时间长度!

参考资料

使用 textgenrnn 生成文本(最后生成中文)

Kanglei Zhou wechat
觉得好、还想看,评论留言点个赞。关注稷殿下,天天都好看!