【白话深度学习】轻松了解几种常用的激活函数

 

如图所示,表示神经元的O中明确显示了激活函数的计算过程,即信号的加权总和为节点a,然后节点a被激活函数h()转换成节点y。简单的说,我们可以将上图用以下两个函数表示:

  1. a = b + w_{1}x_{1} + w_{2}x_{2}
  2. y = h(a)

表示先计算输入信号的加权总和,然后用激活函数转换这一总和。因此,登场的h(a)函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。

 

二、神经网络的激活函数为什么必须使用非线性函数

换句话说,激活函数不能使用线性函数。为什么不能使用线性函数呢?

因为使用线性函数的话,加深神经网络的层数就没有意义了。 线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。为了具体地(稍微直观地)理解这一点,我们来思考下面这个简单的例子。

这里我们考虑把线性函数h(x) = cx作为激活函数,把y(x) = h(h(h(x)))的运算对应3层神经网络A。这个运算会进行y(x) = c × c × c × x 的乘法运算,但是同样的处理可以由y(x) = ax(注意,a = c^{^{3}})这一次乘法运算(即没有隐藏层的神经网络)来表示。如本例所示, 使用线性函数时,无法发挥多层网络带来的优势。因此,为了发挥叠加层所带来的优势,激活函数必须使用非线性函数。

如果使用非线性函数的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。

 

三、几种激活函数

3.1 阶跃函数

公式:

h(x) = \left\{\begin{matrix} 0 \quad (x\leqslant 0)\\ 1\quad (x> 0) \end{matrix}\right.

1. 阶跃函数的实现

当输入超过0时,输出1, 否则输出0。可以像下面这样简单地实现阶跃函数。

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

这个实现简单、易于理解,但是参数x只能接受实数(浮点数)。也就是说,允许形如step_function(3.0)的调用,但不允许参数取NumPy数组,例如step_function(np.array([1.0, 2.0]))。为了便于后面的操作,因此我们把它修改为支持NumPy数组的实现。

def step_function(x):
    y = x > 0
    return y.astype(np.int)

上述函数的内容只有两行。由于使用了NumPy中的“技巧”,可能会有点难理解。因此将用几行代码让你更好地理解这种“技巧”。

import numpy as np
x = np.array([-1.0, 1.0, 2.0])
print(x)   # [-1., 1., 2.]
y = x > 0
print(y)   # [False, True, True]
Y = y.astype(np.int)
print(Y)   # [0, 1, 1]
[-1.  1.  2.]
[False  True  True]
[0 1 1]

其中astype()方法通过参数指定期望的类型,这个例子中是np.int型。Python中将布尔型转换为int型后,True会转换为1,False会转换为0。以上就是阶跃函数的实现中所用到的NumPy的“技巧”。

2. 阶跃函数的图形

# 导包
import numpy as np
import matplotlib.pylab as plt

# 定义阶跃函数
def step_function(x):
    return np.array(x > 0, dtype=np.int)

# 模拟数据
x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)

# 绘图
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴的范围
plt.show()

阶跃函数以0为界,输出从0切换为1(或者从1切换为0)。它的值呈阶梯式变化,所以称为阶跃函数。

3.2 sigmoid函数

公式:

h(x) = \frac{1}{1 + e^{-x}}

1. sigmoid函数的实现

下面,用Python可以像下面代码表示的sigmoid函数。

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

如果在这个sigmoid函数中输入一个NumPy数组,结果会如何呢?

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.array([-1.0, 1.0, 2.0])
print(sigmoid(x))
[0.26894142 0.73105858 0.88079708]

之所以sigmoid函数的实现能支持NumPy数组,秘密就在于NumPy的广播功能。根据NumPy 的广播功能,如果在标量和NumPy数组 之间进行运算,则标量会和NumPy数组的各个元素进行运算。

2. sigmoid函数的图形

# 导包
import numpy as np
import matplotlib.pylab as plt

# 定义sigmoid函数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 模拟数据
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)

# 绘图
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴的范围
plt.show()

纵轴的取值范围(0, 1)。

3.3 ReLU函数

公式:

h(x) = \left\{\begin{matrix} x \quad (x > 0)\\ 0\quad (x\leqslant 0) \end{matrix}\right.

1. ReLu函数的实现

ReLU(Rectified Linear Unit)函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0。因此,ReLU函数的实现也很简单,可以写成如下形式。

def relu(x):
    return np.maximum(0, x)

这里使用了NumPy的maximum函数。maximum函数会从输入的数值中选择较大的那个值进行输出。

2. ReLu函数的图形

# 导包
import numpy as np
import matplotlib.pylab as plt

# 定义relu函数
def relu(x):
    return np.maximum(0, x)

# 模拟数据
x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)

# 绘图
plt.plot(x, y)
plt.ylim(-1, 5) # 指定y轴的范围
plt.show()

3.4 softmax函数

公式:

y^{_{k}} = \frac{e^{a_{k}}}{\sum_{i=1}^{n}e^{a_{i}}}

softmax函数的分子是输入信号a_{k}的指数函数,分母是所有输入信号的指数函数的和。

1. softmax函数的实现

用图表示softmax函数的话,如图所示,softmax函数的输出通过箭头与所有的输入信号相连。输出层的各个神经元都受到所有输入信号的影响。

下面,用Python可以像下面代码表示的softmax函数。

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

2. 实现softmax函数时的注意事项

上面的softmax函数的实现虽然正确描述了公式,但在计算机的运算上有一定的缺陷。这个缺陷就是溢出问题。softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。比如,e^{10}的值 会超过20000,e^{100}会变成一个后面有40多个0的超大值,e^{1000}的结果会返回一个表示无穷大的inf。如果在这些超大值之间进行除法运算,结果会出现“不确定”的情况。

softmax函数的实现可以像下式这样进行改进。

y^{_{k}} = \frac{e^{a_{k}}}{\sum_{i=1}^{n}e^{a_{i}}}= \frac{Ce^{a_{k}}}{C \sum_{i=1}^{n}e^{a_{i}}} = \frac{e^{a_{k}+logC}}{\sum_{i=1}^{n}e^{a_{i}+logC}} = \frac{e^{a_{k}+C'}}{\sum_{i=1}^{n}e^{a_{i}+C'}}

 

这里的C'可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。我们来看一个具体的例子。

 
a = np.array([1010, 1000, 990])
print(np.exp(a) / np.sum(np.exp(a))) # softmax函数的运算

c = np.max(a) # 1010
print(a - c)
print(np.exp(a - c) / np.sum(np.exp(a - c)))
[nan nan nan]
[  0 -10 -20]
[9.99954600e-01 4.53978686e-05 2.06106005e-09]

我们发现原本为nan(not a number,不确定)的地方,现在被正确计算了。综上,我们可以像下面这样实现softmax函数。

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

3. softmax函数的特征

使用softmax()函数,可以按如下方式计算神经网络的输出。

import numpy as np

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

a = np.array([0.3, 2.9, 4.0])
y = softmax(a)

print(y)
print(np.sum(y))
[0.01821127 0.24519181 0.73659691]
1.0

如上所示,softmax函数的输出是0.0到1.0之间的实数。并且softmax函数的输出值的总和是1。输出总和为1是softmax函数的一个重要性质。

正因为有了这个性质,我们才可以把softmax函数的输出解释为“概率”。 比如,上面的例子可以解释成y[0]的概率是0.018(1.8%),y[1]的概率是0.245(24.5%),y[2]的概率是0.737(73.7%)。从概率的结果来看,可以说“因为第2个元素的概率最高,所以答案是第2个类别”。而且,还可以回答“有74%的概率是第2个类别,有25%的概率是第1个类别,有1%的概率是第0个类别”。也就是说,通过使用softmax函数,我们可以用概率的(统计的)方法处理问题。

 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章