霍格沃兹见!用RNN和TensorFlow创作自己的《哈利波特》小说


全文共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

如转载,请后台留言,遵守转载规范

推荐文章阅读

ACL2018论文集50篇解读

EMNLP2017论文集28篇论文解读

2018年AI三大顶会中国学术成果全链接

ACL2017论文集:34篇解读干货全在这里

10篇AAAI2017经典论文回顾

长按识别二维码可添加关注

读芯君爱你

猜你喜欢

转载自blog.csdn.net/duxinshuxiaobian/article/details/108439529