第三章 神经网络
- 上一章中我们学习了感知机,感知机的优势是即便对于复杂的函数,感知机也隐含着能够表示它的可能性,但是确定合适的、能够符合预期输入与输出的权重,需要人工进行,这是感知机的劣势所在。
- 神经网络的出现解决了感知机的劣势。具体而言,神经网络的一个重要性质是它可以自动地从数据中学习权重参数。
3.1神经网络的结构
如图3-1所示,最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层,中间层有时也称为隐藏层。在之后的讨论中,把输入层到输出层依次称为第0层、第1层、第2层。
ps:上图中的神经网络拥有三层神经元,因此被称为“3层网络”。但是,它只有两层神经元拥有权重参数,因此也可以将其称为“2层网络”。在之后的讨论中,将根据实质上拥有权重的层数(输入层、隐藏层、输出层的总数减去 1后的数量)来表示网络的名称。
3.2神经网络中信号的传递方法
神经网络中信号的传递方法和感知机类似,我们以如图所示的结构为例
- 这个神经网络将x1、x2、1三个信号作为神经元的输入,将其和各自的权重相乘后,传送至下一个神经元。在下一个神经元中,计算这些加权信号的总和。如果这个总和超过0,则输出1,否则输出0。
- b是被称为偏置的参数,用于控制神经元被激活的容易程度;而w1和w2是表示各个信号的权重的参数,用于控制各个信号的重要性。
- 如果用数学式来表示图中的神经网络,如式(3.1):
这里的y是一个分段函数的函数形式,为了简洁表示,我们用一个函数h(x)来表示这种分情况的输出,如下式(3.2)和(3.3)
- 式(3.2)中,h(x)函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。
- 式(3.3)中,函数h(x),在输入超过0时返回1,否则返回0。
3.3激活函数
刚才登场的h(x)函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。激活函数的作用在于决定如何来激活输入信号的总和。
-
进一步改写式(3.2)。式(3.2)分两个阶段进行处理,先计算输入信号的加权总和,然后用激活函数转换这一总和。可分为如下两个式子。
-
首先,式(3.4)计算加权输入信号和偏置的总和,记为a。然后,式(3.5)用h()函数将a转换为输出y。
-
之前的神经元都是用一个O表示的,如果要在图中明确表示出式(3.4)和式(3.5),则可以像图3-4这样做。
表示神经元的O中明确显示了激活函数的计算过程,即信号的加权总和为节点a,然后节点a被激活函数h()转换成节点y。
3.4 常见的激活函数
在感知机中使用了阶跃函数(以阈值为界,一旦输入超过阈值,就切换输出)作为激活函数。如
果将激活函数从阶跃函数换成其他函数,就可以进入神经网络的世界了。可以说激活函数是连接感知机和神经网络的桥梁。
3.4.1 sigmoid函数
神经网络中经常使用的一个激活函数就是式(3.6)表示的sigmoid函数(sigmoid function)。
- 式(3.6)中的exp(−x)表示e−x的意思。
下面我们来实现sigmoid函数:
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
如下所示,为sigmoid函数的图像:
3.4.2 ReLU函数
在神经网络发展的历史上,sigmoid函数很早就开始被使用了,而最近则主要
使用ReLU(Rectified Linear Unit)函数。
- ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输
出0。ReLU函数可以表示为下面的式(3.7)。
下面我们来实现ReLU函数:
import numpy as np
def relu(x):
return np.maximum(0, x)
- 这里使用了NumPy的maximum函数。maximum函数会从输入的数值中选择较大的那个值进行输出。
如下所示,为ReLU函数的图像:
3.5 神经网络的内积
下面我们使用NumPy矩阵来实现神经网络。以一个简单神经网络为对象,这个神经网络忽略了偏置和激活函数,只有权重。
- 实现该神经网络时,要注意X、W、Y的形状,特别是X和W的对应维度的元素个数是否一致
- X是输入的训练数据,通常是一个多维数组。当输入一个数据时,为一维数组,数组元素的个数代表了训练特征的个数,也就是输入层神经元的个数。当输入多个数据时,为一个矩阵,矩阵的行数代表了输入数据的个数。
- W是权重矩阵,如图所示是一个两行三列的矩阵,权重矩阵的行数等于输入层神经元的个数,列数等于与其连接的隐藏层的神经元个数。
- Y是输出的数据,同X一样也是一个多维数组。它的维数取决于X的维数,每一维元素的个数等于对应隐藏层神经元的个数。
下面我们来实现图3-14展示的神经网络:
import numpy as np
X = np.array([1, 2])
W = np.array([[1, 3, 5], [2, 4, 6]])
Y = np.dot(X, W)
3.6 三层神经网络的实现
我们以图3-15的3层神经网络为对象,实现从输入到输出的(前向)处理。
分析:
输入单个数据而言
- 输入数据的维度为(1,2)
- 输入层有2个神经元,第一个隐藏层有3个神经元,因此第一个权重矩阵的维度为(2,3)
- 第一个隐藏层有3个神经元,第2个隐藏层有2个神经元,因此第二个权重矩阵的维度为(3,2)
- 第2个隐藏层有2个神经元,输出层有2个神经元,因此第三个权重矩阵的维度为(2,2)
- 输出数据的维度为(1,2)
3.6.1 各层间信号传递的实现
现在看一下从输入层到第1层的第1个神经元的信号传递过程,如图3-17所示。
-
第一层的第一个神经元通过加权信号和偏置的和按如下方式进行计算
-
如果使用矩阵的乘法运算,则可以将第1层的加权和表示成下面的式(3.9)
-
在 W权重矩阵中,第一行为神经元x1传递给第1层各个神经元的权重,第二行为神经元x2传递给第1层各个神经元的权重…;第一列为x1和x2传递给第1层第1个神经元的权重,第二列为x1和x2传递给第一层第2个神经元的权重…
下面我们用NumPy多维数组来实现式(3.9),这里将输入信号、权重、偏置设置成任意值。
import numpy as np
def sigmoid(x): #sigmoid函数
return 1 / (1 + np.exp(-x))
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X, W1) + B1 #式3.9
Z1 = sigmoid(A1) #增加激活函数的计算过程,如下图
print(A1) #[0.3 0.7 1.1]
print(Z1) #[0.57444252 0.66818777 0.75026011]
- 第1层的加权和经过计算,还要通过激活函数的处理,这里所使用的激活函数为sigmoid函数,如图3-18
- 输出的A1和Z1数组的维度为(1,3),其中Z1又作为第1层到第2层的信号传递的输入数据进行同样的处理输出Z2,如图(3-19)
- Z2又作为第2层到输出层的输入数据来进行信号的传递,直至输出y。如图(3-20)。输出层的实现也和之前的实现基本相同。但是,在最后的激活函数和之前的隐含层有所不同。输出层的激活函数用σ()表示(σ读作sigma)。这里输出层的激活函数使用的是“恒等函数”,“恒等函数”会将输入按原样输出。
3.6.2代码实现小结
现在我们来完整实现以下上述代码:
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def identity_function(x):
return x
def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)
3.7输出层的设计
神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出的激活函数。在上面的实现中,我们所使用的激活函数为“恒等函数”。然而,就一般而言,回归问题用恒等函数,分类问题用softmax函数。
3.7.1恒等函数和 softmax函数
恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直接输出。因此,在输出层使用恒等函数时,输入信号会原封不动地被输出。将恒等函数的处理过程用之前的神经网络图来表示的话,则如图3-21所示。恒等函数进行的转换处理可以用一根箭头来表示。
分类问题中使用的softmax函数可以用下面的式(3.10)表示。
- 式(3.10)表示假设输出层共有n个神经元,计算第k个神经元的输出yk。
- softmax函数的分子是输入信号ak的指数函数,分母是所有输入信号的指数函数的和。
- 用图表示softmax函数的话,如图3-22所示。图3-22中,softmax函数的输出通过箭头与所有的输入信号相连。这是因为,从式(3.10)可以看出,输出层的各个神经元都受到所有输入信号的影响(分母)。
下面我们来实现softmax函数。
import numpy as np
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
3.7.2 softmax函数的注意事项
因为softmax函数做的是指数级的运算,因此很容易发生溢出现象。为了解决这一问题,softmax函数的实现可以像式(3.11)这样进行改进。
- 式(3.11)说明,在进行softmax的指数函数的运算时,加上(或者减去)某个常数并不会改变运算的结果。
- 这里的常数可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。
下面我们来实现改进的softmax函数。
import numpy as np
def softmax(a):
max = np.max(a)
exp_a = np.exp(a - max)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
3.7.3 softmax函数的特征
softmax函数的输出是0.0到1.0之间的实数。并且,softmax函数的输出值的总和是1。输出总和为1是softmax函数的一个重要性质。基于这一性质,我们可以把softmax函数的输出解释为“概率”。
一般而言,神经网络只把输出值最大的神经元所对应的类别作为识别结果。并且,即便使用softmax函数,输出值最大的神经元的位置也不会变。因此,神经网络在进行分类时,输出层的softmax函数可以省略。在实际的问题中,由于指数函数的运算需要一定的计算机运算量,因此输出层的softmax函数一般会被省略。