幼敏 發表於 2021-5-12 11:06:12

YOLO v4常见的非线性激活函数详解

<p>YOLO v4中用到的激活函数是Mish激活函数<br />
在YOLO v4中被提及的激活函数有: ReLU, Leaky ReLU, PReLU, ReLU6, SELU, Swish, Mish<br />
其中Leaky ReLU, PReLU难以训练,ReLU6转为量化网络设计</p>
<p>激活函数使用过程图:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483542.png" /></p>
<h2>一、饱和激活函数</h2>
<h3>&nbsp;1.1、Sigmoid</h3>
<p>函数表达式:</p>
<p style="text-align: center"></p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023902.png?202141210523" /></center>
<p>Sigmoid函数图像及其导数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483543.png" /></p>
<p>优点:</p>
<ul>
    <li>是一个便于求导的平滑函数;</li>
    <li>能压缩数据,使输出保证在 [ 0 , 1 ] 之间(相当于对输出做了归一化),保证数据幅度不会有问题;</li>
    <li>(有上下界)适合用于前向传播,但是不利于反向传播。</li>
</ul>
<p>缺点:</p>
<ul>
    <li>容易出现梯度消失(gradient vanishing),不利于权重更新;</li>
    <li>不是0均值(zero-centered)的,这会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响。以 f=sigmoid(wx+b)为例, 假设输入均为正数(或负数),那么对w的导数总是正数(或负数),这样在反向传播过程中要么都往正方向更新,要么都往负方向更新,导致有一种捆绑效果,使得收敛缓慢。</li>
    <li>指数运算,相对耗时。</li>
</ul>
<h3>1.2、hard-Sigmoid函数</h3>
<p>hard-Sigmoid函数时Sigmoid激活函数的分段线性近似。</p>
<p>函数公式:</p>
<p style="text-align: center"></p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023903.png?2021412105347" /></center>
<p>hard-Sigmoid函数图像和Sigmoid函数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483544.png" /></p>
<p>hard-Sigmoid函数图像及其导数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483545.png" /></p>
<p>优点:</p>
<ol>
    <li>从公示和曲线上来看,其更易计算,没有指数运算,因此会提高训练的效率。</li>
</ol>
<p>缺点:</p>
<ol>
    <li>首次派生值为零可能会导致神经元died或者过慢的学习率。</li>
</ol>
<h3>1.3、Tanh双曲正切</h3>
<p>函数表达式:</p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023904.png?2021412105452" /></center>
<p>Tanh函数图像及其导函数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483546.png" /></p>
<p>优点:</p>
<ol>
    <li>解决了Sigmoid函数的非zero-centered问题</li>
    <li>能压缩数据,使输出保证在 [ 0 , 1 ] 之间(相当于对输出做了归一化),保证数据幅度不会有问题;(有上下界)</li>
</ol>
<p>缺点:</p>
<ul>
    <li>还是容易出现梯度消失(gradient vanishing),不利于权重更新;</li>
    <li>指数运算,相对耗时。</li>
</ul>
<h2>二、非饱和激活函数</h2>
<h3>&nbsp;2.1、ReLU(修正线性单元)</h3>
<p>函数表达式:</p>
<center>f ( z ) = m a x ( 0 , x ) f(z)=max(0,x) f(z)=max(0,x)</center>
<p>ReLU函数图像及其导数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483547.png" /></p>
<p>优点:</p>
<ol>
    <li>ReLu的收敛速度比 sigmoid 和 tanh 快;</li>
    <li>输入为正时,解决了梯度消失的问题,适合用于反向传播。;</li>
    <li>计算复杂度低,不需要进行指数运算;</li>
</ol>
<p>缺点:</p>
<ul>
    <li>ReLU的输出不是zero-centered;</li>
    <li>ReLU不会对数据做幅度压缩,所以数据的幅度会随着模型层数的增加不断扩张。(有下界无上界)</li>
    <li>Dead ReLU Problem(神经元坏死现象):x为负数时,梯度都是0,这些神经元可能永远不会被激活,导致相应参数永远不会被更新。(输入为负时,函数存在梯度消失的现象)</li>
</ul>
<h3>&nbsp;2.2、ReLU6(抑制其最大值)</h3>
<p>函数表达式:</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023905.png?2021412105634" /></p>
<p>ReLU函数图像和ReLU6函数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483648.png" /></p>
<p>ReLU6函数图像及其导数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483649.png" /></p>
<h3>2.3、Leakly ReLU</h3>
<p>函数表达式:</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023906.png?2021412105810" /></p>
<p>ReLU函数图像和Leakly ReLU函数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483650.png" /></p>
<p>Leakly ReLU函数图像及其导数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483651.png" /></p>
<p>优点:</p>
<ol>
    <li>解决上述的dead ReLU现象, 让负数区域也会梯度消失;</li>
</ol>
<p>理论上Leaky ReLU 是优于ReLU的,但是实际操作中,并不一定。</p>
<h3>2.4、PReLU(parametric ReLU)</h3>
<p>函数公式:</p>
<p style="text-align: center"></p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023907.png?2021412105911" /></center>
<p>注意:</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023908.png?20214121100" /></p>
<p>函数图像:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483652.png" /></p>
<p>优点:</p>
<ul>
    <li>可以避免dead ReLU现象;</li>
    <li>与ELU相比,输入为负数时不会出现梯度消失。</li>
</ul>
<h3>2.5、ELU(指数线性函数)</h3>
<p>函数表达式:</p>
<p style="text-align: center"></p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023909.png?202141211049" /></center>
<p>ELU函数图像及其导数图像( α = 1.5 \alpha=1.5 α=1.5):</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483653.png" /></p>
<p>优点:</p>
<ul>
    <li>有ReLU的所有优点,且没有Dead ReLU Problem(神经元坏死现象);</li>
    <li>输出是zero-centered的,输出平均值接近0;</li>
    <li>通过减少偏置偏移的影响,使正常梯度更加接近自然梯度,从而使均值向0加速学习。</li>
</ul>
<p>缺点:</p>
<ul>
    <li>计算量更高了。</li>
</ul>
<p>理论上ELU优于ReLU, 但是真实数据下,并不一定。</p>
<h3>2.6、SELU</h3>
<p>SELU就是在ELU的基础上添加了一个 λ \lambda λ参数,且 λ &gt; 1 \lambda&gt;1 λ&gt;1</p>
<p>函数表达式:</p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023910.png?202141211157" /></center>
<p>ELU函数图像和SELU函数图像对比( α = 1.5 , λ = 2 \alpha=1.5, \lambda=2 α=1.5,λ=2):</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483654.png" /></p>
<p>SELU函数图像及其导数图像( α = 1.5 , λ = 2 \alpha=1.5, \lambda=2 α=1.5,λ=2):</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483655.png" /></p>
<p>优点:</p>
<ol>
    <li>以前的ReLU、P-ReLU、ELU等激活函数都是在负半轴坡度平缓,这样在激活的方差过大时可以让梯度减小,防止了梯度爆炸,但是在正半轴其梯度简答的设置为了1。而SELU的正半轴大于1,在方差过小的时候可以让它增大,但是同时防止了梯度消失。这样激活函数就有了一个不动点,网络深了之后每一层的输出都是均值为0,方差为1. 2.7、Swish</li>
</ol>
<p>函数表达式:</p>
<center><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512105023911.png?202141211241" /></center>
<p>Swish函数图像( β = 0.1 , β = 1 , β = 10 \beta=0.1, \beta=1,\beta=10 β=0.1,β=1,β=10):</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483756.png" /></p>
<p>Swish函数梯度图像( β = 0.1 , β = 1 , β = 10 \beta=0.1, \beta=1,\beta=10 β=0.1,β=1,β=10):</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483757.png" /></p>
<p>优点:</p>
<ul>
    <li>在x &gt; 0的时候,同样是不存在梯度消失的情况;而在x &lt; 0时候,神经元也不会像ReLU一样出现死亡的情况。</li>
    <li>同时Swish相比于ReLU导数不是一成不变的,这也是一种优势。</li>
    <li>而且Swish处处可导,连续光滑。</li>
</ul>
<p>缺点:</p>
<ul>
    <li>计算量大,本来sigmoid函数就不容易计算,它比sigmoid还难。 2.8、hard-Swish</li>
</ul>
<p>hard = 硬,就是让图像在整体上没那么光滑(从下面两个图都可以看出来)</p>
<p>函数表达式:</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512110343275.png?20214121143" /></p>
<p>hard-Swish函数图像和Swish( β = 1 \beta=1 β=1)函数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483758.png" /></p>
<p>hard-Swish函数图像和Swish( β = 1 \beta=1 β=1)函数梯度图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483759.png" /></p>
<p>优点:</p>
<ol>
    <li>hard-Swish近似达到了Swish的效果;</li>
    <li>且改善了Swish的计算量过大的问题,在量化模式下,ReLU函数相比Sigmoid好算太多了;</li>
</ol>
<h3>&nbsp;2.9、Mish</h3>
<p>论文地址:</p>
<blockquote>
<p>https://arxiv.org/pdf/1908.08681.pdf</p>
</blockquote>
<p>关于激活函数改进的最新一篇文章,且被广泛用于YOLO4中,相比Swish有0.494%的提升,相比ReLU有1.671%的提升。</p>
<p>Mish函数公式:</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202105/2021512110450026.png?20214121159" /></p>
<p>Mish函数图像和Swish( β = 1 \beta=1 β=1)函数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483760.png" /></p>
<p>Mish函数图像和Swish( β = 1 \beta=1 β=1)函数导数图像对比:</p>
<p style="text-align: center"><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483761.png" /></p>
<p>为什么Mish表现的更好:</p>
<blockquote>
<p>上面无边界(即正值可以达到任何高度)避免了由于封顶而导致的饱和。理论上对负值的轻微允许更好的梯度流,而不是像ReLU中那样的硬零边界。<br />
最后,可能也是最重要的,目前的想法是,平滑的激活函数允许更好的信息深入神经网络,从而得到更好的准确性和泛化。Mish函数在曲线上几乎所有点上都极其平滑。</p>
</blockquote>
<h2>三、PyTorch 实现</h2>
<div class="jb51code">
<pre class="brush:plain;">
import matplotlib.pyplot as plt
import numpy as np

class ActivateFunc():
    def __init__(self, x, b=None, lamb=None, alpha=None, a=None):
      super(ActivateFunc, self).__init__()
      self.x = x
      self.b = b
      self.lamb = lamb
      self.alpha = alpha
      self.a = a

    def Sigmoid(self):
      y = np.exp(self.x) / (np.exp(self.x) + 1)
      y_grad = y*(1-y)
      return

    def Hard_Sigmoid(self):
      f = (2 * self.x + 5) / 10
      y = np.where(np.where(f &gt; 1, 1, f) &lt; 0, 0, np.where(f &gt; 1, 1, f))
      y_grad = np.where(f &gt; 0, np.where(f &gt;= 1, 0, 1 / 5), 0)
      return

    def Tanh(self):
      y = np.tanh(self.x)
      y_grad = 1 - y * y
      return

    def ReLU(self):
      y = np.where(self.x &lt; 0, 0, self.x)
      y_grad = np.where(self.x &lt; 0, 0, 1)
      return

    def ReLU6(self):
      y = np.where(np.where(self.x &lt; 0, 0, self.x) &gt; 6, 6, np.where(self.x &lt; 0, 0, self.x))
      y_grad = np.where(self.x &gt; 6, 0, np.where(self.x &lt; 0, 0, 1))
      return

    def LeakyReLU(self):   # a大于1,指定a
      y = np.where(self.x &lt; 0, self.x / self.a, self.x)
      y_grad = np.where(self.x &lt; 0, 1 / self.a, 1)
      return

    def PReLU(self):    # a大于1,指定a
      y = np.where(self.x &lt; 0, self.x / self.a, self.x)
      y_grad = np.where(self.x &lt; 0, 1 / self.a, 1)
      return

    def ELU(self): # alpha是个常数,指定alpha
      y = np.where(self.x &gt; 0, self.x, self.alpha * (np.exp(self.x) - 1))
      y_grad = np.where(self.x &gt; 0, 1, self.alpha * np.exp(self.x))
      return

    def SELU(self):# lamb大于1,指定lamb和alpha
      y = np.where(self.x &gt; 0, self.lamb * self.x, self.lamb * self.alpha * (np.exp(self.x) - 1))
      y_grad = np.where(self.x &gt; 0, self.lamb * 1, self.lamb * self.alpha * np.exp(self.x))
      return

    def Swish(self): # b是一个常数,指定b
      y = self.x * (np.exp(self.b*self.x) / (np.exp(self.b*self.x) + 1))
      y_grad = np.exp(self.b*self.x)/(1+np.exp(self.b*self.x)) + self.x * (self.b*np.exp(self.b*self.x) / ((1+np.exp(self.b*self.x))*(1+np.exp(self.b*self.x))))
      return

    def Hard_Swish(self):
      f = self.x + 3
      relu6 = np.where(np.where(f &lt; 0, 0, f) &gt; 6, 6, np.where(f &lt; 0, 0, f))
      relu6_grad = np.where(f &gt; 6, 0, np.where(f &lt; 0, 0, 1))
      y = self.x * relu6 / 6
      y_grad = relu6 / 6 + self.x * relu6_grad / 6
      return

    def Mish(self):
      f = 1 + np.exp(x)
      y = self.x * ((f*f-1) / (f*f+1))
      y_grad = (f*f-1) / (f*f+1) + self.x*(4*f*(f-1)) / ((f*f+1)*(f*f+1))
      return

def PlotActiFunc(x, y, title):
    plt.grid(which='minor', alpha=0.2)
    plt.grid(which='major', alpha=0.5)
    plt.plot(x, y)
    plt.title(title)
    plt.show()

def PlotMultiFunc(x, y):
    plt.grid(which='minor', alpha=0.2)
    plt.grid(which='major', alpha=0.5)
    plt.plot(x, y)

if __name__ == '__main__':
    x = np.arange(-10, 10, 0.01)
    activateFunc = ActivateFunc(x)
    activateFunc.a = 100
    activateFunc.b= 1
    activateFunc.alpha = 1.5
    activateFunc.lamb = 2

    plt.figure(1)
    PlotMultiFunc(x, activateFunc.Sigmoid())
    PlotMultiFunc(x, activateFunc.Hard_Sigmoid())
    PlotMultiFunc(x, activateFunc.Tanh())
    PlotMultiFunc(x, activateFunc.ReLU())
    PlotMultiFunc(x, activateFunc.ReLU6())
    PlotMultiFunc(x, activateFunc.LeakyReLU())
    PlotMultiFunc(x, activateFunc.ELU())
    PlotMultiFunc(x, activateFunc.SELU())
    PlotMultiFunc(x, activateFunc.Swish())
    PlotMultiFunc(x, activateFunc.Hard_Swish())
    PlotMultiFunc(x, activateFunc.Mish())

    plt.legend(['Sigmoid', 'Hard_Sigmoid', 'Tanh', 'ReLU', 'ReLU6', 'LeakyReLU',
                'ELU', 'SELU', 'Swish', 'Hard_Swish', 'Mish'])
    plt.show()</pre>
</div>
<h2>四、结果显示</h2>
<p><img alt="在这里插入图片描述" src="https://img.jbzj.com/file_images/article/202105/2021051210483762.png" /></p>
<p>Reference</p>
<p>链接1: link.</p>
<p>链接2: link.</p>
<p>https://arxiv.org/pdf/1908.08681.pdf</p>
<p>到此这篇关于YOLO v4常见的非线性激活函数详解的文章就介绍到这了,更多相关YOLO v4激活函数内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>pytorch/transformers 最后一层不加激活函数的原因分析</li><li>python人工智能tensorflow常用激活函数Activation Functions</li><li>python 深度学习中的4种激活函数</li><li>使用keras实现非线性回归(两种加激活函数的方式)</li><li>Keras 中Leaky ReLU等高级激活函数的用法</li><li>tensorflow自定义激活函数实例</li><li>pytorch方法测试——激活函数(ReLU)详解</li><li>详解Python中常用的激活函数(Sigmoid、Tanh、ReLU等)</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: YOLO v4常见的非线性激活函数详解