pvt_demo.ipynb

pvt

image.pngimage.png

  1. def sr(x, H, W, dim, sr_ratio):
  2. """ PVT的改进创新点
  3. 输入 token.
  4. 先featureMap化,并卷积操作使得featureMap尺寸减少
  5. 再token化。
  6. 上诉操作的好处主要2点:
  7. 第一点:等价于减少k、v的token数量,即缓解vit显存占用
  8. 第二点:获得了多种尺寸的featureMap,后续可搭建金字塔结构,以解决下游任务问题。
  9. 思考1:这里可能导致感受野问题
  10. 思考2:多头注意力的heads给予了多种语义特征的自相关,但是图片不同语义特征的分布并不均匀。
  11. 如何将这种不均匀可设置或者可学习化?
  12. """
  13. assert dim == x.shape[-1] # token shape
  14. # tokens -> featureMap
  15. x_rearr2Map = Rearrange('b (h w) dim -> b dim h w', h=H, w=W)
  16. x_rearr2Token = Rearrange('b c h w -> b (h w) c') # Map -> tokens
  17. sr_conv = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)
  18. sr_norm = nn.LayerNorm(dim)
  19. # 减小featureMap尺寸,等价于减少k、v的token数量。# 这里可能导致感受野问题
  20. sr_x = sr_conv(x_rearr2Map(x))
  21. sr_x = sr_norm(x_rearr2Token(sr_x))
  22. return sr_x
  • 思考1:这里可能导致感受野问题? kv比q多做了conv操作。
  • 思考2:多头和特征聚类的关系?多头注意力的heads给予了多种特征的自相关,但是图片不同特征的分布其实是并不均匀的。

    1. 那么如何将这种不均匀可设置或者可学习化呢?

swin-Transformer

image.png
image.png

  • 一个对cnn中的featureMap的思考:featureMap内部应该需要跨距离的交互。
    • swin-T中的WSA可以翻译为窗口多尺度自注意力,SWSA可以翻译为跨窗口之间的多尺度自注意力。论文的结果显示必须要“WSA + SWSA“ 成对使用最好, 单用WSA并不是很理想(差1个多点)。所以 窗口内部的token需要交互,跨窗口的token也需要交互。

更多思考

cv下游任务有应用transfomer有两大难点:显存占用和表示方法。
image.png

  • 首先关于显存占有问题,attn的自相关矩阵必然是存在的,该矩阵空间复杂度为O(N^2)。
    • 局部区域的进行自相关,可以缓解问题。

image.png

  • 比如token共享的方法,也可以缓解问题。
  • 可见:patch越大,qk越多,导致显存极速上升。反之patch越小,qk越少,归结到featureMap即导致分辨率不够,所以影响下游任务的性能。
  • 表示方法的思考应该从图像数据本身的特点出发,如何人为构建一些设定。

cnn与mlp到底有什么联系和区别?

  • cnn可以说是mlp在内存和图像表示方法上的改进。
  • 这种在图像表示上的改进,帮助了cnn更好地收敛。这些改进就是所谓cnn的偏置归纳:参数共享、局部区域计算、滑动计算。

一些联想

  • cnn的特别机制能否推广到cv-transfomer中呢?

目前transformer源于序列数据,它还没有类似cnn这样的偏置归纳,正因为此,现有的cv-tansformer必须要在ImageNet甚至更大的数据预训练上才能真正超越cnn。

  • 其实关于位置编码的个人理解:transformer 需要位置编码,其实算是对于图像特性的人为设定,也是一种偏置归纳。
  • transformer可以推广到cnn中吗?

cnn的表达方式,恰好限制了感受野的获取,必须要足够深才能拿到全局感受野。在vit的论文中,作者给出了transfomer优秀的全局”感受野“和之后的全局归纳能力。

  • cnn中的金字塔结构,几乎是cnn下游任务的标准结构(如下FPN结构)。pvt和swin-T的工作也正是为了它,两者工作都证明了Vit+FPN > cnn + FPN

image.png

其他: 在cnn上辅以attn方案,可能更好。当然这违背了cn-nlp统一框架的目的。

cnn和self-attn有什么联系?

  • 对最小计算单位的计算方式上:
    • cnn的最小计算单位的由来是(滑动出)“3x3xc的立方体“,计算单位之间的计算方式是加权求和。
    • self-attn的最小计算方式是token,它是“1x1xc的立方体”,计算单位之间的计算方式是加权求和。
      • token的一种由来是(裁剪出)”p_hp_wc的立方体”,然后进行直接展平。
      • token另一种由来是conv2d滑动生成。
  • 从加权求和的”操作数“上:假设输入为x。
    • cnn是x、filter,其中filter来自人为初始化。
    • self-attn是q、k、v , 其中qkv来自x自身。
  • 所以,可以在cnn做如下的改变吗?
    • 既然pvt中conv2d能用于patch_embed(即token化)和创新的sr过程,那么是否说明con2d与self-attn之间可以混用吗?
    • 先用conv2d(inc,3*inc,kernel = 16 ,stride=8) , 再将输出按通道分为q、k、v 以进行self-atten。
  • tokens -> Map

x_rearr2Map = Rearrange('b (h w) dim -> b dim h w', h=H, w=W)

  • Map -> tokens

x_rearr2Token = Rearrange('b c h w -> b (h w) c')

backbone = - [Conv(Map) -> Attn(Token)] ->

image.png

  1. x = torch.randn(1,3,48,48)
  2. inc :int= ...
  3. outc :int= ...
  4. # 尺寸不变的conv
  5. conv_same = nn.Conv2d(inc,outc,kernel_size=3,stride=1,padding=1)
  6. # 尺寸下采样的conv
  7. conv_donw = nn.Conv2d(inc,outc,kernel_size=3,stride=2,padding=1)
  8. # 尺寸上采样的conv
  9. conv_up = nn.Conv2d(inc, outc, ...)
  10. # 用于patch_embed的conv(感受野更大,但是输出尺寸更小,依然保留cnn的滑动窗口和参数共享)
  11. patch_conv = nn.Conv2d(inc, outc*3,kernel_size=16,stride=8) # “*3”与q、k、v有关
  12. # 计算self-attention的函数
  13. def cnn_attn(Map_q,Mao_k,Map_v):
  14. #
  15. _,C,H,W = x.shape
  16. x_rearr2Token = Rearrange('b c h w -> b (h w) c') # token化
  17. """
  18. 这里是自注意力过程
  19. """
  20. x_rearr2Map = Rearrange('b (h w) dim -> b dim h w', h=H, w=W) # Map化
  21. return x_rearr2Map
  22. # 传播过程
  23. x = conv_same(x) + conv_up(cnn_attn(patch_conv(x)))
  24. x = conv_donw(x) + conv_up(cnn_attn(patch_conv(x)))
  25. ...
  26. x = conv_donw(x) + conv_up(cnn_attn(patch_conv(x)))

pvt

  1. import torch
  2. from torch import nn, einsum
  3. import torch.nn.functional as F
  4. from functools import partial
  5. from timm.models.layers import DropPath, to_2tuple, trunc_normal_
  6. from timm.models.registry import register_model
  7. from timm.models.vision_transformer import _cfg
  8. from einops import rearrange, repeat
  9. from einops.layers.torch import Rearrange
  1. def sr(x, H, W, dim, sr_ratio):
  2. """ PVT的改进创新点
  3. 输入 token.
  4. 先featureMap化,并卷积操作使得featureMap尺寸减少
  5. 再token化。
  6. 上诉操作的好处主要2点:
  7. 第一点:等价于减少k、v的token数量,即缓解vit显存占用
  8. 第二点:获得了多种尺寸的featureMap,后续可搭建金字塔结构,以解决下游任务问题。
  9. 思考1:这里可能导致感受野问题
  10. 思考2:多头注意力的heads给予了多种语义特征的自相关,但是图片不同语义特征的分布并不均匀。
  11. 如何将这种不均匀可设置或者可学习化?
  12. """
  13. assert dim == x.shape[-1] # token shape
  14. # tokens -> featureMap
  15. x_rearr2Map = Rearrange('b (h w) dim -> b dim h w', h=H, w=W)
  16. x_rearr2Token = Rearrange('b c h w -> b (h w) c') # Map -> tokens
  17. sr_conv = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)
  18. sr_norm = nn.LayerNorm(dim)
  19. # 减小featureMap尺寸,等价于减少k、v的token数量。# 这里可能导致感受野问题
  20. sr_x = sr_conv(x_rearr2Map(x))
  21. sr_x = sr_norm(x_rearr2Token(sr_x))
  22. return sr_x
  1. def attentionPvt(x,H,W,dim,num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0., sr_ratio=1):
  2. B, N, C = x.shape # token shape
  3. assert C == dim
  4. assert dim % num_heads == 0, f"dim {dim} should be divided by num_heads {num_heads}."
  5. head_dim = dim // num_heads
  6. get_q = nn.Linear(dim,dim,bias=qkv_bias)
  7. get_kv = nn.Linear(dim,dim*2,bias=qkv_bias)
  8. scale = qk_scale or head_dim ** -0.5
  9. attn_drop = nn.Dropout(attn_drop)
  10. proj = nn.Linear(dim, dim)
  11. proj_drop = nn.Dropout(proj_drop)
  12. # 创建多头q,k,v
  13. mul_q_reaar = Rearrange('b n (heads dim) -> b heads n dim',heads=num_heads)
  14. mul_kv_reaar = Rearrange('b n (kv heads dim) -> kv b heads n dim',kv=2,heads=num_heads)
  15. q = mul_q_reaar(get_q(x))
  16. if sr_ratio > 1:
  17. sr_x = sr(x,H,W,dim,sr_ratio)
  18. kv = mul_kv_reaar(get_kv(sr_x))
  19. k,v = kv[0],kv[1]
  20. else:
  21. kv = mul_kv_reaar(get_kv(x))
  22. k,v = kv[0],kv[1]
  23. # attn
  24. attn = (q @ k.transpose(-2, -1)) * scale
  25. attn = attn.softmax(dim=-1)
  26. attn = attn_drop(attn)
  27. x = (attn @ v).transpose(1, 2).reshape(B, N, C) # token-qkv
  28. x = proj(x)
  29. x = proj_drop(x)
  30. return x
  1. def patchEmbed_pvt(x,ph,pw,embed_dim:int):
  2. b,c,h,w = x.shape
  3. assert h % ph == 0 and w % pw == 0
  4. conv = nn.Conv2d(c,embed_dim , kernel_size=(ph, pw), stride=(ph, pw),)
  5. norm = nn.LayerNorm(embed_dim)
  6. rearr = Rearrange('b dim h w -> b (h w) dim')
  7. x = norm(rearr(conv(x)))
  8. H,W = h//ph,w//pw # 下一轮的token转patch所需要。
  9. return x,H,W
  10. def patchEmbed_vit(x,ph,pw,embed_dim:int):
  11. _, c, h, w = x.shape
  12. assert h % ph == 0 and w % pw == 0
  13. rearr = Rearrange('b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=ph, p2=pw)
  14. linear = nn.Linear(c*ph*pw,embed_dim)
  15. norm = nn.LayerNorm(embed_dim)
  16. x = norm(linear(rearr(x)))
  17. return x
  1. b,c,h,w = 2,3,24,24
  2. ph, pw = 4, 4
  3. num_heads = 2
  4. embed_dim = 8
  5. pvt_sr_ratio = 2
  6. drop_path = 0.1
  7. x = torch.randn(b, c, h, w)
  8. drop_path = DropPath(drop_path)
  9. x_vit = patchEmbed_vit(x, ph, pw, embed_dim)
  10. x_pvt,H,W = patchEmbed_pvt(x, ph, pw, embed_dim)
  11. y_vit = attentionPvt(x_vit, H=None, W=None, dim=embed_dim,
  12. num_heads=num_heads, sr_ratio=1)
  13. y_pvt = attentionPvt(x_vit, H=H, W=W, dim=embed_dim,
  14. num_heads=num_heads, sr_ratio=pvt_sr_ratio)
  15. y_vit = x_vit + drop_path(y_vit) # drop path
  16. y_pvt = x_pvt + drop_path(y_pvt)
  17. y_vit.shape,y_pvt.shape