基于莎士比亚作品数据集的循环神经网络(RNN)文本生成(1)

2周前 57次点击 来自 TensorFlow

收录专题: TensorFlow官方教程笔记

本文来自于官方教程循环神经网络(RNN)文本生成
许多更详细的细节请参考官方文档,本文只是笔者的阅读笔记。

很多RNN教程的数据集都使用了莎士比亚作品数据集。
对于入门而言,使用英文数据集的好处在于模型基于字符,使用的词表更小,数据集大小也合适,能更好的训练出效果。

1.获取数据集

import tensorflow as tf

import numpy as np
import os
import time

path_to_file = tf.keras.utils.get_file('shakespeare.txt',
                                       'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')

# 文本长度是指文本中的字符个数
print('Length of text: {} characters'.format(len(text)))
# 看一看文本中的前 250 个字符
print(text[:250])
# 文本中的非重复字符
vocab = sorted(set(text))
print('{} unique characters'.format(len(vocab)))

# 创建从非重复字符到索引的映射
char2idx = {u: i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

text_as_int = np.array([char2idx[c] for c in text])
print('{')
for char, _ in zip(char2idx, range(20)):
    print('  {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print('  ...\n}')

2.处理文本

2.1 向量化文本

训练之前,做文本向量化的好处在于,计算机更易于处理数据,文本字符映射到数字表示值。两个查找表格:

  1. 字符映射到数字
  2. 数字映射到字符

这种处理方式,在RNN文本数据向量化的过程中是通用的,无论处理的是中文或者英文,只不过表格的大小不同罢了。

# 创建从非重复字符到索引的映射
char2idx = {u: i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

text_as_int = np.array([char2idx[c] for c in text])
print('{')
for char, _ in zip(char2idx, range(20)):
    print('  {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print('  ...\n}')
# 显示文本首 13 个字符的整数映射
print('{} ---- characters mapped to int ---- > {}'.format(repr(text[:13]), text_as_int[:13]))

2.2 预测任务

给定一个字符a或者字符串ab,预测下一个字符是什么?

2.3 创建训练样本和目标

使用 tf.data.Dataset.from_tensor_slices 函数把文本向量转换为字符索引流。

# 设定每个输入句子长度的最大值
seq_length = 100
examples_per_epoch = len(text) // seq_length  # 向下取整

# 创建训练样本 / 目标
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)

for i in char_dataset.take(5):
    print(idx2char[i.numpy()])

batch 方法使我们能轻松把单个字符转换为所需长度的序列, 截取 seq_length + 1 为一个batch的原因是,之后需要做进一步的数据处理。

sequences = char_dataset.batch(seq_length + 1, drop_remainder=True)

for item in sequences.take(5):
    print(repr(''.join(idx2char[item.numpy()])))

输入 abcde -> 输出 abcd,bcde 两组数据 , map 方法可以将一个简单的函数应用到每一个批次 (batch)。

def split_input_target(chunk):
    input_text = chunk[:-1]
    target_text = chunk[1:]
    return input_text, target_text


dataset = sequences.map(split_input_target)

打印第一批样本的输入与目标值:

for input_example, target_example in dataset.take(1):
    print('Input data: ', repr(''.join(idx2char[input_example.numpy()])))
    print('Target data:', repr(''.join(idx2char[target_example.numpy()])))

观察一下输出:

Input data:  'First Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou'
Target data: 'irst Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou '

字符数据:
'First Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou '

通过split_input_target函数进行处理后生成了以上Input data和Target data

这些向量的每个索引均作为一个时间步来处理。作为时间步 0 的输入,模型接收到 “F” 的索引,并尝试预测 “i” 的索引为下一个字符。在下一个时间步,模型执行相同的操作,但是 RNN 不仅考虑当前的输入字符,还会考虑上一步的信息。

for i, (input_idx, target_idx) in enumerate(zip(input_example[:5], target_example[:5])):
    print("Step {:4d}".format(i))
    print("  input: {} ({:s})".format(input_idx, repr(idx2char[input_idx])))
    print("  expected output: {} ({:s})".format(target_idx, repr(idx2char[target_idx])))

2.4 创建训练批次

将数据重新排列 (shuffle) 并打包为批次

# 批大小
BATCH_SIZE = 64

# 设定缓冲区大小,以重新排列数据集
# (TF 数据被设计为可以处理可能是无限的序列,
# 所以它不会试图在内存中重新排列整个序列。相反,
# 它维持一个缓冲区,在缓冲区重新排列元素。) 
BUFFER_SIZE = 10000

dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)

dataset最后的shape:
<BatchDataset shapes: ((64, 100), (64, 100)), types: (tf.int64, tf.int64)>

Card image cap
开发者雷

尘世间一个小小的开发者,每天增加一些无聊的知识,就不会无聊了

要加油~~~

技术文档 >> 系列应用 >>
热推应用
Let'sLearnSwift
学习Swift的入门教程
PyPie
Python is as good as Pie
标签