使用RNN生成莎士比亚文本(一)处理训练集


该系列的Jupyter源码以及Char-RNN的模型权重&训练历史已放到附件以供下载:

源码和模型数据点击下载

使用RNN进行自然语言处理

这个任务来自于项目Char-CNN。我们可以搭建RNN,通过学习莎士比亚的全部作品,学习单词、语法、标点符号的正确用法。这个任务最难的部分其实就是准备数据集。

创建数据集

我们可以用Keras直接从Char-CNN项目下载莎士比亚的作品数据:

shakespeare_url = "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt"
filepath = keras.utils.get_file("shakespeare.txt", shakespeare_url)
with open(filepath) as f:
    shakespeare_text = f.read()

输出数据集的前几句话看一下:

print(shakespeare_text[:148])

输出:

First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

分词器
首先需要对文本数据集编码,将每个字符转换成整数(字符ID),可以使用keras的Tokenizer进行操作:

tokenizer = keras.preprocessing.text.Tokenizer(char_level=True)
tokenizer.fit_on_texts(shakespeare_text)

分词器可以将一个句子编码为字符ID列表并返回:

tokenizer.texts_to_sequences(["First"])

输出:

[[20, 6, 9, 8, 3]]

也可以将编码转化为字符

['f i r s t']

查看文本的字符种类数:

max_id = len(tokenizer.word_index)
max_id
39

查看文本的字符总数:

dataset_size = tokenizer.document_count
dataset_size
1115394

对全文进行编码,每个字符映射为ID表示 (编码减1,得到0-38的ID,而不是1-39的ID)

[encoded] = np.array(tokenizer.texts_to_sequences([shakespeare_text])) - 1

划分数据集

由于序列有极强的时序关系,所以不能像先前那样混洗。序列的两种划分方式:

  • 跨时间进行划分。如:使用2000年到2012年作为训练集,2013年-2015年作为验证集,2016年-2018年作为测试集。
  • 有其他维度时,可以按照其他维度划分。如有2000-2018年间10000家公司的财务数据,则可以按照不同的公司划分训练集。

我们使用文本的前90%作为训练集,其余部分保留位验证集和测试集,并创建一个tf.data.Dataset:

train_size = dataset_size * 90 // 100
dataset = tf.data.Dataset.from_tensor_slices(encoded[:train_size])

将顺序数据切分成多个窗口
使用window()函数可将长字符序列转换为许多较小的文本窗口

n_steps = 100
window_length = n_steps + 1 # t窗口长度为101,前移一个字符
dataset = dataset.window(window_length, shift=1, drop_remainder=True)
# drop_remainder=True 舍弃最后100个窗口不足101个字符的情况

window方法返回的值是嵌套的数据集,类似于列表的列表。可以通过flat_map方法将其展平。

  • flat_map不指定参数时,可以将嵌套数据及${{1,2},{3,4,5,6}}$展平为数据集${1,2,3,4,5,6}$。
  • flat_map指定参数时(参数为1个函数),可以变换嵌套数据集中的每个数据集。
    如:flat_map(lambda ds:ds.batch(2)可以将嵌套数据集${{1,2},{3,4,5,6}}$转换为:${[1,2],[3,4],[5,6]}$

    dataset = dataset.flat_map(lambda window: window.batch(window_length))

在每个窗口上调用batch:由于所有窗口的长度都恰好相同,因此每个窗口都获得一个张量。当训练集中的实例独立且分布相同时,梯度下降效果最好,所以需要对窗口进行混洗,然后可以批处理训练集的窗口,并将输入(前100)个字符与目标(最后一个字符)分开。

np.random.seed(42)
tf.random.set_seed(42)
batch_size = 32
dataset = dataset.shuffle(10000).batch(batch_size)
dataset = dataset.map(lambda windows: (windows[:, :-1], windows[:, 1:]))

由于数据集只有39种字符,所以可以使用1个独热向量对每个字符编码,并添加预取:

dataset = dataset.map(
    lambda X_batch, Y_batch: (tf.one_hot(X_batch, depth=max_id), Y_batch))
for X_batch, Y_batch in dataset.take(1):
    print(X_batch.shape, Y_batch.shape)

声明:奋斗小刘|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 使用RNN生成莎士比亚文本(一)处理训练集


Make Everyday Count