语言模型 language model

时序数据的采样

在训练中我们需要每次随机读取小批量样本和标签。与之前章节的实验数据不同的是,时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即X=“想要有直升”,Y=“要有直升机”。

现在我们考虑序列“想要有直升机,想要和你飞到宇宙去”,如果时间步数为5,有以下可能的样本和标签:

  • X:“想要有直升”,Y:“要有直升机”
  • X:“要有直升机”,Y:“有直升机,”
  • X:“有直升机,”,Y:“直升机,想”
  • ...
  • X:“要和你飞到”,Y:“和你飞到宇”
  • X:“和你飞到宇”,Y:“你飞到宇宙”
  • X:“你飞到宇宙”,Y:“飞到宇宙去”

可以看到,如果序列的长度为T,时间步数为n,那 么一共有T−n个合法的样本,但是这些样本有大量的重合,我们通常采用更加高效的采样方式。我们有两种方式对时序数据进行采样,分别是随机采样和相邻采样。

随机采样

每次从数据里随机采样一个小批量。其中批量大小batch_size是每个小批量的样本数,num_steps是每个样本所包含的时间步数。 在随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。

随机采样中每个样本只包含局部的时间序列信息,因为样本不完整,所以每个批量需要重新初始化隐藏状态。

import torch
import random

def data_iter_random(corpus_indices, batch_size, num_steps, device=None):
    # 减1是因为对于长度为n的序列,X最多只有包含其中的前n - 1个字符
    num_examples = (len(corpus_indices) - 1) // num_steps  # 下取整,得到不重叠情况下的样本个数
    example_indices = [i * num_steps for i in range(num_examples)]  # 每个样本的第一个字符在corpus_indices中的下标
    random.shuffle(example_indices)

    def _data(i):
        # 返回从i开始的长为num_steps的序列
        return corpus_indices[i: i + num_steps]
    if device is None:
        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    for i in range(0, num_examples, batch_size):
        # 每次选出batch_size个随机样本
        batch_indices = example_indices[i: i + batch_size]  # 当前batch的各个样本的首字符的下标
        X = [_data(j) for j in batch_indices]
        Y = [_data(j + 1) for j in batch_indices]
        yield torch.tensor(X, device=device), torch.tensor(Y, device=device)

 

相邻采样

在相邻采样中,相邻的两个随机小批量在原始序列上的位置相毗邻。这时候,我们就可以⽤⼀个⼩批量最终时间步的隐藏状态初始化下⼀个⼩批量的隐藏状态,从⽽使下⼀个⼩批量的输出也取决于当前⼩批量的输⼊,并如此循环下去。

这对实现循环神经⽹络造成了两⽅⾯影响:⼀⽅⾯, 在训练模型时,我们只需在每⼀个迭代周期开始时初始化隐藏状态;另⼀⽅⾯,当多个相邻⼩批量通过传递隐藏状态串联起来时,模型参数的梯度计算将依赖所有串联起来的⼩批量序列
同⼀迭代周期中,随着迭代次数的增加,梯度的计算开销会越来越⼤。 为了使模型参数的梯度计算只依赖⼀次迭代读取的⼩批量序列,我们可以在每次读取⼩批量前将隐藏状态从计算图中分离出来。
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
    if device is None:
        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    corpus_len = len(corpus_indices) // batch_size * batch_size  # 保留下来的序列的长度
    corpus_indices = corpus_indices[: corpus_len]  # 仅保留前corpus_len个字符
    indices = torch.tensor(corpus_indices, device=device)
    indices = indices.view(batch_size, -1)  # resize成(batch_size, )
    batch_num = (indices.shape[1] - 1) // num_steps
    for i in range(batch_num):
        i = i * num_steps
        X = indices[:, i: i + num_steps]
        Y = indices[:, i + 1: i + num_steps + 1]
        yield X, Y

 

你可能感兴趣的:(总结)