第TR4周:Transformer中的位置编码详解

本人往期文章可查阅: 深度学习总结

 

       在之前的教案中,我们了解到了单词的顺序及其在句子中的位置是非常重要的。 如果重新排列单词,整个句子的意思可能会发生变化。

       在实施 NLP 解决方案时,循环神经网络(RNN)具有处理序列的内置机制。Transformer 则是引入位置编码机制保存文本中字符的位置信息。

本文的学习目标:

  • 了解什么是位置编码
  • Transformer 中的位置编码机制
  • 使用 NumPy 实现位置编码矩阵
  • 理解和可视化位置编码矩阵

一、什么是位置编码

1.位置编码定义

       位置编码记录了文本中字符的位置信息,这里位置信息的记录不使用单个数字(例如索引值)来记录位置信息的原因有很多。对于长序列,索引的大小可能会变大,不利于存储。如果将索引值规范化为介于0~1之间,则可能会为可变长度序列带来问题,因为他们的标准化方式不同

       Transformers 使用只能位置编码方案,其中每个位置/索引都映射到一个向量。因此,位置编码层的输出是一个矩阵,其中矩阵的每一行代表序列中的一个编码对象与其位置信息相加。下图显示了仅对位置信息进行编码的矩阵示例。

2.三角函数

       这是对正弦函数的快速回顾;你可以等效地使用余弦函数。函数的取值范围是[-1,+1]。该波形的频率是一秒内完成的周期数。波长是波形重复自身的距离。不同波形的波长和频率如下所示:

 

3.位置编码公式 

      让我们直接进入这个主题,假设你有一个长度为L的输入序列,要计算第 k 哥元素的位置编码。位置编码由不同频率的正弦和余弦函数给出:

P(k,2i)=sin(\frac{k}{n^{2i/d}})   (1)

P(k,2i+1)=cos(\frac{k}{n^{2i/d}})   (2)

参数详解:

  • k:对象(即句子中的字符)在输入序列中的位置
  • d:输出嵌入空间的维度
  • P(k,j):位置函数,用于映射输入序列中 k 处的元素到位置矩阵的 (k,j) 处
  • n:用户定义的标量,可任意取值(Attention Is All You Need 作者设置的值是10000)
  • i:无实际含义,用于映射到列索引(即2i=j or 2i+1=j , 0<=i<d/2

4.位置编码示例

       为了理解上面的表达式,让我们以短语 "I am a robot" 为例,其中 n=100,d=4 。下表显示了该短语的位置编码矩阵。事实上,对于任何 n=100 和 d=4 的四字母短语,位置编码矩阵都是相同的。

红色部分的计算结果需要先带入上面的公式1、公式2

二、可视化理解位置编码 

1.Python实现位置编码

import math,os,torch
import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    def __init__(self,embed_dim,max_len=500):
        super(PositionalEncoding,self).__init__()
        
        # 创建一个大小为 [max_len,embed_dim] 零张量
        pe=torch.zeros(max_len,embed_dim)
        # 创建一个形状为 [max_len,1] 的位置索引张量
        position=torch.arange(0,max_len,dtype=torch.float).unsqueeze(1)
        
        div_term=torch.exp(torch.arange(0,embed_dim,2).float()*(-math.log(100.0)/embed_dim))
        
        pe[:,0::2]=torch.sin(position * div_term)  # 计算 PE(pos,2i)
        pe[:,1::2]=torch.cos(position * div_term)  # 计算 PE(pos,2i+1)
        pe=pe.unsqueeze(0).transpose(0,1)
        
        # 将位置编码张量注册为模型的缓冲区,参数不参与梯度下降,保存model的时候会将其保存下来
        self.register_bufferu('pe',pe)
        
    def forward(self,x):
        # x 的形状为(seq_len,batch_size,embed_dim)
        # 将位置编码添加到输入张量中,注意位置编码的形状
        x=x+self.pe[:x.size(0),:]
        return x

pe[:,0::2]=torch.sin(position * div_term)  
pe[:,1::2]=torch.cos(position * div_term)

代码解释如下:

  • pe[:,0::2]:选择所有位置的偶数索引(例如 0,2,4,……)
  • torch.sin(position * div_term):计算正弦函数并赋值给  pe  的偶数索引位置
  • pe[:,1::2]:选择所有位置的奇数索引(例如 1,3,5,……)
  • torch.cos(position * div_term):计算余弦函数并赋值给  pe  的奇数索引位置

2.单个字符可视化

要理解位置编码,让我们从查看 n=10000 和 d=512 的不同位置的正弦波开始。

import matplotlib.pyplot as plt

def plotSinusoid(k,d=512,n=10000):
    x=np.arange(0,100,1)
    denominator=np.power(n,2*x/d)
    y=np.sin(k/denominator)
    plt.plot(x,y)
    plt.title('k='+str(k))
    
fig=plt.figure(figsize=(15,4))
for i in range(4):
    plt.subplot(141+i)
    plotSinusoid(i*4)

输出:

       可以看到,每个位置对应于不同的正弦曲线,它将每个位置编码为向量。如果仔细观察位置编码函数,你会发现固定 i 时对应的波长:

\lambda _{i}=2\pi n^{2i/d}

因此,正弦曲线的波长形成几何级数。位置编码方案具有许多优点。

  • 正弦和余弦函数的值在 [-1,1] 内,这使位置编码矩阵的值保持在归一化范围内。
  • 由于每个位置的正弦曲线都不同,因此你可以采用独特的方式对每个位置进行编码。
  • 有一种方法可以测量或量化不同位置之间的相似性,从而使你能够对单词的相对位置进行编码。

3.整句话的位置编码可视化

       让我们可视化更大值的位置矩阵。使用 matplotlib 库中的 mashow()  方法。如原始论文中所做的那样设置 n=10000 ,将得到以下结果:

def getPositionalEncoding(seq_len, d, n=10000):
    # 创建一个形状为 [seq_len, d] 的零张量
    pe = torch.zeros(seq_len, d)
    
    # 创建一个形状为 [seq_len, 1] 的位置索引张量
    position = torch.arange(0, seq_len, dtype=torch.float).unsqueeze(1)
    
    # 计算分母项
    div_term = torch.exp(torch.arange(0, d, 2).float() * (-math.log(n) / d))
    
    # 计算位置编码
    pe[:, 0::2] = torch.sin(position * div_term)  # 计算 PE(pos, 2i)
    pe[:, 1::2] = torch.cos(position * div_term)  # 计算 PE(pos, 2i+1)
    
    # 返回位置编码
    return pe

p=getPositionalEncoding(seq_len=100,d=512,n=10000)
cax=plt.matshow(p)
plt.gcf().colorbar(cax)

输出:

这里使用颜色来表示位置编码中的数值

三、位置编码层的最终输出

       Transformer 中的位置编码层把位置向量与单词编码相加,并为后续层输出该矩阵。整个过程如下图所示。

<think>好的,我现在需要帮助用户了解如何使用PyTorch实现Transformer模型。首先,我应该回顾一下PyTorch中Transformer的相关组件。记得PyTorch提供了nn.Transformer模块,包括编码器、解码器、多头注意力机制等。用户可能需要一个完整的示例代码,所以得一步步来。 首先,数据准备部分可能需要用到Dataset和DataLoader。然后,模型构建方面,用户可能需要继承nn.Module,定义Embedding层、位置编码Transformer层等。这里要注意的是,PyTorch的Transformer模型需要指定d_model、nhead、num_encoder_layers等参数。 接下来,前向传播的逻辑需要正确处理src和tgt的输入,以及生成mask和padding mask。这里可能会用到generate_square_subsequent_mask函数来创建因果掩码,防止解码器看到未来信息。 然后,训练循环部分需要定义损失函数和优化器,比如交叉熵损失和Adam优化器。还需要处理序列的批次数据,可能需要对输入进行padding或者截断,确保序列长度一致。 用户可能还关心如何应用Transformer到具体任务,比如机器翻译或文本生成。这时候需要调整模型的输入输出结构,比如在生成任务中使用自回归的方式逐步生成输出。 另外,要注意引用提供的资料。引用[1]提到官方实例中的data.py、model.py等文件结构,可以建议用户参考官方代码组织方式。引用[2]和[3]提供了模型组件的实现细节,比如Embedding层、位置编码和初始化方法,这些都需要在代码中体现。 可能遇到的问题包括维度不匹配、mask设置错误,或者训练不稳定。这时候需要检查各层的输入输出维度,确保mask正确应用,并考虑使用学习率预热或梯度裁剪等优化策略。 最后,生成相关问题时要围绕Transformer实现的关键点,比如参数配置、数据处理、训练技巧和应用场景,帮助用户深入理解。</think>使用PyTorch实现Transformer模型需要理解其核心组件和数据处理流程。以下是分步实现方法及示例代码: ### 一、模型结构搭建 PyTorch提供原生Transformer模块(`torch.nn.Transformer`),主要包含: 1. **Embedding层**:将输入序列转换为稠密向量 2. **位置编码**:注入序列位置信息 3. **多头注意力机制**:计算自注意力和交叉注意力 4. **前馈网络**:进行非线性变换 ```python import torch import torch.nn as nn class TransformerModel(nn.Module): def __init__(self, vocab_size, d_model=512, nhead=8, num_encoder_layers=6, num_decoder_layers=6): super().__init__() self.embedding = nn.Embedding(vocab_size, d_model) self.pos_encoder = PositionalEncoding(d_model) self.transformer = nn.Transformer( d_model=d_model, nhead=nhead, num_encoder_layers=num_encoder_layers, num_decoder_layers=num_decoder_layers ) self.fc_out = nn.Linear(d_model, vocab_size) def forward(self, src, tgt, src_mask=None, tgt_mask=None): src = self.pos_encoder(self.embedding(src)) tgt = self.pos_encoder(self.embedding(tgt)) output = self.transformer(src, tgt, src_mask=src_mask, tgt_mask=tgt_mask) return self.fc_out(output) ``` ### 二、关键组件实现 #### 位置编码(Positional Encoding) $$ PE_{(pos,2i)} = \sin(pos/10000^{2i/d_{model}}) $$ $$ PE_{(pos,2i+1)} = \cos(pos/10000^{2i/d_{model}}) $$ ```python class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer('pe', pe.unsqueeze(0)) def forward(self, x): return x + self.pe[:, :x.size(1)] ``` ### 三、训练流程示例 ```python # 初始化模型 model = TransformerModel(vocab_size=10000) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.0001) # 创建mask def generate_mask(src, tgt): src_mask = (src != 0).unsqueeze(1).unsqueeze(2) tgt_mask = nn.Transformer.generate_square_subsequent_mask(tgt.size(0)) return src_mask, tgt_mask # 训练循环 for epoch in range(10): for src, tgt in dataloader: src_mask, tgt_mask = generate_mask(src, tgt) output = model(src, tgt[:, :-1], src_mask=src_mask, tgt_mask=tgt_mask) loss = criterion(output.view(-1, output.size(-1)), tgt[:, 1:].reshape(-1)) optimizer.zero_grad() loss.backward() optimizer.step() ``` ### 四、核心注意事项 1. **维度对齐**:输入张量应为$(seq\_len, batch\_size, d\_model)$格式[^3] 2. **掩码应用**:需正确处理padding mask和sequence mask 3. **参数初始化**:推荐使用Xavier均匀初始化 ```python nn.init.xavier_uniform_(self.embedding.weight) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值