首页 > 其他分享 >动手学深度学习10.6. 自注意力和位置编码-笔记&练习(PyTorch)

动手学深度学习10.6. 自注意力和位置编码-笔记&练习(PyTorch)

时间:2024-12-03 15:29:58浏览次数:11  
标签:编码 10.6 self torch PyTorch num 序列 注意力

本节课程地址:67 自注意力【动手学深度学习v2】_哔哩哔哩_bilibili

本节教材地址:10.6. 自注意力和位置编码 — 动手学深度学习 2.0.0 documentation

本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>self-attention-and-positional-encoding.ipynb


自注意力和位置编码

在深度学习中,经常使用卷积神经网络(CNN)或循环神经网络(RNN)对序列进行编码。 想象一下,有了注意力机制之后,我们将词元序列输入注意力池化中, 以便同一组词元同时充当查询、键和值。 具体来说,每个查询都会关注所有的键-值对并生成一个注意力输出。 由于查询、键和值来自同一组输入,因此被称为 自注意力(self-attention) (href="https://zh.d2l.ai/chapter_references/zreferences.html#id94">Linet al., 2017,="https://zh.d2l.ai/chapter_references/zreferences.html#id174">Vaswaniet al., 2017), 也被称为内部注意力(intra-attention) (ef="https://zh.d2l.ai/chapter_references/zreferences.html#id22">Chenget al., 2016,f="https://zh.d2l.ai/chapter_references/zreferences.html#id119">Parikhet al., 2016,f="https://zh.d2l.ai/chapter_references/zreferences.html#id121">Pauluset al., 2017)。 本节将使用自注意力进行序列编码,以及如何使用序列的顺序作为补充信息。

import math
import torch
from torch import nn
from d2l import torch as d2l

[自注意力]

给定一个由词元组成的输入序列 \mathbf{x}_1, \ldots, \mathbf{x}_n , 其中任意 \mathbf{x}_i \in \mathbb{R}^d ( 1 \leq i \leq n )。 该序列的自注意力输出为一个长度相同的序列 \mathbf{y}_1, \ldots, \mathbf{y}_n ,其中:

\mathbf{y}_i = f(\mathbf{x}_i, (\mathbf{x}_1, \mathbf{x}_1), \ldots, (\mathbf{x}_n, \mathbf{x}_n)) \in \mathbb{R}^d

根据 (10.2.4)中定义的注意力汇聚函数 f 。 下面的代码片段是基于多头注意力对一个张量完成自注意力的计算, 张量的形状为(批量大小,时间步的数目或词元序列的长度, d )。 输出与输入的张量形状相同。

num_hiddens, num_heads = 100, 5
attention = d2l.MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
                                   num_hiddens, num_heads, 0.5)
attention.eval()

输出结果:
MultiHeadAttention(
(attention): DotProductAttention(
(dropout): Dropout(p=0.5, inplace=False)
)
(W_q): Linear(in_features=100, out_features=100, bias=False)
(W_k): Linear(in_features=100, out_features=100, bias=False)
(W_v): Linear(in_features=100, out_features=100, bias=False)
(W_o): Linear(in_features=100, out_features=100, bias=False)
)

batch_size, num_queries, valid_lens = 2, 4, torch.tensor([3, 2])
X = torch.ones((batch_size, num_queries, num_hiddens))
attention(X, X, X, valid_lens).shape

输出结果:
torch.Size([2, 4, 100])

比较卷积神经网络、循环神经网络和自注意力

接下来比较下面几个架构,目标都是将由 n 个词元组成的序列映射到另一个长度相等的序列,其中的每个输入词元或输出词元都由 d 维向量表示。具体来说,将比较的是卷积神经网络、循环神经网络和自注意力这几个架构的计算复杂性、顺序操作和最大路径长度。请注意,顺序操作会妨碍并行计算,而任意的序列位置组合之间的路径越短,则能更轻松地学习序列中的远距离依赖关系 (Hochreiteret al., 2001)。

考虑一个卷积核大小为 k 的卷积层。 在后面的章节将提供关于使用卷积神经网络处理序列的更多详细信息。 目前只需要知道的是,由于序列长度是 n ,输入和输出的通道数量都是 d , 所以卷积层的计算复杂度为 \mathcal{O}(knd^2) 。 如 图10.6.1 所示, 卷积神经网络是分层的,因此为有 \mathcal{O}(1) 个顺序操作, 最大路径长度为 \mathcal{O}(n/k) 。 例如, \mathbf{x}_1 和 \mathbf{x}_5 处于 图10.6.1 中卷积核大小为3的双层卷积神经网络的感受野内。

当更新循环神经网络的隐状态时, d \times d 权重矩阵和 d 维隐状态的乘法计算复杂度为 \mathcal{O}(d^2) 。 由于序列长度为 n ,因此循环神经网络层的计算复杂度为 \mathcal{O}(nd^2) 。 根据 图10.6.1, 有 \mathcal{O}(n) 个顺序操作无法并行化,最大路径长度也是 \mathcal{O}(n)

在自注意力中,查询、键和值都是 n \times d 矩阵。 考虑 (10.3.5)中缩放的”点-积“注意力, 其中 n \times d 矩阵乘以 d \times n 矩阵。 之后输出的 n \times n 矩阵乘以 n \times d 矩阵。 因此,自注意力具有 \mathcal{O}(n^2d) 计算复杂性。 正如在 图10.6.1 中所讲, 每个词元都通过自注意力直接连接到任何其他词元。 因此,有 \mathcal{O}(1) 个顺序操作可以并行计算, 最大路径长度也是 \mathcal{O}(1) 。

总而言之,卷积神经网络和自注意力都拥有并行计算的优势, 而且自注意力的最大路径长度最短。 但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。

[位置编码]

重复地处理词元的, 而自注意力则因为并行计算而放弃了顺序操作。 为了使用序列的顺序信息,通过在输入表示中添加 位置编码(positional encoding)来注入绝对的或相对的位置信息。 位置编码可以通过学习得到也可以直接固定得到。 接下来描述的是基于正弦函数和余弦函数的固定位置编码 (="https://zh.d2l.ai/chapter_references/zreferences.html#id174">Vaswaniet al., 2017)。

假设输入表示 \mathbf{X} \in \mathbb{R}^{n \times d} 包含一个序列中 n 个词元的 d 维嵌入表示。 位置编码使用相同形状的位置嵌入矩阵 \mathbf{P} \in \mathbb{R}^{n \times d} 输出 \mathbf{X} + \mathbf{P} , 矩阵第 i 行、第 2j 列和 2j+1 列上的元素为:

\begin{aligned} p_{i, 2j} &= \sin\left(\frac{i}{10000^{2j/d}}\right),\\p_{i, 2j+1} &= \cos\left(\frac{i}{10000^{2j/d}}\right).\end{aligned}(10.6.2)

乍一看,这种基于三角函数的设计看起来很奇怪。 在解释这个设计之前,让我们先在下面的PositionalEncoding类中实现它。

#@save
class PositionalEncoding(nn.Module):
    """位置编码"""
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)
        # 创建一个足够长的P,形状为(batch_size, n, d)
        # max_len是序列的最大长度, num_hiddens是每个位置的编码维度
        self.P = torch.zeros((1, max_len, num_hiddens))
        X = torch.arange(max_len, dtype=torch.float32).reshape(
            -1, 1) / torch.pow(10000, torch.arange(
            0, num_hiddens, 2, dtype=torch.float32) / num_hiddens)
        self.P[:, :, 0::2] = torch.sin(X)
        self.P[:, :, 1::2] = torch.cos(X)

    def forward(self, X):
        # self.P[:, :X.shape[1], :] 选择与输入序列长度相同的编码
        X = X + self.P[:, :X.shape[1], :].to(X.device)
        # dropout防止模型对P过于敏感
        return self.dropout(X)

在位置嵌入矩阵 \mathbf{P} 中, [行代表词元在序列中的位置,列代表位置编码的不同维度]。 从下面的例子中可以看到位置嵌入矩阵的第6列和第7列的频率高于第8列和第9列。 第6列和第7列之间的偏移量(第8列和第9列相同)是由于正弦函数和余弦函数的交替。

encoding_dim, num_steps = 32, 60
pos_encoding = PositionalEncoding(encoding_dim, 0)
pos_encoding.eval()
X = pos_encoding(torch.zeros((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :]
d2l.plot(torch.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
         figsize=(6, 2.5), legend=["Col %d" % d for d in torch.arange(6, 10)])

绝对位置信息

为了明白沿着编码维度单调降低的频率与绝对位置信息的关系, 让我们打印出 0, 1, \ldots, 7 的[二进制表示]形式。 正如所看到的,每个数字、每两个数字和每四个数字上的比特值 在第一个最低位、第二个最低位和第三个最低位上分别交替。

for i in range(8):
    print(f'{i}的二进制是:{i:>03b}')

输出结果:
0的二进制是:000
1的二进制是:001
2的二进制是:010
3的二进制是:011
4的二进制是:100
5的二进制是:101
6的二进制是:110
7的二进制是:111

在二进制表示中,较高比特位的交替频率低于较低比特位, 与下面的热图所示相似,只是位置编码通过使用三角函数[在编码维度上降低频率]。 由于输出是浮点数,因此此类连续表示比二进制表示法更节省空间。

P = P[0, :, :].unsqueeze(0).unsqueeze(0)
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
                  ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')

相对位置信息

除了捕获绝对位置信息之外,上述的位置编码还允许模型学习得到输入序列中相对位置信息。 这是因为对于任何确定的位置偏移 \delta ,位置 i + \delta 处 的位置编码可以线性投影位置 i 处的位置编码来表示。

这种投影的数学解释是,令 \omega_j = 1/10000^{2j/d} , 对于任何确定的位置偏移 \delta , (10.6.2)中的任何一对 (p_{i, 2j}, p_{i, 2j+1}) 都可以线性投影到 (p_{i+\delta, 2j}, p_{i+\delta, 2j+1}) :

\begin{aligned} &\begin{bmatrix} \cos(\delta \omega_j) & \sin(\delta \omega_j) \\ -\sin(\delta \omega_j) & \cos(\delta \omega_j) \\ \end{bmatrix} \begin{bmatrix} p_{i, 2j} \\ p_{i, 2j+1} \\ \end{bmatrix}\\ =&\begin{bmatrix} \cos(\delta \omega_j) \sin(i \omega_j) + \sin(\delta \omega_j) \cos(i \omega_j) \\ -\sin(\delta \omega_j) \sin(i \omega_j) + \cos(\delta \omega_j) \cos(i \omega_j) \\ \end{bmatrix}\\ =&\begin{bmatrix} \sin\left((i+\delta) \omega_j\right) \\ \cos\left((i+\delta) \omega_j\right) \\ \end{bmatrix}\\ =& \begin{bmatrix} p_{i+\delta, 2j} \\ p_{i+\delta, 2j+1} \\ \end{bmatrix}, \end{aligned}

2\times 2 投影矩阵不依赖于任何位置的索引 i 。

小结

  • 在自注意力中,查询、键和值都来自同一组输入。
  • 卷积神经网络和自注意力都拥有并行计算的优势,而且自注意力的最大路径长度最短。但是因为其计算复杂度是关于序列长度的二次方,所以在很长的序列中计算会非常慢。
  • 为了使用序列的顺序信息,可以通过在输入表示中添加位置编码,来注入绝对的或相对的位置信息。

练习

  1. 假设设计一个深度架构,通过堆叠基于位置编码的自注意力层来表示序列。可能会存在什么问题?
    解:
    1)由于自注意力的计算复杂度大,因此这种方式可能会导致模型的计算复杂度过高,参数数量过多且计算资源需求过大。 2)尽管自注意力机制可以捕捉长距离依赖关系,但在非常深的网络中,学习这些依赖关系可能会变得更加困难,因为信息需要通过更多的层来传播。 3)固定的位置编码可能不足以捕捉所有类型的序列结构,特别是当序列具有复杂的结构或模式时。 4)位置编码通常预定义了最大序列长度(比如教材中的1000),这限制了模型处理更长序列的能力。
  2. 请设计一种可学习的位置编码方法。
    解:代码如下:
class LearnablePositionalEncoding(nn.Module):
    """可学习的位置编码"""
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(LearnablePositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)
        self.P = nn.Parameter(torch.randn((1, max_len, num_hiddens)))

    def forward(self, X):
        X = X + self.P[:, :X.shape[1], :].to(X.device)
        return self.dropout(X)
encoding_dim, num_steps = 32, 60
pos_encoding = LearnablePositionalEncoding(encoding_dim, 0)
pos_encoding.eval()
X = pos_encoding(torch.randn((1, num_steps, encoding_dim)))
P = pos_encoding.P[:, :X.shape[1], :].detach()
d2l.plot(torch.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
         figsize=(6, 2.5), legend=["Col %d" % d for d in torch.arange(6, 10)])

标签:编码,10.6,self,torch,PyTorch,num,序列,注意力
From: https://blog.csdn.net/scdifsn/article/details/144215743

相关文章

  • 大模型面试题:目前大模型中的位置编码有哪些?
    我整理了1000道算法面试题,可以在下面的地方获取,面试题还是有点多的在大模型中,位置编码主要分为两大派:绝对位置编码和相对位置编码。主流的几种脍炙人口的位置编码如下所示:正弦编码正弦曲线(Sinusoidal)位置编码:这是Transformer原始论文中提出的位置编码方式。它通过正弦和......
  • java特殊编码生成
    工作中想要生成一个特殊编码,比如:SZ-2412030009,前面三位是编码固定开头,然后是yyMMdd,最后是当天的个数。期望能够生成一个计算当天task个数,第二天重新计数的一个编码,用于插入到数据库中作为特殊标识。便于用户快速查看任务时间和个数@ResourceprivateRedisTemplat......
  • VMware Cloud Director 10.6 - 领先的云服务交付平台
    VMwareCloudDirector10.6-领先的云服务交付平台VMwareCloudDirector|LeadingCloudServiceDeliveryPlatform请访问原文链接:https://sysin.org/blog/vmware-cloud-director-10/查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgVMwareCloudDirector10.6......
  • 【知识】Prufer 编码
    Prüfer序列Prufer序列可以将一个带标号\(n\)个节点的树用\([1,n]\)中的\(n-2\)个整数表示,即\(n\)个点的完全图的生成树与长度为\(n-2\),值域为\([1,n]\)的数列构成的双射。Prufer序列可以方便地解决一类树相关的计数问题,比如凯莱定理:\(n^{n-2}\)个点的完全图......
  • 【NLP高频面题 - LLM架构篇】旋转位置编码RoPE如何进行外推?
    【NLP高频面题-LLM架构篇】旋转位置编码RoPE如何进行外推?重要性:★★★......
  • 1201-字符串编码
    最小栈leetcode394.题目大意:[]前的数字为出现的次数,中的内容会要重复的数据,例如输入:s="3[a2[c]]"输出:"accaccacc"解题思路:主要难点为嵌套中括号,利用栈的特点设计两个LinkedList存储次数和重复值,每次遇到左括号的时候将当前的数字和重复值分别入栈,遇到右括号的时候将数......
  • pytorch框架的模型定义以及推理数据流向
    文章目录一、前言二、模型初始化2.1定义顺序2.2参数顺序2.3在优化器中的顺序2.4在计算图中的顺序2.5总结三、推理数据流3.1init定义顺序3.2forward中的执行顺序3.3训练与优化器中的顺序3.4总结:一、前言这篇博文旨在基于pytorch深度学习框,讲解模型定义......
  • 基于TCN-Transformer-KAN混合模型实现电力负荷时序预测——Kolmogorov-Arnold Network
    前言系列专栏:【深度学习:算法项目实战】✨︎涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记忆......
  • 从 0 到 1 制作自定义镜像并用于训练(Pytorch+CPU/GPU)
    本章节介绍如何从0到1制作镜像,并使用该镜像在ModelArts平台上进行训练。镜像中使用的AI引擎是PyTorch,训练使用的资源是CPU或GPU。说明:本实践教程仅适用于新版训练作业。场景描述本示例使用Linuxx86_64架构的主机,操作系统ubuntu-18.04,通过编写Dockerfile文件制作自定义镜像......
  • 【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势?
    【NLP高频面题-LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势?重要性:⭐⭐⭐......