(一)读取数据集
预备知识:
zipfile是python中用来做zip格式编码的压缩和解压缩的。zipfile中有两个重要的class,分别是ZipFile和ZipInfo。ZipFile是用来创建和读取zip文件,而ZipInfo是存储的zip文件的每个文件的信息。
class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)
当mode为‘r’时,是指读取一个已经存在的文件,当mode为‘w’时,是指截断并重新写入一个新的文件,
当mode为‘a’时是在一个已存的文件后面进行追加,当mode为‘x’时,是额外的创建并写入新文件。
ZipFile.open(name, mode='r', pwd=None)
将存档中的成员提取为类似文件的对象(zipextfile)。name是存档文件或zipinfo对象的名称。
open()也是一个上下文管理器,因此支持with语句,open()返回的对象可以独立于zipfile操作。
代码实现:
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]
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
# \r换到当前行的最左边,\n向下移动一行,并不移动左右位置
corpus_chars = corpus_chars[0:10000] # 取前一万字符来训练模型
(二)建立字符索引
idx_to_char = list(set(corpus_chars))
# list:列表,表达形式 [ ],或者list(),有序,通过索引值进行查找
# set:集合,表达形式set([ ]),无序自动去重
# 得到数据集中所有不同字符的有序排列
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
# dict全称dictionary,使用键-值(key-value)存储,具有极快的查找速度。
# 定义形式:{‘Michael’: 95, ‘Bob’: 75, ‘Tracy’: 85}
# 以字符为key,以排列的序号作为value来一一对应形成字典
# dict与List的区别:
# 1. dict的查找速度很快,而且不论它有多少元素,查找速度都一样;List查找速度相对慢,而且查找速度随着元素个数字增加而增加。
# 2. dict的查找快是以占用内存大为代价的,List占用内存相对小。
# 3. dict中的元素是没有顺序的,也就是说内存空间不连续
# 4. List不可作为key
vocab_size = len(char_to_idx)
# 求解字典大小vacab_size
vocab_size
corpus_chars是歌词数据集
idx_to_char是字符的去重有序排列
char_to_idx是字符和索引映射得到的字典
vocab_size是字典长度
将训练数据集中每个字符转化为索引,并打印前20个字符及其对应的索引。
join()函数 用特定的字符或者符号来分隔一串元素
语法: ' A ' . join ( B )
参数说明
A:分隔符。可以为空或者空格甚至是数字
B:要连接的元素序列、字符串、元组、字典
上面的语法即:以 A 作为分隔符,将 B 所有的元素合并成一个新的字符串
返回值:返回一个以分隔符 A 连接各个元素后生成的字符串
corpus_indices = [char_to_idx[char] for char in corpus_chars]
# d[key] Return the item of d with key key. Raises a KeyError if key is not in the map.
# 得到所有数据集中的索引
sample = corpus_indices[:20]
# 选择前20个索引
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
# 由得到的20个索引找出对于的字符,并连接起来后打印
print('indices:', sample)
# 打印索引
(三)时序数据的采样
(1)随机采样
在随机采样中,每个样本是原始序列上任意截取的一段序列。相邻的两个随机小批量在原始序列上的位置不一定相毗邻。因此,我们无法用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态。在训练模型时,每次随机采样前都需要重新初始化隐藏状态。
# 本函数已保存在d2lzh包中方便以后使用
def data_iter_random(corpus_indices, batch_size, num_steps, ctx=None):
# 减1是因为输出的长度是相应输入的索引加1,-1 就是为了避免溢出的情况。
num_examples = (len(corpus_indices) - 1) // num_steps # 得到样本个数
epoch_size = num_examples // 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 # 第(i+1)个批量
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)
my_seq = list(range(30))
for X, Y in data_iter_random(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
分析运算过程:
选择索引在0–29的字符作为样本数据集,由于每个样本内有6个时间步长,所以样本可以有29//6=4个,每个批量大小为2,所以一共有2个批量,样本索引为(0,1,2,3),然后把顺序打乱。
在第一个批量中,批量包含的样本索引为example_indices,由于样本索引顺序已经随机打乱,所以每个批量得到的样本不一定是毗邻的,得到样本索引后,利用函数_data()得到样本中的数据。此外,两个批量的数据也不一定是毗邻的:
(2)相邻采样
相邻的两个随机小批量在原始序列上的位置相毗邻。因此,我们就可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态,从而使下一个小批量的输出也取决于当前小批量的输入,并如此循环下去。这对实现循环神经网络造成了两方面影响:一方面, 在训练模型时,我们只需在每一个迭代周期开始时初始化隐藏状态;另一方面,当多个相邻小批量通过传递隐藏状态串联起来时,模型参数的梯度计算将依赖所有串联起来的小批量序列。同一迭代周期中,随着迭代次数的增加,梯度的计算开销会越来越大。
# 本函数已保存在d2lzh包中方便以后使用
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)) # 把索引变成第一维为每批量的样本个数,第二维为每批量长度的数组
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
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
可以看到两个相邻的小批量的数据也是批量的。