Transformer 中 Positional Encoding 实现

文章介绍了位置编码在Transformer模型中的重要性,特别是对比一维绝对编码(如经典Transformer和1D可训练版本)与二维相对编码(如SwinTransformer中的基于位置偏差的方法),解释了它们的原理和实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考博文:

https://www.cnblogs.com/nickchen121/p/16470736.html

解决问题 

位置编码的主要目的是确保模型能够理解序列中的元素之间的相对位置和顺序,从而更好地捕捉到语义信息。在Transformer模型中,位置编码通常与词嵌入(word embeddings)相加,以形成模型的输入表示。这有助于模型在处理序列数据时更好地理解元素的位置和顺序,从而提高其性能,特别是在自然语言处理任务中。

原理

这里就是拿经典款transformer举例了

这个i是维度,2i这块是告诉你是sin还是cos的,是0~dimension/2

详细过程:

sin(pos+k) = sin(pos)*cos(k)+cos(pos)*sin(k) #sin表示偶数维度

cos(pos+k) = cos(pos)cos(k) +sin(pos)sin(k) #cos表示奇数维度

!pos+k可是pos和k的线性组合!

例如

pos+K=5, 当我计算第五个单词的位置编码时:

pos=1, k=4; pos=2, k=3;

这样就可以得知几个位置之间的相对关系

代码实现

Transformer

一维绝对的位置编码

def create_1d_absolute_sincos_embeddings(n_pos_vec,dim):
    assert dim % 2 == 0, "wrong dimension" # dim must be even
    # 初始化position embedding
    position_embedding = torch.zeros(n_pos_vec.numel(), dim, dtype=torch.float) #numel()返回数组元素个数
    # omega是对i进行遍历
    omega = torch.arange(dim//2, dtype=torch.float) #//是整除
    omega /= dim/2.
    omega = 1./(10000**omega)

    out = n_pos_vec[:, None]@omega[None, :] # 先把n_pos_vec变成列向量,一个维度加上None相当于扩了一维;接下来是把omega拓成一个行向量, @是矩阵乘法
    emb_sin = torch.sin(out)
    emb_cos = torch.cos(out)

    # 接下来是偶数位用sin赋值,奇数位用cos去赋值
    position_embedding[:, 0::2] = emb_sin
    position_embedding[:, 1::2] = emb_cos

    return position_embedding

if __name__ == '__main__':
    n_pos = 4
    dim = 4
    n_pos_vec = torch.arange(n_pos, dtype=torch.float)
    print(n_pos_vec)
    pe = create_1d_absolute_sincos_embeddings(n_pos_vec,dim)
    print("pe", pe)

Vision Transformer

一维的,绝对的,可训练的

这里用的也是一维的位置编码因为论文里做了实验表明二维的位置编码对模型效果并没有提升

def create_1d_absolute_trainable_embeddings(n_pos_vec,dim):
    # 传入索引
    # n_pos_vec: torch.aramge(n_pos, dtype=torch.float)
    # 因为可学习所以用nn.embedding来实现
    position_embedding = nn.Embedding(n_pos_vec.numel(), dim)
    # 初始化weight(parameter class)
    nn.init.constant_(position_embedding.weight, 0.)
    return position_embedding  # 一维的,绝对的,可学习的embedding

 Swin Transformer

二维的,相对的,基于位置偏差的

相对位置,可学习

def create_2d_relative_bias_trainable_embeddings(n_head,height,width,dim):
    # embeddings的行数就是bias的个数,列数就是num_heads
    # 横轴取值 width:5[0,1,2,3,4] bias ={-width+1, width-1 }{-4,4} 4-(-4)+1 = 9
    # 纵轴取值 height:5[0,1,2,3,4] bias ={-height+1, height-1} 1-(-1)+1 = 3
    position_embedding = nn.Embedding((2*width-1)*(2*height-1), n_head)
    # 初始化weight(parameter class)
    nn.init.constant_(position_embedding.weight, 0.)
    # 获取window中二维的,两两之间的位置偏差
    # step1:算出横轴和纵轴各自的位置偏差,用网格法把横轴的位置索引和纵轴的位置索引定义出来
    def get_2d_relative_position_index(height, width):
        m1, m2 = torch.meshgrid(torch.arange(height), torch.arange(width)) # m1行一样,m2列一样
        coords = torch.stack([m1, m2]) # 把m1和m2拼接起来,dim=-1表示最后一个维度 #2*height*width
        coords_flatten = torch.flatten(coords,1) # 把coords压缩成一维,dim=1表示第一个维度,得到2*【height*width】
        ralative_coords_bias = coords_flatten[:, :, :None]- coords_flatten[:, None, :]#得到网格里任意两点横轴纵轴坐标的差值,[2,height*width,height*width]
        # 把它们都变成正数
        ralative_coords_bias[0, :, :] += height-1 # 横轴坐标的差值,0代表高度维
        ralative_coords_bias[1, :, :] += width-1 # 纵轴坐标的差值 1代表宽度维
        # 把两个方向上的坐标转化成一个方向上的坐标,类似于把一个2dtensor赋值到1dtensor
        # A;2d,B:1d B[i*cols+j] = A[i,j]
        ralative_coords_bias[0,:,:] += ralative_coords_bias[1, :, :].max()+1 # 把横轴坐标的差值转化成一维坐标,即i*cols
        # 相对位置索引
        return ralative_coords_bias.sum(0) # [height*width,height*width] # 两个方向上的坐标相加,得到相对位置索引
    relative_position_bias = get_2d_relative_position_index(height, width) # [height*width,height*width]
    bias_embedding = position_embedding(torch.flatten(relative_position_bias)).reshape(height*width,height*width,n_head) # [height*width,height*width,n_head]
    bias_embedding.permute(2,0,1).unsqueeze(0) # [1, n_head,height*width,height*width]
    return bias_embedding # 二维的,相对的,可学习的embedding
Swin Transformer使用的是Learned Positional Encoding,如果要将其替换为Sinusoidal Positional Encoding,需要进行一些修改。 首先,可以定义一个Sinusoidal Positional Encoding的函数,如下所示: ```python import math import torch import torch.nn as nn class SinusoidalPositionalEmbedding(nn.Module): def __init__(self, d_model, max_len=512): super().__init__() self.d_model = d_model self.max_len = max_len 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) pe = pe.unsqueeze(0).transpose(0, 1) self.register_buffer('pe', pe) def forward(self, x): x = x * math.sqrt(self.d_model) seq_len = x.size(1) pe = self.pe[:seq_len, :] pe = pe.repeat(x.size(0), 1, 1) x = x + pe.to(x.device) return x ``` 然后,在Swin Transformer的构造函数中,将使用Learned Positional Encoding的部分替换为Sinusoidal Positional Encoding,如下所示: ```python import torch import torch.nn as nn from einops.layers.torch import Rearrange class SwinTransformer(nn.Module): def __init__(self, img_size=224, patch_size=4, in_chans=3, num_classes=1000, embed_dim=96, depths=[2, 2, 18, 2], num_heads=[3, 6, 12, 24], window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.): super().__init__() norm_layer = nn.LayerNorm self.num_classes = num_classes self.num_features = self.embed_dim = embed_dim # stochastic depth decay rule dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth decay rule # patch embedding self.patch_embed = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) self.norm1 = norm_layer(embed_dim) # pos embedding self.pos_embed = SinusoidalPositionalEmbedding(embed_dim, max_len=(img_size//patch_size)**2+1) # swin transformer blocks self.blocks = nn.ModuleList([ SwinTransformerBlock(dim=embed_dim, num_heads=num_heads[i], window_size=window_size, shift_size=window_size // 2 if i == 0 else 0, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[sum(depths[:i]):sum(depths[:i+1])]) for i in range(len(depths))]) # norm before classifier self.norm2 = norm_layer(embed_dim) # classification head self.head = nn.Linear(embed_dim, num_classes) if num_classes > 0 else nn.Identity() def forward_features(self, x): x = self.patch_embed(x) x = self.norm1(x) x = x.flatten(2).transpose(1, 2) x = self.pos_embed(x) for i, blk in enumerate(self.blocks): x = blk(x) x = self.norm2(x) return x def forward(self, x): x = self.forward_features(x) x = x.mean(dim=1) # global average pooling x = self.head(x) return x ``` 这样,就完成了Swin Transformer模型中Positional Encoding形式的替换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值