<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Pytorch之Spatial-Shift-Operation的5種實現策略

      Pytorch之Spatial-Shift-Operation的5種實現策略

      本文已授權極市平臺, 并首發于極市平臺公眾號. 未經允許不得二次轉載.

      原始文檔(可能會進一步更新): https://www.yuque.com/lart/ugkv9f/nnor5p

      前言

      之前看了一些使用空間偏移操作來替代區域卷積運算的論文:

      看完這些論文后, 通過參考他們提供的核心代碼(主要是后面那些MLP方法), 讓我對于實現空間偏移有了一些想法.
      通過整合現有的知識, 我歸納總結了五種實現策略.
      由于我個人使用pytorch, 所以這里的展示也可能會用到pytorch自身提供的一些有用的函數.

      問題描述

      在提供實現之前, 我們應該先明確目的以便于后續的實現.
      這些現有的工作都可以簡化為:

      給定tensor \(X \in \mathbb{R}^{1 \times 8 \times 5 \times 5}\), 這里遵循pytorch默認的數據格式, 即 B, C, H, W .

      通過變換操作\(\mathcal{T}: x \rightarrow \tilde{x}\), 將\(X\)轉換為\(\tilde{X}\).

      這里tensor \(\tilde{X} \in \mathbb{R}^{1 \times 8 \times 5 \times 5}\), 為了提供合理的對比, 這里統一使用后面章節中基于"切片索引"策略的結果作為\(\tilde{X}\)的值.

      import torch
      
      xs = torch.meshgrid(torch.arange(5), torch.arange(5))
      x = torch.stack(xs, dim=0)
      x = x.unsqueeze(0).repeat(1, 4, 1, 1).float()
      print(x)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      方法1: 切片索引

      這是最直接和簡單的策略了. 這也是S2-MLP系列中使用的策略.
      我們將其作為其他所有策略的參考對象. 后續的實現中同樣會得到這個結果.

      direct_shift = torch.clone(x)
      direct_shift[:, 0:2, :, 1:] = torch.clone(direct_shift[:, 0:2, :, :4])
      direct_shift[:, 2:4, :, :4] = torch.clone(direct_shift[:, 2:4, :, 1:])
      direct_shift[:, 4:6, 1:, :] = torch.clone(direct_shift[:, 4:6, :4, :])
      direct_shift[:, 6:8, :4, :] = torch.clone(direct_shift[:, 6:8, 1:, :])
      print(direct_shift)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      方法2: 特征圖偏移—— torch.roll

      pytorch提供了一個直接對特征圖進行偏移的函數, 即 torch.roll . 這一操作在最近的transformer論文和mlp中有一些工作已經開始使用, 例如SwinTransformer和AS-MLP.

      這里展示下AS-MLP論文中提供的偽代碼:

      其主要作用就是將特征圖沿著某個軸向進行偏移, 并支持同時沿著多個軸向偏移, 從而構造更多樣的偏移方向.
      為了實現與前面相同的結果, 我們需要首先對輸入進行padding.
      因為直接切片索引有個特點就是邊界值是會重復出現的, 而若是直接roll操作, 會導致所有的值整體移動.
      所以為了實現類似的效果, 先對四周各padding一個網格的數據.
      注意這里選擇使用重復模式(replicate)以實現最終的邊界重復值的效果.

      import torch.nn.functional as F
      
      pad_x = F.pad(x, pad=[1, 1, 1, 1], mode="replicate")  # 這里需要借助padding來保留邊界的數據
      

      接下來開始處理, 沿著四個方向各偏移一個單位的長度:

      roll_shift = torch.cat(
          [
              torch.roll(pad_x[:, c * 2 : (c + 1) * 2, ...], shifts=(shift_h, shift_w), dims=(2, 3))
              for c, (shift_h, shift_w) in enumerate([(0, 1), (0, -1), (1, 0), (-1, 0)])
          ],
          dim=1,
      )
      
      '''
      tensor([[[[0., 0., 0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4., 4., 4.]],
      
               [[4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.],
                [4., 0., 0., 1., 2., 3., 4.]],
      
               [[0., 0., 0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.],
                [0., 1., 2., 3., 4., 4., 0.]],
      
               [[4., 4., 4., 4., 4., 4., 4.],
                [0., 0., 0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4., 4., 4.],
                [0., 0., 0., 0., 0., 0., 0.]],
      
               [[0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.]]]])
      '''
      

      接下來只需要剪裁一下即可:

      roll_shift = roll_shift[..., 1:6, 1:6]
      print(roll_shift)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      方法3: 1x1 Deformable Convolution—— ops.deform_conv2d

      在閱讀Cycle FC的過程中, 了解到了Deformable Convolution在實現空間偏移操作上的妙用.
      由于torchvision最新版已經集成了這一操作, 所以我們只需要導入函數即可:

      from torchvision.ops import deform_conv2d
      

      為了使用它實現空間偏移, 我在對Cycle FC的解讀中, 對相關代碼添加了一些注釋信息:

      要想理解這一函數的操作, 需要首先理解后面使用的deform_conv2d_tv的具體用法.

      具體可見:https://pytorch.org/vision/0.10/ops.html#torchvision.ops.deform_conv2d
      這里對于offset參數的要求是:

      offset (Tensor[batch_size, 2 _ offset_groups _ kernel_height * kernel_width, out_height, out_width])

      offsets to be applied for each position in the convolution kernel.

      也就是說, 對于樣本 s 的輸出特征圖的通道 c 中的位置 (x, y) , 這個函數會從 offset 中取出, 形狀為 kernel_height*kernel_width 的卷積核所對應的偏移參數, 其為 offset[s, 0:2*offset_groups*kernel_height*kernel_width, x, y] . 也就是這一系列參數都是對應樣本 s 的單個位置 (x, y) 的.

      針對不同的位置可以有不同的 offset , 也可以有相同的 (下面的實現就是后者).

      對于這 2*offset_groups*kernel_height*kernel_width 個數, 涉及到對于輸入特征通道的分組.

      將其分成 offset_groups 組, 每份單獨擁有一組對應于卷積核中心位置的相對偏移量, 共 2*kernel_height*kernel_width 個數.

      對于每個核參數, 使用兩個量來描述偏移, 即h方向和w方向相對中心位置的偏移, 即對應于后面代碼中的減去 kernel_height//2 或者 kernel_width//2 .

      需要注意的是, 當偏移位置位于 padding 后的 tensor 的邊界之外, 則是將網格使用0補齊. 如果網格上有邊界值, 則使用邊界值和用0補齊的網格頂點來計算雙線性插值的結果.

      該策略需要我們去構造特定的相對偏移值offset來對1x1卷積核在不同通道的采樣位置進行調整.

      我們先構造我們需要的offset \(\Delta \in \mathbb{R}^{1 \times 2C_iK_hK_w \times 1 \times 1}\). 這里之所以將 out_height & out_width 兩個維度設置為1, 是因為我們對整個空間的偏移是一致的, 所以只需要簡單的重復數值即可.

      offset = torch.empty(1, 2 * 8 * 1 * 1, 1, 1)
      for c, (rel_offset_h, rel_offset_w) in enumerate([(0, -1), (0, -1), (0, 1), (0, 1), (-1, 0), (-1, 0), (1, 0), (1, 0)]):
          offset[0, c * 2 + 0, 0, 0] = rel_offset_h
          offset[0, c * 2 + 1, 0, 0] = rel_offset_w
      offset = offset.repeat(1, 1, 7, 7).float()  # 針對空間偏移重復偏移量
      

      在構造offset的時候, 我們要明確, 其通道中的數據都是兩兩一組的, 每一組包含著沿著H軸和W軸的相對偏移量 (這一相對偏移量應該是以其作用的卷積權重位置為中心 —— 這一結論我并沒有驗證, 只是個人的推理, 因為這樣可能在源碼中實現起來更加方便, 可以直接作用權重對應位置的坐標. 在不讀源碼的前提下理解函數的功能, 那就需要自行構造數據來驗證性的理解了).

      為了更好的理解offset的作用的原理, 我們可以想象對于采樣位置\((h, w)\), 使用相對偏移量\((\delta_h, \delta_w)\)作用后, 采樣位置變成了\((h+\delta_h, w+\delta_w)\). 即原來作用于\((h, w)\)的權重, 偏移后直接作用到了位置\((h+\delta_h, w+\delta_w)\)上.

      對于我們的前面描述的沿著四個軸向各自一個單位偏移, 可以通過對\(\delta_h\)\(\delta_w\)分別賦予\(\{-1, 0, 1\}\)中的值即可實現.

      由于這里僅需要體現通道特定的空間偏移作用, 而并不需要Deformable Convolution的卷積功能, 我們需要將卷積核設置為單位矩陣, 并轉換為分組卷積對應的卷積核的形式:

      weight = torch.eye(8).reshape(8, 8, 1, 1).float()
      # 輸入8通道,輸出8通道,每個輸入通道只和一個對應的輸出通道有映射權值1
      

      接下來將權重和偏移送入導入的函數中.
      由于該函數對于偏移超出邊界的位置是使用0補齊的網格計算的, 所以為了實現前面邊界上的重復值的效果, 這里同樣需要使用重復模式下的padding后的輸入.
      并對結果進行一下修剪:

      deconv_shift = deform_conv2d(pad_x, offset=offset, weight=weight)
      deconv_shift = deconv_shift[..., 1:6, 1:6]
      print(deconv_shift)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      方法4: 3x3 Depthwise Convolution—— F.conv2d

      在S2MLP中提到了空間偏移操作可以通過使用特殊構造的3x3 Depthwise Convolution來實現.
      由于基于3x3卷積操作, 所以為了實現邊界值的重復效果仍然需要對輸入進行重復padding.
      首先構造對應四個方向的卷積核:

      k1 = torch.FloatTensor([[0, 0, 0], [1, 0, 0], [0, 0, 0]]).reshape(1, 1, 3, 3)
      k2 = torch.FloatTensor([[0, 0, 0], [0, 0, 1], [0, 0, 0]]).reshape(1, 1, 3, 3)
      k3 = torch.FloatTensor([[0, 1, 0], [0, 0, 0], [0, 0, 0]]).reshape(1, 1, 3, 3)
      k4 = torch.FloatTensor([[0, 0, 0], [0, 0, 0], [0, 1, 0]]).reshape(1, 1, 3, 3)
      weight = torch.cat([k1, k1, k2, k2, k3, k3, k4, k4], dim=0)  # 每個輸出通道對應一個輸入通道
      

      接下來將卷積核和數據送入 F.conv2d 中計算即可, 輸入在四邊各padding了1個單位, 所以輸出形狀不變:

      conv_shift = F.conv2d(pad_x, weight=weight, groups=8)
      print(conv_shift)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      方法5: 網格采樣—— F.grid_sample

      最后這里提到的基于 F.grid_sample , 該操作是pytorch提供的用于構建STN的一個函數, 但是其在光流預測任務以及最近的一些分割任務中開始出現:

      • AlignSeg: Feature-Aligned Segmentation Networks
      • Semantic Flow for Fast and Accurate Scene Parsing

      針對4Dtensor, 其主要作用就是根據給定的網格采樣圖grid\(\Gamma = \mathbb{R}^{B \times H_o \times W_o \times 2}\)來對數據點\((\gamma_h, \gamma_w)\)進行采樣以放置到輸出的位置\((h, w)\)中.
      要注意的是, 該函數對限制了采樣圖grid的取值范圍是對輸入的尺寸歸一化后的結果, 并且\(\Gamma\)的最后一維度分別是在索引W軸、H軸. 即對于輸入tensor的布局 B, C, H, W 的四個維度從后往前索引. 實際上, 這一規則在pytorch的其他函數的設計中廣泛遵循. 例如pytorch中的pad函數的規則也是一樣的.
      首先根據需求構造基于輸入數據的原始坐標數組 (左上角為\((h_{coord}[0, 0], w_{coord}[0, 0])\), 右上角為\((h_{coord}[0, 5], w_{coord}[0, 5])\)):

      h_coord, w_coord = torch.meshgrid(torch.arange(5), torch.arange(5))
      print(h_coord)
      print(w_coord)
      h_coord = h_coord.reshape(1, 5, 5, 1)
      w_coord = w_coord.reshape(1, 5, 5, 1)
      
      '''
      tensor([[0, 0, 0, 0, 0],
              [1, 1, 1, 1, 1],
              [2, 2, 2, 2, 2],
              [3, 3, 3, 3, 3],
              [4, 4, 4, 4, 4]])
      tensor([[0, 1, 2, 3, 4],
              [0, 1, 2, 3, 4],
              [0, 1, 2, 3, 4],
              [0, 1, 2, 3, 4],
              [0, 1, 2, 3, 4]])
      '''
      

      針對每一個輸出\(\tilde{x}\), 計算對應的輸入\(x\)的的坐標 (即采樣位置):

                  torch.cat(
                      [  # 請注意這里的堆疊順序,先放靠后的軸的坐標
                          2 * torch.clamp(w_coord + w, 0, 4) / (5 - 1) - 1,
                          2 * torch.clamp(h_coord + h, 0, 4) / (5 - 1) - 1,
                      ],
                      dim=-1,
                  )
      

      這里的參數\(w\&h\)表示基于原始坐標系的偏移量.
      由于這里直接使用clamp限制了采樣區間, 靠近邊界的部分會重復使用, 所以后續直接使用原始的輸入即可.
      將新坐標送入函數的時候, 需要將其轉換為\([-1, 1]\)范圍內的值, 即針對輸入的形狀W和H進行歸一化計算.

              F.grid_sample(
                  x,
                  torch.cat(
                      [
                          2 * torch.clamp(w_coord + w, 0, 4) / (5 - 1) - 1,
                          2 * torch.clamp(h_coord + h, 0, 4) / (5 - 1) - 1,
                      ],
                      dim=-1,
                  ),
                  mode="bilinear",
                  align_corners=True,
              )
      

      要注意, 這里使用的是 align_corners=True , 關于pytorch中該參數的介紹可以查看https://www.yuque.com/lart/idh721/ugwn46.
      True :

      False :

      所以可以看到, 這里前者更符合我們的需求, 因為這里提到的涉及雙線性插值的算法(例如前面的Deformable Convolution)的實現都是將像素放到網格頂點上的 (按照這一思路理解比較符合實驗現象, 我就姑且這樣描述).

      grid_sampled_shift = torch.cat(
          [
              F.grid_sample(
                  x,
                  torch.cat(
                      [
                          2 * torch.clamp(w_coord + w, 0, 4) / (5 - 1) - 1,
                          2 * torch.clamp(h_coord + h, 0, 4) / (5 - 1) - 1,
                      ],
                      dim=-1,
                  ),
                  mode="bilinear",
                  align_corners=True,
              )
              for x, (h, w) in zip(x.chunk(4, dim=1), [(0, -1), (0, 1), (-1, 0), (1, 0)])
          ],
          dim=1,
      )
      print(grid_sampled_shift)
      
      '''
      tensor([[[[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.],
                [0., 0., 1., 2., 3.]],
      
               [[0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.]],
      
               [[1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.],
                [1., 2., 3., 4., 4.]],
      
               [[0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]],
      
               [[1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4.]],
      
               [[0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.],
                [0., 1., 2., 3., 4.]]]])
      '''
      

      另外的一些思考

      關于 F.grid_sample 的誤差問題

      由于 F.grid_sample 涉及到歸一化操作, 自然而然存在精度損失.
      所以實際上如果想要實現精確控制的話, 不太建議使用這個方法.
      如果位置恰好在但單元格角點上, 倒是可以使用最近鄰插值的模式來獲得一個更加整齊的結果.
      下面是一個例子:

      h_coord, w_coord = torch.meshgrid(torch.arange(7), torch.arange(7))
      h_coord = h_coord.reshape(1, 7, 7, 1)
      w_coord = w_coord.reshape(1, 7, 7, 1)
      grid = torch.cat(
          [
              2 * torch.clamp(w_coord, 0, 6) / (7 - 1) - 1,
              2 * torch.clamp(h_coord, 0, 6) / (7 - 1) - 1,
          ],
          dim=-1,
      )
      print(grid)
      print(pad_x[:, :2])
      
      print("mode=bilinear\n", F.grid_sample(pad_x[:, :2], grid, mode="bilinear", align_corners=True))
      print("mode=nearest\n", F.grid_sample(pad_x[:, :2], grid, mode="nearest", align_corners=True))
      
      '''
      tensor([[[[-1.0000, -1.0000],
                [-0.6667, -1.0000],
                [-0.3333, -1.0000],
                [ 0.0000, -1.0000],
                [ 0.3333, -1.0000],
                [ 0.6667, -1.0000],
                [ 1.0000, -1.0000]],
      
               [[-1.0000, -0.6667],
                [-0.6667, -0.6667],
                [-0.3333, -0.6667],
                [ 0.0000, -0.6667],
                [ 0.3333, -0.6667],
                [ 0.6667, -0.6667],
                [ 1.0000, -0.6667]],
      
               [[-1.0000, -0.3333],
                [-0.6667, -0.3333],
                [-0.3333, -0.3333],
                [ 0.0000, -0.3333],
                [ 0.3333, -0.3333],
                [ 0.6667, -0.3333],
                [ 1.0000, -0.3333]],
      
               [[-1.0000,  0.0000],
                [-0.6667,  0.0000],
                [-0.3333,  0.0000],
                [ 0.0000,  0.0000],
                [ 0.3333,  0.0000],
                [ 0.6667,  0.0000],
                [ 1.0000,  0.0000]],
      
               [[-1.0000,  0.3333],
                [-0.6667,  0.3333],
                [-0.3333,  0.3333],
                [ 0.0000,  0.3333],
                [ 0.3333,  0.3333],
                [ 0.6667,  0.3333],
                [ 1.0000,  0.3333]],
      
               [[-1.0000,  0.6667],
                [-0.6667,  0.6667],
                [-0.3333,  0.6667],
                [ 0.0000,  0.6667],
                [ 0.3333,  0.6667],
                [ 0.6667,  0.6667],
                [ 1.0000,  0.6667]],
      
               [[-1.0000,  1.0000],
                [-0.6667,  1.0000],
                [-0.3333,  1.0000],
                [ 0.0000,  1.0000],
                [ 0.3333,  1.0000],
                [ 0.6667,  1.0000],
                [ 1.0000,  1.0000]]]])
      tensor([[[[0., 0., 0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.]]]])
      mode=bilinear
       tensor([[[[0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
                 0.0000e+00, 0.0000e+00],
                [1.1921e-07, 1.1921e-07, 1.1921e-07, 1.1921e-07, 1.1921e-07,
                 1.1921e-07, 1.1921e-07],
                [1.0000e+00, 1.0000e+00, 1.0000e+00, 1.0000e+00, 1.0000e+00,
                 1.0000e+00, 1.0000e+00],
                [2.0000e+00, 2.0000e+00, 2.0000e+00, 2.0000e+00, 2.0000e+00,
                 2.0000e+00, 2.0000e+00],
                [3.0000e+00, 3.0000e+00, 3.0000e+00, 3.0000e+00, 3.0000e+00,
                 3.0000e+00, 3.0000e+00],
                [4.0000e+00, 4.0000e+00, 4.0000e+00, 4.0000e+00, 4.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [4.0000e+00, 4.0000e+00, 4.0000e+00, 4.0000e+00, 4.0000e+00,
                 4.0000e+00, 4.0000e+00]],
      
               [[0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00],
                [0.0000e+00, 1.1921e-07, 1.0000e+00, 2.0000e+00, 3.0000e+00,
                 4.0000e+00, 4.0000e+00]]]])
      mode=nearest
       tensor([[[[0., 0., 0., 0., 0., 0., 0.],
                [0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2.],
                [3., 3., 3., 3., 3., 3., 3.],
                [4., 4., 4., 4., 4., 4., 4.],
                [4., 4., 4., 4., 4., 4., 4.]],
      
               [[0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.],
                [0., 0., 1., 2., 3., 4., 4.]]]])
      '''
      

      F.grid_sample 與Deformable Convolution的關系

      雖然二者都實現了對于輸入與輸出位置映射關系的調整, 但是二者調整的方式有著明顯的差別.

      • 參考坐標系不同
        • 前者的坐標系是基于整體輸入的一個歸一化坐標系, 原點為輸入的HW平面的中心位置, H軸和W軸分別以向下和向右為正向. 而在坐標系WOH中, 輸入數據的左上角為\((-1, -1)\), 右上角為\((1, -1)\).
        • 后者的坐標系是相對于權重初始作用位置的相對坐標系. 但是實際上, 這里其實理解為沿著H軸和W軸的_相對偏移量_更為合適. 例如, 將權重作用位置向左偏移一個單位, 實際上讓其對應的偏移參數組\((\delta_h, \delta_w)\)取值為\((0, -1)\)即可, 即將作用位置相對于原始作用位置的\(w\)坐標加上個\(-1\).
      • 作用效果不同
        • 前者直接對整體輸入進行坐標調整, 對于輸入的所有通道具有相同的調整效果.
        • 后者由于構建于卷積操作之上, 所以可以更加方便的處理不同通道( offset_groups )、不同的實際上可能有重疊的局部區域( kernel_height * kernel_width ). 所以實際功能更加靈活和可調整.

      Shift操作的第二春

      雖然在之前的工作中已經探索了多種空間shift操作的形式, 但是卻并沒有引起太多的關注.

      • (CVPR 2018) [Grouped Shift] Shift: A Zero FLOP, Zero Parameter Alternative to Spatial Convolutions:
      • (ICCV 2019) 4-Connected Shift Residual Networks
      • (NIPS 2018) [Active Shift] Constructing Fast Network through Deconstruction of Convolution
      • (CVPR 2019) [Sparse Shift] All You Need Is a Few Shifts: Designing Efficient Convolutional Neural Networks for Image Classification

      這些工作大多專注于輕量化網絡的設計, 而現在的這些基于shift的方法, 則結合了MLP這一快船, 好像又激起了一些新的水花.
      當前的這些方法, 往往會采用更有效的訓練設定, 這些模型之外的策略在一定程度上也極大的提升了模型的表現. 這其實也會讓人疑惑, 如果直接遷移之前的那些shift操作到這里的MLP框架中, 或許性能也不會差吧?

      這一想法其實也適用于傳統的CNN方法, 之前的那些結構如果使用相同的訓練策略, 相比現在, 到底能差多少? 這估計只能那些有卡有時間有耐心的大佬們能夠一探究竟了.

      實際上綜合來看, 現有的這些基于空間偏移的MLP的方法, 更可以看作是 (NIPS 2018) [Active Shift] Constructing Fast Network through Deconstruction of Convolution 這篇工作的特化版本.

      也就是將原本這篇工作中的自適應學習的偏移參數改成了固定的偏移參數.

      posted @ 2022-01-16 13:26  lart  閱讀(331)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲男女羞羞无遮挡久久丫| 精品久久精品久久精品九九| 亚洲精品国模一区二区| 亚洲韩欧美第25集完整版| 精品一区二区免费不卡| 亚洲春色在线视频| 国产成人A在线视频免费| 国产高清一区二区三区视频| 中文字幕免费不卡二区| 免费看黄色亚洲一区久久| 久久精品a亚洲国产v高清不卡| 寿光市| 日韩老熟女av搜索结果| 国产成人亚洲一区二区三区| 日本中文字幕不卡在线一区二区| 俄罗斯老熟妇性爽xxxx| 久久毛片少妇高潮| 狠狠色狠狠色综合| 熟女在线视频一区二区三区 | 国产高清在线精品一区二区三区| 国产亚洲精品久久77777| 国产高颜值极品嫩模视频| 枣庄市| 人妻少妇精品系列一区二区| 久久久国产精品樱花网站| 亚洲va久久久噜噜噜久久狠狠| 久久99精品久久久大学生| 麻花传剧mv在线看免费| 欧美熟妇乱子伦XX视频| 爆乳女仆高潮在线观看| 国产乱女乱子视频在线播放| 国产一区二区三区av在线无码观看 | 无码h黄肉动漫在线观看| 小伙无套内射老熟女精品| 免费国产午夜理论片不卡| 精品国产成人午夜福利| 国产美女午夜福利视频| 成 人 色 网 站免费观看| 偷拍精品一区二区三区| 国产一区二区三区精品综合 | 国产情侣激情在线对白|