Transformer(2)位置编码器


一、嵌入表示层

  对于输入文本序列,首先通过输入嵌入层(Input Embedding)将每个单词转换为其相对应的向量表示。通常直接对每个单词创建一个向量表示。由于 Transfomer 模型不再使用基于循环的方式建模文本输入,序列中不再有任何信息能够提示模型单词之间的相对位置关系。在送入编码器端建模其上下文语义之前,一个非常重要的操作是在词嵌入中加入位置编码(Positional Encoding)这一特征。具体来说,序列中每一个单词所在的位置都对应一个向量。这一向量会与单词表示对应相加并送入到后续模块中做进一步处理。在训练的过程当中,模型会自动地学习到如何利用这部分位置信息。
  为了得到不同位置对应的编码,Transformer 模型使用不同频率的正余弦函数如下所示:

for pos in range(max_seq_len):
    for i in range(0, d_model, 2):
     	pe[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model)))
     	pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model)))

  其中,pos 表示单词所在的位置,2i 和 2i+ 1 表示位置编码向量中的对应维度,d 则对应位置编码的总维度。通过上面这种方式计算位置编码有这样几个好处:首先,正余弦函数的范围是在 [-1,+1],导出的位置编码与原词嵌入相加不会使得结果偏离过远而破坏原有单词的语义信息。其次,依据三角函数的基本性质,可以得知第 pos + k 个位置的编码是第 pos 个位置的编码的线性组合,这就意味着位置编码中蕴含着单词之间的距离信息。

二、流程详解

1.初始化位置编码器

# 初始化位置编码矩阵 pe,形状为 (max_seq_len, d_model)
pe = torch.zeros(max_seq_len, d_model)

打印初始化位置编码矩阵

(Pdb) p pe
tensor([[0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.]])

2.计算位置编码

for pos in range(max_seq_len):
    for i in range(0, d_model, 2):
        pe[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model)))
        pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model)))

打印位置编码矩阵

tensor([[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  ...,  1.0000e+00,
          0.0000e+00,  1.0000e+00],
        [ 8.4147e-01,  5.6969e-01,  8.0196e-01,  ...,  1.0000e+00,
          1.0746e-08,  1.0000e+00],
        [ 9.0930e-01, -3.5090e-01,  9.5814e-01,  ...,  1.0000e+00,
          2.1492e-08,  1.0000e+00],
        ...,
        [ 3.7961e-01,  7.8033e-01,  7.4511e-01,  ...,  1.0000e+00,
          1.0424e-06,  1.0000e+00],
        [-5.7338e-01,  9.5851e-01, -8.9752e-02,  ...,  1.0000e+00,
          1.0531e-06,  1.0000e+00],
        [-9.9921e-01,  3.1179e-01, -8.5234e-01,  ...,  1.0000e+00,
          1.0639e-06,  1.0000e+00]])

3.扩维,与输入张量匹配

pe = pe.unsqueeze(0)
(Pdb) p pe.shape
torch.Size([1, 100, 512])

4.添加位置编码到输入张量上

x = x + self.pe[:, :seq_len].detach().to(x.device)
示例 1: 在 CPU 上运行
位置编码后的张量 (CPU): tensor([[[ -4.4866,  -3.6170,   7.9131,  ...,  -5.4459,  15.9657,   4.2406],
         [-47.0210, -13.7024, -40.5477,  ...,  34.5023,   0.4545, -32.0102],
         [ 16.6810,  12.8272,  40.9043,  ..., -12.4140,  70.6676, -14.0449],
         ...,
         [ -8.1882,   1.9146,  25.2393,  ...,  16.1251, -24.0830, -25.0094],
         [ 35.0248,  -0.2711, -40.9559,  ...,  -3.2930,  29.2630,  13.0763],
         [ -2.8143, -10.6067,  43.7963,  ...,   8.7323,   7.0742,  -8.5050]],

三、完整代码

import math
import torch
import torch.nn as nn


class PositionalEncoder(nn.Module):
    def __init__(self, d_model, max_seq_len=100):
        """
        初始化位置编码器。

        参数:
        - d_model: 每个位置的嵌入维度。
        - max_seq_len: 支持的最大序列长度。
        """
        super(PositionalEncoder, self).__init__()
        self.d_model = d_model
        # 初始化位置编码矩阵 pe,形状为 (max_seq_len, d_model)
        pe = torch.zeros(max_seq_len, d_model)

        # 计算位置编码值
        for pos in range(max_seq_len):
            for i in range(0, d_model, 2):
                pe[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model)))
                pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model)))

        # 增加批次维度,形状变为 (1, max_seq_len, d_model)
        pe = pe.unsqueeze(0)
        # 注册位置编码矩阵为缓冲区,确保其不会作为模型参数被更新
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        前向传播方法,将位置编码添加到输入张量 x 上。

        参数:
        - x: 输入张量,形状为 (batch_size, seq_len, d_model)

        返回:
        - 带有位置编码的输入张量
        """
        # 使得单词嵌入表示相对大一些
        x = x * math.sqrt(self.d_model)

        # 获取输入序列长度
        seq_len = x.size(1)

        # 检查输入序列长度是否超过最大序列长度
        if seq_len > self.pe.size(1):
            raise ValueError(
                f"Input sequence length ({seq_len}) exceeds maximum sequence length ({self.pe.size(1)}) for positional encoding.")

        # 添加位置编码到输入张量上,并确保张量在同一个设备上
        x = x + self.pe[:, :seq_len].detach().to(x.device)
        """
        [:, :seq_len] 表示对一个张量(或数组)进行切片操作,其中 : 表示对第一个维度(通常是行)进行完整切片,而 :seq_len 表示对第二个维度(通常是列)进行从第0列到第 seq_len - 1 列的切片。
        detach() 是一个函数调用,用于创建一个新的张量,与原始张量共享相同的数据,但不进行梯度追踪。.detach() 的目的是将切片操作的结果从计算图中分离出来,以便后续的计算不会影响到原始张量的梯度计算。
        to(x.device) 是一个张量的方法,用于将张量移动到指定的计算设备上。其中 x.device 表示张量 x 当前所在的计算设备。这个操作的目的是将切片结果转移到与张量 x 相同的设备上,以便后续的计算能够在相同的设备上进行。
        """
        return x


# 使用示例
d_model = 512  # 每个位置的嵌入维度
seq_len = 100  # 输入序列的长度
batch_size = 32  # 批次大小

# 初始化位置编码器,确保 max_seq_len >= seq_len
pos_encoder = PositionalEncoder(d_model, max_seq_len=seq_len)

# 创建一个随机张量作为输入,形状为 (batch_size, seq_len, d_model)
x = torch.randn(batch_size, seq_len, d_model)

# 示例 1: 在 CPU 上运行
print("示例 1: 在 CPU 上运行")
x_cpu = x  # 确保张量在 CPU 上
pos_encoder_cpu = pos_encoder  # 确保位置编码器在 CPU 上
x_encoded_cpu = pos_encoder_cpu(x_cpu)  # 添加位置编码
print("位置编码后的张量 (CPU):", x_encoded_cpu)

# 示例 2: 在 GPU 上运行(如果可用)
if torch.cuda.is_available():
    print("示例 2: 在 GPU 上运行")
    device = torch.device("cuda")
    x_gpu = x.to(device)  # 将张量移动到 GPU
    pos_encoder_gpu = pos_encoder.to(device)  # 将位置编码器移动到 GPU
    x_encoded_gpu = pos_encoder_gpu(x_gpu)  # 添加位置编码
    print("位置编码后的张量 (GPU):", x_encoded_gpu)
else:
    print("GPU 不可用,跳过 GPU 示例")

VIT(Vision Transformer)是一种基于Transformer结构的视觉编码器模型。它将图像分割为一系列的图像块(patches),然后将这些图像块转换为序列数据,再通过Transformer模型进行处理。 VIT的编码器结构主要包括以下几个部分: 1. 图像块分割:将输入图像分割为固定大小的图像块,通常是将图像划分为非重叠的块。 2. 嵌入层(Embedding):将每个图像块映射到一个低维特征向量,常用的方式是通过一个线性变换将每个图像块的像素值转换为一个固定大小的向量。 3. 位置编码(Positional Encoding):为了保留序列的位置信息,在嵌入特征向量中添加位置编码,使得不同位置的图像块可以在模型中进行区分。 4. Transformer编码器层:VIT使用多层Transformer编码器来处理嵌入特征向量序列。每个Transformer编码器层由多头自注意力机制(Multi-head Self-Attention)、前馈神经网络(Feed-Forward Neural Network)和残差连接(Residual Connections)组成。 5. 全局平均池化(Global Average Pooling):为了得到整个图像的表示,对最后一个Transformer编码器层的输出进行全局平均池化操作,将序列数据转换为一个固定维度的向量表示。 6. 分类层:将全局平均池化的输出连接到一个分类层(通常是一个全连接层),用于进行最终的分类预测。 总结起来,VIT的编码器结构可以看作是将图像块分割、嵌入、位置编码和多层Transformer编码器结合起来,最后通过全局平均池化和分类层进行预测。这种结构使得VIT能够在视觉任务中取得很好的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值