xiaoxiaokeke 2020-08-04
我太爱北极猴子乐队了,但他们已经很久没有发行新单曲了。久久欠缺精神食粮的我某天晚上突然灵机一动,我可以自给自足呀!于是我写了个简单的代码,用Keras和TensorFlow训练了一个文本生成模型,写出一首全新的北极猴子的歌。
不过条件有限,这玩意儿无法跟真正的北极猴子的歌曲相提并论,但安慰一下长期缺新歌的自己还是可以的。
本文将简单介绍这个代码,完整的代码放在笔者的GitHub上:https://github.com/Rajwrita/Sequence-Models-for-Literature/blob/master/NLP-AM2.0.ipynb。
首先,你得建立一个几乎将全部北极猴子的歌曲包括在内的数据集(https://github.com/Rajwrita/Sequence-Models-for-Literature/blob/master/AM.txt),之后如果继续执行此代码,请尝试使用自己的数据集生成文本。
导入
首先要为深度学习模型导入通用的数据帧操作库以及TensorFlow和Keras库包:
import numpy as np from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout,Bidirectional from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.models import Sequential from tensorflow.keras.optimizers import Adam from tensorflow.keras import regularizers import tensorflow.keras.utils as ku
接着,导入数据:
data = open('AM.txt').read()
再给文本装上一个分词器(tokenizer)。分词器可以生成覆盖整个语料库的单词词典,实质上就是键值对。键是单词,而值则是为该单词生成的标记。简言之,分词器将成句的字符串分解为独立的单词,然后赋予每个单词一个唯一整数值。这一步很关键,为后续嵌入层数据的准备打下基础。
获取单词索引的长度,可以得出语料库里单词的总量。在此基础上加1,就可以引入外部词汇。相应的代码如下:
tokenizer = Tokenizer()data = open('AM.txt').read() tokenizer.fit_on_texts(corpus) total_words = len(tokenizer.word_index) + 1
再然后,使用token列表创建导入序列。导入序列说白了就是一个python列表,文本语料库的每一行都通过分词器生成一个token列表。是像这样的一行文本:
通过这道程序,就会将其转化为代表这些单词的一串token。数据集的每一行都会这样处理。其代码如下:
input_sequences = [] for line in corpus: token_list =tokenizer.texts_to_sequences([line])[0] for i in range(1, len(token_list)): n_gram_sequence = token_list[:i+1] input_sequences.append(n_gram_sequence)
可以看出,导入序列只是被分解为短语的句子,紧接着要获取语料库中最长句子的长度。这一步很简单,只需要将所有句子遍历循环并找出最长那句即可。
max_sequence_len = max([len(x) for x in input_sequences])
现在填充所有序列使它们全部一样长。用零预填充序列,这样更容易提取到标签值,只要抓取最后一个标记就可以得到标签值了。
input_sequences = np.array(pad_sequences(input_sequences,maxlen=max_sequence_len, padding='pre'))
填充之后,创建预测值和标签值,这样序列基本上就被分解为x数组和y数组了。这里用到的是python的切片属性。代码如下:
predictors, label = input_sequences[:,:-1],input_sequences[:,-1]
现在,数据已经分为x数组和y数组,可以开始创建神经网络,对给定词组进行分类预测了。
从嵌入层开始
嵌入层是任何一种理解单词的深度学习模型不可或缺的一层,其实际作用是通过赋予同样含义词汇以同样数值,将较高维空间的向量投影到较低维空间,这样就可以直接对向量进行数学运算了。
在一行文本中,它处理了所有词汇并且赋予其在神经网络中的含义。第一个参数处理的是单词,第二个参数则是绘制单词矢量的维数,最后一个参数是输入维度的尺寸,其实就是最长序列的长度减1。减掉1是因为前面为了得到标签值,我们将每个序列的最后一个词砍掉了,所以得到的序列比最大序列长度要小1。
model.add(Embedding(total_words, 100, input_length=max_sequence_len-1))
添加LSTM(长短期记忆网络)层
LSTM层的细胞状态保存了整个上下文语境,从而保证了对下一个词汇产生影响的不止有相邻词汇。
除了单层的LSTM层,还可以使用堆叠多层的LSTM。使用双向LSTM层,我们可以从头到尾再从尾到头将原始数据导入学习算法中,这有助于神经网络更好地理解文本。双向LSTM还可以帮助神经网络更快收敛。
将返还序列标注设为True,这样就能将序列信息传递到第二层LSTM层,而不是直接到最终状态。
model.add(Bidirectional(LSTM(150, return_sequences = True)))
接下来,运用密集层进一步捕获线性关系,以上各层的输出将转化为单词概率。softmax激活函数会将所有输入单词概率从(-∞,∞ ) 转化为(0,1)。
model.add(Dense(total_words/2, activation='relu', kernel_regularizer=regularizers.l2(0.01)))model.add(Dense(total_words ,activation='softmax'))
由于这里做的是categorical分类,所以要将定律设为分类交叉熵。至于优化器,这里选用adam优化器。
最后一步——Epochs
最后要花一点时间训练模型,数据集的数据不多,大概要训练模型500个epoch左右。
history = model.fit(predictors, label, epochs=100, verbose=1)
要预测的单词越多,产生的乱码也会越多,因为每一个单词都要预测,其下一个和下下个单词也是,那么下一个单词永远比上一个有更多不确定性。来看看网络最后预测出来的文本!
seed_text = "I really like the Arctic Monkeys and