1. 程式人生 > 實用技巧 >深度學習模型元件 ------ 深度可分離卷積、瓶頸層Bottleneck、CSP瓶頸層BottleneckCSP、ResNet模組、SPP空間金字塔池化模組

深度學習模型元件 ------ 深度可分離卷積、瓶頸層Bottleneck、CSP瓶頸層BottleneckCSP、ResNet模組、SPP空間金字塔池化模組

YOLOv5 元件


作者:elfin 資料來源:yolov5


目錄


1、標準卷積: Conv + BN + activate

class Conv(nn.Module):
    # Standard convolution
    # ch_in, ch_out, kernel, stride, padding, groups
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.Hardswish() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))

引數說明:

  • g:groups,通道分組的引數,輸入通道數、輸出通道數必須同時滿足被groups整除;

    groups: 如果輸出通道為6,輸入通道也為6,假設groups為3,卷積核為 1x1 ; 則卷積核的shape為2x1x1,即把輸入通道分成了3份;那麼卷積核的個數呢?之前是由輸出通道決定的,這裡也一樣,輸出通道為6,那麼就有6個卷積核!這裡實際上是將卷積核也平分為groups份,在groups份特徵圖上計算,以輸入、輸出都為6為例,每個2xhxw的特徵圖子層就有且僅有2個卷積核,最後相加恰好是6。這裡可以起到的作用是不同通道分別計算特徵!

    引數的參考資料1參考資料2

Top --- Bottom


2、DWConv深度可分離卷積

def DWConv(c1, c2, k=1, s=1, act=True):
    # Depthwise convolution
    return Conv(c1, c2, k, s, g=math.gcd(c1, c2), act=act)

這裡的深度可分離卷積,主要是將通道按輸入輸出的最大公約數進行切分,在不同的通道圖層上進行特徵學習!

關於深度可分離卷積的更早資料參考:我的github

3、Bottleneck瓶頸層

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super(Bottleneck, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

引數說明:

  • c1:bottleneck 結構的輸入通道維度;
  • c2:bottleneck 結構的輸出通道維度;
  • shortcut:是否給bottleneck 結構新增shortcut連線,新增後即為ResNet模組;
  • ggroups,通道分組的引數,輸入通道數、輸出通道數必須同時滿足被groups整除;
  • e:expansion: bottleneck 結構中的瓶頸部分的通道膨脹率,使用0.5即為變為輸入的\(\frac{1}{2}\)

模型結構:

這裡的瓶頸層,瓶頸主要體現在通道數channel上面!一般1x1卷積具有很強的靈活性,這裡用於降低通道數,如上面的膨脹率為0.5,若輸入通道為640,那麼經過1x1的卷積層之後變為320;經過3x3之後變為輸出的通道數,這樣引數量會大量減少!

這裡的shortcut即為圖中的紅色虛線,在實際中,shortcut(捷徑)不一定是上面都不操作,也有可能有卷積處理,但此時,另一支一般是多個ResNet模組串聯而成!這裡使用的shortcut也成為identity分支,可以理解為恆等對映,另一個分支被稱為殘差分支(Residual分支)

我們常使用的殘差分支實際上是1x1+3x3+1x1的結構!

Top --- Bottom


4、BottleneckCSP-CSP瓶頸層

class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    # ch_in, ch_out, number, shortcut, groups, expansion
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  
        super(BottleneckCSP, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        self.act = nn.LeakyReLU(0.1, inplace=True)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))

引數說明:

  • c1:BottleneckCSP 結構的輸入通道維度;
  • c2:BottleneckCSP 結構的輸出通道維度;
  • n:bottleneck 結構 結構的個數;
  • shortcut:是否給bottleneck 結構新增shortcut連線,新增後即為ResNet模組;
  • ggroups,通道分組的引數,輸入通道數、輸出通道數必須同時滿足被groups整除;
  • e:expansion: bottleneck 結構中的瓶頸部分的通道膨脹率,使用0.5即為變為輸入的\(\frac{1}{2}\)
  • torch.cat((y1, y2), dim=1):這裡是指定在第\(1\)個維度上進行合併,即在channel維度上合併;
  • c_:BottleneckCSP 結構的中間層的通道數,由膨脹率e決定。

模型結構:

CSP瓶頸層結構在Bottleneck部分存在一個可修改的引數n,標識使用的Bottleneck結構個數!這一條也是我們的主分支,是對殘差進行學習的主要結構,右側分支nn.Conv2d實際上是shortcut分支實現不同stage的連線。

Top --- Bottom


5、ResNet模組

殘差模組是深度神經網路中非常重要的模組,在建立模型的過程中經常被使用。

殘差模組結構如其名,實際上就是shortcut的直接應用,最出名的殘差模組應用這樣的:

左邊這個結構即Bottleneck結構,也叫瓶頸殘差模組!右邊的圖片展示的是基本的殘差模組!

Top --- Bottom


6、SPP空間金字塔池化模組

class SPP(nn.Module):
    # Spatial pyramid pooling layer used in YOLOv3-SPP
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super(SPP, self).__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

SPP即為空間金字塔池化模組!上面的程式碼是yolov5的模型程式碼,視覺化為:

三種池化核,padding都是根據核的大小自適應,保證池化後的特徵圖[H, W]保持一致!

Top --- Bottom


完!