在构建循环神经网络时,首先需要处理数据集。接下来需要读取数据,对其进行合理采样,即将数据集划分为若干子序列。采样后的数据进行one-hot编码后,即可输入循环神经网络进行学习。
数据集的采样主要分为两种方法,随机采样和顺序分区,接下来依次进行介绍。(本文基于mxnet框架构建,使用Time Machine数据集)
随机采样
随机采样只有每个批量内部一个时间步长度的值是顺序分布的。为更好的了解采样的代码,我们首先解释用到的各种参数。
参数说明
- num_steps:时间步,即生成的每个序列连续多少个值。
- batch_size:批量大小,即生成多少批数据
- corpus:全体语料
- num_subseqs:生成子列数量(特征-标签对)
函数说明
- random.shuffle(),标准类库方法,用于将内容打乱。
def seq_data_iter_random(corpus, batch_size, num_steps): #@save
"""使用随机抽样生成一个小批量子序列"""
# 从随机偏移量开始对序列进行分区,随机范围包括num_steps-1
corpus = corpus[random.randint(0, num_steps - 1):]
# 减去1,是因为我们需要考虑标签
num_subseqs = (len(corpus) - 1) // num_steps
# 长度为num_steps的子序列的起始索引
initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
# 在随机抽样的迭代过程中,
# 来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻
random.shuffle(initial_indices)
def data(pos):
# 返回从pos位置开始的长度为num_steps的序列
return corpus[pos: pos + num_steps]
num_batches = num_subseqs // batch_size
for i in range(0, batch_size * num_batches, batch_size):
# 在这里,initial_indices包含子序列的随机起始索引
initial_indices_per_batch = initial_indices[i: i + batch_size]
X = [data(j) for j in initial_indices_per_batch]
Y = [data(j + 1) for j in initial_indices_per_batch]
yield np.array(X), np.array(Y)
下面我们生成一个从0到34的序列。 假设批量大小为2,时间步数为5,这意味着可以生成 ⌊(35−1)/5⌋=6个“特征-标签”子序列对。 如果设置小批量大小为2,我们只能得到3个小批量。
my_seq = list(range(35))
for X, Y in seq_data_iter_random(my_seq, batch_size=2, num_steps=5):
print('X: ', X, '\nY:', Y)
运行结果如下:共生成三个小批量,每个批量有两批数据(X的第一维度大小为2)
X: [[22. 23. 24. 25. 26.] [27. 28. 29. 30. 31.]] Y: [[23. 24. 25. 26. 27.] [28. 29. 30. 31. 32.]] X: [[ 7. 8. 9. 10. 11.] [12. 13. 14. 15. 16.]] Y: [[ 8. 9. 10. 11. 12.] [13. 14. 15. 16. 17.]] X: [[17. 18. 19. 20. 21.] [ 2. 3. 4. 5. 6.]] Y: [[18. 19. 20. 21. 22.] [ 3. 4. 5. 6. 7.]] [07:06:42] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU
顺序分区
顺序分区保证了同一批量的数据是连续的,而不同批的数据之间是无序的。
顺序分区将数据集简单的分割为一个shape(0)=batch_size的多维数组。
def seq_data_iter_sequential(corpus, batch_size, num_steps): #@save
"""使用顺序分区生成一个小批量子序列"""
# 从随机偏移量开始划分序列
offset = random.randint(0, num_steps)
num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
Xs = np.array(corpus[offset: offset + num_tokens])
Ys = np.array(corpus[offset + 1: offset + 1 + num_tokens])
Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
num_batches = Xs.shape[1] // num_steps
for i in range(0, num_steps * num_batches, num_steps):
X = Xs[:, i: i + num_steps]
Y = Ys[:, i: i + num_steps]
yield X, Y
测试输出结果:
for X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5):
print('X: ', X, '\nY:', Y)
输出结果如下:
X: [[ 0. 1. 2. 3. 4.] [17. 18. 19. 20. 21.]] Y: [[ 1. 2. 3. 4. 5.] [18. 19. 20. 21. 22.]] X: [[ 5. 6. 7. 8. 9.] [22. 23. 24. 25. 26.]] Y: [[ 6. 7. 8. 9. 10.] [23. 24. 25. 26. 27.]] X: [[10. 11. 12. 13. 14.] [27. 28. 29. 30. 31.]] Y: [[11. 12. 13. 14. 15.] [28. 29. 30. 31. 32.]]
包装使用
将两种采样包装为统一的类,使用load_data_time_machine()函数来直接对数据集进行采样。
class SeqDataLoader: #@save
"""加载序列数据的迭代器"""
def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):
if use_random_iter:
self.data_iter_fn = d2l.seq_data_iter_random
else:
self.data_iter_fn = d2l.seq_data_iter_sequential
self.corpus, self.vocab = d2l.load_corpus_time_machine(max_tokens)
self.batch_size, self.num_steps = batch_size, num_steps
def __iter__(self):
return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)
默认情况下,直接使用顺序分区的方法来划分数据集。
def load_data_time_machine(batch_size, num_steps, #@save
use_random_iter=False, max_tokens=10000):
"""返回时光机器数据集的迭代器和词表"""
data_iter = SeqDataLoader(
batch_size, num_steps, use_random_iter, max_tokens)
return data_iter, data_iter.vocab
引入本文内容,即可直接使用最后的函数来进行数据集的采样工作,并将输出作为循环神经网络的输入。