专栏首页GiantPandaCV激活还是不激活?CVPR2021-Activate Or Not

激活还是不激活?CVPR2021-Activate Or Not

简介

在该工作中,我们提出了一种名为ACON(Activate Or Not)激活函数。此外,我们发现由NAS搜索得到的Swish函数,是我们常用的ReLU激活函数的平滑形式。我们将该形式推广到ReLU函数的其他变体(Leaky-ReLU, PReLU等)。最后我们提出了一个meta-ACON激活函数和其设计空间,它可以自适应的选择是否激活神经元,通过替换原网络的激活层,能提升1-2个点的网络精度(ResNet-152)。

论文地址:Activate or Not: Learning Customized Activation(https://arxiv.org/abs/2009.04759)

代码地址:nmaac/ACON.pytorch(https://github.com/nmaac/ACON.pytorch)

Smooth Maximum

我们常用的ReLU激活函数本质是一个MAX函数,其公式如下

而MAX函数的平滑,可微分变体我们称为Smooth Maximum,其公式如下

其中 是一个平滑因子当 趋近于无穷大时,Smooth Maximum就变为标准的MAX函数,而当 为0时,Smooth Maximum就是一个算术平均的操作

这里我们只考虑Smooth Maximum只有两个输入量的情况,于是有以下公式

Smooth Maximum

其中 表示 sigmoid函数

笔者写了一段关于Smooth Maximum的代码

import numpy as np
import matplotlib.pyplot as plt


def smooth_maximum(x, x1_func, x2_func, beta=0.0):
    """
    Smooth Maximum 
    :param x: The input variable
    :param x1_func: The functor of n1
    :param x2_func: The functor of n2
    :param beta: The beta value
    :return: 
    """
    a_x = x1_func(x)
    b_x = x2_func(x)

    e_beta_a_x = np.exp(beta * a_x)
    e_beta_b_x = np.exp(beta * b_x)

    return ((a_x * e_beta_a_x) + (b_x * e_beta_b_x)) / (e_beta_a_x + e_beta_b_x)

从ReLU推广到Swish

考虑平滑形式下的ReLU,即

代入公式我们得到

而这个结果就是Swish激活函数!所以我们可以得到,Swish激活函数是ReLU函数的一种平滑近似。我们称其为ACON-A

我们也可以用上述代码验证下

def swish(x, beta=1.0):
    return beta * x / (1 + np.exp(-x))


Acon_a = lambda x: smooth_maximum(x, x1_func=lambda x: x, x2_func=lambda x: 0, beta=1.0)

x = np.arange(-5, 5, 0.01).astype(np.float32)

acon_a_out = Acon_a(x)
swish_out = swish(x)

plt.plot(x, acon_a_out, ls='--')
plt.plot(x, swish_out, ls='-.')
plt.grid()
plt.show()

ACON-A

ACON-B

上述的平滑形式可以推广到ReLU激活函数家族里(PReLU,Leaky-ReLU) 因此我们提出了ACON-B的变体,即

我们按照前面的代码实验一下Leaky ReLU和它的平滑形式

def leaky_relu(x, beta):
    return np.maximum(x, beta * x)

Acon_b = lambda x: smooth_maximum(x, x1_func=lambda x: x, x2_func=lambda x: 0.2 * x, beta=1.0)

x = np.arange(-5, 5, 0.01).astype(np.float32)
acon_b_out = Acon_b(x)
leaky_relu_out = leaky_relu(x, 0.2)

plt.plot(x, acon_b_out)
plt.plot(x, leaky_relu_out)

plt.grid()
plt.show()

ACON-B

ACON-C

最后我们提出最广泛的一种形式ACON-C,即

它能涵盖之前的,甚至是更复杂的形式,在代码实现中,p1和p2使用的是两个可学习参数来自适应调整

我们简单看下ACON-C的函数性质

对其求一阶导,可以得到

一阶导数

当 x 趋近于正无穷时,其梯度为 p1,当x趋近于负无穷时,其梯度为 p2。

对其求二阶导,有

二阶导

为了得到一阶导的上下界,我们令其二阶导为0,求得一阶导上下界分别为

一阶导上下界

可以看到ACON-C下,一阶导的上下界也是通过p1和p2两个参数来共同决定的

最后总结下各个形态的公式

总结

Meta-ACON

前面我们有提到,ACON系列的激活函数通过 的值来控制是否激活神经元(为0,即不激活)。因此我们需要为ACON设计一个计算 的自适应函数。

而自适应函数的设计空间包含了layer-wise,channel-wise,pixel-wise这三种空间,分别对应的是层,通道,像素。

这里我们选择了channel-wise,首先分别对H, W维度求均值,然后通过两个卷积层,使得每一个通道所有像素共享一个权重。公式如下

为了节省参数量,我们在和之间加了个缩放参数r,我们设置为16

代码解读

具体的代码很简单,实现为:

import torch
from torch import nn

class MetaAconC(nn.Module):
    r""" ACON activation (activate or not).
    MetaAconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x
    """
    def __init__(self, width):
        super().__init__()
        self.fc1 = nn.Conv2d(width, width//16, kernel_size=1, stride=1, bias=False)
        self.fc2 = nn.Conv2d(width//16, width, kernel_size=1, stride=1, bias=False)

        self.p1 = nn.Parameter(torch.randn(1, width, 1, 1))
        self.p2 = nn.Parameter(torch.randn(1, width, 1, 1))

        self.sigmoid = nn.Sigmoid()

    def forward(self, x, **kwargs):
        beta = self.sigmoid(self.fc2(self.fc1(x.mean(dim=2, keepdims=True).mean(dim=3, keepdims=True))))
        return (self.p1 * x - self.p2 * x) * self.sigmoid( beta * (self.p1 * x - self.p2 * x)) + self.p2 * x

首先作者设置了p1和p2这两个可学习参数,并且设置了fc1和fc2两个1x1卷积。在前向过程中,首先计算beta,x首先在H和W维度上求均值,然后经过两层1x1卷积,最后由sigmoid激活函数得到一个(0, 1)的值,用于控制是否激活。

接着就是按照前面的公式进行计算,大家可以参考上文的图

实验

实验对比

作者针对不同大小的网络做出了调整,针对小网络它替换了所有ReLU激活层,针对大网络(如ResNet50/101)只替换了每一个Block中3x3卷积后面的ReLU激活层,作者怎么设置的理由是避免过拟合,但我个人认为如果全都换成Meta-ACON,额外增加的参数量是很大的。

Meta-ACON虽然带来了一定的参数量,但是对大网络和小网络上都是有一定的提升

作者还针对设计空间做了一系列消融实验,其中channel-wise的效果是最好的

设计空间的消融实验

小结

该工作是之前旷视FReLU激活函数,ShuffleNet的作者MaNingNing提出的,构思十分巧妙,将ReLU和NAS搜索出来的Swish激活函数联系起来,并推广到一般的形式,为了让网络自适应的调整是否激活,设置了两层1x1卷积来控制。从实验上也从各个角度论证其有效性,期待后续对激活函数的进一步探索。

本文分享自微信公众号 - GiantPandaCV(BBuf233),作者:zzk

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-03-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 语义分割算法之DeepLabV3+论文理解及源码解析

    之前讲了DeepLabV1,V2,V3三个算法,DeepLab系列语义分割还剩下最后一个DeepLabV3+,以后有没有++,+++现在还不清楚,我们先来解读一...

    BBuf
  • Pytorch实现卷积神经网络训练量化(QAT)

    深度学习在移动端的应用越来越广泛,而移动端相对于GPU服务来讲算力较低并且存储空间也相对较小。基于这一点我们需要为移动端定制一些深度学习网络来满足我们的日常续需...

    BBuf
  • 基于Pytorch的动态卷积复现

    【GaintPandaCV导语】 最近动态卷积开始有人进行了研究,也有不少的论文发表(动态卷积论文合集https://github.com/kaijieshi7...

    BBuf
  • 【C语言笔记】时间日期函数

    time.h是C/C++中的日期和时间头文件。用于需要时间方面的函数。下面分享time.h头文件中几个常用函数的用法:

    正念君
  • USB OTG ID 检测原理【转】

    OTG 检测的原理是: USB OTG标准在完全兼容USB2.0标准的基础上,增添了电源管理(节省功耗)功能,它允许设备既可作为主机,也可作为外设操作(两用O...

    233333
  • USB描述符

    当某个设备被连接到 USB 主机上,该设备会向主机提供其功能和电源要求。通常,设备会通过一个描述符表格(其固件的一部分)来提供这些信息。描述符表格是数据的结构化...

    用户1519739
  • Android 内存优化杂谈

    Android 内存优化是我们性能优化工作中比较重要的一环,本文的着重总结概述降低应用运行内存的技巧。

    微信终端开发团队
  • 牛客网剑指offer-2

    输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

    earthchen
  • 教你一步步扣代码解出你需要找到的加密参数

    上周的pdd很多人说看了还不会找,都找我要写一篇来教教如何扣代码的,那就应大家要求,今天来写一篇详细的扣代码过程,完全从零到一,如果对你有帮助,还望大力分享,这...

    sergiojune
  • 基于Lockset的数据竞争检测方法汇总(三)

            上一篇文章中我们看到了有关共享对象状态变迁在Eraser基础上进行的改进,但是改进的不是特别明显,下面这篇论文不是单纯的用Lockset作为数据...

    chain

扫码关注云+社区

领取腾讯云代金券