基于深度学习的文本自动生成
导读:本章主要介绍如何通过文本到文本的文本复述技术,进行基于深度学习的文本自动生成。文本复述技术的现有方法能够为给定的文本生成具有较小差异的复述文本,但是难以有效生成具有很大差异的高质量复述文本。原因在于对改写甚多的复述文本而言,难以保证生成文本与原文本的语义一致性,也难以保证生成文本的可读性。
相信未来有一天,计算机能够像人类一样会写作,能够撰写出高质量的自然语言文本。文本自动生成就是实现这一目的的关键技术。按照不同的输入划分,文本生成可包括文本到文本的生成、意义到文本的生成、数据到文本的生成以及图像到文本的生成等。
文本自动生成是自然语言处理(Natural language processing,NLP)领域的一个重要研究方向,实现文本自动生成也是人工智能走向成熟的一个重要标志。文本自动生成技术极具应用前景。例如,可以应用于智能问答与对话、机器翻译等系统,实现更加智能和自然的人机交互;也可以通过文本生成小说和诗歌等艺术作品;也可以通过文本自动生成系统替代编辑实现新闻的自动撰写与发布,最终将有可能颠覆新闻出版行业;甚至可以用于帮助学者进行学术论文撰写,进而改变科研创作模式。
在自然语言处理的前沿研究正在迅猛发展,近几年已产生了若干具有国际影响力的成果与应用。由于深度学习具有更复杂的分布式表示、更精细的功能块模块化设计(如层级注意力机制)和基于梯度的高效学习方法,它已经成为解决越来越多自然语言处理问题的主要范式和先进方法,广泛应用于相关领域,比如语音翻译、对话系统、词法分析、语法分析、知识图谱、机器翻译、问题回答、情绪分析、社会计算以及自然语言生成。
本章主要讲解文本到文本的生成。该技术主要是指对给定文本进行变换和处理从而获得新文本的技术,具体来说包括文本摘要(DocumentSummarization)、句子压缩(Sentence Compression)、句子融合(Sentence Fusion)、文本复述(Paraphrase Generation)等。文本摘要,主要是通过自动分析给定的文档或文档集,摘取其中的要点信息,最终输出一篇短小的摘要,目的是通过对原文本进行压缩、提炼,为用户提供简明扼要的内容描述;句子压缩,基于一个长句生成一个短句,在语句通顺的情况下保留长句中的重要信息;句子融合,是合并两个或多个包含重叠内容的相关句子得到一个句子;文本复述,是通过读给定文本进行改写,生成全新的复述文本,要求输出文本与输入文本在表达上有所不同,但表达的意思基本相同。在接下来,将会以五言律诗的生成为例讲解文本生成。
10.1训练文本数据采集
10.1.1训练文本数据源
五言律诗的生成采用的是文本到文本的文本复述技术,即通过输入的文本生成新的文本,在输出的表达上各有不同,但是其结构基本相同。因此,五言律诗的自动生成输入的训练数据集也是五言律诗,由于五言律诗属于文学作品,在图书馆类型网站也可找到,比如360个人图书馆(http://www.360doc.com)、百度文库(https://wenku.baidu.com)、短美文网(http://www.duanmeiwen.com)等。当然,把训练数据集换成小说也是可以的,小说的数据源可以在GitHub(https://github.com/JinpengLI/chinese_text_dataset)网站下载。
10.1.2训练文本数据整理
下载好文本数据后会发现内容类似如下这种:
1. 秋浦歌唐李白
2. 炉火照天地,红星乱紫烟。
3. 赧郎明月夜,歌曲动寒川。
4. 玉阶怨唐李白
5. 玉阶生白露,夜久侵罗袜。
6. 却下水精帘,玲珑望秋月。
7. 相思唐王维
8. 红豆生南国,春来发几枝。
9. 愿君多采撷,此物最相思。
下载的文本数据包含了诗歌名和作者名,这部分不是训练数据集想要的部分。所以就要对下载后的数据集做一些处理。观察已有的数据后可以发现诗歌名和作者均在同一行上,只要找到那一行就可以对数据集进行一个简单的清理。
1. importre
2. withopen('../src/data/poem5.txt',encoding='utf-8')asf:
3. f2=open('../src/data/poem5final.txt','w',encoding='utf-8')
4. line=f.readline()
5. whileline:
6. iflen(line)>=12:
7. print(line)
8. f2.write(line)
9. line=f.readline()
10. f2.close()
11. f.close()
运行以上代码可得。
1. 炉火照天地,红星乱紫烟。
2. 赧郎明月夜,歌曲动寒川。
3. 玉阶生白露,夜久侵罗袜。
4. 却下水精帘,玲珑望秋月。
5. 红豆生南国,春来发几枝。
6. 愿君多采撷,此物最相思。
10.2 LSTM五言律诗自动生成设计
在进行长短期记忆网络五言律诗自动生成时,首先需要获取足够的训练五言律诗数据集。训练的五言律诗数据集越大,五言律诗自动生成的多样性就越多。获取到足够的训练五言律诗数据集后,进一步对五言律诗数据集进行规范化,规范化就是确保训练数据集只包含有五言律诗,每一句诗歌均是五言律诗。
在获取到足够的训练五言律诗数据后,开始搭建长短期记忆网络,使用的是Keras函数库搭建长短期记忆网络模型。长短期记忆网络包含了输入层、LSTM层、全连接层和输出层。当然,每层之间会有一层Dropout正则化。
确定好层数后,进一步确定输出维度是多少,输出张量是属于3D还是2D,以及激活函数是ReLu函数还是Tanh函数。每层之间的Dropout正则化参数是多少,以及全连接层的激活函数是什么。最值得注意的就是只有第一层需要输入数据,后面的网络层的输入来源于上一层。
基于深度学习的诗歌自动生成,是使用Python实现神经网络搭建和文本数据预处理等过程,再使用Keras接口调用Theano或Tensorflow后端进行训练。
10.2.1文本预处理
文本预处理就是统计文本训练数据的长度、训练数据的字库及长度、创建唯一字符到整数的映射和反向映射。
1. '''数据预处理'''
2. #汇总加载数据
3. len_seq=len(seq_chr)#语料长度
4. chr_set=set(seq_chr)#字库
5. len_chr=len(chr_set)#字库长度
6. print('总字符:',len_seq)
7. print('总字库:',len_chr)
8. chr_ls=Counter(list(seq_chr)).most_common(len_chr)
9. chr_ls=[i[0]foriinchr_ls]
10. #创建唯一字符到整数的映射和反向映射
11. chr2id={c:ifori,cinenumerate(chr_ls)}#文字到数字的映射
12. id2chr={i:cforc,iinchr2id.items()}
13. seq_id=[chr2id[c]forcinseq_chr]#文字序列-->索引序列
10.2.2文本数据标准化
五言律诗文本数据的标准化包括数据采集后的诗歌名删除、作者名删除和多余的标点符号进行删除。理论上来讲,最后的数据集只包含了五言律诗,诗歌的标点符号只包含有“,”、“;”和“。”。同时文本数据放入输入层之前也要进行数据的标准化,下方代码的x和y就是经过标准化的数据。
1. reshape=lambdax:np.reshape(x,(-1,window,1))/len_chr
2. x=[seq_id[i:i+window]foriinrange(len_seq-window)]
3. x=reshape(x)
4. y=[seq_id[i+window]foriinrange(len_seq-window)]
5. y=to_categorical(y,num_classes=len_chr)
6. print('x.shape',x.shape,'y.shape',y.shape)
10.2.3 LSTM模型搭建
长短期记忆网络的搭建使用的是Keras函数库进行搭建,在Theano后端运行。使用Keras搭建长短期记忆网络需要导入Keras相关的函数模块,包括了keras.layers模块里的Dense、LSTM、Dropout模块,还包括keras.models里的Sequential、load_model模块和keras.utils的to_categorical、np_utils模块。完整的网络模型搭建如下:
1. model=Sequential()
2. model.add(LSTM(output_dim=256,
3. input_shape=(x.shape[1],x.shape[2]),
4. return_sequences=True,
5. activation='tanh'))
6. model.add(Dropout(0.2))
7. model.add(LSTM(output_dim=256,activation='tanh'))
8. model.add(Dropout(0.2))
9. model.add(Dense(y.shape[1],activation='softmax'))
10. model.compile(optimizer='adam',
11. loss='categorical_crossentropy',
12. metrics=['accuracy'])
首先,先使用Sequential函数定义模型的创建是自上而下的,然后再逐层的搭建。
LSTM层里的input_dim表示的是输出维度,input_shape表示的是输入,值得注意的是仅仅第一层需要输入,其他层的输入来源于上一层的输出,return_sequences表示返回的张量,当等于True时,说明返回的是3D张量,否则说明返回的是2D张量,activation就是大家都熟悉的激活函数。
Dropout正则化可以说是Keras减少过拟合的一个重要函数,也是最简单的神经网络正则化方法。其原理非常简单粗暴,就是任意丢弃神经网络层中的输入,该层可以是数据样本中的输入变量或来自先前层的激活。Dropout能够模拟具有大量不同网络结构的神经网络,并且反过来使网络中的节点更具有鲁棒性。在创建Dopout正则化时,可以将dropout rate的设为某一固定值,当dropout rate=0.8时,实际上,保留概率为0.2(建议将dropout rate参数设置为0.2)。
10.2.4训练LSTM模型
训练模型的目的是为了使得模型可以自动生成诗歌,直接读取数据,并把输入数据标准化后放入模型中训练,然后把训练结果保存在.hdf5文件中。
1. '''语料加载'''
2. withopen(corpus_path,encoding='utf-8')asf:
3. '''诗歌语料加载(所有语句均拼接了起来)'''
4. seq_chr=f.read().replace('\n','')
5. '''训练'''
6. foreinrange(times):
7. model.fit(x,y,batch_size,epochs,verbose=0)
8. model.save(filepath)
fit函数中的batch_size为每次训练的样本数,epochs为训练轮数。
10.3测试LSTM模型
说到LSTM测试,有两个问题不得不思考清楚,一个是如何生成序列数据,另一个是如何定义采样方法。
10.3.1 生成序列数据
用深度学习生成序列数据的通用方法就是使用前面的标记作为输入,训练一个循环网络或卷积网络来预测序列中接下来的一个或多个标记。标记(token)通常是字或词语(单词或字符),给定前面的标记,能够对下一个目标的概率进行建模的任何网络都叫语言模型(language model)。语言模型能够捕捉到语言的潜在空间(latent space),即语言的统计结构。
通常文本生成的基本策略是借助语言模型,这是一种基于概率的模型,可根据输入数据预测下一个最有可能出现的词,而文本作为一种序列数据(sequence data),词与词之间存在上下文关系,所以使用循环神经网络(RNN)基本上是标配。在训练完一个语言模型后,可以输入一段初始文本,让模型生成一个词,把这个词加入到输入文本中,再预测下一个词。过程如图10-1所示。
10.3.2 定义采样方法
生成文本时,如何选择下一个字符至关重要。一种简单的方法就是贪婪采样(greedy sampling),就是始终选择可能性最大的下一个字符。但这种方法会得到重复的字符串,看起来不像连贯的语言。还有一种就是随机采样,在采样过程中引入随机性,即从下一个字符的概率分布中进行采样。随机采样会根据模型结果来进行选择,如果下一个字符是“棒”的概率是0.4,那么就会用40%的概率会选择它。
从模型的softmax输出中进行概率采样是一种特别巧妙的方法,甚至可以在某些时候采样到不常见的字符,从而生成看起来更有趣的句子,而且有时候会得到训练数据中没有的并且听起来像真实存在的新词语,进而表现出创造性。但是,这种方法有一个问题,就是采样过程中的随机性无法控制。
为了采样过程中随机性可控,引入一个叫softmax温度(softmax temprature)的参数,用于表示采样概率分布的熵,即表示所选择的下一个字符会有多么的出人意料或多么的可预测。给定一个temperature值对原始概率分布(即模型的softmax输出)进行重新加权,计算得到一个新的概率分布。
1. '''随机采样:对概率分布重新加权'''
2. defdraw_sample(predictions,temperature):
3. pred=predictions.astype('float64')#提高精度防报错
4. pred=np.log(pred)/temperature
5. pred=np.exp(pred)
6. pred=pred/np.sum(pred)
7. pred=np.random.multinomial(1,pred,1)
8. returnnp.argmax(pred)
更高的温度得到的是熵更大的采样分布,会生成更加出人意料、更加无结构的生成数据,而最低的温度对应更小的随机性,以及更加可预测的生成数据。
1. fortin(None,1,1.5,2):
2. predict(t)
以上代码表示当t等于None时才是贪婪采样,其他的1、1.5、2均是温度。接下来将对模型进行测试,从四个方面进行测试,分别是贪婪采样、温度为1的随机采样、温度为1.5的随机采样和温度为2的随机采样。
1. defpredict(t,pred=None):
2. ifpredisNone:
3. randint=np.random.randint(len_seq-window)
4. pred=seq_id[randint:randint+window]
5. ift:
6. print('随机采样,温度:%.1f'%t)
7. sample=draw_sample
8. else:
9. print('贪婪采样')
10. sample=np.argmax
11. for_inrange(window):
12. x_pred=reshape(pred[-window:])
13. y_pred=model.predict(x_pred)[0]
14. i=sample(y_pred,t)
15. pred.append(i)
16. text=''.join([id2chr[i]foriinpred[-window:]])
17. print('%s'%text)
知道采样方法后,即可开始对模型进行测试:
1. if__name__=='__main__':
2. fortin(None,1,1.5,2):
3. predict(t,seq_id[-window:])
4. whileTrue:
5. title=input('输入标题:').strip()+'。'
6. len_t=len(title)
7. randint=np.random.randint(len_seq-window+len_t)
8. randint=int(randint//12*12)
9. pred=seq_id[randint:randint+window-len_t]+[c2i(c)forcintitle]
10. fortin(None,1,1.5,2):
11. predict(t,pred)
输出结果如图10-2和图10-3所示。
以上采用是的长短期记忆网络搭建的模型,在10.3.1中也说明了,卷积神经网络也可以实现文本的自动生成,以下给出简单的CNN模型搭建,只要用以下代码替换LSTM模型部分的代码即可。
1. model=Sequential()
2. model.add(Conv1D(filters,kernel_size*3,activation='relu'))
3. model.add(MaxPool1D())
4. model.add(Conv1D(filters*2,kernel_size,activation='relu'))
5. model.add(GlobalMaxPool1D())
6. model.add(Dense(len_chr,activation='softmax'))
7. model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])
内容选自国防科技大学邓劲生教授的《实战深度学习—原理、框架及应用》