文章原創來自做者的微信公衆號:【機器學習煉丹術】。交流羣氛圍超好,我但願能夠建議一個:當一我的遇到問題的時候,有這樣一個平臺能夠快速討論並解答,目前已經1羣已經滿員啦,2羣歡迎你的到來哦。加入羣惟一的要求就是,你對AI有興趣。加個人微信我邀請進羣cyx645016617。python
這個ACNet是一個不錯的對於卷積核結構的一個創新。總的來講是一個值得在CNN模型中嘗試的trick,至於有沒有效果還得看緣分。不過這個trick的聽同行來講,算是一個好的trick,因此值得嘗試。微信
這個trick的代價是增長了訓練階段的時間和參數,可是並不會增長推理階段的時長,也不會增長最終模型的參數。網絡
這個方法挺簡單了,能夠用這一張圖來展現:
機器學習
煉丹兄帶你理解這圖:ide
我的的理解,通常的模型也是有可能訓練出ACNet的效果的,由於二者的參數徹底等價。可是ACNet多是由於強化了橫向和縱向的特徵,因此會取得更好的效果。而且這個至關於,給卷積核增長了一層限制,卷積核的每個參數再也不是同等中重要的,中心更爲重要。由於增長了限制,可能也會避免過擬合。這是我的從實驗中獲得的一些猜測和思考。學習
下面看一下另一篇文章的解釋,看得懂的朋友能夠驗證本身理解的是否正確:
測試
我先寫一個用通常卷積的很是簡單的分類網絡:.net
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.features = nn.Sequential( nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.Conv2d(64, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2) ) self.classifier = nn.Sequential( nn.Dropout(p = 0.5), nn.Linear(64 * 7 * 7, 512), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Dropout(p = 0.5), nn.Linear(512, 512), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Dropout(p = 0.5), nn.Linear(512, 10), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x
下面我來把這個網絡轉成使用ACNet的結構,先構建一個acblock來代替卷積:code
class ACConv2d(nn.Module): def __init__(self,in_channels,out_channels,kernel_size=3,stride=1,padding=1,bias=True): super(ACConv2d,self).__init__() self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size, stride=stride,padding=padding,bias=True) self.ac1 = nn.Conv2d(in_channels,out_channels,kernel_size=(1,kernel_size), stride=stride,padding=(0,padding),bias=True) self.ac2 = nn.Conv2d(in_channels,out_channels,kernel_size=(kernel_size,1), stride=stride,padding=(padding,0),bias=True) def forward(self,x): ac1 = self.ac1(x) ac2 = self.ac2(x) x = self.conv(x) return (ac1+ac2+x)/3
而後把網路中的nn.Conv2d
替換成ACConv2d
便可:orm
class ACNet(nn.Module): def __init__(self): super(ACNet, self).__init__() self.features = nn.Sequential( ACConv2d(1, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), ACConv2d(32, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), ACConv2d(32, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), ACConv2d(64, 64, kernel_size=3, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2) ) self.classifier = nn.Sequential( nn.Dropout(p = 0.5), nn.Linear(64 * 7 * 7, 512), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Dropout(p = 0.5), nn.Linear(512, 512), nn.BatchNorm1d(512), nn.ReLU(inplace=True), nn.Dropout(p = 0.5), nn.Linear(512, 10), ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x
效果上看,模型在ImageNet上是有必定的效果的。爲何會有這樣的提高呢?論文中給出了一種解釋,由於1x3和3x1的卷積覈對於豎直翻轉和水平翻轉是有魯棒性的。看下圖:
特徵圖豎直翻轉以後,對於1x3的卷積核的特徵並無影響,可是3x3的卷積核中的特徵已經發生改變。同理,3x1的卷積覈對於水平翻轉也有魯棒性。
這個翻轉魯棒性是一種解釋,下面還有另一種解釋:
這部分的緣由我的理解是來自梯度差別化,原來只有一個[公式]卷積層,梯度能夠看出一份,而添加了1x3和3x1卷積層後,部分位置的梯度變爲2份和3份,也是更加細化了。並且理論上能夠融合無數個卷積層不斷逼近現有網絡的效果極限,融合方式不限於相加(訓練和推理階段一致便可),融合的卷積層也不限於1x3或3x1尺寸。
我把這個方法用在我MNIST數據集的識別上,不過沒有什麼效果哈哈。但願未來能夠個人項目有提高效果,是一個值得嘗試的trick,歡迎你們收藏點贊。
最後,若是你耐心看到這裏,而且對以前的內容加以思考,就會發現,我寫的ac卷積,並無實如今推理過程的卷積核融合。我後來完善了一下代碼,當調用model.eval()
後,acconv卷積就會融合成一個卷積層,而不是3個並行的卷積層:
class ACConv2d(nn.Module): def __init__(self,in_channels,out_channels,kernel_size=3,stride=1,padding=1,bias=False): super(ACConv2d,self).__init__() self.bias = bias self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size, stride=stride,padding=padding,bias=bias) self.ac1 = nn.Conv2d(in_channels,out_channels,kernel_size=(1,kernel_size), stride=stride,padding=(0,padding),bias=bias) self.ac2 = nn.Conv2d(in_channels,out_channels,kernel_size=(kernel_size,1), stride=stride,padding=(padding,0),bias=bias) self.fusedconv = nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size, stride=stride,padding=padding,bias=bias) def forward(self,x): if self.training: ac1 = self.ac1(x) ac2 = self.ac2(x) x = self.conv(x) return (ac1+ac2+x)/3 else: x = self.fusedconv(x) return x def train(self,mode=True): super().train(mode=mode) if mode is False: weight = self.conv.weight.cpu().detach().numpy() weight[:,:,1:2,:] = weight[:,:,1:2,:] + self.ac1.weight.cpu().detach().numpy() weight[:,:,:,1:2] = weight[:,:,:,1:2] + self.ac2.weight.cpu().detach().numpy() self.fusedconv.weight = torch.nn.Parameter(torch.FloatTensor(weight/3)) if self.bias: bias = self.conv.bias.cpu().detach().numpy()+self.conv.ac1.cpu().detach().numpy()+self.conv.ac2.cpu().detach().numpy() self.fusedconv.bias = torch.nn.Parameter(torch.FloatTensor(bias/3)) if torch.cuda.is_available(): self.fusedconv = self.fusedconv.cuda()
感謝各位的閱讀,喜歡的能夠點個「贊」和「在看」!
參考文章: