《动手学深度学习(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 plt

tokens = 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_size
    Xs = 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_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)
    
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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络——07协议层次及服务模型

协议层次及服务模型 协议层次 网络是一个复杂的系统 网络功能复杂:数字信号的物理信号承载、点到点、路由、rdt、进程区分、应用等现实来看,网络的许多构成元素和设备: 主机路由器各种媒体的链路应用协议硬件,软件 问题是&am…

单片机学习路线(简单介绍)

学习单片机对于电子爱好者和未来的嵌入式系统工程师来说是一段激动人心的旅程。单片机因其强大的功能、灵活性以及在各种智能设备中的广泛应用,成为了电子和计算机科学领域一个不可或缺的组成部分。如果你对如何开始这段旅程感到好奇,那么你来对地方了。…

Sqlite3安装步骤

1、Sqlite3以下载文件,配置环境变量的方式进行安装。 2、下方链接为官方的下载地址。 sqlite下载地址 2.1、需要两个下载文件,解压后将他们放在一起,假设解压后的路径为E:\sqlite。 sqlite-dll-win-x64-3450100.zip sqlite-tools-win-x6…

什么是CDR数字音频广播

一、什么是数字音频广播 CDR(China DigilalRadio),即中国数字音领广播,是运用广播数字化技术,通过对音领信号进行信源编码、信道编码和载波调制传输,来实现数字音频广播业务和数据业务的播出。CDR与传统的FM调频广播相比&#xff…

【蓝桥杯冲冲冲】k 短路 / [SDOI2010] 魔法猪学院

蓝桥杯备赛 | 洛谷做题打卡day33 文章目录 蓝桥杯备赛 | 洛谷做题打卡day33题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示数据规模数据更新日志 题解代码我的一些话 【模板】k 短路 / [SDOI2010] 魔法猪学院 题目背景 注:对于 k k k 短路问…

第9章 智能租房——详情页

学习目标 掌握详情页房源数据展示功能的逻辑,能够实现在详情页上展示基本信息和配套设施 了解数据可视化,能够说出数据可视化的概念和流程 熟悉ECharts的用法和配置项,能够通过ECharts绘制常用图表,并为图表添加配置项 掌握户型…

2024年 前端JavaScript入门到精通 第一天

主要讲解JavaScript核心知识,包含最新ES6语法,从基础到API再到高级。让你一边学习一边练习,重点知识及时实践,同时每天安排大量作业,加深记忆,巩固学习成果。 1.1 基本软件与准备工作 1.2 JavaScript 案例 …

前端开发_AJAX基本使用

AJAX概念 AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。 简单点说,就是使用XMLHttpRequest对象与服务器通信。 它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。 AJAX最吸引人的就是它的“异步"特性&am…

python-分享篇-GUI界面开发-PyQt5-对QListWidget表格进行数据绑定

代码 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file bindtable.ui # # Created by: PyQt5 UI code generator 5.11.3 # # WARNING! All changes made in this file will be lost! 对QTableWidget表格进行数据绑定from PyQt5 import QtCore, Q…

Wireshark不显示Thrift协议

使用Wireshark对thrift协议进行抓包,但是只显示了传输层的tcp协议: "右键" -> "Decode As" 选择thrift的tcp端口 将“当前”修改为Thrift,然后点击“确定” 设置后,可以发现Wireshark里面显示的协议从Tcp变…

正版软件 - Proxyman:让网络调试变得更智能、更高效

在软件开发的世界里,网络调试一直是开发者和测试工程师的痛点。传统的调试工具往往操作复杂,界面不够直观,而且性能上也难以满足现代应用的需求。今天,我要向大家介绍一款名为Proxyman的网络调试工具,它以其简洁的界面…

第十八篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:图像修复和恢复

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文目录前言一、常用的图像修复与恢复技术二、插值方法示例代码三、基于纹理合成的方法示例代码四、基于边缘保持的方法示例代码五、基于图像修复模型的方法示例代码六、基于深度学习的方法示例代码七…

Vulnhub靶机:hacksudo-search

一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.15) 靶机:hacksudo-search(10.0.2.50) 目标:获取靶机root权限和flag 靶机下载地址:https://download.vulnhub.co…

【leetcode热题100】 格雷编码

n 位格雷码序列 是一个由 2n 个整数组成的序列,其中: 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)第一个整数是 0一个整数在序列中出现 不超过一次每对 相邻 整数的二进制表示 恰好一位不同 ,且第一个 和 最后一…

c语言游戏实战(4):人生重开模拟器

前言: 人生重开模拟器是前段时间非常火的一个小游戏,接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏: 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 (1) 游戏开始的时…

Python算法题集_随机链表的复制

Python算法题集_随机链表的复制 题138:随机链表的复制1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【字典哈希】3) 改进版二【单层哈希】4) 改进版三【递归大法】 4. 最优算法 本文为Python算法题集之一的…

Python 错误及其解决方法

Python 是一种易于学习的编程语言,但初学者在学习和使用 Python 的过程中难免会遇到一些错误。以下是一些常见的 Python 错误及其解决方法: 1. 语法错误(SyntaxError): python # 错误示例 print("Hello, World!…

elasticsearch下载及可视化工具下载使用

elasticsearch下载及配置、启动 一、下载 Download Elasticsearch | Elastic 二、启动 双击bat即可。 出现如下说明启动成功: 访问测试: 三、注意 (1)因为es启动默认端口是:9200,所以需要检查此端口是否被占用。…

js中bind、call、apply 区别(如何实现)

文章目录 一、作用二、区别applycallbind小结 三、实现 一、作用 call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向 那么什么情况下需要改变this的指向呢?下面举个例子 var name "lucy"; var obj {n…

python统计分析——两样本t检验

参考资料:用python动手学统计学 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 用于绘图的库 from matplotlib import pyplot as plt import seaborn as sns sns.set() 2、准备数…