语言模型数据集

读取数据集

1
2
3
4
5
6
7
8
from mxnet import nd
import random
import zipfile

with zipfile.ZipFile('../data/jaychou_lyrics.txt.zip') as zin:
with zin.open('jaychou_lyrics.txt') as f:
corpus_chars = f.read().decode('utf-8')
corpus_chars[:40]
'想要有直升机\n想要和你飞到宇宙去\n想要和你融化在一起\n融化在宇宙里\n我每天每天每'
1
2
3
corpus_chars = corpus_chars.replace('\n', ' ').replace('\t', ' ')
corpus_chars = corpus_chars[0:10000]
corpus_chars[0:49]
'想要有直升机 想要和你飞到宇宙去 想要和你融化在一起 融化在宇宙里 我每天每天每天在想想想想著你 '
1
2
3
4
5
6
idx_to_char = list(set(corpus_chars)) #去重复,求得不重复的字符
char_to_idx = dict([(char, i)for i, char in enumerate(idx_to_char)])# 将字符与索引一一映射构造字典
vocab_size = len(char_to_idx)
print(vocab_size)
print(char_to_idx['有'])
#print(idx_to_char)
1027
596
1
2
3
4
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: [63, 155, 596, 58, 143, 855, 670, 63, 155, 607, 151, 597, 227, 181, 161, 459, 670, 63, 155, 607]

时序数据的采样

在训练过程中,我们每次需要随机读取小批量的样本x和标签值y,时序数据的一个样本通常包含多个连续的字符。以上文数据“想要有直升机 想要和你飞到宇宙去 想要和”为例,假设时间步数为5,样本序列中就会有五个字符——“想要有直升”,而标签序列的字符为样本字符在训练集中的下一个字符——“要有直升机”。

对于时序数据采样,我们有两种方法,随机采样和相邻采样

随机采样

batch_size为每个小批量的样本数,num_steps为单个样本的时间步数,随机采样中,每个样本为原始序列中任意截取长度为num_steps的一段序列,相邻的两个小批量在原始序列的位置并不一定相邻。因此,我们无法用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态。在训练模型时,每次随机采样前都需要重新初始化隐藏状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#随机采样
def data_iter_random(corpus_indices, batch_size, num_steps, ctx=None):
# 减1是因为输出的索引是相应输入的索引加1
num_examples = (len(corpus_indices) - 1) // num_steps #下取整,得到的是总样本个数
epoch_size = num_examples // batch_size #总共有多少个小批量样本,每个小批量样本个数为batch_size

example_indices = list(range(num_examples))#打乱顺序
random.shuffle(example_indices)

# 返回从pos开始的长为num_steps的序列
def _data(pos):
return corpus_indices[pos: pos + num_steps]

for i in range(epoch_size):
# 每次读取batch_size个随机样本
i = 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 nd.array(X, ctx), nd.array(Y, ctx)
1
2
3
my_seq = list(range(40))
for X, Y in data_iter_random(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
X:  
[[ 6.  7.  8.  9. 10. 11.]
 [18. 19. 20. 21. 22. 23.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[ 7.  8.  9. 10. 11. 12.]
 [19. 20. 21. 22. 23. 24.]]
<NDArray 2x6 @cpu(0)> 

X:  
[[24. 25. 26. 27. 28. 29.]
 [ 0.  1.  2.  3.  4.  5.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[25. 26. 27. 28. 29. 30.]
 [ 1.  2.  3.  4.  5.  6.]]
<NDArray 2x6 @cpu(0)> 

X:  
[[12. 13. 14. 15. 16. 17.]
 [30. 31. 32. 33. 34. 35.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[13. 14. 15. 16. 17. 18.]
 [31. 32. 33. 34. 35. 36.]]
<NDArray 2x6 @cpu(0)> 

以这个输入的示例为例,针对单个X,每个X包含batch_size=2个批量的样本,每个样本包含num_steps=6个字符,而Y的每个样本为X每个样本的后一个字符;
然后我们比较相邻的两个X,会发现第一个X的首字符为6,而第二个X的首字符为24,两者不相邻。
同理我们可以比较相邻采样,这样就会更加容易理解。

相邻采样

除对原始序列做随机采样之外,我们还可以令相邻的两个随机小批量在原始序列上的位置相毗邻。这时候,我们就可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态,从而使下一个小批量的输出也取决于当前小批量的输入,并如此循环下去。这对实现循环神经网络造成了两方面影响:一方面, 在训练模型时,我们只需在每一个迭代周期开始时初始化隐藏状态;另一方面,当多个相邻小批量通过传递隐藏状态串联起来时,模型参数的梯度计算将依赖所有串联起来的小批量序列。同一迭代周期中,随着迭代次数的增加,梯度的计算开销会越来越大。 为了使模型参数的梯度计算只依赖一次迭代读取的小批量序列,我们可以在每次读取小批量前将隐藏状态从计算图中分离出来。

1
2
3
4
5
6
7
8
9
10
11
12

def data_iter_consecutive(corpus_indices, batch_size, num_steps, ctx=None):
corpus_indices = nd.array(corpus_indices, ctx=ctx)
data_len = len(corpus_indices) #字符长度
batch_len = data_len // batch_size#可以有多少个小批量
indices = corpus_indices[0: batch_size*batch_len].reshape((batch_size, batch_len))#转换维度batch_size行,batch_len列
epoch_size = (batch_len - 1) // num_steps
for i in range(epoch_size):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
1
2
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
X:  
[[ 0.  1.  2.  3.  4.  5.]
 [20. 21. 22. 23. 24. 25.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[ 1.  2.  3.  4.  5.  6.]
 [21. 22. 23. 24. 25. 26.]]
<NDArray 2x6 @cpu(0)> 

X:  
[[ 6.  7.  8.  9. 10. 11.]
 [26. 27. 28. 29. 30. 31.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[ 7.  8.  9. 10. 11. 12.]
 [27. 28. 29. 30. 31. 32.]]
<NDArray 2x6 @cpu(0)> 

X:  
[[12. 13. 14. 15. 16. 17.]
 [32. 33. 34. 35. 36. 37.]]
<NDArray 2x6 @cpu(0)> 
Y: 
[[13. 14. 15. 16. 17. 18.]
 [33. 34. 35. 36. 37. 38.]]
<NDArray 2x6 @cpu(0)>