前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【组队学习】Task02:学习Attention和Transformer

【组队学习】Task02:学习Attention和Transformer

作者头像
诡途
发布2022-01-07 08:19:14
3560
发布2022-01-07 08:19:14
举报
文章被收录于专栏:诡途的python路诡途的python路

目录

组队学习资料:

datawhale8月组队学习 -基于transformers的自然语言处理(NLP)入门

Task02主要学习内容: 2.1-图解attention.md 2.2-图解transformer.md

声明:NLP纯小白,本文内容主要是作为个人学习笔记,可能很多地方我自己理解也不是很到位,仅供参考,有争议的话可以多查点儿其他资料,并请评论区留言指正!谢谢

学习建议 对于我这种小白来说,应该以2.2transformer为主,2.1attention作为补充知识,一开始先看2.1完全不知道在讲什么,看完2.2再回过来看2.1很多就通了,所以一开始我觉得顺序错了,看到后面发现不应该有顺序,2.1应该是对2.2的补充说明。总的来说还是先看2.2的transformer更好理解些。

另外建议提前了解下word2vec,RNN

一、Transformer概述

在这里插入图片描述
在这里插入图片描述

前面有提到attention是对transformer的补充解释,所以笔记以介绍Transformer为主,attention作为补充知识穿插

1.1、transformer是干什么的

1.1.1相对于传统RNN网络结构一种加强

在这里插入图片描述
在这里插入图片描述

如上图所示:transformer作为一种网络结构取代了传统seq2seq中的RNN模型,解决了并行计算的问题,为模型训练提供了加速能力

用来并行计算的机制主要是Self.attention机制,为了区分一句话中的侧重点信息,即赋予每个单词不同权重,相比较于传统RNN模型多考虑了位置序列信息

在这里插入图片描述
在这里插入图片描述

1.1.2 相对于传统word2vec的区别

传统的word2vec:

  • 训练好的词向量是静态的,固定不变的,同词同向量

Transform:

  • 动态变化的词向量,结合不同的上下文,同一个词的词向量可能是不同的

1.1.3transform的整体架构

在这里插入图片描述
在这里插入图片描述

学习目标 | transform需要做什么?

  • 输入如何编码
  • 输出结果是什么
  • Attention的目的
  • 怎么组合

二 Self.attention机制

2.1、Attention什么意思

  • 对于输入数据,你的关注点是什么
  • 如何才能让计算机关注到这些有价值的信息

2.2、Self.attention是如何计算的

矩阵运算的方式,使得 Self Attention 的计算能够并行化,这也是 Self Attention 最终的实现方式

在这里插入图片描述
在这里插入图片描述
  • 第 1 步:对输入编码器的每个词向量,都创建 3 个向量,分别是:Q向量(query 要去查询的),K向量(key等着被查的),V向量(Value实际的特征信息)。这 3 个向量是词向量分别和 3 个矩阵相乘得到的,而这个矩阵是我们要学习的参数(先初始化,然后调整),最终需要整合的是V向量
  • 第 2 步:计算 Attention Score(注意力分数)

通过计算 “Thinking” 对应的 Query 向量和其他位置的每个词的 Key 向量的点积(内积),而得到的。如果我们计算句子中第一个位置单词的 Attention Score(注意力分数),那么第一个分数就是 q1 和 k1 的内积,第二个分数就是 q1 和 k2 的点积(内积)

第m个词(共n个词)得到n个内积: qmk1,qmk2,、、、qmkm,、、、qnkn 内积分乘法:对应位置元素相乘求和,内积越大,相关性越大

  • 第 3 步:把每个分数除以 (√dkey) (dkey是 Key 向量的长度)

为了剔除向量维度影响,避免向量维度越大分数越高的不合理性

  • 第 4 步:接着把这些分数经过一个 Softmax 层,Softmax可以将分数归一化,这样使得分数都是正数并且加起来等于 1

剔除度量值,比例化,方便对比

  • 第 5 步:得到每个位置的分数后,将每个分数分别与每个 Value 向量相乘
  • 第 6 步:把上一步得到的向量相加,就得到了 Self Attention 层在这个位置的输出。
在这里插入图片描述
在这里插入图片描述

每个词不再代表自己,代表的是全文加权后的特征

总结: 假设所有词向量组成矩阵X ,权重矩阵W^Q ,W^K ,W^V

根据第1步,可得:

Q = X \cdot W^Q
K = X \cdot W^K
V = X \cdot W^V

根据第2~6步,可得Self-Attention的输出

Z = \Sigma\text{softmax}\left( \frac{Q \cdot K^T}{\sqrt{d_{K}}} \right) \cdot V

三、multi-head attention多头注意力机制

类似于卷积神经网络中的filter卷积核与特征图

multi-head:

  • 通过不同的head得到多个特征表达(一般做8个头)
  • 将所有特征拼接在一起
  • 可以通过再一层全连接来降维

通俗地讲,不知道哪种方法好,多做几种方法,最后把几种方法的特征拼接在一起再降维

四、其他相关知识点

4.1堆叠多层:

  • 经过self.attention&多头机制得到的输出还是向量
  • 把得到的向量再重复做self.attention&多头机制继续输出向量
  • 循环多层堆叠,计算方法一致,一般只做一层是不够的

4.2位置信息表达:

在self-attention中每个词都会考虑整个序列的加权,所以其出现位置并不会对结果产生什么影响,相当于放哪都无所谓,但是这跟实际就有些不符合了,我们希望模型能对位置有额外的认识。

解决方法如下图:在embeddings后加一个位置编码

在这里插入图片描述
在这里插入图片描述

传统机器学习一般使用one-hot编码,transformer中使用余弦|正弦的周期性表达

4.3 残差连接

卷积神经网络中《深度残差网络》

在这里插入图片描述
在这里插入图片描述

两条路:

  • 经过self.attention 多头等特征变换
  • 直接把原先特征拿过来

丢到网络中,让网络自己选择最优的特征

残差链接的结果:至少不比原来差

4.4 归一化

传统:batch的归一化 transform:layer层的归一化

五、代码实现

5.1使用PyTorch的MultiheadAttention来实现Attention的计算

torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)

参数说明:

  • embed_dim:最终输出的 K、Q、V 矩阵的维度,这个维度需要和词向量的维度一样
  • num_heads:设置多头注意力的数量。如果设置为 1,那么只使用一组注意力。如果设置为其他数值,那么 num_heads 的值需要能够被 embed_dim 整除
  • dropout:这个 dropout 加在 attention score 后面

forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)

参数说明:

  • query:对应于 Key 矩阵,形状是 (L,N,E) 。其中 L 是输出序列长度,N 是 batch size,E 是词向量的维度
  • key:对应于 Key 矩阵,形状是 (S,N,E) 。其中 S 是输入序列长度,N 是 batch size,E 是词向量的维度
  • value:对应于 Value 矩阵,形状是 (S,N,E) 。其中 S 是输入序列长度,N 是 batch size,E 是词向量的维度
  • key_padding_mask:如果提供了这个参数,那么计算 attention score 时,忽略 Key 矩阵中某些 padding 元素,不参与计算 attention。形状是 (N,S)。其中 N 是 batch size,S 是输入序列长度。
    • 如果 key_padding_mask 是 ByteTensor,那么非 0 元素对应的位置会被忽略
    • 如果 key_padding_mask 是 BoolTensor,那么 True 对应的位置会被忽略
  • attn_mask:计算输出时,忽略某些位置。形状可以是 2D (L,S),或者 3D (N∗numheads,L,S)。其中 L 是输出序列长度,S 是输入序列长度,N 是 batch size。
    • 如果 attn_mask 是 ByteTensor,那么非 0 元素对应的位置会被忽略
    • 如果 attn_mask 是 BoolTensor,那么 True 对应的位置会被忽略
代码语言:javascript
复制
import torch
import torch.nn as nn

## nn.MultiheadAttention 输入第0维为length
# batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
query = torch.rand(12,64,300)
# batch_size 为 64,有 10 个词,每个词的 Key 向量是 300 维
key = torch.rand(10,64,300)
# batch_size 为 64,有 10 个词,每个词的 Value 向量是 300 维
value= torch.rand(10,64,300)

embed_dim = 300
num_heads = 1
# 输出是 (attn_output, attn_output_weights)
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output = multihead_attn(query, key, value)[0]
# output: torch.Size([12, 64, 300])
# batch_size 为 64,有 12 个词,每个词的向量是 300 维
print(attn_output.shape)

torch.Size([12, 64, 300])

5.2使用自编程实现Attention的计算

代码语言:javascript
复制
import torch
from torch import nn


class MultiheadAttention(nn.Module):

    def __init__(self, hid_dim, n_heads, dropout):
        super(MultiheadAttention, self).__init__()
        # 每个词输出的向量维度
        self.hid_dim = hid_dim
        # 多头注意力的数量
        self.n_heads = n_heads

        # 强制 hid_dim 必须整除 n_heads
        assert hid_dim % n_heads == 0
        # 定义 W_q 矩阵
        self.w_q = nn.Linear(hid_dim, hid_dim)
        # 定义 W_k 矩阵
        self.w_k = nn.Linear(hid_dim, hid_dim)
        # 定义 W_v 矩阵
        self.w_v = nn.Linear(hid_dim, hid_dim)
        self.fc = nn.Linear(hid_dim, hid_dim)
        self.do = nn.Dropout(dropout)
        # 缩放
        self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))

    def forward(self, query, key, value, mask=None):
        # K: [64,10,300], batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
        # V: [64,10,300], batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        # Q: [64,12,300], batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        bsz = query.shape[0]
        Q = self.w_q(query)
        K = self.w_k(key)
        V = self.w_v(value)
        # 这里把 K Q V 矩阵拆分为多组注意力,变成了一个 4 维的矩阵
        # 最后一维就是是用 self.hid_dim // self.n_heads 来得到的,表示每组注意力的向量长度, 每个 head 的向量长度是:300/6=50
        # 64 表示 batch size,6 表示有 6组注意力,10 表示有 10 词,50 表示每组注意力的词的向量长度
        # K: [64,10,300] 拆分多组注意力 -> [64,10,6,50] 转置得到 -> [64,6,10,50]
        # V: [64,10,300] 拆分多组注意力 -> [64,10,6,50] 转置得到 -> [64,6,10,50]
        # Q: [64,12,300] 拆分多组注意力 -> [64,12,6,50] 转置得到 -> [64,6,12,50]
        # 转置是为了把注意力的数量 6 放到前面,把 10 和 50 放到后面,方便下面计算
        Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //
                   self.n_heads).permute(0, 2, 1, 3)
        K = K.view(bsz, -1, self.n_heads, self.hid_dim //
                   self.n_heads).permute(0, 2, 1, 3)
        V = V.view(bsz, -1, self.n_heads, self.hid_dim //
                   self.n_heads).permute(0, 2, 1, 3)

        # 第 1 步:Q 乘以 K的转置,除以scale
        # [64,6,12,50] * [64,6,50,10] = [64,6,12,10]
        # attention:[64,6,12,10]
        attention = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale

        # 把 mask 不为空,那么就把 mask 为 0 的位置的 attention 分数设置为 -1e10
        if mask is not None:
            attention = attention.masked_fill(mask == 0, -1e10)

        # 第 2 步:计算上一步结果的 softmax,再经过 dropout,得到 attention。
        # 注意,这里是对最后一维做 softmax,也就是在输入序列的维度做 softmax
        # attention: [64,6,12,10]
        attention = self.do(torch.softmax(attention, dim=-1))

        # 第三步,attention结果与V相乘,得到多头注意力的结果
        # [64,6,12,10] * [64,6,10,50] = [64,6,12,50]
        # Z: [64,6,12,50]
        Z = torch.matmul(attention, V)

        # 因为 query 有 12 个词,所以把 12 放到前面,把 5 和 60 放到后面,方便下面拼接多组的结果
        # Z: [64,6,12,50] 转置-> [64,12,6,50]
        Z = Z.permute(0, 2, 1, 3).contiguous()
        # 这里的矩阵转换就是:把多组注意力的结果拼接起来
        # 最终结果就是 [64,12,300]
        # Z: [64,12,6,50] -> [64,12,300]
        Z = Z.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))
        Z = self.fc(Z)
        return Z



# batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
query = torch.rand(64, 12, 300)
# batch_size 为 64,有 12 个词,每个词的 Key 向量是 300 维
key = torch.rand(64, 10, 300)
# batch_size 为 64,有 10 个词,每个词的 Value 向量是 300 维
value = torch.rand(64, 10, 300)
attention = MultiheadAttention(hid_dim=300, n_heads=6, dropout=0.1)
output = attention(query, key, value)
# output: torch.Size([64, 12, 300])
print(output.shape)

torch.Size([64, 12, 300])

六、篇章小测

问题1: Transformer中的softmax计算为什么需要除以dk?

为了剔除向量维度影响,避免向量维度越大分数叠加越高的不合理性 具体可参考:transformer中的attention为什么scaled?

问题2: Transformer中attention score计算时候如何mask掉padding位置?

在训练的过程中,自然语言数据往往都是以Batch的形式输入进的模型,而一个batch中的每一句话不能保证长度都是一样的,所以需要使用PADDING的方式将所有的句子都补全到最长的长度,比如拿0进行填充,但是我们知道这种用0填充的位置的信息是完全没有意义的,因此我们希望这个位置不参与后期的反向传播过程。以此避免最后影响模型自身的效果,因此提出了在训练时将补全的位置给Mask掉的做法。而在Self-attention的计算当中,我们自然也不希望有效词的注意力集中在这些没有意义的位置上,因此使用了PADDING MASK的方式. PADDING MASK在attention的计算过程中处于softmax之前,通过PADDING MASK的操作,使得补全位置上的值成为一个非常大的负数(可以是负无穷),这样的话,经过Softmax层的时候,这些位置上的概率就是0。以此操作就相当于把补全位置的无用信息给遮蔽掉了(Mask掉了) 具体可参考:Transformer 中self-attention以及mask操作的原理以及代码解析

问题3: 为什么Transformer中加入了positional embedding?

位置和顺序对于一些任务十分重要,例如理解一个句子、一段视频。位置和顺序定义了句子的语法、视频的构成,它们是句子和视频语义的一部分。循环神经网络RNN本质上考虑到了句子中单词的顺序。因为RNN以顺序的方式逐字逐句地解析一个句子,这将把单词的顺序整合到RNN中。 Transformer使用MHSA(Multi-Head Self-Attention),从而避免使用了RNN的递归方法,加快了训练时间,同时,它可以捕获句子中的长依赖关系,能够应对更长的输入。 当句子中的每个单词同时经过Transformer的Encoder/Decoder堆栈时,模型本身对于每个单词没有任何位置/顺序感 (permutation invariance)。 因此,仍然需要一种方法来将单词的顺序信息融入到模型中。 为了使模型能够感知到输入的顺序,可以给每个单词添加有关其在句子中位置的信息,这样的信息就是位置编码(positional embedding, PE) 具体可参考:Transformer 中的 positional embedding

其他参考资料

attention主要的发展路径及目前的主流方法 唐宇迪老师-深度学习-PyTorch框架实战系列 面经:什么是Transformer位置编码? 深度学习attention机制中的Q,K,V分别是从哪来的?

插个题外话: csdn的markdown写it类的文章还是比较方便的,但是我要微信公众号上再排版一遍还是比较麻烦的,今天发现一个特别的东西,可以一键转微信公众号排版,还是比较方便的,

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 组队学习资料:
  • 一、Transformer概述
    • 1.1、transformer是干什么的
    • 二 Self.attention机制
      • 2.1、Attention什么意思
        • 2.2、Self.attention是如何计算的
        • 三、multi-head attention多头注意力机制
        • 四、其他相关知识点
          • 4.1堆叠多层:
            • 4.2位置信息表达:
              • 4.3 残差连接
                • 4.4 归一化
                • 五、代码实现
                  • 5.1使用PyTorch的MultiheadAttention来实现Attention的计算
                    • 5.2使用自编程实现Attention的计算
                    • 六、篇章小测
                    • 其他参考资料
                    相关产品与服务
                    GPU 云服务器
                    GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档