首页 > 其他分享 >《动手学深度学习 Pytorch版》 8.3 语言模型和数据集

《动手学深度学习 Pytorch版》 8.3 语言模型和数据集

时间:2023-10-09 21:44:06浏览次数:42  
标签:8.3 batch 单词 动手 Pytorch num steps 序列 corpus

8.3.1 学习语言模型

依靠在 8.1 节中对序列模型的分析,可以在单词级别对文本数据进行词元化。基本概率规则如下:

\[P(x_1,x_2,\dots,x_T)=\prod^T_{t=1}P(x_t|x_1,\dots,x_{t-1}) \]

例如,包含了四个单词的一个文本序列的概率是:

\[P(deep,learning,is,fun)=P(deep)P(learning|deep)P(is|deep,learning)P(fun|deep,learning,is) \]

语言模型就是要计算单词的概率,以及给定前面几个单词后出现某个单词的条件概率。这些概率本质上就是语言模型的参数。

假设训练数据集是一个大型的文本语料库。训练数据集中词的概率可以根据给定词的相对词频来计算。对于频繁出现的单词可以统计单词“deep”在数据集中的出现次数,然后将其除以整个语料库中的单词总数。接下来尝试估计

\[\hat{P}(learning|deep)=\frac{n(deep,learning)}{n(deep)} \]

其中 \(n(x)\) 和 \(n(x,x')\) 分别是单个单词和连续单词对的出现次数。

对于一些不常见的单词组合,要想找到足够的出现次数来获得准确的估计可能都不容易。如果数据集很小,或者单词非常罕见,那么这类单词出现一次的机会可能都找不到。这里一种常见的策略是执行某种形式的拉普拉斯平滑(Laplace smoothing),具体方法是在所有计数中添加一个小常量。用 \(n\) 表示训练集中的单词总数,用 \(m\) 表示唯一单词的数量。例如通过:

\[\begin{align} \hat{P}(x)&=\frac{n(x)+\epsilon_1/m}{n+\epsilon_1}\\ \hat{P}(x'|x)&=\frac{n(x,x')+\epsilon_2\hat{P}(x')}{n(x)+\epsilon_2}\\ \hat{P}(x"|x,x')&=\frac{n(x,x',x")+\epsilon_3\hat{P}(x")}{n(x,x')+\epsilon_3} \end{align} \]

其中 \(\epsilon_1\),\(\epsilon_2\) 和 \(\epsilon_3\) 是超参数。例如当 \(\epsilon_1=0\) 时,不应用平滑;当 \(\epsilon_1\) 接近无穷大时,\(\hat{P}(x)\) 基金均匀概率分布 \(1/m\)。

上述方案也存在问题,模型很容易变得无效,原因如下:

  • 需要存储所有的计数;

  • 完全忽略了单词的意思。例如,“猫”(cat)和“猫科动物”(feline)可能出现在相关的上下文中,但是想根据上下文调整这类模型其实是相当困难的。

  • 长单词序列大部分是没出现过的,因此一个模型如果只是简单地统计先前“看到”的单词序列频率,那么模型面对这种问题肯定是表现不佳的。

8.3.2 马尔可夫模型与 n 元语法

如果 \(P(x_{t+1}|x_t,\dots,x_1)=P(x_{t+1}|x_t)\),则序列上的分布满足一阶马尔可夫性质。阶数越高则对应的依赖关系就越长。这种性质可以推导出许多可以应用于序列建模的近似公式:

\[\begin{align} P(x_1,x_2,x_3,x_4)&=P(x_1)P(x_2)P(x_3)P(x_4)\\ P(x_1,x_2,x_3,x_4)&=P(x_1)P(x_2|x_1)P(x_3|x_2)P(x_4|x_3)\\ P(x_1,x_2,x_3,x_4)&=P(x_1)P(x_2|x_1)P(x_3|x_1,x_2)P(x_4|x_2,x_3) \end{align} \]

通常,涉及一个、两个和三个变量的概率公式分别被称为一元语法(unigram)、二元语法(bigram)和三元语法(trigram)模型。

以下将对模型进行更好的设计。

8.3.3 自然语言统计

import random
import torch
from d2l import torch as d2l
tokens = d2l.tokenize(d2l.read_time_machine())
corpus = [token for line in tokens for token in line]  # 将文本行拼接到一起
vocab = d2l.Vocab(corpus)
vocab.token_freqs[:10]  # 打印前10个频率最高的单词
[('the', 2261),
 ('i', 1267),
 ('and', 1245),
 ('of', 1155),
 ('a', 816),
 ('to', 695),
 ('was', 552),
 ('in', 541),
 ('that', 443),
 ('my', 440)]
freqs = [freq for token, freq in vocab.token_freqs]
d2l.plot(freqs, xlabel='token: x', ylabel='frequency: n(x)',
         xscale='log', yscale='log')

image

频率最高的词都是停用词(stop words),可以被过滤掉。但它们本身仍然是有意义的,我们仍然会在模型中使用它们。

此外,还有个明显的问题是词频衰减的速度相当地快。从词频图看到,词频衰减大致遵循双对数坐标图上的一条直线。这意味着单词的频率满足齐普夫定律(Zipf’s law),即第 \(i\) 个最常用单词的频率 \(n_i\) 为:

\[n_i\propto\frac{1}{i^\alpha} \]

可以等价为

\[\log{n_i}=-\alpha\log{i}+c \]

其中 \(\alpha\) 是刻画分布的指数,\(c\) 是常数。

所以,上面通过计数统计和平滑来建模单词是不可行的,因为这样建模的结果会大大高估尾部(也就是所谓的不常用单词)的频率。

下面尝试一下二元语法的频率是否与一元语法的频率表现出相同的行为方式。

bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]  # 优雅 实在优雅
bigram_vocab = d2l.Vocab(bigram_tokens)
bigram_vocab.token_freqs[:10]
[(('of', 'the'), 309),
 (('in', 'the'), 169),
 (('i', 'had'), 130),
 (('i', 'was'), 112),
 (('and', 'the'), 109),
 (('the', 'time'), 102),
 (('it', 'was'), 99),
 (('to', 'the'), 85),
 (('as', 'i'), 78),
 (('of', 'a'), 73)]

可以看到二元语法大部分也是两个停用词组成的。下面的三元语法就好些。

trigram_tokens = [triple for triple in zip(
    corpus[:-2], corpus[1:-1], corpus[2:])]
trigram_vocab = d2l.Vocab(trigram_tokens)
trigram_vocab.token_freqs[:10]
[(('the', 'time', 'traveller'), 59),
 (('the', 'time', 'machine'), 30),
 (('the', 'medical', 'man'), 24),
 (('it', 'seemed', 'to'), 16),
 (('it', 'was', 'a'), 15),
 (('here', 'and', 'there'), 15),
 (('seemed', 'to', 'me'), 14),
 (('i', 'did', 'not'), 14),
 (('i', 'saw', 'the'), 13),
 (('i', 'began', 'to'), 13)]
bigram_freqs = [freq for token, freq in bigram_vocab.token_freqs]
trigram_freqs = [freq for token, freq in trigram_vocab.token_freqs]
d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x',
         ylabel='frequency: n(x)', xscale='log', yscale='log',
         legend=['unigram', 'bigram', 'trigram'])

image

从这张一元语法、二元语法和三元语法的直观对比图可以看到:

  • 除了一元语法词,单词序列似乎也遵循齐普夫定律,指数的大小受序列长度的影响。

  • 词表中 n 元组的数量并没有那么大,这说明语言中存在相当多的结构,这些结构给了我们应用模型的希望;

  • 很多 n 元组很少出现,这使得拉普拉斯平滑非常不适合语言建模。作为代替,我们将使用基于深度学习的模型。

8.3.4 读取长序列数据

长序列不能被模型一次性全部处理时,依然采用第一节的拆分序列方法。不同的是,步长不选择固定的而是从随机偏移量开始划分序列,以同时获得覆盖性(coverage)和随机性(randomness)。

随机采样

在随机采样中,每个样本都是在原始的长序列上任意捕获的子序列。 在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。

def seq_data_iter_random(corpus, batch_size, num_steps):  #@save
    """使用随机抽样生成一个小批量子序列"""
    corpus = corpus[random.randint(0, num_steps - 1):]  # 从头随机截一下,保证第一个序列的随机性
    num_subseqs = (len(corpus) - 1) // 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 torch.tensor(X), torch.tensor(Y)
my_seq = list(range(35))  # 生成一个从0到34的序列
for X, Y in seq_data_iter_random(my_seq, batch_size=2, num_steps=5):
    print('X: ', X, '\nY:', Y)
X:  tensor([[18, 19, 20, 21, 22],
        [13, 14, 15, 16, 17]]) 
Y: tensor([[19, 20, 21, 22, 23],
        [14, 15, 16, 17, 18]])
X:  tensor([[ 8,  9, 10, 11, 12],
        [ 3,  4,  5,  6,  7]]) 
Y: tensor([[ 9, 10, 11, 12, 13],
        [ 4,  5,  6,  7,  8]])
X:  tensor([[23, 24, 25, 26, 27],
        [28, 29, 30, 31, 32]]) 
Y: tensor([[24, 25, 26, 27, 28],
        [29, 30, 31, 32, 33]])

顺序分区

在小批量的迭代过程中保留了拆分的子序列的顺序,可以保证两个相邻的小批量中的子序列在原始序列上也是相邻的。

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 = torch.tensor(corpus[offset: offset + num_tokens])  # 获取词元起始下标
    Ys = torch.tensor(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:  tensor([[ 5,  6,  7,  8,  9],
        [19, 20, 21, 22, 23]]) 
Y: tensor([[ 6,  7,  8,  9, 10],
        [20, 21, 22, 23, 24]])
X:  tensor([[10, 11, 12, 13, 14],
        [24, 25, 26, 27, 28]]) 
Y: tensor([[11, 12, 13, 14, 15],
        [25, 26, 27, 28, 29]])

将上述两个采样函数包装到一个类中,再定义一个返回数据迭代器和词表的 load 函数。

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

练习

(1)假设训练数据集中有 10 万个单词。一个四元语法需要存储多少个词频和相邻多词频率?

这应该不好说吧。


(2)我们如何对一系列对话建模?

不会,略。


(3)一元语法、二元语法和三元语法的齐普夫定律的指数是不一样的,能设法估计么?

不会,略。


(4)想一想读取长序列数据的其他方法?

固定最大长度,截取多余的部分


(5)考虑一下我们用于读取长序列的随机偏移量。

a. 为什么随机偏移量是个好主意?

b. 它真的会在文档的序列上实现完美的均匀分布吗?

c. 要怎么做才能使分布更均匀?

总比从头到尾顺着读好。


(6)如果我们希望一个序列样本是一个完整的句子,那么这在小批量抽样中会带来怎样的问题?如何解决?

不会,略。

标签:8.3,batch,单词,动手,Pytorch,num,steps,序列,corpus
From: https://www.cnblogs.com/AncilunKiang/p/17753242.html

相关文章

  • pytorch(8-2) 文本语言处理 拆分成字符统计词频并从高到底分配ID
    https://zh.d2l.ai/chapter_recurrent-neural-networks/language-models-and-dataset.html  importcollectionsimportrefromd2limporttorchasd2l#@saved2l.DATA_HUB['time_machine']=(d2l.DATA_URL+'timemachine.txt',......
  • Docker 搭建 SonarQube8.3 社区版
    Docker搭建SonarQube8.3社区版 docker安装sonarQube 参考: https://www.cnblogs.com/shenh/p/13428029.html为了测试,使用Docker搭建SonarQube8.3社区版步骤#创建sonarqube工作目录,映射目录都放在这里mkdir-p/usr/local/sonarqube&&cd/usr/local/so......
  • 安装pytorch报错,没解决
    environmentvariables:CIO_TEST=CLASS_PATH=.:/exe/jdk/jdk1.8.0_341/lib/dt.jar:/exe/jdk/jdk1.8.0_341/lib/tools.jar:/exe/jdk/jdk1.8.0_341/jre/libCONDA_DEFAULT_ENV=test1CONDA_EXE=/exe/conda/yes/bin/condaCONDA_PREFIX=/exe/conda/yes/envs/test1CONDA_PROMP......
  • 【Pytorch】小土堆笔记(未完成)
    transforms在训练的过程中,神经网络模型接收的数据类型是Tensor,而不是PIL对象,因此我们还需要对数据进行预处理操作,比如图像格式的转换。同时我们可以对图片进行一系列图像变换与增强操作,例如裁切边框、调整图像比例和大小、标准化等,以便模型能够更好地学习到数据的特征。这些......
  • 《动手学深度学习 Pytorch版》 8.2 文本预处理
    importcollectionsimportrefromd2limporttorchasd2l解析文本的常见预处理步骤:将文本作为字符串加载到内存中。将字符串拆分为词元(如单词和字符)。建立一个词表,将拆分的词元映射到数字索引。将文本转换为数字索引序列,方便模型操作。8.2.1读取数据集本文......
  • windows下安装conda和安装GPU版本的tensorflow和pytorch
    windows下安装conda和安装GPU版本的tensorflow和pytorch驱动下载查看自己电脑的独立显卡型号如:NVIDIAGeForceRTX3060在查看自己电脑是否已经安装了显卡驱动,如果显卡可用,那么就是安装了驱动;否则就要到NVIDIA官网下载驱动NVIDIA驱动程序下载找到自己对应型号的显卡驱动下载......
  • 《动手学深度学习 Pytorch版》 8.1 序列模型
    到目前为止,我们遇到的数据主要是表格数据和图像数据,并且所有样本都是独立同分布的。然而,大多数的数据并非如此。比如语句中的单词、视频中的帧以及音频信号,都是有顺序的。简言之,如果说卷积神经网络可以有效地处理空间信息,那么本章的循环神经网络(recurrentneuralnetwork,RNN)则可......
  • pytorch(8-1) 循环神经网络 序列模型
    https://zh.d2l.ai/chapter_recurrent-neural-networks/sequence.html     #%matplotlibinlineimporttorchfromtorchimportnnfromd2limporttorchasd2lfromAPI_Drawimport*T=1000#总共产生1000个点#time[0,1...,999]time=torch.arange(......
  • pytorch训练模版
    train.pyimporttorchimportnumpyasnpimportosimportmathimportsysimportargparseimportpsutilimporttorchimporttorch.optimasoptimimporttorch.optim.lr_scheduleraslr_schedulerfromtorchvisionimporttransformsimporttorchvisionimportd......
  • pytorch 自定义dataset类
    实现模版classour_dataset(Dataset):def__init__(self,···):super(our_dataset,self).__init__()#初始化,可以自定义添加参数def__getitem__(self,index):···returnimg,label#根据索引(0,len(dataset)-1)获取......