1. 程式人生 > 實用技巧 >Dynamic ReLU(ECCV'20):一種根據輸入自適應動態調節的啟用函式

Dynamic ReLU(ECCV'20):一種根據輸入自適應動態調節的啟用函式

Dynamic ReLU(ECCV'20):一種根據輸入自適應動態調節的啟用函式

Mark 十月的清晨和你都值得期待 KevinCK 王晉東不在家

導讀:ReLU是深度神經網路中常用的啟用函式。到目前為止,ReLU及其推廣(非引數或引數)都是靜態的,對所有的輸入樣本執行相同的操作。在本文中,作者提出了Dynamic ReLU啟用函式(DY-ReLU),它的引數依賴於所有輸入。其關鍵在於DY-ReLU將全域性上下文編碼為函式,並相應地調整分段線性啟用函式。與靜態模型相比,DY-ReLU的額外計算開銷可以忽略不計,但其表現能力顯著提高,特別是對於輕量神經網路。僅僅通過簡單地在MobileNetV2上使用DY-ReLU ,ImageNet分類的最高精度就可以從72.0%提高到76.2%,而計算量只增加了5%.

1.Motivation

ReLU是深度學習中很重要的里程碑,簡單但強大,能夠極大地提升神經網路的效能,比如ResNet, MobileNet and ShuffleNet。目前也有很多ReLU的改進版,比如Leaky ReLU和 PReLU,而這些改進版和原版的最終引數都是固定的。所以論文想到,是否能夠根據輸入特徵來調整ReLU的引數。

本文提出的DY-ReLU它是一個引數依賴於輸入的動態分段線性函式。DY-ReLU既不增加網路的深度,也不增加網路的寬度,但可以有效地增加模型能力.

ReLU:分段函式的都依賴於輸入

2.方法

定義ReLU函式為y=max(x, 0), x為輸入值,那麼y=max(x,0). 我們可以寫成更加一般的形式y=max(ax+b),對應ReLU

y=max(ax+b),

s.t., a=1, b=0, when x > 0

a=0, b=0, when x<=0

這篇論文是對a, b進行了進一步的擴充套件, y=max{a1x+b1, a2x+b2}(畫外音:可以新增更多的函式 akx+bk,不過太多複雜度就上去了,reviewer應該會吐槽,作者這裡用了兩個, 我這裡就直接寫成了a1,a2, b1, b2與原文稍有不同)。

這裡把a, b看做是一個關於輸入x的一個函式,怎麼關聯呢?簡單粗暴的方式先對x平均,然後通過matrix transformation得到對應的引數個數就行,根據不同的情況,需要的引數量不一樣,具體這裡作者介紹了三種:

圖1 每個小方塊都apply一樣的分段函式

(1)如果每一個entry都apply一樣的DYReLU, 也就是圖1上面的小方塊都是長得一樣的分段函式,那麼我們只需要用四個引數{a1,a2,b1,b2},

圖2 每一個channel上面的分段函式一樣

(2)如果是每一個channel apply不同的函式,圖2中的channel-wise,每一個channel是一樣的引數, 就需要4*C個引數。

第1個feature map:

{a11,a12,b11,b12}

第2個feature map:

{a21,a22,b21,b22}

第3個feature map:

{a31,a32,b31,b32}

....

第C個feature map:

{aC1,aC2,bC1,bC2}

圖3每一個元素上apply不一樣的啟用函式

(3)如果更凶殘一些,每一個通道的每一個entry都不一樣,也就是spatial-wise和channel-wise,那就需要4C*H*W個引數了。(畫外音:如果每一層都加入這樣的一個操作,引數量還小嗎?)。

在related work中,作者list出了相關的文獻,並做了一下總結和對比,如下表所示:

後續作者在ImageNet,COCO上使用MobileNetV2做了一系列的實驗,感興趣的同學,可以瞭解一下實驗performance。這裡我思考了一下,如果在節點分類的任務上加上這樣的操作,也就是對於每一個節點的N維特徵進行average,再加上linear操作等得到4或者4C個引數值,用於max{a1x+b, a2x+b}, 這個操作倒是像是一種attention. Attention關注了node feature哪個維度的重要度以及與當前維度的相關度, 具體效果怎麼樣,感興趣的同學做完實驗可以與我聯絡,我們一起交流一下~. 下面是作者post的程式碼,在下方加上小編寫的main函式,可以執行一下看每一層對應的輸出,深入理解本文的內容,當然也可以改寫成節點分類,圖分類等等任務上。

import torch
import torch.nn as nn

class DyReLU(nn.Module):
    def __init__(self, channels, reduction=4, k=2, conv_type='2d'):
        super(DyReLU, self).__init__()
        self.channels = channels
        self.k = k
        self.conv_type = conv_type
        assert self.conv_type in ['1d', '2d']

        self.fc1 = nn.Linear(channels, channels // reduction)
        self.relu = nn.ReLU(inplace=True)
        self.fc2 = nn.Linear(channels // reduction, 2*k)
        self.sigmoid = nn.Sigmoid()

        self.register_buffer('lambdas', torch.Tensor([1.]*k + [0.5]*k).float())
        self.register_buffer('init_v', torch.Tensor([1.] + [0.]*(2*k - 1)).float())

    def get_relu_coefs(self, x):
        theta = torch.mean(x, axis=-1)
        if self.conv_type == '2d':
            theta = torch.mean(theta, axis=-1)
        theta = self.fc1(theta)
        theta = self.relu(theta)
        theta = self.fc2(theta)
        theta = 2 * self.sigmoid(theta) - 1
        return theta

    def forward(self, x):
        raise NotImplementedError


class DyReLUA(DyReLU):
    def __init__(self, channels, reduction=4, k=2, conv_type='2d'):
        super(DyReLUA, self).__init__(channels, reduction, k, conv_type)
        self.fc2 = nn.Linear(channels // reduction, 2*k)

    def forward(self, x):
        assert x.shape[1] == self.channels
        theta = self.get_relu_coefs(x)

        relu_coefs = theta.view(-1, 2*self.k) * self.lambdas + self.init_v
        # BxCxL -> LxCxBx1
        x_perm = x.transpose(0, -1).unsqueeze(-1)
        output = x_perm * relu_coefs[:, :self.k] + relu_coefs[:, self.k:]
        # LxCxBx2 -> BxCxL
        result = torch.max(output, dim=-1)[0].transpose(0, -1)
        return result


class DyReLUB(DyReLU):
    def __init__(self, channels, reduction=4, k=2, conv_type='2d'):
        super(DyReLUB, self).__init__(channels, reduction, k, conv_type)
        self.fc2 = nn.Linear(channels // reduction, 2*k*channels)

    def forward(self, x):
        assert x.shape[1] == self.channels
        theta = self.get_relu_coefs(x)

        relu_coefs = theta.view(-1, self.channels, 2*self.k) * self.lambdas + self.init_v

        if self.conv_type == '1d':
            # BxCxL -> LxBxCx1
            x_perm = x.permute(2, 0, 1).unsqueeze(-1)
            output = x_perm * relu_coefs[:, :, :self.k] + relu_coefs[:, :, self.k:]
            # LxBxCx2 -> BxCxL
            result = torch.max(output, dim=-1)[0].permute(1, 2, 0)

        elif self.conv_type == '2d':
            # BxCxHxW -> HxWxBxCx1
            x_perm = x.permute(2, 3, 0, 1).unsqueeze(-1)
            output = x_perm * relu_coefs[:, :, :self.k] + relu_coefs[:, :, self.k:]
            # HxWxBxCx2 -> BxCxHxW
            result = torch.max(output, dim=-1)[0].permute(2, 3, 0, 1)

        return result

if __name__ == '__main__':
    x = torch.randn(1, 50, 100)
    model = DyReLUA(channels=50, conv_type='1d')
    y = model(x)

最後:圖網路相關論文裡面的theorem滿天飛,突然回頭看看ECCV的論文,總感覺少點什麼.

釋出於 09-12 深度學習(Deep Learning) 圖神經網路(GNN) 圖卷積神經網路 (GCN)

文章被以下專欄收錄

圖網路學習與PyTorch 不定時更新圖網路學習筆記

推薦閱讀

注意力機制+ReLU啟用函式=自適應引數化ReLU

雲程萬里

南航提出ATAC:啟用函式和注意力機制的統一

CVer

圖神經網路的新基準Benchmarking Graph Neural Networks

Mark發表於圖網路學習...

圖卷積網路(GCN)入門詳解

圖卷積網路(GCN)入門詳解什麼是GCNGCN 概述模型定義數學推導Graph Laplacianref圖神經網路領域算是一個比較新的領域,有非常多的探索潛力,所以我也一直想著要入門。其中圖卷積網路就非常…

Don.hub

4 條評論

寫下你的評論...
  • foolbird09-13

    這年頭relu改進還可以發頂會的嗎。。

  • 胡椒肥牛飯09-25

    感覺這論文 作者應該要公開原始碼,不然很難復現。

  • Mark(作者)回覆胡椒肥牛飯09-25 已經公開了