Transformer学习总结


Transformer架构

一、Self-Attention详解

Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V Attention(Q,K,V)=softmax(dk QKT)V
Q(Query)、K(Key)、V(Value)、 d k d_k dk为Key向量的维度

1、如何计算Q、K、V

Q、K、V计算逻辑
如图中所示,Q、K、V通过输入对应的嵌入向量与对应的 W Q 、 W K 、 W V W^Q、W^K、W^V WQWKWV相乘得到, W Q 、 W K 、 W V W^Q、W^K、W^V WQWKWV是模型训练过程中学习到的权重矩阵,通过初试化、前向传播、计算损失、反向传播等方式进行更新,以达到最优的结果。

2、 Q K T QK^T QKT相乘的原理是什么

在自注意力机制中,我们计算 Q K T QK^T QKT 来衡量查询向量和键向量之间的相关性或相似性。
$Q$和 $K$通常表示多个查询和键向量组成的矩阵。设 $Q$是一个形状为 n × d k n \times d_k n×dk 的矩阵, K K K是一个形状为 m × d k m \times d_k m×dk的矩阵,其中 n n n m m m 分别为查询和键的数量, d k d_k dk 为它们的维度。
Q K T QK^T QKT的结果是一个形状为 n × m n \times m n×m的矩阵,其中每个元素 ( i , j ) (i, j) (i,j)表示第 i i i个查询向量 Q i Q_i Qi与第 j j j个键向量 K j K_j Kj 的内积。这些值反映了每个查询向量对每个键向量的注意力权重。

3、为什么要除以 d k \sqrt{d_k} dk ,以及为什么要开根号

计算 Q K T QK^T QKT时值可能会变得非常的大,在进一步计算中(如 s o f t m a x softmax softmax 操作)出现数值不稳定的问题,例如梯度爆炸或消失。
采用 d k \sqrt{d_k} dk 而不是其他数值是因为这样的缩放与下列情况有直接关系:高维向量的长度在期望上与维度的平方根成比例,如果输入是均匀随机向量,这样可以更公平地处理不同维度的向量
详细过程如下:
我们考虑一个高维向量,它的每一个元素是从均值为0,标准差为1的均匀或者标准正态分布中采样得到的随机变量。这个向量的维度为 d k d_k dk。我们要计算这个向量的长度,即它的欧几里得范数:
∥ x ∥ 2 = x 1 2 + x 2 2 + ⋯ + x n 2 \| \mathbf{x} \|_2 = \sqrt{x_1^2 + x_2^2 + \cdots + x_n^2} x2=x12+x22++xn2
由于每个 x i x_i xi是一个独立随机变量,并且服从均值为0和方差为1的分布,得到的平方
x i 2 x_i^2 xi2服从方差为1的卡方分布。从统计学中我们知道,求和的均值将是这些变量的期望之和,因此期望是 d k ⋅ E ( x i 2 ) d_k⋅E(x_i^2) dkE(xi2)。由于每个 x i 2 x_i^2 xi2的期望为1,算出来的期望是 d k d_k dk
因此,向量的期望的平方根(即均方根值)就是 d k \sqrt{d_k} dk

4、计算过程

详细的整体计算过程
将得到的 S o f t m a x Softmax Softmax分数分别与每个Value向量相乘。这种办法的原理是,相乘后的值越大,就将更多的注意力放在他们身上。对于分数低的地方,这些位置的的词相关性不大,便可以忽略掉这些未知的词。

二、多头注意力机制(Multi-head Attention)

在这里插入图片描述
原理:通过 h h h个不同的线性变换对Q、K、V进行映射,然后将得到的h个不同的Attention拼接起来,再通过一个线性变换就可以得到Multi-head Attention。
本质:将同样的Q、K、V映射到原来的高维空间的不同子空间中进行Attention计算,最后再合并不同子空间的Attention信息。这样降低了每个head的Attention时没个向量的维度,在某种意义上防止了过拟合;由于Attention的不同的子空间有不同的分布,Multi-head Attention实际上是寻找了序列之间不同角度的关联关系,最后通过拼接这一步骤,将不同子空间捕获的关联关系再综合起来。
Scaled Dot-Product Attention(缩放点积注意力):表示子注意力机制的计算过程

# [2, 4, 100]/[2, 6, 100], 5
def transpose_qkv(X, num_heads):
    """为了多注意力头的并行计算而变换形状"""
    # 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,num_hiddens/num_heads)
    # reshape(-1):首先把张量中的所有元素平铺,然后在变形成指定的形状
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)  # 2*4*100/2*4*5 = 20, -1就代表20
    # print(X.size())
 
    # 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数, num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)  # 更改矩阵形状 torch.Size([2, 5, 4, 20])
    # print(X.size())
 
    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数, num_hiddens/num_heads)
    output = X.reshape(-1, X.shape[2], X.shape[3])  # 2*5*4*20/4*20 = 10, -1就代表10
    print("transpose_qkv:", output.size())
 
    return output
 
# [10, 4, 20], 5
def transpose_output(X, num_heads):
    """逆转transpose_qkv函数的操作"""
    # print(X.size())
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])  # [2, 5, 4, 20]
    X = X.permute(0, 2, 1, 3)  # [2, 4, 5, 20]
    output = X.reshape(X.shape[0], X.shape[1], -1)  # [2, 4, 100]
    print("transpose_output:", output.size())
 
    return output
 
class MultiHeadAttention(nn.Module):
    """多头注意力"""
    # 100, 100, 100, 100, 5, 0.5
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.DP_Attention = d2l.DotProductAttention(dropout)  # 缩放点积注意力,舍弃50%的神经元参数
 
        # 输入样本的大小、输出样本的大小、偏置设置为False
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)  # [100, 100]
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)  # [100, 100]
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)  # [100, 100]
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)  # [100, 100]
     # [2, 4, 100], [2, 6, 100], [2, 6, 100], torch.tensor([3, 2])
    def forward(self, queries, keys, values, valid_lens):
        # queries,keys,values的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
        # valid_lens 的形状:(batch_size,)或(batch_size,查询的个数)
        # 经过变换后,输出的 queries,keys,values 的形状:
        # (batch_size*num_heads,查询或者“键-值”对的个数,num_hiddens/num_heads)
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)
        # print(queries)
 
        if valid_lens is not None:
            # 在轴0,将第一项(标量或者矢量)复制num_heads次,
            # 然后如此复制第二项,然后诸如此类。
            valid_lens = torch.repeat_interleave(valid_lens, repeats=self.num_heads, dim=0)
            # print(valid_lens)  # tensor([3, 3, 3, 3, 3, 2, 2, 2, 2, 2])
            print("valid_lens:", valid_lens.size())
 
        # output的形状:(batch_size*num_heads,查询的个数,num_hiddens/num_heads)
        # torch.Size([10, 4, 20])、torch.Size([10, 6, 20])、torch.Size([10, 6, 20])、torch.Size([10])
        output = self.DP_Attention(queries, keys, values, valid_lens)
 
        # output_concat的形状:(batch_size,查询的个数,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)
num_hiddens, num_heads = 100, 5
attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
                               num_hiddens, num_heads, 0.5)
attention.eval()
 
batch_size, num_queries = 2, 4
num_kvpairs, valid_lens = 6, torch.tensor([3, 2])
X = torch.ones((batch_size, num_queries, num_hiddens))  # [2, 4, 100]
Y = torch.ones((batch_size, num_kvpairs, num_hiddens))  # [2, 6, 100]
print("result:", attention(X, Y, Y, valid_lens).shape)  # torch.Size([2, 4, 100])

三、位置前馈网络(Position-wise Feed-Forward Networks)

  1. 位置不变性:位置前馈网络是对每个位置单独应用的相同的全连接网络。这意味着它在序列中的每个位置上独立地处理输入特征,不依赖于其他位置的特征。这种设计保留了位置的不变性。

  2. 网络结构:位置前馈网络由两层全连接层组成,通常包含非线性激活函数。给定输入向量 ( \boldsymbol{x} ),网络实现的公式是:

    FFN ( x ) = ReLU ( x W 1 + b 1 ) W 2 + b 2 \text{FFN}(\boldsymbol{x}) = \text{ReLU}( \boldsymbol{x} \mathbf{W}_1 + \mathbf{b}_1) \mathbf{W}_2 + \mathbf{b}_2 FFN(x)=ReLU(xW1+b1)W2+b2

    其中, W 1 \mathbf{W}_1 W1 W 2 \mathbf{W}_2 W2 是线性变换的权重矩阵, b 1 \mathbf{b}_1 b1 b 2 \mathbf{b}_2 b2 是偏置向量, ReLU \text{ReLU} ReLU 是一种非线性激活函数。

  3. 维度变化:通常第一个全连接层会将输入向量的维度从较低维度增加到较高维度(例如乘以一个放大因子),然后第二层将维度缩减回原始大小。这样做的目标是提高模型的表达能力。

  4. 独立运算:由于位置前馈网络对每个位置应用相同的参数,它非常高效并且容易并行化。这种并行性的设计非常适合在 GPU 上进行加速计算。

  5. 结合层归一化:在 Transformer 中,位置前馈网络通常结合层归一化(Layer Normalization)使用,以确保模型的稳定性和训练效率。

作用:位置前馈网络在语言模型中起到提升特征表达能力的作用,帮助模型更好地捕获输入序列的复杂模式和结构,同时保证计算效率。

import math
import pandas as pd
import torch
from matplotlib import pyplot as plt
from torch import nn
from d2l import torch as d2l
class PositionWiseFFN(nn.Module):
    """基于位置的前馈网络"""
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)
 
    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))
# demo
# [2, 3, 4] * [4, 4] * [4, 8] = [2, 3, 8]
ffn = PositionWiseFFN(4, 4, 8)
ffn.eval()
print("基于位置的前馈网络:")
print(ffn(torch.ones((2, 3, 4)))[0])
 
ln = nn.LayerNorm(2)  # 层规范化
bn = nn.BatchNorm1d(2)  # 批标准化
X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32)
# 在训练模式下计算 X 的均值和方差
print('layer norm:', ln(X), '\nbatch norm:', bn(X))

四、残差连接与层归一化

残差连接与层归一化
残差连接(Residual Connection)和层归一化(Layer Normalization)是深度学习及 Transformer 模型中的关键技术,有助于模型的稳定性和训练效率。以下是对这两者的详细讲解:

1、残差连接

  1. 概念与目的
    残差连接是一种连接方式,用于缓解梯度消失或梯度爆炸问题,尤其是在深层神经网络中。
    其主要思想是通过引入身份映射(identity mapping),使得网络相对更容易优化。具体来说,它允许梯度直接通过跳跃层(skip-layer)进行反向传播,从而减轻了梯度消失的问题。
  2. 优点
    残差连接的优点包括改善梯度流、提升网络表现以及加快收敛速度。

2、层归一化

  1. 概念与目的
    层归一化是一种归一化技术,它对单个数据样本的特征进行标准化,而非对整个批次进行处理。
    层归一化旨在减少内部协变量偏移,提高训练稳定性和加速模型训练效果。

  2. 公式
    对于输入向量 x i \boldsymbol{x}_i xi的每个元素 x i x_i xi,层归一化的计算公式为:
    x ^ i = x i − μ σ \hat{x}_i = \frac{x_i - \mu}{\sigma} x^i=σxiμ
    其中, μ \mu μ 是输入向量的均值, σ \sigma σ 是输入向量的标准差。

    在归一化后,还会应用可训练的偏置 β \boldsymbol{\beta} β 和缩放参数 γ \boldsymbol{\gamma} γ
    LN ( x ) = γ ⋅ x ^ + β \text{LN}(\boldsymbol{x}) = \boldsymbol{\gamma} \cdot \hat{\boldsymbol{x}} + \boldsymbol{\beta} LN(x)=γx^+β

  3. 优点
    优点包括减小训练的不稳定性,提高收敛速度,并且不依赖批次大小。

在 Transformer 模型中,残差连接和层归一化通常结合使用,以确保每一层处理后结果的稳定性和模型的高效训练。通过确保模型的稳定性和可训练性,这两者共同提升了网络的整体性能。

class AddNorm(nn.Module):
    """残差连接后进行层规范化"""
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(normalized_shape)
 
    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)
 
# demo
add_norm = AddNorm([3, 4], 0.5)
add_norm.eval()
print("残差连接后进行层规范化:")
print(add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape)

五、位置编码(Positional Encoding)

位置编码( P o s i t i o n a l E n c o d i n g Positional Encoding PositionalEncoding)是 Transformer 模型中的重要机制之一,用来处理输入序列的位置信息。由于自注意力机制不配备序列的绝对位置意识,这导致必须使用额外的信息来让模型理解序列元素的顺序。位置编码填补了这个空缺,它能够把输入中的位置信息注入到模型中。

1. 位置编码的必要性

输入序列的无序性:自注意力机制处理序列的方式不依赖于元素的顺序,它仅关注元素之间的相似性和关系。因此,在输入的序列中需要一种方法来注入位置信息,以便模型能够区别开原始输入的位置。
传统 RNN 的劣势:传统的循环神经网络(RNN)由于其内在结构能够隐式捕获序列顺序信息,但它们难以并行化。而 Transformer 模型完全基于注意力机制,提升了并行计算能力,但需要外部编码策略来引入位置信息。

2. 位置编码的实现

正弦和余弦函数:用来生成固定的位置编码,是最普遍的实现方法。给定位置 (pos) 和维度 (i),位置编码的公式是:

P E ( p o s , 2 i ) = sin ⁡ ( p o s 1000 0 2 i d model ) PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) PE(pos,2i)=sin(10000dmodel2ipos)

P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 1000 0 2 i d model ) PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) PE(pos,2i+1)=cos(10000dmodel2ipos)

这里, d model d_{\text{model}} dmodel 是模型的输入向量的维度。例如,在一个序列中,偶数位置使用正弦函数,奇数位置使用余弦函数。

  • 特点
    • 正弦和余弦函数提供了一种周期性的位置信息编码方式,它允许模型可以通过这些变换的信息量来推断序列位置。
    • 这种方法不增加额外的可学习参数,并且提供了一种稳定的编码方式。

六、Mask(掩码)

Mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 Padding Mask 和 Sequence Mask。其中,Padding Mask 在所有的 scaled dot-product attention 里面都需要用到,而 Sequence Mask 只有在 Decoder 的 Self-Attention 里面用到。

1、Padding Mask

什么是 Padding mask 呢?因为每个批次输入序列的长度是不一样的,所以我们要对输入序列进行对齐。具体来说,就是在较短的序列后面填充 0(但是如果输入的序列太长,则是截断,把多余的直接舍弃)。因为这些填充的位置,其实是没有什么意义的,所以我们的 Attention 机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。
  具体的做法:把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过 Softmax 后,这些位置的概率就会接近 0。

2、Sequence Mask

Sequence Mask 是为了使得 Decoder 不能看见未来的信息。也就是对于一个序列,在 t t t 时刻,我们的解码输出应该只能依赖于 t t t 时刻之前的输出,而不能依赖 t t t 之后的输出。因为我们需要想一个办法,把 t t t 之后的信息给隐藏起来。

具体的做法:产生一个上三角矩阵,上三角的值全为 0。把这个矩阵作用在每个序列上,就可以达到我们的目的。

参考内容

Transformer模型讲解
Transformer代码

<think>好的,用户现在想了解Transformer模型,之前已经讨论过CNN、RNN和GAN。我需要先回顾之前的对话,确保内容连贯。用户可能对深度学习的基础模型有一定了解,现在想扩展到更先进的架构,比如Transformer。 首先,用户的问题是关于Transformer学习,我需要解释它的核心概念、结构、优势以及应用场景。考虑到用户之前已经接触过RNN和CNN,应该对比一下Transformer和它们的区别,尤其是解决长距离依赖和并行计算的问题。 接下来,要确保内容结构清晰。可能需要分几个部分:背景与动机、核心结构、数学原理、优势分析、应用场景和总结。同时,用户之前的回答中使用了数学公式,如CNN的卷积操作和GAN的目标函数,所以这里也需要包括Transformer的关键公式,比如自注意力机制的计算。 要注意用户提到的系统级指令,特别是数学表达式必须用$...$或$$...$$格式。比如自注意力的计算公式需要正确使用LaTeX,并且独立公式单独成段。此外,回答要使用中文,保持条理清晰,逐步展开。 用户可能需要知道Transformer为什么取代了RNN,尤其是在处理长序列时的优势。这里需要解释RNN的局限性,比如梯度消失和无法并行化,而Transformer通过自注意力和位置编码解决了这些问题。 还要提到Transformer的核心组件,如多头注意力、前馈网络、残差连接和层归一化。每个部分的作用需要简明扼要地解释,可能结合结构图或公式说明。 另外,应用场景部分要涵盖NLP和其他领域,比如计算机视觉,说明Transformer的通用性。可能提到BERT、GPT等具体模型,展示其实际影响。 需要检查是否有遗漏的关键点,比如位置编码的具体方法(正弦函数或学习得到的),以及Transformer的变体如Vision Transformer。同时,要避免技术细节过于复杂,保持解释的易懂性。 最后,总结部分要强调Transformer的重要性,并呼应之前讨论的模型,展示深度学习模型的演进。确保回答符合用户的要求:结构清晰、真实可靠,并且遵循格式要求。</think>### 对Transformer的理解 Transformer是一种基于**自注意力机制(Self-Attention)**的深度学习模型,由Vaswani等人在2017年提出。它彻底改变了序列建模(如自然语言处理)的范式,克服了传统RNN的局限性(如长距离依赖问题和无法并行计算),并成为BERT、GPT等现代预训练模型的基础。 --- ### 1. 核心思想与动机 - **传统模型的局限**: RNN因**顺序计算**导致训练效率低,且长序列中易出现梯度消失/爆炸;CNN的局部感受野难以建模全局依赖。 - **Transformer的突破**: 完全依赖注意力机制,实现**并行化计算**,同时直接捕获序列中任意位置的关系。 --- ### 2. 核心结构 #### (1)编码器-解码器架构 - **编码器(Encoder)**:由$N$个相同层堆叠,每层包含**多头自注意力**和**前馈网络**。 - **解码器(Decoder)**:在编码器结构基础上增加**掩码多头注意力**,防止预测时“偷看”未来信息。 #### (2)自注意力机制(Self-Attention) - **输入表示**: 每个词嵌入(Embedding)与**位置编码(Positional Encoding)**相加,保留序列顺序信息。 位置编码公式(正弦函数): $$ PE_{(pos,2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) $$ $$ PE_{(pos,2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) $$ - **注意力计算**: 输入向量通过线性变换生成**Query(Q)**、**Key(K)**、**Value(V)**: $$ \text{Attention}(Q, K, V) = \text{softmax}\left( \frac{QK^T}{\sqrt{d_k}} \right)V $$ 其中$d_k$为Key的维度,用于缩放点积防止梯度消失。 #### (3)多头注意力(Multi-Head Attention) - 将Q、K、V投影到多个子空间,独立计算注意力后拼接: $$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W^O $$ 每个$\text{head}_i$的计算为: $$ \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) $$ #### (4)其他关键组件 - **残差连接(Residual Connection)**:解决深层网络梯度退化问题。 - **层归一化(Layer Normalization)**:加速训练收敛。 - **前馈网络(Feed-Forward Network)**:对每个位置独立应用两层全连接层。 --- ### 3. 数学优势分析 | 特性 | Transformer | RNN | |---------------------|------------------------------|------------------------------| | **长距离依赖** | 直接通过注意力建模 | 依赖循环逐步传递,易丢失信息 | | **计算复杂度** | $O(n^2 \cdot d)$(n为序列长)| $O(n \cdot d^2)$ | | **并行能力** | 完全并行 | 序列顺序计算 | --- ### 4. 应用场景 - **自然语言处理(NLP)**: - 机器翻译(如原始Transformer论文) - 文本生成(GPT系列) - 语义理解(BERT、RoBERTa) - **计算机视觉(CV)**: - 图像分类(Vision Transformer, ViT) - 目标检测(DETR) - **跨模态任务**: - 图文生成(CLIP、DALL·E) --- ### 5. 总结与延伸 - **核心贡献**: Transformer通过自注意力机制实现了**全局依赖建模**和**高效并行计算**,成为现代深度学习的基础架构。 - **变体与演进**: - **稀疏注意力**(如Sparse Transformer)降低计算开销 - **预训练+微调**范式(如BERT、GPT-3)推动NLP的突破 - **跨领域扩展**(如Swin Transformer用于图像分割) --- ### 附:Transformer结构示意图 ``` 输入序列 → 词嵌入 + 位置编码 → 编码器堆叠(自注意力 + 前馈网络) ↓ 解码器堆叠(掩码自注意力 + 编码器-解码器注意力 + 前馈网络) → 输出序列 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值