【0x0001】文本生成:从零实现的char-RNN

事情的起因是这样的,在某个秋日午后,突然想到了前段时间在GitHub上大火的狗屁不通文章生成器,这个分分钟生成万字长文的神器,效果就像下面这样[1]

文本生成 decoder的输入维度 文本生成算法_生成器


在我兴致勃勃找到他的源码后,却发现不是自己想的那样,原作者并没有使用任何的NLP算法,只是使用程序在语料库中进行的随机摘抄[2]

文本生成 decoder的输入维度 文本生成算法_生成器_02

文本生成 decoder的输入维度 文本生成算法_文本生成 decoder的输入维度_03


短短54行代码就实现了这么高大上的功能,我等自然也不能浪费这个创意,所以我打算从零学起,用接下来几篇文章实现文本生成。

这是我第二篇原创文章,首先我会介绍什么是文本生成,什么是char-RNN,用char-RNN实现文本生成的流程,在文末会提供本篇文章中所有的代码和数据。

文本生成

文本生成是比较学术的说法,通常在媒体上见到的“机器人写作”、“人工智能写作”、“自动对话生成”、“机器人写古诗”等,都属于文本生成的范畴。文本生成的主要应用领域也是集中在创作、对话、信息提取几个领域[3]

文本生成的方法有多种分类,比如基于规则的和基于模型的,基于规则、基于规划和基于数据的,等等多种分类方法[4][5]。基于规则的方法有一定的模板,上面的狗屁不通文章生成器也可以算作这一类,这类方法根据用户的需求选择不同的模板,再根据一定的生成规则产生文本。基于模型或者说数据驱动的方法,早期的是基于马尔可夫的语言模型,后来是基于深度学习的方法。本文以及后面的几篇文章都是基于深度学习的方法,将会介绍RNN、LSTM、GRU、Seq2Seq、Attention、Transformer、BERT、GPT等模型或方法。

char-RNN

关于char-RNN比较好的一个介绍是Andrej Karpathy的博客《The Unreasonable Effectiveness of Recurrent Neural Networks》[6]。这篇博客生动的介绍了什么是RNN和char-based model,并且在最后提供了多个文本生成的结果,包括Paul Graham的文章、Shakespeare的文章、Wikipedia生成、Latex文章生成、Linux Source Code生成等等,强烈推荐大家读一下。

下面这副图片直观展示了char-RNN的原理,以要让模型学习写出“hello”为例,Char-RNN的输入输出层都是以字符为单位。输入“h”,应该输出“e”;输入“e”,则应该输出后续的“l”。输入层我们可以用独热编码(one-hot),例如,h被编码为“1000”、“e”被编码为“0100”,而“l”被编码为“0010”。使用RNN的学习目标是,可以让生成的下一个字符尽量与训练样本里的目标输出一致。在图一的例子中,根据前两个字符产生的状态和第三个输入“l”预测出的下一个字符的向量为<0.1, 0.5, 1.9, -1.1>,最大的一维是第三维,对应的字符则为“0010”,正好是“l”。这就是一个正确的预测。但从第一个“h”得到的输出向量是第四维最大,对应的并不是“e”,这样就产生代价。学习的过程就是不断降低这个代价。学习到的模型,对任何输入字符可以很好地不断预测下一个字符,如此一来就能生成句子或段落[3]

文本生成 decoder的输入维度 文本生成算法_python_04

使用char-RNN实现文本生成

数据的准备

Andrej Karpathy最基础的两个示例使用的是Paul Graham和莎士比亚的文本,在这里我改成了中文文本,小数据是朱自清的《春》、大数据使用的经典网络小说《斗破苍穹》。测试数据是输入数据中的一段。输入数据后构建字典,实现汉字和索引的一一对应。

文本生成 decoder的输入维度 文本生成算法_生成器_05

构造RNN网络

RNN作为最基本的几种网络之一,在序列生成任务中有着很好的表现。在下一篇文章中我会详细介绍RNN,这里先简单提一下RNN的更新公式。

文本生成 decoder的输入维度 文本生成算法_数据_06

文本生成 decoder的输入维度 文本生成算法_python_07

正向传播与梯度计算

RNN的正向传播就是不断重复上面两个公式,反向传播相较一般的神经网络略复杂,因为正向传播是按时间顺序前进的,所以RNN的反向传播有时也叫做BPTT(back-propagation through time)[7]

文本生成 decoder的输入维度 文本生成算法_深度学习_08

文本生成 decoder的输入维度 文本生成算法_深度学习_09


之后可以使用梯度裁剪防止梯度爆炸。

采样文本

提供现阶段的记忆,采样开始时的文字,生成固定长度的文本。其主要思想就是正向传播,并按概率选择输出中的汉字。

文本生成 decoder的输入维度 文本生成算法_python_10

训练模型

有了上面的准备,我们就可以开始模型的训练了。即选择固定长度的文本输入到模型中,进行正向反向传播计算梯度,更新参数,不断迭代。使用Adagrad来调整参数更新速率。

文本生成测试

首先我测试了小数据样本,使用朱自清的《春》训练1000次,结果如下:

文本生成 decoder的输入维度 文本生成算法_python_11

训练过程中的损失变化如下:

文本生成 decoder的输入维度 文本生成算法_文本生成 decoder的输入维度_12

之后使用了斗破进行训练,由于时间问题,没有使用全部章节,使用前50章训练5次,效果如下:

文本生成 decoder的输入维度 文本生成算法_数据_13

可以看到模型已经可以生成正确的人名和大概的逻辑,如果增加数据和训练次数,效果会更好,但因为这是一个手动实现的模型,在效率上肯定有所欠缺,所以训练完整的数据意义不大。

顺便一提,仅仅使用numpy也是可以用cuda加速的,读者可自行学习cupy相关内容,将代码中所有np换成cp即可。如下。

import numpy as np
import cupy as cp