過往做卷積(convolution)通常 kernel size 是固定的,而 SKNet[1] 的主要改進就是讓神經網路模型使用數種 kernel size 進行卷積並且能學習到如何選擇 kernel size(所以才叫 selective kernel)。SKNet 先使用多個不同 kernel size 的卷積進行運算,再用類似 SENet 的方式進行整合,讓神經網路不僅能夠學習到不同通道(channel)間的關係,還能學習到不同 kernel size 之間的關係。簡單來說可以想成:分成多個不同 kernel size 進行卷積再選擇性整合起來。至於詳細做法為何?讓我們繼續看下去。
分支的數量可以調整,為方便討論,以下都用兩個分支的例子講解,多個分支的概念也是一樣的。
分成數個平行的分支,每個分支皆為卷積層但各自的 kernel size 不同。以上圖為例,分成兩種 kernel size:3x3 和 5x5。實作上,5x5 不一定會真的有 25 個參數,可能用 3x3 且 dilation 為 2 的卷積層代替。Dilation 為 1 和為 2 的比較如下方兩張圖:
一般的卷積(no padding, dilation = 1)
Dilation = 2 範例
以上兩圖的來源:https://github.com/vdumoulin/conv_arithmetic [2]
所有分支用不同的 kernel size 對輸入做 grouped/depthwise convolution + Batch Normalization + ReLU(圖中兩個頭上有東西的 F),兩個分支就會產生兩個輸出,圖裡表示為兩個頭上有標記的 U。
上圖右下角的式子看起來複雜但其實意思是:
圖中 SK[M=......] 表示 SK 的超參數。
感受野(receptive field),受到影響的區域。以下圖為例,做 3x3 的卷積時,Layer 3 的黃色格子受到 Layer 2 中間那 3x3 格影響;而這 3x3 格又是受到前一層 5x5 格的影響。用這個概念來思考,如果 kernel size 變大,感受野也會變大。SKNet 作者之一的李翔在他一篇文章[5]提到「SKNet 啟發自皮質神經元根據不同的刺激可動態調節其自身的感受野」。
圖片來源:[4]
SKNet 結合了 SENet 的想法,除了學習感受野的調節,也學習到不同通道間的關係。
以下範例程式碼修改自 developer0hye 在 GitHub 上的 repository "SKNet-PyTorch" [6]。
class SKConv(nn.Module):
def __init__(self, features, M=2, G=32, r=16, stride=1 ,L=32):
""" Constructor
Args:
features: input channel dimensionality.
M: the number of branchs.
G: num of convolution groups.
r: the ratio for compute d, the length of z.
stride: stride, default 1.
L: the minimum dim of the vector z in paper, default 32.
"""
super(SKConv, self).__init__()
d = max(int(features/r), L) # z 的維度
self.M = M
self.features = features
self.convs = nn.ModuleList([]) # 各分支的卷積層
for i in range(M):
self.convs.append(nn.Sequential(
nn.Conv2d(features, features, kernel_size=3, stride=stride, padding=1+i, dilation=1+i, groups=G, bias=False),
nn.BatchNorm2d(features),
nn.ReLU(inplace=False)
))
self.gap = nn.AdaptiveAvgPool2d((1,1)) # Global average pooling
self.fc = nn.Sequential(nn.Conv2d(features, d, kernel_size=1, stride=1, bias=False),
nn.BatchNorm2d(d),
nn.ReLU(inplace=False)) # Fuse 的全連結層
self.fcs = nn.ModuleList([]) # 各分支加起來前的全連結層
for i in range(M):
self.fcs.append(
nn.Conv2d(d, features, kernel_size=1, stride=1)
)
self.softmax = nn.Softmax(dim=1) # Softmax
def forward(self, x):
batch_size = x.shape[0]
# 各分支的卷積計算
feats = [conv(x) for conv in self.convs]
# U
feats = torch.cat(feats, dim=1)
feats = feats.view(batch_size, self.M, self.features, feats.shape[2], feats.shape[3])
feats_U = torch.sum(feats, dim=1)
# s
feats_S = self.gap(feats_U)
# z
feats_Z = self.fc(feats_S)
# 各分支的全連結層
attention_vectors = [fc(feats_Z) for fc in self.fcs]
# Softmax
attention_vectors = torch.cat(attention_vectors, dim=1)
attention_vectors = attention_vectors.view(batch_size, self.M, self.features, 1, 1)
attention_vectors = self.softmax(attention_vectors)
# 最後加起來
feats_V = torch.sum(feats*attention_vectors, dim=1)
return feats_V
[1] X. Li, W. Wang, X. Hu and J. Yang, "Selective Kernel Networks," 2019 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 2019, pp. 510-519, doi: 10.1109/CVPR.2019.00060.
[2] https://github.com/vdumoulin/conv_arithmetic
[3] 引用自 CSDN 博主「ITOMG」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本聲明。原文連結:https://blog.csdn.net/ITOMG/article/details/89673593
[4] Lin, H.; Shi, Z.; Zou, Z. Maritime Semantic Labeling of Optical Remote Sensing Images with Multi-Scale Fully Convolutional Network. Remote Sens. 2017, 9, 480. https://doi.org/10.3390/rs9050480
[5] 李翔:SKNet——SENet孪生兄弟篇(https://zhuanlan.zhihu.com/p/59690223)
[6] Repository "SKNet-PyTorch" on GitHub by pppLang (https://github.com/developer0hye/SKNet-PyTorch)