基於Pytorch的動態卷積復現


【GaintPandaCV導語】 最近動態卷積開始有人進行了研究,也有很多的論文發表(動態卷積論文合集https://github.com/kaijieshi7/awesome-dynamic-convolution),可是動態卷積具體的實現代碼卻不多有文章給出。本文以微軟發表在CVPR2020上面的文章爲例,詳細的講解了動態卷積實現的難點以及如何動分組卷積巧妙的解決。但願能給你們以啓發。git

這篇文章也同步到知乎平臺,連接爲:https://zhuanlan.zhihu.com/p/208519425github

論文的題目爲《Dynamic Convolution: Attention over Convolution Kernels》微信

paper的地址arxiv.org/pdf/1912.0345網絡

代碼實現地址,其中包含一維,二維,三維的動態卷積;分別能夠用於實現eeg的處理,正常圖像的處理,醫療圖像中三維腦部的處理等等(水漫金山)github.com/kaijieshi7/D,你們以爲有幫助的話,能夠點個星星。app

一句話描述下文的內容:  的大小視爲分組卷積裏面的組的大小進行動態卷積。如  ,那麼就轉化成  ,  的分組卷積。ide

簡單回顧

這篇文章主要是改進傳統卷積,讓每層的卷積參數在推理的時候也是隨着輸入可變的,而不是傳統卷積中對任何輸入都是固定不變的參數。(因爲本文主要說明的是代碼如何實現,因此推薦給你們一個講解論文的鏈接:Happy:動態濾波器卷積|DynamicConv)svg

推理的時候:紅色框住的參數是固定的,黃色框住的參數是隨着輸入的數據不斷變化的。

對於卷積過程當中生成的一個特徵圖  ,先對特徵圖作幾回運算,生成  個和爲  的參數  ,而後對  個卷積核參數進行線性求和,這樣推理的時候卷積核是隨着輸入的變化而變化的。(能夠看看其餘的講解文章,本文主要理解怎麼寫代碼)ui

下面是attention代碼的簡易版本,輸出的是[  ,  ]大小的加權參數。  對應着要被求和的卷積核數量。spa

class attention2d(nn.Module):
def __init__(self, in_planes, K,):
super(attention2d, self).__init__()
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.fc1 = nn.Conv2d(in_planes, K, 1,)
self.fc2 = nn.Conv2d(K, K, 1,)

def forward(self, x):
x = self.avgpool(x)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x).view(x.size(0), -1)
return F.softmax(x, 1)

下面是文章中  個卷積核求和的公式。.net

其中  是輸入,  是輸出;能夠看到  進行了兩次運算,一次用於求注意力的參數(用於生成動態的卷積核),一次用於被卷積。

可是,寫代碼的時候若是直接將  個卷積核求和,會出現問題。接下來咱們先回顧一下Pytorch裏面的卷積參數,而後描述一下可能會出現的問題,再講解如何經過分組卷積去解決問題。

Pytorch卷積的實現

我會從維度的視角回顧一下Pytorch裏面的卷積的實現(你們也能夠手寫一下,幾個重點:輸入維度、輸出維度、正常卷積核參數維度、分組卷積維度、動態卷積維度、attention模塊輸出維度)。

輸入:輸入數據維度大小爲[ ,  ,  ,  ]。

輸出:輸出維度爲[  ,  ,  ,  ]。

卷積核:正常卷積核參數維度爲[  ,  ,  ,  ]。(在Pytorch中,2d卷積核參數應該是固定這種維度的)

這裏咱們能夠注意到,正常卷積核參數的維度是不存在  的。由於對於正常的卷積來講,不一樣的輸入數據,使用的是相同的卷積核,卷積核的數量與一次前向運算所輸入的  大小無關(相同層的卷積核參數只須要一份)。

可能會出現的問題

這裏描述一下實現動態卷積代碼的過程當中可能由於  大於1而出現的問題。

對於圖中attention模塊最後softmax輸出的  個數,他們的維度爲[  ,  ,  ,  ],能夠直接.view成[  ,  ],緊接着  做用於  卷積核參數上(造成動態卷積)。

問題所在:正常卷積,一次輸入多個數據,他們的卷積核參數是同樣的,因此只須要一份網絡參數便可;可是對於動態卷積而言,每一個輸入數據用的都是不一樣的卷積核,因此須要  份網絡參數,不符合Pytorch裏面的卷積參數格式,會出錯。

看下維度運算[  ,  ]*[  ,  ,  ,  ,  ],生成的動態卷積核是[  ,  ,  ,  ,  ],不符合Pytorch裏面的規定,不能直接參與運算(你們能夠按照這個思路寫個代碼看看,體會一下,光看可能感受不出來問題),最簡單的解決辦法就是  等於1,不會出現錯誤,可是慢啊!!!

總之,  大於1會致使中間卷積核參數不符合規定。

分組卷積以及如何經過分組卷積實現  大於1的動態卷積

一句話描述分組卷積:對於多通道的輸入,將他們分紅幾部分各自進行卷積,結果concate。

組卷積過程用廢話描述:對於輸入的數據[  ,  ,  ,  ],假設  爲  ,那麼分組卷積就是將他分爲兩個  爲  的數據(也能夠用其餘方法分),那麼維度就是[  , 5x2 ,  ,  ],換個維度換下視角,[  ,  ,  ,  ],那麼  爲2的組卷積能夠當作  的正常卷積。(若是仍是有點不瞭解分組卷積,能夠閱讀其餘文章仔細瞭解一下。

巧妙的轉換:上面將  翻倍便可將分組卷積轉化成正常卷積,那麼反向思考一下,將  變爲1,是否是能夠將正常卷積變成分組卷積?

咱們將  大小當作分組卷積中  的數量,令  所在維度直接變爲  !!!直接將輸入數據從 ,  ,  ,  ]變成[1,  ,  ,  ],就能夠用分組卷積解決問題了!!!

詳細描述實現過程:將輸入數據的維度當作[1,  ,  ,  ](分組卷積的節奏);卷積權重參數初始化爲[  ,  ,  ,  ,  ],attention模塊生成的維度爲[  ,  ],直接進行正常的矩陣乘法[  ,  ]*[  ,  * *  *  ]生成動態卷積的參數,生成的動態卷積權重維度爲[  ,  ,  ,  ,  ],將其當作分組卷積的權重[  ,  ,  ,  ](過程當中包含reshape)。這樣的處理就完成了,輸入數據[  ,  ,  ,  ],動態卷積核[  ,  ,  ,  ],直接是  的分組卷積,問題解決。

具體代碼以下:

class Dynamic_conv2d(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, K=4,):
super(Dynamic_conv2d, self).__init__()
assert in_planes%groups==0
self.in_planes = in_planes
self.out_planes = out_planes
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.bias = bias
self.K = K
self.attention = attention2d(in_planes, K, )

self.weight = nn.Parameter(torch.Tensor(K, out_planes, in_planes//groups, kernel_size, kernel_size), requires_grad=True)
if bias:
self.bias = nn.Parameter(torch.Tensor(K, out_planes))
else:
self.bias = None


def forward(self, x):#將batch視做維度變量,進行組卷積,由於組卷積的權重是不一樣的,動態卷積的權重也是不一樣的
softmax_attention = self.attention(x)
batch_size, in_planes, height, width = x.size()
x = x.view(1, -1, height, width)# 變化成一個維度進行組卷積
weight = self.weight.view(self.K, -1)

# 動態卷積的權重的生成, 生成的是batch_size個卷積參數(每一個參數不一樣)
aggregate_weight = torch.mm(softmax_attention, weight).view(-1, self.in_planes, self.kernel_size, self.kernel_size)
if self.bias is not None:
aggregate_bias = torch.mm(softmax_attention, self.bias).view(-1)
output = F.conv2d(x, weight=aggregate_weight, bias=aggregate_bias, stride=self.stride, padding=self.padding,
dilation=self.dilation, groups=self.groups*batch_size)
else:
output = F.conv2d(x, weight=aggregate_weight, bias=None, stride=self.stride, padding=self.padding,
dilation=self.dilation, groups=self.groups * batch_size)

output = output.view(batch_size, self.out_planes, output.size(-2), output.size(-1))
return output

完整的代碼在github.com/kaijieshi7/D,你們以爲有幫助的話,求點個星星。

紙上得來終覺淺,絕知此事要躬行。試下代碼,方能體會其中妙處。


對文章有疑問或者想加入交流羣,歡迎添加BBuf微信


爲了方便各位獲取公衆號獲取資料,能夠加入QQ羣獲取資源,更歡迎分享資源


本文分享自微信公衆號 - GiantPandaCV(BBuf233)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索