九: 神经网络与激活函数

在上一章中,设定权重的工作,即确定合适的、能符合预期的输入与输出的权重,现在还是由人工进行的,

神经网络的出现就是为了解决这个问题。具体地讲,神经网络的一个重要性质是它可以自动地从数据中学习到合适的权重参数。

1. 从感知机到神经网络

神经网络和上一章介绍的感知机有很多共同点。这里,我们主要以两者的差异为中心,来介绍神经网络的结构。

1.1 神经网络的例子

用图来表示神经网络的话,如图 1-1 所示。我们把最左边的一列称为输入层 ,最右边的一列称为输出层 ,中间的一列称为中间层 。中间层有时也称为隐藏层 。“隐藏”一词的意思是,隐藏层的神经元(和输入层、输出层不同)肉眼看不见。另外,把输入层到输出层依次称为第 0 层、第 1 层、第 2 层(层号之所以从 0 开始,是为了方便后面基于 Python 进行实现)。图 1-1 中,第 0 层对应输入层,第 1 层对应中间层,第 2 层对应输出层。

图 1-1 神经网络的例子
在这里插入图片描述
图 1-1 中的网络一共由 3 层神经元构成,但实质上只有 2 层神经元有权重,因此将其称为“2 层网络”。请注意,有的书也会根据构成网络的层数,把图 3-1 的网络称为“3 层网络”。本书将根据实质上拥有权重的层数(输入层、隐藏层、输出层的总数减去 1 后的数量)来表示网络的名称。

只看图 1-1 的话,神经网络的形状类似上一章的感知机。实际上,就神经元的连接方式而言,与上一章的感知机并没有任何差异。那么,神经网络中信号是如何传递的呢?

1.2 复习感知机

在上一章中,我们用数学表达式来表示感知机

表达式 1.1
在这里插入图片描述
b 是被称为偏置的参数,用于控制神经元被激活的容易程度;而 w1 和 w2 是表示各个信号的权重的参数,用于控制各个信号的重要性。

之前的感知机图中没有加入偏置b的输入,如果要明确地表示出 b ,可以像图 1-2 那样做。图 1-3 中添加了权重为 b 的输入信号 1。这个感知机将 x1、 x2、1 三个信号作为神经元的输入,将其和各自的权重相乘后,传送至下一个神经元。在下一个神经元中,计算这些加权信号的总和。如果这个总和超过 0,则输出 1,否则输出 0。另外,由于偏置的输入信号一直是 1,所以为了区别于其他神经元,我们在图中把这个神经元整个涂成灰色。

图 1-2 明确表示出偏置
在这里插入图片描述
现在将表达式(1.1)改写成更加简洁的形式。为了简化表达式(1.1),我们用一个函数来表示这种分情况的动作(超过 0 则输出 1,否则输出 0)。引入新函数 h (x ),将表达式(1.1)改写成下面的表达式。

在这里插入图片描述
在这里插入图片描述
h(x) 函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数 (activation function)。如“激活”一词所示,激活函数的作用在于决定如何来激活输入信号的总和。

上面的计算过程包括两部分, 第一部分计算加权输入信号和偏置的总和,记为 a:

		a = b + w1x1+w2x2

然后用h()函数将a转换为输出y

		y=h(a)

之前的神经元都是用一个○表示的,如果要在图中明确表示出上面的表达式,则可以像图 1-3 这样做。
在这里插入图片描述
图 1-3 明确显示激活函数的计算过程

如图 1-3 所示,表示神经元的○中明确显示了激活函数的计算过程,即信号的加权总和为节点 a ,然后节点 a 被激活函数 h () 转换成节点 y 。本书中,“神经元”和“节点”两个术语的含义相同。这里,我们称 a 和 y 为“节点”,其实它和之前所说的“神经元”含义相同。

通常如图 1-4 的左图所示,神经元用一个○表示。本书中,在可以明确神经网络的动作的情况下,将在图中明确显示激活函数的计算过程,如图 1-4 的右图所示。
在这里插入图片描述
图 1-4 左图是一般的神经元的图,右图是在神经元内部明确显示激活函数的计算过程的图(a 表示输入信号的总和,h () 表示激活函数,y 表示输出)

2. 激活函数

激活函数是连接感知机和神经网络的桥梁。

表达式 1.1 表示的激活函数以阈值为界,一旦输入超过阈值,就切换输出。这样的函数称为“阶跃函数”。因此,可以说感知机中使用了阶跃函数作为激活函数。也就是说,在激活函数的众多候选函数中,感知机使用了阶跃函数。那么,如果感知机使用其他函数作为激活函数的话会怎么样呢?实际上,如果将激活函数从阶跃函数换成其他函数,就可以进入神经网络的世界了。下面我们就来介绍一下神经网络使用的激活函数。

2.1 sigmoid 函数

神经网络中经常使用的一个激活函数就是式(1.2)表示的 sigmoid 函数 (sigmoid function)。
在这里插入图片描述
表达式1.2 sigmoid 函数

exp(-x ) 表示 e的意
思。e 是纳皮尔常数 2.7182 …

纳皮尔常数(以约翰・纳皮尔命名),一般用符号 e 表示,是一个重要的数学常数。它是一个无限不循环小数,其值约为 2.718281828459045…e 和 π 一样,都是超越数,不能表示为任何整数的根。在数学和物理等学科中有着广泛的应用。

sigmoid 函数看上去有些复杂,但它也仅仅是个函数而已。而函数就是给定某个输入后,会返回某个输出的转换器。

神经网络中用 sigmoid 函数作为激活函数,进行信号的转换,转换后的信号被传送给下一个神经元。

实际上,上一章介绍的感知机和接下来要介绍的神经网络的主要区别就在于这个激活函数。其他方面,比如神经元的多层连接的构造、信号的传递方法等,基本上和感知机是一样的。下面,让我们通过和阶跃函数的比较来详细学习作为激活函数的 sigmoid 函数。

2.2 阶跃函数的实现

可以用Python实现一个简单的阶跃函数

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

这个实现简单、易于理解,但是参数 x 只能接受实数(浮点数),不允许参数取 NumPy 数组.
为了便于后面的操作,我们把它修改为支持 NumPy 数组的实现。为此,可以考虑下述实现

import numpy as np

def step_function(x):
	# 此处会生成一个新的数组,x数组中的元素如果符合条件则数组中元素为True
	# 否则数组中对应元素为False
    y = x > 0
    # astype 将数组y中的布尔元素转为int, True->1,False->0
    return y.astype(int)

print(step_function(np.array([-11, 2, 3, 0, -0.6, 22, -100])))

输出:
[0 1 1 0 0 1 0]

这里有个前面提到的NumPy的技巧,对 NumPy 数组进行不等号运算后,数组的各个元素都会进行不等号运算,生成一个布尔型数组。这里,数组 x 中大于 0 的元素被转换为 True ,小于等于 0 的元素被转换为 False ,从而生成一个新的数组y.

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

2.3 阶跃函数的图形

我们用matplotlib库来画一下阶跃函数的图像

import matplotlib.pylab as plt
import numpy as np


def step_function(x):
    return np.array(x > 0, dtype=int)


x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
# 指定y轴的范围
plt.ylim(-0.1, 1.1)
plt.show()

在这里插入图片描述
图 1-5 阶跃函数图

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

2.4 sigmoid 函数的实现

from matplotlib import pyplot as plt


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()

在这里插入图片描述
图1-6 sigmoid 函数图

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

sigmoid 函数的实现也是如此,因为 np.exp(-x) 会生成 NumPy 数组,所以 1 / (1 + np.exp(-x)) 的运算将会在 NumPy 数组的各个元素间进行。

2.5 sigmoid 函数和阶跃函数的比较

import numpy as np
from matplotlib import pyplot as plt

def step_function(x):
    return np.array(x > 0, dtype=int)

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

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
y2 = step_function(x)

plt.plot(x, y, label="sigmoid")
plt.plot(x, y2, linestyle='--', label="step_function")
plt.legend()
plt.ylim(-0.1, 1.1)  # 指定y轴的范围
plt.show()

在这里插入图片描述
图1-7 阶跃函数与 sigmoid 函数图比较

观察图 1-7,首先注意到的是“平滑性”的不同。sigmoid 函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以 0 为界,输出发生急剧性的变化。sigmoid 函数的平滑性对神经网络的学习具有重要意义。

另一个不同点是,相对于阶跃函数只能返回 0 或 1,sigmoid 函数可以返回 0.731 …、0.880 … 等实数(这一点和刚才的平滑性有关)。也就是说,感知机中神经元之间流动的是 0 或 1 的二元信号,而神经网络中流动的是连续的实数值信号。

阶跃函数和 sigmoid 函数的共同性质: 阶跃函数和 sigmoid 函数虽然在平滑性上有差异,但是如果从宏观视角看图 1-7,可以发现它们具有相似的形状。实际上,两者的结构均是“输入小时,输出接近 0(为 0);随着输入增大,输出向 1 靠近(变成 1)”。也就是说,当输入信号为重要信息时,阶跃函数和 sigmoid 函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值。还有一个共同点是,不管输入信号有多小,或者有多大,输出信号的值都在 0 到 1 之间。

2.6 非线性函数

阶跃函数和 sigmoid 函数还有其他共同点,就是两者均为非线性函数 。sigmoid 函数是一条曲线,阶跃函数是一条像阶梯一样的折线,两者都属于非线性的函数。

在介绍激活函数时,经常会看到“非线性函数”和“线性函数”等术语。函数本来是输入某个值后会返回一个值的转换器。向这个转换器输入某个值后,输出值是输入值的常数倍的函数称为线性函数(用数学式表示为 h (x ) = cx 。c 为常数)。因此,线性函数是一条笔直的直线。而非线性函数,顾名思义,指的是不像线性函数那样呈现出一条直线的函数。

神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。为什么不能使用线性函数呢?因为使用线性函数的话,加深神经网络的层数就没有意义了。

线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。为了具体地(稍微直观地)理解这一点,我们来思考下面这个简单的例子。这里我们考虑把线性函数 h (x ) = cx 作为激活函数,把 y (x ) = h (h (h (x ))) 的运算对应 3 层神经网络 3 。这个运算会进行 y (x ) = c × c × c × x 的乘法运算,但是同样的处理可以由 y (x ) = ax (注意,a= c^3 )这一次乘法运算(即没有隐藏层的神经网络)来表示。如本例所示,使用线性函数时,无法发挥多层网络带来的优势。因此,为了发挥叠加层所带来的优势,激活函数必须使用非线性函数。

2.7 ReLU函数

到目前为止,我们介绍了作为激活函数的阶跃函数和 sigmoid 函数。在神经网络发展的历史上,sigmoid 函数很早就开始被使用了,而最近则主要使用 ReLU (Rectified Linear Unit)函数。线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元

ReLU 函数在输入大于 0 时,直接输出该值;在输入小于等于 0 时,输出 0,ReLU 函数可以表示为下面的式
在这里插入图片描述
ReLU 函数的实现也很简单,可以写成如下形式:

import numpy as np
from matplotlib import pyplot as plt


def step_function(x):
    return np.array(x > 0, dtype=int)


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


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


x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
y2 = step_function(x)
y3 = relu(x)

plt.plot(x, y, linestyle='dotted', label="sigmoid")
plt.plot(x, y2, linestyle='--', label="step_function")
plt.plot(x, y3, label="relu")
plt.legend()
plt.ylim(-0.1, 1.1)  # 指定y轴的范围
plt.show()

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

剩余部分的内容仍将使用 sigmoid 函数作为激活函数,但在学习的后半部分,则将主要使用 ReLU 函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值