本节将介绍如何预处理一个语言模型数据集,并将其转换成字符级循环神经网络所需要的输入格式。
为此,收集了周杰伦从第一张专辑《Jay》到第十张专辑《跨时代》的歌词,并将在后几节应用循环神经网络来训练一个语言模型,并利用该模型来创作歌词。
import random
import numpy as np
import tensorflow as tf
print(tf.__version__)
2.0.0
(1) 读取数据集,查看前40个字符。
with open('jaychou_lyrics.txt') as f:
corpus_chars = f.read()
corpus_chars[:40]
输出:
'想要有直升机\n想要和你飞到宇宙去\n想要和你融化在一起\n融化在宇宙里\n我每天每天每'
(2) 查看数据集总字符数。
len(corpus_chars)
输出:
63282
(3) 将换行符替换为空格,仅使用前1万个字符来训练模型。
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[:10000]
(1) 构造词典
将每个字符映射成一个从0开始的连续整数,又称索引,以方便后续的数据处理。
即,将数据集中所有不同的字符,逐一映射到索引来构造词典。
之后,打印vocab_size
,即词典中不同字符的个数,又称词典大小。
idx_to_char = list(set(corpus_chars))
char_to_idx = {v:k for k,v in enumerate(unique_char)}
vocab_size = len(char_to_idx)
vocab_size
输出:
1027
(2) 构造索引
将训练数据集中每个字符依次转化为索引,并打印前20个字符及其对应的索引。
corpus_indices = [char_to_idx[char] for char in corpus_chars]
sample = corpus_indices[:20]
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
print('indices:', sample)
输出:
chars: 想要有直升机 想要和你飞到宇宙去 想要和
indices: [944, 233, 900, 908, 47, 723, 301, 944, 233, 1022, 366, 206, 961, 619, 304, 124, 301, 944, 233, 1022]
与先前其他章节的实验数据不同,时序数据的一个样本通常包含连续的字符。
假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。
该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”。
在训练中,需要每次随机读取小批量样本和标签。
对时序数据进行采样,存在两种方式,分别为:随机采样和相邻采样。
每次从数据随机采样一个小批量,代码示例如下:
def data_iter_random(corpus_indices, num_steps, batch_size):
# 采样的单个样本,最后一个单词不为序列的最后一个字(否则,没有输入子序列所对应的输出子序列),因此减1。
# 下取整,得到不重叠情况下的样本个数。
num_examples = (len(corpus_indices)-1) // num_steps
epoch_size = num_examples // batch_size
example_indices = list(range(num_examples))
# e.g., x from [0, 1, 2, 3, 4] to [0, 4, 3, 2, 1]
random.shuffle(example_indices)
# 返回从pos开始的长为num_steps的序列
def _data(pos):
return corpus_indices[pos:pos+num_steps]
for i in range(epoch_size):
i *= batch_size
batch_indices = example_indices[i:i+batch_size]
X = [_data(j*num_steps) for j in batch_indices]
# 该序列的下一个词
Y = [_data(j*num_steps+1) for j in batch_indices]
yield np.array(X), np.array(Y)
其中,num_steps
为每个样本所包含的时间步数,批量大小batch_size
指每个小批量的样本数。
在随机采样中,每个样本是原始序列上任意截取的一段序列。相邻的两个随机小批量在原始序列上的位置不一定相毗邻。
因此,无法用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态。在训练模型时,每次随机采样前都需要重新初始化隐藏状态。
输入一个从0到29的连续整数的人工序列,设批量大小和时间步数分别为2和6,打印随机采样每次读取的小批量样本的输入X和标签Y,示例如下:
my_seq = list(range(30))
for X, Y in data_iter_random(my_seq, num_steps=6, batch_size=2):
print('X: ', X, '\nY:', Y, '\n')
输出:
X: [[18 19 20 21 22 23]
[ 0 1 2 3 4 5]]
Y: [[19 20 21 22 23 24]
[ 1 2 3 4 5 6]]
X: [[ 6 7 8 9 10 11]
[12 13 14 15 16 17]]
Y: [[ 7 8 9 10 11 12]
[13 14 15 16 17 18]]
可见,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。
除了对原始序列做随机采样之外,还可以令相邻的两个随机小批量在原始序列上的位置相毗邻。
代码示例如下:
def data_iter_consecutive(corpus_indices, num_steps, batch_size):
corpus_indices = np.array(corpus_indices)
data_len = len(corpus_indices)
batch_len = data_len // batch_size
indices = corpus_indices[:batch_size*batch_len].reshape((batch_size, batch_len))
epoch_size = (batch_len-1) // num_steps
for i in range(epoch_size):
i *= num_steps
X = indices[:, i:i+num_steps]
Y = indices[:, i+1:i+num_steps+1]
yield X, Y
此时,可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态,从而使下一个小批量的输出也取决于当前小批量的输入,并如此循环。
这对实现循环神经网络造成了两方面影响:
一方面, 在训练模型时,仅需在每一个迭代周期开始时初始化隐藏状态;
另一方面,当多个相邻小批量通过传递隐藏状态串联时,模型参数的梯度计算将依赖所有串联的小批量序列。
相同设置下,打印相邻采样每次读取的小批量样本的输入X和标签Y(相邻的两个随机小批量在原始序列上的位置相毗邻),示例如下:
for X, Y in data_iter_consecutive(my_seq, num_steps=6, batch_size=2):
print('X: ', X, '\nY:', Y, '\n')
输出:
X: [[ 0 1 2 3 4 5]
[15 16 17 18 19 20]]
Y: [[ 1 2 3 4 5 6]
[16 17 18 19 20 21]]
X: [[ 6 7 8 9 10 11]
[21 22 23 24 25 26]]
Y: [[ 7 8 9 10 11 12]
[22 23 24 25 26 27]]
《动手学深度学习》(TF2.0版)