TextGeneration
1、简介
可以训练一个RNN, 它它自动生成文本, 生成的文本就像人写的一样。如果用莎士比亚的书来训练RNN, 生成的文本就像莎士比亚写的一样, 举个例子来解释文本生成。
输入本句话the cat sat on the ma, 要求训练一个神经网络来预测下一个字符。训练数据是很多文本, 把文本分割成字符, 用one-hot encoding来表示字符,这样每个字符就表示成一个one hot向量。把这些one-hot向量依次输入到RNN, RNN的状态向量h会积累看到的信息, RNN返回最后一个状态向量h。RNN层上面是一个softmax分类器, 把h与参数矩阵w相乘, 得到一个向量,经过softmax函数的变换, 最终输出是一个向量。向量每个元素都在0~1之间,元素全加起来等于1, softmax分类器的输出其实就是一个概率分布。
假设这个神经网络已经训练好了, 我们把the cat sat on the mat输入到这个神经网络中, 神经网络最上层softmax分类器会输出这些概率值。每个字符对应一个概率值, 其中概率最大的是字符t, 大小约为0.175。有了这些概率值, 我们就可以预测下一个字符了。
有了概率值就可以选择概率最大的字符t, 也可以按照概率值值随机抽样。假设我们抽到的是字符t, 然后就把t接到输入的文本末尾。
将得到的the cat sat on the mat这句话作为输入, 计算下一个字符的概率分布,从而生成下一个字符。
下一轮抽到的字符可能是句号。于是这句话就变成了the cat sat on the mat. 再重复这个过程, 下一次可能抽到空格, 再下次可能会抽到一个字母, 不断重复下去可以生成一段话, 一篇文章甚至一本书。
2、训练
现在看一下究竟如何训练RNN, 训练数据是文本, 比如英文维基百科的所有文章。 把文章划分成很多片段,这些片段可以有重叠, 比如红色片段作为输入的文本, 后面的蓝色字符a作为标签。把红色的这个片段输入到神经网络, 希望神经网络做出的预测是这个蓝色的字符a。假设设置的片段的长度segment length等于40,意思就是红色的片段有40个字符。设置的步长等于3, 意思是下一个片段会往右平移三个字符的长度。
平移三个字符后, 这是下一个片段。从字符h到空格, 中间这40个红色的字符会被用作输入文本。蓝色的字母i是标签。由于设置的stride等于3, 这个片段会往右移动三个字符的长度。假设文章大约有3000个字符, 那么会得到大约1000个红色的片段和1000个蓝色的标签。
这些红色的片段会被用于神经网络的输入, 蓝色的字符会被作为标签。训练数据就是把这些片段和标签的pairs。假如一篇文章有3000个字符, 这篇文章会被切分成约1000个pairs。
训练神经网络的目的是给定输入的片段, 神经网络可以预测下一个字符,其实就是一个多分类的问题。 加入包括字母空格标点在内, 一共有50个不同的字符,那么分类类别的数量就是50。输入一个片段, 输出50个概率值, 每个类别对应一个概率值。
文本生成可以用来做一些有趣的事情, 有人拿8000个英文名字做训练数据, 用来生成新的英文名字。这些全都是神经网络生成的名字。绝大多数都是新的,没有出现在训练数据里面, 这些名字看起来很像英文名, 但是绝大多数都没有人用过。这说明文本生成器并不是记住训练数据, 然后重复训练数据。相反, 它能生成新的东西, 能生成新的东西就是一个好的性质。
3、代码实现
随便拿一本书作为训练数据, 用python读取这本书得到一个很长的字符串。大约有60万个字符, 这是前1000个字符。字符主要是拉丁字母, 空格, 标点以及换行符号。为了方便把所有字母都变成小写字母。
首先来准备训练数据, 原始数据长度是60多万的字符串, 每次取长度为60的片段作为输入, 片段后面的字符作为label。将这个片段存到segment列表中, 字符存到next_chars列表中。
完成文本切分后, 需把一个字符变成一个向量。于是一个片段就变成了一个矩阵。首先建立一个字典, 把每个字符映射到一个整数, 比如a映射到1, b映射到2。在之前的讲解中, 一个token就是一个word, 这里一个token就是一个字符。
然后把每个正整数用one-hot向量表示。做完one-hot encoding, 一个字符串就被编码成一个数值矩阵。如果这个字符串的长度是8个字符, 那么这个矩阵就有8行。之前对于word level在得到one hot向量之后还要进一步做word embedding,用一个低维向量表示一个词。这里面不需要embedding层, 原因是这样的, 之前用word level tokenization把一句话分成多个单词, 英语里大约有1万个常用词。所以vocabulary设置为1万。那么one-hot encoding得到的one-hot向量是1万维。维度太高了, 所以要用word embedding把高维的one hot向量变成低维的词向量。而这里用的是character level tokenization, 把一句话切成很多字符, 文本里通常也就是几十个常用字符, 比如字母, 数字, 标点。所以字典里面的vocabulary最多也就是100个。做one hot encoding得到的one hot向量最多也就只有100维。维度已经够低了, 所以不需要进一步做embedding。
训练时, vocabulary里面的v设置为57, 意思是字典里面一共有57个不同的字符。包括26个小写字母、空格、标点符号等等。之前设置每个滑动窗口的长度是60, 每个片段里面有60个字符。于是长度为60的片段就变成了60×57的矩阵。每个字符用57维的one hot向量表示, 标签是这个片段的下一个字符, 这个字符编码成了57维的one hot向量。
接下来搭建一个神经网络, 第一层是LSTM, 输入是60×57的矩阵, 每个片段被编码成了60×57的矩阵。LSTM层状态向量h设置为128。注意这里只用了单向LSTM, 不能用双向LSTM。文本生成需要预测下一个字符, 必须是从前往后,这是一个单向问题。LSTM之后再加上一个全连接层, 用softmax激活函数。这一层的输出是57维的向量, 向量的每一个元素是一个字符的概率值, 字典里面有57个不同的字符, 于是输出的向量是57维的。
1 | from tensorflow.keras import layers |
介绍三种选择字符的方法, 一种是greedy selection。哪个字符的概率最大,就选择哪个字符。python中的np.argmax()就得到下一个字符的index, 根据index就能得到该字符。这种方法是确定的, 没有随机性。给定初始的几个字符, 后面生成的文本完全是确定的,完全取决于初始的输入。这种确定性的文本生成并不好, 我们希望生成的文本尽量多元化,这样才能得到很多有意思的结果。
第二种方法是多项式随机抽取。随机抽样需要用到函数np.random.multionmial()。假如一个字符的概率等于0.3, 那么它被选中的概率就是0.3, 这样抽样过于随机生成的文本会犯很多拼写和语法错误。
第三种方法介于二者之间。它有随机性, 但随机性并不是很大。具体做法是使用temperature调整概率值。这里要用到介于0到1之间的数temperature。把概率值做这种逆变换, 然后再归一化,把概率值做这种变换后大的概率值会变大,小的概率值会变小。极端情况下最大的概率值会变成1, 其余的概率值会变成0, 这就相当于第一种确定性的选择。