《动手学深度学习(PyTorch版)》笔记8.3

注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过,同时对于书上部分章节也做了整合。

Chapter8 Recurrent Neural Networks

8.3 Language Models and the Dataset

假设长度为 T T T的文本序列中的词元依次为 x 1 , x 2 , … , x T x_1, x_2, \ldots, x_T x1,x2,,xT。于是, x t x_t xt 1 ≤ t ≤ T 1 \leq t \leq T 1tT)可以被认为是文本序列在时间步 t t t处的观测或标签。在给定这样的文本序列时,语言模型(language model)的目标是估计序列的联合概率 P ( x 1 , x 2 , … , x T ) . P(x_1, x_2, \ldots, x_T). P(x1,x2,,xT).例如,只需要一次抽取一个词元 x t ∼ P ( x t ∣ x t − 1 , … , x 1 ) x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1) xtP(xtxt1,,x1),一个理想的语言模型就能够基于模型本身生成自然文本。

8.3.1 Learning a Language Model

假设在单词级别对文本数据进行词元化,包含了四个单词的一个文本序列的概率是:

P ( deep , learning , is , fun ) = P ( deep ) P ( learning ∣ deep ) P ( is ∣ deep , learning ) P ( fun ∣ deep , learning , is ) . P(\text{deep}, \text{learning}, \text{is}, \text{fun}) = P(\text{deep}) P(\text{learning} \mid \text{deep}) P(\text{is} \mid \text{deep}, \text{learning}) P(\text{fun} \mid \text{deep}, \text{learning}, \text{is}). P(deep,learning,is,fun)=P(deep)P(learningdeep)P(isdeep,learning)P(fundeep,learning,is).

为了训练语言模型,我们需要计算单词的概率,以及给定前面几个单词后出现某个单词的条件概率,这些概率本质上就是语言模型的参数。假设训练集是一个大型的文本语料库,训练集中词的概率可以根据给定词的相对词频来计算,比如可以将估计值 P ^ ( deep ) \hat{P}(\text{deep}) P^(deep)计算为任何以单词“deep”开头的句子的概率。一种(稍稍不太精确的)方法是统计单词“deep”在数据集中的出现次数,然后将其除以整个语料库中的单词总数(不太精确指的是:单个词的出现概率可能会受到其周围上下文的影响;“稀有词”可能会导致参数估计不准确,因为在训练集中可能无法捕获到这些词在其他文本中的真实分布情况;某个词在语料库中出现的次数可能会受到文本主题、文体等因素的影响)。接下来,我们可以尝试估计

P ^ ( learning ∣ deep ) = n ( deep, learning ) n ( deep ) , \hat{P}(\text{learning} \mid \text{deep}) = \frac{n(\text{deep, learning})}{n(\text{deep})}, P^(learningdeep)=n(deep)n(deep, learning),

其中 n ( x ) n(x) n(x) n ( x , x ′ ) n(x, x') n(x,x)分别是单个单词和连续单词对的出现次数。不幸的是,由于连续单词对“deep learning”的出现频率要低得多,所以估计这类单词正确的概率要困难得多。除非我们提供某种解决方案,来将这些单词组合指定为非零计数,否则将无法在语言模型中使用它们。一种常见的策略是执行某种形式的拉普拉斯平滑(Laplace smoothing),具体方法是在所有计数中添加一个小常量。用 n n n表示训练集中的单词总数,用 m m m表示唯一单词的数量,如下式所示:

P ^ ( x ) = n ( x ) + ϵ 1 / m n + ϵ 1 , P ^ ( x ′ ∣ x ) = n ( x , x ′ ) + ϵ 2 P ^ ( x ′ ) n ( x ) + ϵ 2 , P ^ ( x ′ ′ ∣ x , x ′ ) = n ( x , x ′ , x ′ ′ ) + ϵ 3 P ^ ( x ′ ′ ) n ( x , x ′ ) + ϵ 3 . \begin{aligned} \hat{P}(x) & = \frac{n(x) + \epsilon_1/m}{n + \epsilon_1}, \\ \hat{P}(x' \mid x) & = \frac{n(x, x') + \epsilon_2 \hat{P}(x')}{n(x) + \epsilon_2}, \\ \hat{P}(x'' \mid x,x') & = \frac{n(x, x',x'') + \epsilon_3 \hat{P}(x'')}{n(x, x') + \epsilon_3}. \end{aligned} P^(x)P^(xx)P^(x′′x,x)=n+ϵ1n(x)+ϵ1/m,=n(x)+ϵ2n(x,x)+ϵ2P^(x),=n(x,x)+ϵ3n(x,x,x′′)+ϵ3P^(x′′).

其中, ϵ 1 、 e p s i l o n 2 \epsilon_1、epsilon_2 ϵ1epsilon2 ϵ 3 \epsilon_3 ϵ3是超参数。以 ϵ 1 \epsilon_1 ϵ1为例:当 ϵ 1 = 0 \epsilon_1 = 0 ϵ1=0时,不应用平滑;当 ϵ 1 \epsilon_1 ϵ1接近正无穷大时, P ^ ( x ) \hat{P}(x) P^(x)接近均匀概率分布 1 / m 1/m 1/m
然而,这样的模型很容易变得无效,原因如下:首先,我们需要存储所有的计数;其次,模型完全忽略了单词的意思;最后,长单词序列大部分是没出现过的,因此一个模型如果只是简单地统计先前“看到”的单词序列频率,面对这种问题时肯定表现不佳。

如果 P ( x t + 1 ∣ x t , … , x 1 ) = P ( x t + 1 ∣ x t ) P(x_{t+1} \mid x_t, \ldots, x_1) = P(x_{t+1} \mid x_t) P(xt+1xt,,x1)=P(xt+1xt),则序列上的分布满足一阶马尔可夫性质。阶数越高,对应的依赖关系就越长。这种性质推导出了许多可以应用于序列建模的近似公式:
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 ) \begin{aligned} 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 \mid x_1) P(x_3 \mid x_2) P(x_4 \mid x_3) \\ P(x_1, x_2, x_3, x_4) &= P(x_1) P(x_2 \mid x_1) P(x_3 \mid x_1, x_2) P(x_4 \mid x_2, x_3) \end{aligned} P(x1,x2,x3,x4)P(x1,x2,x3,x4)P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4)=P(x1)P(x2x1)P(x3x2)P(x4x3)=P(x1)P(x2x1)P(x3x1,x2)P(x4x2,x3)
通常,涉及一个、两个和三个变量的概率公式分别被称为一元语法(unigram)、二元语法(bigram)和三元语法(trigram)模型。也就是说,一元语法假设文本中的每个词都是相互独立的,即某个词的出现概率只依赖不依赖于其他词,一元语法模型将整个文本的概率表示为每个单词出现的概率的乘积。二元语法考虑了相邻两个词之间的关系,假设某个词的出现概率仅依赖于它前面一个词,三元语法同理。

8.3.2 Natural Language SStatistics

import random
import torch
from d2l import torch as d2l
import matplotlib.pyplot as plttokens = d2l.tokenize(d2l.read_time_machine())
# 因为每个文本行不一定是一个句子或一个段落,因此我们把所有文本行拼接到一起
corpus = [token for line in tokens for token in line]
vocab = d2l.Vocab(corpus)
print(vocab.token_freqs[:10])freqs = [freq for token, freq in vocab.token_freqs]
d2l.plot(freqs, xlabel='token: x', ylabel='frequency: n(x)',xscale='log', yscale='log')
plt.show()

词频图:
在这里插入图片描述

最流行的词看起来很无聊,被称为停用词(stop words),因此可以被过滤掉,但它们本身仍然是有意义的。此外,还有个明显的现象是词频衰减的速度相当快。通过此图我们可以发现:词频以一种明确的方式迅速衰减。将前几个单词作为例外消除后,剩余的所有单词大致遵循双对数坐标图(xscale=‘log’, yscale=‘log’)上的一条直线,这意味着单词的频率满足齐普夫定律(Zipf’s law),即第 i i i个最常用单词的频率 n i n_i ni满足:

log ⁡ n i = − α log ⁡ i + c \log n_i = -\alpha \log i + c logni=αlogi+c

其中 α \alpha α是刻画分布的指数, c c c是常数。上式等价于

n i ∝ 1 i α n_i \propto \frac{1}{i^\alpha} niiα1
这告诉我们想要通过计数统计和平滑来建模单词是不可行的,因为这样建模的结果会大大高估尾部单词的频率,也就是所谓的不常用单词。换句话说,齐普夫定律告诉我们,自然语言中的单词分布呈现出一种“长尾”现象,即少数单词的出现频率非常高,而大多数单词的出现频率则相对较低,呈现出尾部单词的大量分布。

#bigram
bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]
bigram_vocab = d2l.Vocab(bigram_tokens)
print(bigram_vocab.token_freqs[:10])#trigram
trigram_tokens = [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:])]
trigram_vocab = d2l.Vocab(trigram_tokens)
print(trigram_vocab.token_freqs[:10])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'])
plt.show()

一元、二元和三元词频图:
在这里插入图片描述

从上图可看出:

  1. 除了一元语法词,单词序列也遵循齐普夫定律,尽管公式指数 α \alpha α更小;
  2. 词表中 n n n元组的数量并没有那么大,这说明语言中存在相当多的结构(即词元序列组合很丰富);
  3. 很多 n n n元组很少出现,这使得拉普拉斯平滑非常不适合语言建模,因此我们将使用基于深度学习的模型。

8.3.3 Reading Long Sequence Data

当序列变得太长而不能被模型一次性全部处理时,我们可能希望拆分这样的序列方便模型读取。假设我们将使用神经网络来训练语言模型,模型中的网络一次处理具有预定义长度(例如 n n n个时间步)的一个小批量序列。首先,由于文本序列可以是任意长的,于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时,这样的小批量子序列将被输入到模型中。假设网络一次只处理具有 n n n个时间步的子序列。下图画出了从原始文本序列获得子序列的所有不同的方式,其中 n = 5 n=5 n=5,并且每个时间步的词元对应于一个字符。

在这里插入图片描述

事实上,上图中不同的取法都一样好,然而如果只选择一个偏移量,那么用于训练网络的、所有可能的子序列的覆盖范围将是有限的。因此,我们可以从随机偏移量开始划分序列,以同时获得覆盖性(coverage)和随机性(randomness)。

def seq_data_iter_random(corpus, batch_size, num_steps):  #@save"""使用随机抽样生成一个小批量子序列"""#从随机偏移量开始对序列进行分区,随机范围为[0,num_steps-1]corpus = corpus[random.randint(0, num_steps - 1):]num_subseqs = (len(corpus) - 1) // num_steps #将输入序列中的每个词作为训练数据的特征,而将对应的下一个词作为标签,减去1是为了确保每个子序列都有对应的标签#initial_indices为长度为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#batch_size指定每个小批量中子序列样本的数目for i in range(0, batch_size * num_batches, batch_size):initial_indices_per_batch = initial_indices[i: i + batch_size]#initial_indices包含子序列的随机起始索引#X是模型的输入序列,Y是对应于X中每个样本的下一个词的目标序列(标签)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))
for X, Y in seq_data_iter_random(my_seq, batch_size=2, num_steps=5):print('X: ', X, '\nY:', Y)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_sizeXs = torch.tensor(corpus[offset: offset + num_tokens])Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])print(Xs,Ys)Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)print(Xs,Ys)num_batches = Xs.shape[1] // num_stepsfor i in range(0, num_steps * num_batches, num_steps):X = Xs[:, i: i + num_steps]Y = Ys[:, i: i + num_steps]yield X, Yfor X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5):print('X: ', X, '\nY:', Y)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_randomelse:self.data_iter_fn = d2l.seq_data_iter_sequentialself.corpus, self.vocab = d2l.load_corpus_time_machine(max_tokens)self.batch_size, self.num_steps = batch_size, num_stepsdef __iter__(self):return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)def load_data_time_machine(batch_size, num_steps,  #@saveuse_random_iter=False, max_tokens=10000):"""返回时光机器数据集的迭代器和词表"""data_iter = SeqDataLoader(batch_size, num_steps, use_random_iter, max_tokens)return data_iter, data_iter.vocab

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/677778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源】JAVA+Vue.js实现计算机机房作业管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课时管理模块2.4 学生作业模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 课程表3.2.2 课时表3.2.3 学生作业表 四、系统展示五、核心代码5.1 查询课程数据5.2 新增课时5.3 提交作…

【PyTorch][chapter 15][李宏毅深度学习][Neighbor Embedding-LLE]

前言: 前面讲的都是线性降维,本篇主要讨论一下非线性降维. 流形学习(mainfold learning)是一类借鉴了拓扑流行概念的降维方法. 如上图,欧式距离上面 A 点跟C点更近,距离B 点较远 但是从图形拓扑结构来看, …

「C++ 类和对象篇 10」初始化列表

目录 一、什么是初始化列表? 二、为什么需要初始化列表? 三、初始化列表怎么使用? 3.1 在构造函数中使用初始化列表 3.2 注意 3.3 结论 3.4 应用场景 四、初始化列表的初始化顺序 五、另一种初始化成员变量的方法 【总结】 一、什么是初始化…

C++入门篇(4)—— 类与对象(1)

目录 1.类的引入 2.类的定义 3.类的访问限定符 4.类的作用域 5. 类对象的存储方式 6. this指针 6.1 this指针的引入 6.2 this指针的特性 6.3有意思的面试题 1.类的引入 C语言struct 结构体中只能定义变量,而C中可以定义函数。 struct Date {void Init(int…

vue常用9个事件修饰符

第075个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下,本专栏提供行之有效的源代码示例和信息点介绍,做到灵活运用。 提供vue2的一些基本操作:安装、引用,模板使用,computed&a…

vueRouter中Hash模式和History模式有什么区别

VueRouter是Vue.js官方推荐的前端路由库,它提供了一种方便的方式来构建单页应用(SPA)。在使用VueRouter时,我们可以选择不同的路由模式,其中最常见的是Hash模式和History模式。本文将深入探讨这两种模式的区别&#xf…

逐行拆解Guava限流器RateLimiter

逐行拆解Guava限流器RateLimiter 常见限流算法 计数器法 设置一个时间窗口内允许的最大请求量,如果当前窗口请求数超过这个设定数量,则拒绝该窗口内之后的请求。 关键词:时间窗口,计数器。 举个例子,我们设置1秒钟…

CSS3 基本语法

CSS3 基本语法 1. CSS3 新增长度单位 rem 根元素字体大小的倍数,只与根元素字体大小有关。vw 视口宽度的百分之多少 10vw 就是视口宽度的 10% 。vh 视口高度的百分之多少 10vh 就是视口高度的 10% 。vmax 视口宽高中大的那个的百分之多少。(了解即可&am…

JVM相关-JVM模型、垃圾回收、JVM调优

一、JVM模型 JVM内部体型划分 JVM的内部体系结构分为三部分,分别是:类加载器(ClassLoader)子系统、运行时数据区(内存)和执行引擎 1、类加载器 概念 每个JVM都有一个类加载器子系统(class l…

Spring Batch 批处理框架适配达梦数据库,实现从文件批量读取写入数据库(完整教程)

效果展示(达梦数据库): 技术简介: Spring Batch 是一个基于 Spring 的批处理框架,用于开发和执行大规模、高性能、可靠的批处理应用程序。它提供了丰富的功能和组件,用于处理复杂的批处理任务,例如大数据ETL(Extract-Transform-Load)、数据清洗、数据迁移、报表生成…

C# OCR识别图片中的文字

1、从NuGet里面安装Spire.OCR 2、安装之后,找到安装路径下,默认生成的packages文件夹,复制该文件夹路径下的 6 个dll文件到程序的根目录 3、调用读取方法 OcrScanner scanner new OcrScanner(); string path "C:\1.png"; scann…

React18原理: Fiber架构下的单线程CPU调度策略

概述 React 的 Fiber 架构, 它的整个设计思想就是去参考CPU的调度策略CPU现在都是多核多进程的,重点研究的是 CPU是单核单线程,它是如何调度的?为什么要去研究单线程的CPU? 浏览器中的JS它是单线程的JS 的执行线程和浏览器的渲染GUI 是互斥…

缺省参数(c++)

void fun(int a0) { cout<<a<<endl; } 当我们调用函数时: fun(10) 输出10; fun&#xff08;&#xff09; 未传参时&#xff1a; 输出0; 未传参时a就会接受0&#xff0c;相当于这个0就是“备胎” 传参了0就没有用 全缺省 void fun2(int a10,int b3,int…

为什么无法正常访问TikTok?该使用跨境专线吗?

TikTok作为全球范围内备受欢迎的社交媒体平台&#xff0c;吸引了数以亿计的用户。然而&#xff0c;有时候用户可能会遇到无法正常访问TikTok的问题&#xff0c;这可能涉及到多方面的因素。本文将深入探讨为什么可能无法正常访问TikTok&#xff0c;并考虑是否使用 TikTok跨境专线…

使用vue-client-only 解决组件不兼容SSR问题

目录 前言 一、解决方案 1.基于Nuxt 框架的SSR应用 2.基于vue2框架的应用 3.基于vue3框架的应用 二、总结 往期回顾 前言 最近在我的单页面SSR应用上开发JSON编辑器功能&#xff0c;在引入组件后直接客户端跳转OK&#xff0c;但是在直接加载服务端渲染的时候一直报这…

ADMap:Anti-disturbance framework for reconstructing online vectorized HD map

参考代码&#xff1a;ADMap 动机与出发点 局部地图构建算法在实际中会遇到部分车道线偏离的或是错误的情况&#xff0c;这往往是全局信息获取上存在欠缺&#xff0c;毕竟地图元素的回归很依赖于全局信息的获取。那么从特征提取、attention layer设计和loss构建上可以做一些工作…

visual studio和cmake如何编译dlib库

官网 dlib C Library 对应的是最新版本&#xff0c;只能用到vs2015版本及以后 如果使用vs2013&#xff0c;所以需要下载vs2013可用的版本。 就是说dlib版本与vs版本有对应关系 所有版本 dlib C Library - Browse /dlib at SourceForge.net Releases davisking/dlib GitHu…

Go语言每日一练——链表篇(八)

传送门 牛客面试笔试必刷101题 ----------------两个链表的第一个公共结点 题目以及解析 题目 解题代码及解析 解析 这一道题使用的还是双指针算法&#xff0c;我们先求出两个链表的长度差n&#xff0c;然后定义快慢指针&#xff0c;让快指针先走n步&#xff0c;最后快慢指…

【数学建模】【2024年】【第40届】【MCM/ICM】【B题 搜寻潜水器】【解题思路】

一、题目 &#xff08;一&#xff09;赛题原文 2024 MCM Problem A: Resource Availability and Sex Ratios Maritime Cruises Mini-Submarines (MCMS), a company based in Greece, builds submersibles capable of carrying humans to the deepest parts of the ocean. A …

app逆向-⽹络请求库okhttp3

文章目录 一、前言二、应用1、添加权限AndroidManifest.xml2、添加依赖okhttp33、编写界面文件activity_main.xml4、编写Activity代码 三、效果 一、前言 OkHttp是由Square公司开发的用于Java和Android的开源HTTP客户端库。它被广泛用于在Java和Android应用程序中进行HTTP请求…