Mamba学习笔记(2)—序列数据处理基础

发布于:2024-10-18 ⋅ 阅读:(7) ⋅ 点赞:(0)

(1) RNN(Recurrent Neural Networks)

循环神经网络(RNN,Recurrent Neural Networks) 是一种用于处理序列数据的神经网络,广泛应用于时间序列预测、自然语言处理(NLP)、语音识别等领域。RNN的独特之处在于其具有“记忆”能力,即能够通过隐藏层将前一个时间步的输出作为当前时间步的输入,从而保留和利用序列中的上下文信息。

  • (1)RNN的输出取决于当下输入和前一时间的隐变量
  • (2)应用到语言模型中时,RNN根据当前词预测下一次时刻词
  • (3)通常使用困惑度来衡量语言模型的好坏

基本原理

RNN中的信息是通过时间步递归传递,RNN通过隐藏层中的循环连接,将每个时间步的信息与前面的时间步关联起来,适合处理具有时间或序列依赖性的数据。在RNN中,每个输入不仅仅依赖于当前的输入值,还依赖于前一个时间步的隐藏状态。

  • 输入序列 X=(x1, x2,...,xT),其中 xt是在时间步t的输入。
  • 隐藏层计算 ht =f(Wxh·xt+Whh·xt-1+bh),其中ht表示时间步t的隐藏状态,Wxh表示输入到隐藏层的权重矩阵,Whh表示前一时间步的隐藏状态到当前隐藏状态的权重矩阵,bh表示偏置项,f通常为非线性激活函数(tanh
  • 输出计算 yt =g(Why·ht+by),其中Why是隐藏层到输入层的权重矩阵,by是偏置项,g为激活函数(softmax)

代码定义

import torch
import torch.nn as nn

# 定义一个RNN模型
class RNNModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(RNNModel, self).__init__()
        # 参数解释:
        # input_size: 输入特征的维度
        # hidden_size: 隐藏层状态的维度
        # output_size: 输出特征的维度
        # num_layers: RNN的层数
        
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        
        # 定义RNN层
        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
        
        # 定义输出层 (线性层)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        # 初始化隐藏状态 h0,尺寸是 (num_layers, batch_size, hidden_size)
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        
        # 前向传播 RNN (输入x通过RNN层)
        out, _ = self.rnn(x, h0)  # out: tensor of shape (batch_size, seq_length, hidden_size)
        
        # 选择最后一个时间步的输出,用于分类或回归
        out = out[:, -1, :]  # 取最后一个时间步的输出
        
        # 将RNN的输出通过线性层 (fc层)
        out = self.fc(out)
        return out

(2) SLTM (Long Short-Term Memory)

LSTM是循环神经网络(RNN)的改进版本,专门用来解决标准RNN中的梯度消失和梯度爆炸问题,使得它在长序列数据的依赖建模上表现优异。

三个门控单元(Gate)

  • 遗忘门(Forget Gate)
  • 输入门(Input Gate)
  • 输出门(Output Gate)

基本原理

在每个时间步,LSTM单元不仅有标准RNN中的隐藏状态ht,还包含一个额外的细胞状态Ct,用于传递长期以来信息

对于任一时间步t

  • Forget Gate 控制对Ct-1中信息的遗忘量,将值朝0减少:ft =sigmod(Wxf·[Xt, ht-1]+bf)
  • Input Gate 决定要向细胞状态中添加多少新的信息:首先通过一个Sigmoid函数计算输入门的激活值:it =sigmod(Wi[Xt, ht-1]+bf),然后通过tanh函数生成新的候选细胞状态:Ct~=tanh(Wc·[Xt, ht-1]+bc)
  • Cell State 使用遗忘门ft控制上一时间步的细胞状态Ct-1保留多少信息,同时用输入门it控制新信息的添加:Ct=ft*Ci-1+it*Ct~
  • Output Gate 控制当前时间步输出多少隐藏状态ht,通过一个Sigmoid函数计算输出门的激活值,再通过tanh函数对更新后的细胞状态进行缩放,生成最终的隐藏状态:ot =sigmod(Wo·[Xt, ht-1]+bo)ht =ot*tanh(Ct)

代码定义

import torch
import torch.nn as nn

# 定义LSTM模型
class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(LSTMModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        
        # 定义LSTM层
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        
        # 定义输出层 (线性层)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        # 初始化LSTM的隐藏状态和细胞状态
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        
        # 前向传播 LSTM
        out, _ = self.lstm(x, (h0, c0))  # out: tensor of shape (batch_size, seq_length, hidden_size)
        
        # 选择最后一个时间步的输出
        out = out[:, -1, :]
        
        # 将LSTM的输出通过线性层
        out = self.fc(out)
        return out

(3) GRU (Gated Recurrent Unit)

GRU 是一种改进的循环神经网络(RNN)变体,通过引入重置门(Reset Gate)更新门(Update Gate) 来控制信息的流动,避免了标准RNN中的梯度消失问题。

两个门控单元

  • Reset Gate
  • Update Gate

基本原理

GRU和LSTM的核心思想类似,都是为了处理长时间依赖问题。然而,GRU相比LSTM具有更少的参数,因为它没有像LSTM那样的细胞状态(Cell State),也没有输入门和遗忘门,因此计算效率更高。

  • Update Gate 控制前一时间步的隐藏状态ht-1有多少信息被保留,类似LSTM中的Update Gate和Input Gate:zt =sigmod(Wz·[Xt, ht-1]+bz)
  • Reset Gate 控制前一时间步的隐藏状态ht-1有多少信息与当前时间步的输入结合,决定旧信息的重要性,即是否忘记之前的信息:rt=sigmod(Wr·[Xt, ht-1]+br)
  • 候选隐藏状态:利用rt和当前输入计算当前时间步的候选隐藏状态ht~ht~=tanh(Wh·[Xt, rt*ht-1]+bh)
  • 最终隐藏状态:结合zt的输出,决定最终的隐藏状态ht。更新门zt控制着当前时间步的隐藏状态是更多依赖于过去的信息,还是更多依赖于当前时间步的候选状态:ht=zt*ht-1+(1-zt)*ht~

代码定义

import torch
import torch.nn as nn

# 定义GRU模型
class GRUModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(GRUModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        
        # 定义GRU层
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        
        # 定义输出层 (线性层)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        # 初始化GRU的隐藏状态
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        
        # 前向传播 GRU
        out, _ = self.gru(x, h0)  # out: tensor of shape (batch_size, seq_length, hidden_size)
        
        # 选择最后一个时间步的输出
        out = out[:, -1, :]
        
        # 将GRU的输出通过线性层
        out = self.fc(out)
        return out

(4) Transformer(☆☆☆Attention Is All You Need☆☆☆)

  • Transformer架构
  • 自注意力机制
  • 多头注意力

0 Abstract

当前主流的序列转导模型基于复杂的循环神经网络(RNN)或卷积神经网络(CNN),这些网络包含编码器和解码器。表现最好的模型通过注意力机制将编码器和解码器连接起来。我们提出了一种全新的简单网络架构——Transformer,它完全依赖于注意力机制,完全摒弃了循环和卷积网络。在两个机器翻译任务上的实验表明,这些模型在翻译质量上具有更高的性能,同时更易于并行化,训练时间也显著减少。在WMT 2014英语-德语翻译任务上,我们的模型取得了28.4的BLEU分,比现有的最佳结果(包括集成模型)提高了2个BLEU分。在WMT 2014英语-法语翻译任务上,我们的模型在8个GPU上训练了3.5天后,达到了41.0的BLEU分,创下了新的单模型记录,而其训练成本仅为文献中最优模型的一小部分。

序列转导模型(sequence transduction models) 是一类将一个输入序列转换为一个输出序列的模型。序列转导模型的核心挑战:

  • 捕捉长距离依赖关系:模型需要在处理序列时考虑远距离的依赖关系。例如,在翻译句子时,句首的词可能会影响到句尾的翻译。
  • 并行处理:传统的RNN等模型是逐步处理序列的,难以并行,而Transformer通过自注意力机制解决了这个问题,大幅提升了训练效率。

1 Introduction

循环神经网络(RNN),特别是长短期记忆网络(LSTM)和门控循环单元(GRU),已经被广泛认为是序列建模和转导问题(如语言建模和机器翻译)的先进方法。自此之后,许多研究努力继续推进循环语言模型和编码器-解码器架构的边界。
循环模型通常沿着输入和输出序列中的符号位置进行计算。通过将符号位置与计算时间步对齐,它们生成隐藏状态序列ht,该状态是前一隐藏状态ht-1和位置t的输入函数。这种固有的顺序特性限制了训练示例中的并行化,特别是在序列较长时,内存约束会限制跨示例的批处理。最近的工作通过分解技巧和条件计算在计算效率上取得了显著的改进,同时也提高了模型性能。然而,顺序计算的基本限制仍然存在。
注意力机制已成为各种任务中极具吸引力的序列建模和转导模型的重要组成部分,它能够建模输入和输出序列中不受距离影响的依赖关系。然而,除少数情况外,这些注意力机制通常与循环网络结合使用。
在这项工作中,我们提出了Transformer,一种不再使用循环,而是完全依赖注意力机制来在输入和输出之间建立全局依赖关系的模型架构。Transformer允许显著的并行化,并且在只需要12小时、使用8个P100 GPU的训练后就能达到新的翻译质量标准。

传统方法的局限性
循环神经网络(如LSTM、GRU)是序列建模的主流方法,广泛用于语言建模和机器翻译等任务。然而,这些模型存在计算上的顺序性限制,使得它们在处理长序列时难以并行化,导致训练效率低下。

  • 顺序性限制 指的是计算必须严格按照输入序列的顺序进行。这意味着每个时间步的输出依赖于前一个时间步的计算结果,导致:(1)无法并行处理:在RNN中,处理序列中的每个元素(如单词、字符)都需要等待前一个元素的处理完成;(2)处理长序列时效率低下:当输入序列很长时,模型需要逐步遍历整个序列,无法跳过;(3)梯度消失:模型难以有效捕捉序列中前后距离较远的依赖关系。
  • 梯度消失 梯度消失是指在深度神经网络中,反向传播时梯度逐层衰减到接近零,导致参数无法有效更新。

注意力机制的作用
注意力机制在各种序列任务中已经被证明是有效的,尤其在处理长距离依赖时表现优异。然而,大多数模型依然结合了循环网络,未能充分利用注意力机制的并行优势。

Transformer模型的创新 >>>完全基于注意力机制

2 Background

减少顺序计算的目标也构成了扩展神经GPU(Extended Neural GPU)、ByteNet和ConvS2S等模型的基础,这些模型都使用卷积神经网络作为基本构件,能够并行计算所有输入和输出位置的隐藏表示。在这些模型中,关联任意输入或输出位置所需的操作数量随着位置之间的距离而增加:在ConvS2S中是线性增长的,而在ByteNet中是对数增长的。这使得学习远距离位置之间的依赖关系更加困难。
在Transformer模型中,这一依赖关系的操作数减少为常数,尽管代价是通过注意力加权位置的平均,导致有效分辨率的降低。我们通过多头注意力机制(Multi-Head Attention)来应对此效应。
自注意力机制,有时称为内注意力机制,是指同一序列中不同位置之间的关联,用于计算该序列的表示。自注意力机制已经成功应用于多种任务,包括阅读理解、抽象摘要生成、文本蕴含和学习任务无关的句子表示。
端到端记忆网络(End-to-End Memory Networks)基于递归注意力机制,而不是序列对齐的循环网络,在简单语言问题回答和语言建模任务中表现良好。
据我们所知,Transformer是第一个完全依赖自注意力机制来计算输入和输出表示的转导模型,不再使用序列对齐的RNN或卷积网络。在接下来的部分中,我们将描述Transformer模型,阐明自注意力机制,并讨论其相对于其他模型的优势。

传统模型的局限性
像扩展神经GPU(Extended Neural GPU)、ByteNet和ConvS2S等基于卷积神经网络的模型在处理序列任务时,随着位置间距离的增加,操作数也会线性或对数增长。这使得它们难以捕捉长距离的依赖关系。

Transformer的优势
相比之下,Transformer通过自注意力机制减少了依赖关系的操作数,将其缩减为常数,从而有效解决了远距离依赖问题。同时,通过引入多头注意力机制,Transformer克服了由于注意力加权平均导致的分辨率降低问题。

自注意力的成功应用
自注意力机制已经在多个任务中表现出色,包括阅读理解、摘要生成和文本蕴含,这表明它在序列建模中具有广泛的适用性。

Transformer的创新 >>>第一个完全依赖自注意力机制的序列转导模

3 Model Architecture

在这里插入图片描述
大多数的神经序列转导模型都采用编解码器结构。在这种结构中,编码器将输入的符号序列(x1, ..., xn)映射为连续的表示序列z = (z1, ..., zn)。基于这个表示z解码器会逐个生成符号输出序列(y1, ..., ym)。模型在每一步都是自回归的,即在生成下一个符号时,使用前面生成的符号作为额外输入。Transformer遵循这种整体架构,在编码器和解码器中都使用堆叠的自注意力机制逐点全连接层

自回归(Auto-regressive) 是一种模型生成序列的方式,在生成序列的过程中,每一步的输出都依赖于前面已经生成的内容。在自回归模型中,模型在生成序列中的每个元素时,不仅依赖于输入数据,还会使用之前生成的元素作为输入。这意味着:

  • 逐步生成:模型一次只生成一个元素(如一个单词或字符)。
  • 依赖之前的输出:当前步骤生成的元素依赖于模型之前生成的所有元素。

自回归的过程 假设要生成一个序列(y1, ..., ym),自回归模型的生成过程如下:

  • 1、首先基于初始输入(如编码器的输出)生成第一个元素y1
  • 2、然后,使用y1作为输入之一,生成第二个元素y2
  • 3、接着,使用y1y2生成y3,依次类推,直至生成完整的序列。

Transformer中的自回归
在Transformer的解码器中,自回归的特性被保留。解码器会在生成每个输出时,使用之前生成的所有符号,同时通过自注意力机制确保每个生成步骤只考虑到当前位置之前的输出,以保持序列的正确性。

3.1 Encoder and Decoder Stacks

Encoder:编码器由一个堆叠的N=6个相同的层组成。每一层都有两个子层。第一个子层是一个多头自注意力机制(multi-head self-attention mechanism),第二个子层是一个简单的、逐位置的全连接前馈网络(position wise fully connected feed-forward network)。我们在每个子层周围使用了残差连接(Residual Connection),并跟随以层归一化(Layer Normalization)。也就是说,每个子层的输出是LayerNorm(x+Sublayer(x)),其中Sublayer(x)是由该子层实现的函数。为了使残差连接能够正常工作,模型中的所有子层以及嵌入层的输出维度都是512
Decoder:编码器由一个堆叠的N=6个相同的层组成。除了每个编码器层中的两个子层之外,解码器还在每一层中插入了第三个子层,该子层执行对编码器堆叠输出的多头注意力机制。与编码器类似,我们在每个子层周围使用了残差连接,并跟随以层归一化。我们还对解码器堆叠中的自注意力子层进行了修改,以防止某个位置关注到后续位置。这种掩码机制与输出嵌入向后偏移一个位置相结合,确保位置i的预测只能依赖于i之前已知的输出。

逐位置的全连接前馈网络(position wise fully connected feed-forward network) 本质上就是一个多层感知机(MLP)

LayerNorm && BatchNorm

  • LayerNorm是在每个样本的每一层内进行归一化操作的。它对每个样本的特征维度进行标准化,而不是在batch上操作。
  • BatchNorm是在每一层的所有输入样本之间进行归一化。它在整个batch上计算均值和方差,对每个样本的特征进行归一化。

Masked Multi-Head Attention
对于解码器而言,生成当前输出时,不能参考未来的输出,因此需要一种机制来屏蔽(mask)未来的信息。
MaskedAttention(Q,K,V)=softmax(f(Q, K)+M)V
掩码矩阵M, 当超过当前位置时(j>i),则M=-∞,否则M=0

3.2 Attention

注意力函数可以描述为将一个查询(query)和一组键值对(key-value pairs)映射到一个输出,其中查询、键、值和输出都是向量。输出是通过对值的加权和计算得到的,而每个值的权重是通过查询与对应键之间的兼容性函数计算得出的。

查询用于从一组中找到最相关的信息,并根据匹配结果(即权重)对对应的进行加权求和,从而生成输出。

  • Query(查询) 代表当前我们需要“关注”的信息。例如,在处理一个序列中的某个词时,查询向量代表该词的特征,它用于查询其他词与该词的相关性。
  • Key(键) 是用于和查询匹配的。每个输入的特征都会对应一个键,它代表该输入的特征与查询的相关性程度。
  • Value(值) 包含实际信息的向量,当我们根据查询和键计算出相关性之后,值会参与加权求和,最终得到输出。

Transformer中的QKV

  • Q 代表我们当前处理的词(或者当前词的特征表示),即当前我们要“关注”的对象。
  • K 代表句子中所有词的特征表示,与Query进行匹配以计算相关性,用于衡量与查询对象的匹配程度。
  • V 代表每个词的实际信息,结合相关性(权重)生成新的词表示,包含实际信息,根据匹配结果进行加权后用于输出。

例子
假设我们有一个句子:“The cat sat on the mat”。在处理句子时,假设“sat”是当前的查询(query),模型会计算它与句子中每个词(key)的相关性,如“cat”、“on”、“the”等。通过这个过程,模型会知道“sat”可能更多地与“cat”相关,而与“mat”或“on”的相关性较低。根据这种相关性,模型会从这些词的中提取信息,用来更新“sat”的表示。

3.2.1 Scaled Dot-Product Attention

在这里插入图片描述
我们称我们的注意力机制为“缩放点积注意力”(Scaled Dot-Product Attention)。输入由查询(queries)、键(keys)和值(values)组成,它们的维度分别为 dkdkdv。我们首先计算查询所有键点积,然后将其除以sqrt(dk),最后应用softmax函数来获得键对应的权重分布。然后,这些权重再用于对的加权求和,得到最终的输出。
在实际操作中,我们会同时对一组查询进行计算,并将它们打包在矩阵Q中。键和值也分别打包成矩阵KV。输出矩阵计算公式如下:
在这里插入图片描述
两种常用的注意力函数是加性注意力(additive attention)和点积(乘法)注意力(dot-product or multiplicative attention)。点积注意力与我们的算法相同,除了多了一个缩放因子1/sqrt(dk)。加性注意力通过一个包含单隐藏层的前馈网络来计算兼容性函数。虽然两者在理论复杂度上类似,但点积注意力在实际应用中更快、更节省空间,因为它可以通过高度优化的矩阵乘法代码来实现。
对于较小的 dk值,两个机制的表现相似,但当 dk较大时,未缩放的点积注意力的表现会下降。我们推测,这是因为点积值会随着 dk的增大而变大,将softmax函数推入到梯度非常小的区域。为了抵消这一影响,我们将点积除以sqrt(dk)来进行缩放。

缩放点积注意力机制是通过计算查询与键的点积来获得注意力分数,并使用softmax函数将这些分数转换为权重,进而对值进行加权求和。同时,引入了缩放因子sqrt(dk)来避免数值过大导致梯度消失的问题。

3.2.2 Multi-Head Attention

在这里插入图片描述
与其只使用一个注意力机制,我们发现将查询、键和值线性投影到不同的子空间进行多个注意力操作(即多头注意力)是有益的。我们将查询、键和值分别线性投影h次得到dkdkdv维度的不同表示。然后,我们在这些投影版本上并行地执行注意力机制,得到dv维的输出。最后,我们将这些输出拼接(concatenate)在一起,并进行一次线性变换,生成最终的输出。
多头注意力允许模型在不同的表示子空间中,从不同的位置并行地关注信息。如果只有一个注意力头,所有信息都会被平均化,从而限制了模型的表达能力。
具体来说,多头注意力的公式如下:
在这里插入图片描述
其中每个头的计算为:
在这里插入图片描述
在我们的模型中,我们使用h=6个并行的注意力头。对于每个头,我们令dk=dv=dmodel/h=64。由于每个头的维度较小,总的计算开销与单头注意力机制相比几乎没有增加。

为什么使用多头?
多头注意力的主要目的是增强模型的表达能力,让模型可以在不同的表示子空间中并行地“关注”不同的部分。

  • 不同的子空间表示:每个头都有自己独立的查询、键和值投影矩阵,因此每个头在不同的特征空间内捕捉依赖关系。这使得模型能够从多个角度学习到输入序列的不同特征,例如捕捉长距离依赖关系和局部依赖关系。
  • 并行处理不同依赖关系:多头注意力机制允许模型在多个注意力头中并行地处理不同位置之间的依赖关系,从而避免了单头注意力的单一视角。不同的头可以捕捉不同类型的依赖,比如某个头可能专注于局部上下文,另一个头则专注于长距离依赖。
  • 更丰富的信息表示:通过多个头,模型能够学习到更丰富的输入表示,这在序列任务中尤为重要,尤其是像机器翻译、文本生成这类需要捕捉复杂语言依赖关系的任务。
3.2.3 Applications of Attention in our Model

在Transformer中,多头注意力机制有三种不同的应用方式:

  • 编码器-解码器注意力层:在这些层中,查询来自前一层的解码器,而记忆中的键和值来自编码器的输出。这使得解码器中的每个位置可以关注输入序列中的所有位置。这种方式模仿了序列到序列模型中常见的编码器-解码器注意力机制。
  • 编码器中的自注意力层:在自注意力层中,键、值和查询都来自同一个地方,也就是来自编码器中前一层的输出。编码器中的每个位置都可以关注前一层中的所有位置。
  • 解码器中的自注意力层:类似地,解码器中的自注意力层允许解码器中的每个位置关注解码器中到该位置为止的所有位置。为了保持自回归特性,我们必须防止解码器中的当前位置关注到将来的位置。我们通过在缩放点积注意力机制中屏蔽softmax输入中与非法连接对应的部分(将其设置为-(\infty))来实现这一点。
  • 编码器-解码器注意力 用于解码器关注编码器的输出,确保解码器可以获取输入序列的全局信息。
  • 编码器自注意力 允许编码器内部的每个位置关注序列中的所有位置。
  • 解码器自注意力 通过掩码确保解码器只能关注到已生成的部分,而不能看到未来的输出,保持生成的顺序性。

自注意力机制

  • 从表面上理解为“将输入序列复制三份”,分别作为查询、键和值进行操作,但更准确的理解是,输入序列经过三个不同的线性变换生成了查询、键和值,这些向量分别代表不同的角色,并通过注意力机制计算相似度和加权求和来动态组合输入信息,捕捉序列中的依赖关系。。这种设计使得自注意力机制能够高效、灵活地处理长序列中的复杂依赖关系。
3.3 Position-wise Feed-Forward Networks

除了注意力子层外,编码器和解码器中的每一层还包含一个全连接的前馈网络,它独立地应用于序列中的每个位置。这种前馈网络包含两个线性变换,中间使用ReLU激活函数:
在这里插入图片描述
虽然线性变换在不同位置是相同的,但它们使用的参数在不同的层是不同的。另一种描述方式是,前馈网络可以看作是两个卷积核大小为1的卷积操作。输入和输出的维度是512,而内部层的维度是2048

Position-wise Feed-Forward Networks本质上就是一个MLP 与传统MLP的唯一不同点是处理方式的逐位置独立性:

  • MLP通常用于全局输入,比如图像分类中的特征向量,而逐位置前馈网络是在序列模型中使用的,对序列中的每个位置独立处理。
3.4 Embeddings and Softmax

为了将输入和输出序列中的符号(symbols)转换为连续的表示(continuous representations),我们使用了与Vaswani等人模型相同的嵌入方法。具体来说,我们为编码器和解码器的输入嵌入(以及解码器的最终输出层之前的输出嵌入)增加了一个维度为dmodel的可学习嵌入层。我们使用相同的嵌入矩阵用于将词汇表中的符号投影到模型的隐藏维度,并将解码器的输出投影回词汇表维度。嵌入层和Softmax之间的权重矩阵是共享的。此外,我们将这些嵌入乘以sqrt(dmodel)来对它们进行缩放。

embeddings

  • 当前输入是一个个词(token),因此需要将其映射为一个向量。embeddings就是对于任意一个词,学习一个长为d的向量来表示他,当前的dmodel=512
  • 将离散的输入数据(例如单词)转换为连续的向量表示。每个单词通过一个嵌入矩阵映射到一个固定长度的实数向量,这个向量表示该单词在高维空间中的位置。
3.5 Positional Encoding

由于我们的模型中没有递归或卷积网络,为了让模型利用序列中符号的顺序信息,我们必须为序列中的每个位置注入一些位置信息。因此,我们为编码器和解码器的输入嵌入中添加了位置编码(positional encodings),这些编码与嵌入具有相同的维度dmodel,这样两者可以相加。位置编码允许模型利用序列中的位置信息。
我们有多种可能的选择来生成位置编码,尤其是可以学习的位置编码或是固定的位置编码。在这项工作中,我们使用了不同频率的正弦和余弦函数来生成位置编码。对于输入序列中的位置pos和嵌入维度中的第i个维度,位置编码的计算方式如下:
在这里插入图片描述
也就是说,位置pos的每个维度都对应着不同频率的正弦或余弦函数。我们选择这种函数的动机是希望位置编码能够为模型提供对相对位置的顺序敏感性,而且这种编码能够推广到比训练序列更长的序列。
位置编码与嵌入相加后,得到的输入将包含位置信息,从而使模型能够识别符号在序列中的位置。

为什么需要位置编码?
Transformer模型通过自注意力机制并行处理输入序列中的所有符号,而不像RNN按顺序逐步处理,也不像CNN依赖固定的卷积窗口。因此,模型本身并不能自动感知输入符号的顺序。为了让模型能够处理和捕捉序列的位置信息,必须在每个符号的嵌入向量中加入位置信息。

4 Why Self-Attention

在这里插入图片描述
自注意力机制能够捕捉到序列中任意两个位置之间的依赖关系,并且仅使用少量的计算步骤来完成这一点。例如,在一个长度为n的序列中,自注意力机制能够在一次计算中直接关联任意两个位置,而循环网络(RNN)可能需要n步才能将两个位置的依赖关系传递到一起。相比之下,自注意力机制的计算复杂度每层是O(n^2d),而循环网络是O(nd^2),其中
𝑑是表示的维度大小。
对于短序列,循环网络的计算复杂度可能更低,但由于自注意力机制能够提供更高的并行化能力,其优势在长序列任务中表现得尤为明显。例如,使用非常高效的矩阵乘法库(如GEMM),可以在现代机器学习硬件(如GPU或TPU)上高效地并行计算自注意力机制。相反,循环网络的计算依赖于上一个时间步的输出,因此不能进行并行化。
自注意力机制还能减少每层中距离依赖关系所需的路径长度。路径长度是指网络必须沿着计算图传播信号以关联输入和输出之间的依赖关系的步数。在循环网络中,路径长度与输入序列的长度成比例O(n),而在自注意力机制中,这个路径长度是常数O(1)。较短的路径长度使得信号可以更容易传播,从而更有效地捕捉长距离依赖关系。
另一个优点是自注意力机制更适合并行化。与循环网络需要顺序计算不同,自注意力机制能够一次性对所有位置进行并行计算。因此,在训练和推理时,自注意力机制可以极大地提高效率,尤其是在硬件并行化能力强的现代GPU或TPU上。

  • 计算复杂度 自注意力机制的计算复杂度相比RNN更高效,尤其在处理长序列时可以并行化,大幅度提升效率。
  • 路径长度更短 自注意力机制能够更直接地捕捉远距离的依赖关系,使得模型更好地处理序列中的长距离依赖。
  • 并行化能力强 自注意力机制的并行计算能力显著提升了训练和推理的速度,特别适合在现代硬件上实现高效的计算。