全文共5945字,预计学习时长15分钟
图源:unsplash
还在等待霍格沃茨的录取通知书?想在大礼堂里享用晚宴?探索霍格沃茨的秘密通道?在奥利凡德魔杖店买第一根魔杖?叹气,你不是一个人。这么久了,我还是沉迷于哈利波特的魔法世界。
最近我开始学习神经网络,着迷于深度学习的强大创造力。灵光乍现,我为什么不把它们融合在一起呢?因此,我使用TensorFlow执行了一个简单的文本生成模型来创作我自己的《哈利·波特》短篇小说。
本文将介绍了我为实现它而编写的完整代码。各位巫师可以在这里直接找到github代码并自己运行它:https://github.com/amisha-jodhani/text-generator-harry-potter
当你闲来无事时,它可以向无聊施一个驱逐咒。
背景
RNN是什么?
循环神经网络(RNN)与其他神经网络不同,它是有记忆的神经网络,可以存储它处理过的所有层的信息,并在记忆的基础上计算下一层。
GRU vs LSTM
二者在文本生成上都表现出色,GRU(门控循环单元)是比较新的概念,实际上并没有一种方法可以确定二者哪个更好。优化超参数比选择一个好的架构更能提高模型性能。
如果数据量不成问题,那么则是LSTM(长短期记忆网络)性能更优。如果数据量较少,那么GRU参数更少,因此训练更快,并能很好地泛化。
为什么是基于字符的?
在处理这样的大型数据集时,一个语料库中不重复单词的总量远远高于唯一字符的数量。一个大型数据集有许多不重复的单词,当为如此大的矩阵分配独热编码时,很可能会遇到内存问题。光是标签本身就可以占据Tb级的内存。
用来预测单词的原则也可以应用在这里,但是现在要处理的词汇量要小得多。
代码
图源:unsplash
首先导入所需的库
import tensorflow as tfimport numpy as npimport osimport time
现在,读取数据
可以从 一个Kaggle数据集找到并下载所有《哈利波特》的文稿(https://www.kaggle.com/alex44jzy/harrypotter)。我将这七本书合并到一个名为“harrypotter.txt”的文本文件中,你也可以挑选你喜欢的任何一本来训练模型。
files= [‘1SorcerersStone.txt’, ‘2ChamberofSecrets.txt’, ‘3ThePrisonerOfAzkaban.txt’,‘4TheGobletOfFire.txt’, ‘5OrderofthePhoenix.txt’, ‘6TheHalfBloodPrince.txt’,‘7DeathlyHollows.txt’]with open(‘harrypotter.txt’, ‘w’) as outfile:for file in files: with open(file) as infile: outfile.write(infile.read())text = open(‘harrypotter.txt’).read()
查看数据
print(text[:300])
处理数据
通过建立两个查找表将 vocab 中所有唯一的字符串映射到数字:
· 将字符映射到数字(char2index)
· 将数字映射回字符(index2char)
然后把文本转换成数字:
vocab = sorted(set(text))char2index = {u:i for i, u in enumerate(vocab)}index2char = np.array(vocab)text_as_int = np.array([char2index[c] for c in text])#how it looks:print ( {} -- characters mapped to int -- > {} .format(repr(text[:13]),text_as_int[:13]))
“Harry Potter”——字符映射到数字→ [39 64 81 81 88 3 47 78 83 83 68 81 3]
每个输入模型的序列包含文本中seq_length 数量的字符,其对应的目标序列长度相同,所有字符都向右移动了一个位置,因此将文本分为 seq_length+1块。
tf.data.Dataset.from_tensor_slices 将文本向量转换为字符索引流, batch方法(分批处理法)将这些字符按所需的长度分批。
通过使用 map 方法对每个批处理应用一个简单的函数,可以创建输入和目标:
seq_length = 100examples_per_epoch = len(text)//(seq_length+1)char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)sequences = char_dataset.batch(seq_length+1, drop_remainder=True)defsplit_input_target(data): input_text = data[:-1] target_text = data[1:] return input_text, target_textdataset =sequences.map(split_input_target)
在将这些数据输入到模型之前对数据进行随机排列,并将其划分批次。tf.data维护一个缓冲,在缓冲区中它会随机排列元素。
BATCH_SIZE = 64BUFFER_SIZE = 10000dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE,drop_remainder=True)
搭建模型
有了目前算出来的所有字符,下一个字符会是什么?这就要训练RNN模型来预测了。使用 tf.keras.Sequential定义模型,因为其中的所有层都只有一个输入并产生一个输出。使用的不同层是:
· tf.keras.layers.Embedding:这是输入层。嵌入用于将所有唯一字符映射到具有 embedding_dim维数的多维空间中的向量。
· tf.keras.layers.GRU:有rnn_units个单元数的RNN。(也可以在此处使用LSTM来看看哪种最适合已有数据。)
· tf.keras.layers.Dense:输出层,有vocab_size 大小的输出。
单独定义所有的超参数也很有用,这样以后无需编辑模型定义就可以更很容易地修改它们。
文本生成训练示例
vocab_size = len(vocab)embedding_dim = 300# Number of RNN unitsrnn_units1 = 512rnn_units2 = 256rnn_units= [rnn_units1, rnn_units2]def build_model(vocab_size, embedding_dim,rnn_units, batch_size): model = tf.keras.Sequential([ tf.keras.layers.Embedding(vocab_size,embedding_dim, batch_input_shape=[batch_size,None]), tf.keras.layers.GRU(rnn_units1, return_sequences=True, stateful=True,recurrent_initializer= glorot_uniform ), tf.keras.layers.GRU(rnn_units2,return_sequences=True, stateful=True,recurrent_initializer= glorot_uniform ), tf.keras.layers.Dense(vocab_size) ]) return modelmodel = build_model(vocab_size = vocab_size,embedding_dim=embedding_dim,rnn_units=rnn_units,batch_size=BATCH_SIZE)
训练模型
标准的tf.keras.losses.sparse_categorical_crossentropy 损失函数与这个模型一起使用时效果最佳,因为它应用于预测的最后一层。将 from_logits设置为True,因为该模型返回logits。然后选择adam优化器并编译模型。
def loss(labels, logits): returntf.keras.losses.sparse_categorical_crossentropy(labels, logits,from_logits=True)model.compile(optimizer= adam , loss=loss,metrics=[ accuracy ])
可以像这样配置检查点,以确保在训练期间保存了检查点。
# Directory where the checkpoints will be savedcheckpoint_dir = ‘./training_checkpoints’# Name of the checkpoint filescheckpoint_prefix = os.path.join(checkpoint_dir, “ckpt_{epoch}”)checkpoint_callback=tf.keras.callbacks.ModelCheckpoint( filepath=checkpoint_prefix,save_weights_only=True)
每个期(epoch)的训练时间取决于模型层和超参数的使用。我将期设置为50,以观察准确性和损失如何随时间变化,但可能不需要训练所有50期。当你看到损失开始增加或在几个期内保持不变时,一定要停止训练。训练的最后一个期将存储在latest_check中。如果使用Google Colab,则将runtime设置为GPU以减少训练时间。
EPOCHS= 50history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])latest_check = tf.train.latest_checkpoint(checkpoint_dir)
图源:unsplash
文本生成
如果希望使用不同的批处理大小,则需要在运行之前重新构建模型并重新加载检查点。为了保持简单,我设置batch_size 为1。可以运行model.summary() 来了解模型的各个层以及每个层运行之后的输出形状。
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)model.load_weights(latest_check)model.build(tf.TensorShape([1, None]))model.summary()
下面的函数就在生成文本:
· 接收start_string,初始化RNN的状态并将输出字符数赋给 num_generate。
· 使用 start_string 和RNN状态获取下一个字符的预测分布。然后它会计算预测字符的索引,作为模型的下个输入。
· 模型返回的输出状态反馈到模型中,这样它就有了更多的上下文(如下所示)。在预测下一个字符之后,循环继续。通过这种方式,RNN可以从之前的输出中建立记忆。
文本生成示例
· 较低的scaling会产生更可预测的文本,而较高的scaling会产生更令人惊讶的文本。
def generate_text(model, start_string): num_generate = 1000 #can beanything you like input_eval =[char2index[s] for s in start_string] input_eval = tf.expand_dims(input_eval,0) text_generated = [] scaling = 0.5 #kept at a lower valuehere # Here batch size == 1 model.reset_states() for i in range(num_generate): predictions = model(input_eval) # remove the batch dimension predictions = tf.squeeze(predictions,0) predictions = predictions / scaling predicted_id =tf.random.categorical(predictions, num_samples=1)[1,0].numpy() input_eval =tf.expand_dims([predicted_id], 0) text_generated.append(idx2char[predicted_id])return(start_string + ‘’.join(text_generated))
然后就完成了!
输出
可以尝试不同的初始字符串以获得不同的输出。
这是用我最喜欢的人物名输出的一部分:
print(generate_text(model, start_string=u”Severus Snape“))
也可以尝试不同的句子:
如果你只使用第一本《哈利波特与魔法石》来训练模型,可以得到下面结果:
你会发现模型知道什么时候大写,什么时候另起一段,它还模仿了魔法风格的写作词汇!
为了使句子更连贯,你可以通过以下方法改进模型:
· 改变不同参数,如 seq_length , rnn_units , embedding_dims , scaling的值来找出最佳设置。
· 训练更多期
· 为GRU / LSTM添加更多层
图源:unsplash
这个模型可以训练你喜欢的任何系列书籍。好啦,恶作剧完毕~
推荐阅读专题
留言点赞发个朋友圈
我们一起分享AI学习与发展的干货
编译组:闫欣阳、朱颜
相关链接:
https://medium.com/towards-artificial-intelligence/create-your-own-harry-potter-short-story-using-rnn-and-tensorflow-853b3ed1b8f3
如转载,请后台留言,遵守转载规范
推荐文章阅读
长按识别二维码可添加关注
读芯君爱你