该系列的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)
Comments | NOTHING