• Transformer中Relative Position Bias以及DropPath细节梳理


    1、Relative Position Bias[相对位置编码]

    在transformer系列模型结构中,有关位置编码出现了一些变体,transformer以及ViT中使用原生的sine-cosine周期绝对位置编码(periodic absolute position encoding);而在最近的transformer变体工作中,e.g. SwinTransformer,BEIT等模型都使用相对位置编码(relative position encoding)。

    二者有什么异同点?首先从paper empirical experiments上总结得到:对于image classification任务而言,二者差异不大,但是对于更细粒度的任务,如, object detection,image segmentation,相对位置编码泛化能力更强。所以目前在cv上,基于transformer的模型优化采用相对位置编码。

    简要总结一下这二种位置编码方式:

    • 绝对位置编码

    绝对位置编码, 论文中一般称1D绝对位置编码 , 看其张量的shape,本质上是一个二维张量,x.shape = [L, D],L是序列长度,对于每个token都会分配一个D维的位置embedding。其作用于token embedding之上,序列的token embedding + 绝对位置编码embedding作为transformer的序列输入

    • 相对位置编码

    相对位置编码,论文中一般称2D相对位置编码,看其张量的shape,本质上是一个三维张量,

    x.shape = [N, L, L],N是heads数量,L是序列长度,其作用于attention score上。相对位置编码,把图像序列恢复成2维空间结构,然后分别计算x轴,y轴平面上每个坐标点相对于其他所有坐标点的位置差异。

    假设序列长度L = 9, 即3x3[window=3]图像平面,接下来分别计算在x轴以及y轴的坐标位置差异

    x, y = torch.meshgird(torch.arange(3), torch.arange(3))

    X = [[0,0,0],[1,1,1],[2,2,2]] y = [[0,1,2],[0,1,2],[0,1,2]]; 计算x,y平面上每个点相对于其他点的位置差异,应该是一个9x9矩阵; 计算时把 x, y拉成一个向量, x= [0,0,0,1,1,1,2,2,2], y = [0,1,2,0,1,2,0,1,2]

    pos_x = x[:, None] - x[None, :] = [[0,0,0,1,1,1,2,2,2]]-[[0],[0],[0],[1],[1],[1],[2],[2],[2]] = [[0,0,0,1,1,1,2,2,2],[0,0,0,1,1,1,2,2,2],[0,0,0,1,1,1,2,2,2],[1,1,1,0,0,0,-1,-1,-1],[1,1,1,0,0,0,-1,-1,-1],[1,1,1,0,0,0,-1,-1,-1],[2,2,2,1,1,1,0,0,0],[2,2,2,1,1,1,0,0,0],[2,2,2,1,1,1,0,0,0]]

    pos_y = y[:,None] - y[None, :] = [[0,1,2,0,1,2,0,1,2]]-[[0],[1],[2],[0],[1],[2],[0],[1],[2]] =

    [[0,1,2,0,1,2,0,1,2],[-1,0,1,-1,0,1,-1,0,1],[-2,-1,0,-2,-1,0,-2,-1,0],[0,1,2,0,1,2,0,1,2],[-1,0,1,-1,0,1,-1,0,1],[-2,-1,0,-2,-1,0,-2,-1,0],[0,1,2,0,1,2,0,1,2],[-1,0,1,-1,0,1,-1,0,1],[-2,-1,0,-2,-1,0,-2,-1,0]]

    可以看到pos_x, pos_y取值范围是在[-window+1, window-1], pos_x, pos_y同时加上window-1, 使得x轴,y轴上相对位置偏置变成从0开始; 理论上pos_x + pos_y 就是二维的相对位置编码索引了,但是会发现索引位置x+y的值会出现不唯一的情况,因此在pos_x + pos_y 之前对pos_x的坐标乘以2*window-1;

    pos_x *= 2*window-1, pos_index = pos_x + pos_y。此时索引的最大值为: (2*window - 1)(2*window-2)[x轴的最大值] + 2*window - 2[y轴最大值] = (2*window-1)**2 - 1

    二维可学习的相对位置查找表大小为: rel_pos_table = torch.zeros((2*window-1, 2*window-1))[可以看到二维相对位置索引的最大值,刚好覆盖到二维相对位置编码查找表]

    具体源码如下:

    1. class RelativePositionBias(nn.Module):
    2. def __init__(self, window_size, num_heads):
    3. super().__init__()
    4. self.window_size = window_size
    5. self.num_relative_distance = (2 * window_size[0] - 1) * (2 * window_size[1] - 1)
    6. self.relative_position_bias_table = nn.Parameter(
    7. torch.zeros(self.num_relative_distance, num_heads)) # 2*Wh-1 * 2*Ww-1, nH
    8. # get pair-wise relative position index for each token inside the window
    9. coords_h = torch.arange(window_size[0])
    10. coords_w = torch.arange(window_size[1])
    11. coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
    12. coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
    13. relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
    14. relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
    15. relative_coords[:, :, 0] += window_size[0] - 1 # shift to start from 0
    16. relative_coords[:, :, 1] += window_size[1] - 1
    17. relative_coords[:, :, 0] *= 2 * window_size[1] - 1
    18. relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
    19. self.register_buffer("relative_position_index", relative_position_index)
    20. # trunc_normal_(self.relative_position_bias_table, std=.02)
    21. def forward(self):
    22. relative_position_bias = \
    23. self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
    24. self.window_size[0] * self.window_size[1],
    25. self.window_size[0] * self.window_size[1], -1) # Wh*Ww,Wh*Ww,nH
    26. return relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww

    2、DropPath原理分析

    droppath是用来实现随机深度的一种正则化手段,其作用在有多个路径分支的时候,随机丢弃某个路径的输出。数学上等价于,在样本维度,随机丢弃某些样本的输出{本质}。可以用dropout层来实现,dropout的维度在样本维度, tf.keras.layers.Dropout可以通过axis来控制在哪些维度进行dropout。

    pytorch实现:

    1. def drop_path(x, drop_prob: float = 0., training: bool = False):
    2. if drop_prob == 0. or not training:
    3. return x
    4. keep_prob = 1 - drop_prob
    5. # 注意是在样本维度随机丢弃
    6. shape = (x.shape[0],) + (1,) * (x.ndim - 1)
    7. random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)
    8. random_tensor.floor_() # binarize
    9. # 除以keep_prob保证输出期望不变
    10. output = x.div(keep_prob) * random_tensor
    11. return output
    12. class DropPath(nn.Module):
    13. def __init__(self, drop_prob=None):
    14. super(DropPath, self).__init__()
    15. self.drop_prob = drop_prob
    16. def forward(self, x):
    17. return drop_path(x, self.drop_prob, self.training)
    18. # 具体调用
    19. self.drop_path = DropPath(drop_prob) if drop_prob > 0. else nn.Identity()
    20. # -------------note-------------- #
    21. # droppath一般作用在具有多个分支路径的网络结构上
    22. x = x + self.drop_path(self.token_mixer(self.norm1(x)))
    23. x = x + self.drop_path(self.mlp(self.norm2(x)))

  • 相关阅读:
    c#导入二级树代码备份
    服务容错框架Sentinel入门
    蓝桥杯刷题-约数的个数
    OpenHarmony开发实战:HDF驱动开发流程
    靠“山寨”发家的名创优品,如今是什么模样
    【牛客网-公司真题-前端入门篇】——2021牛客模考-卷1
    Java线程面试题合集(含答案)
    高效+灵活+安全=低代码的三位一体解决方案
    使用C语言实现顺序栈
    操作系统:虚拟存储管理技术
  • 原文地址:https://blog.csdn.net/qq_30666517/article/details/125891311