文章目录

  • 神经网络与NumPy
  • 多维数组
  • 矩阵乘法
  • 神经网络内积
  • 简单神经网络实现
  • 符号确认
  • 各层间信号传递
  • 代码实现小结
  • 小结


神经网络与NumPy

为了实现神经网络的前向传播,我们首先来看一下NumPy多维数组的运算。掌握了NumPy多维数组的运算,就可以高效地实现神经网络。

多维数组

多维数组就是“数字的集合”,数字排成一列的集合、排成长方形的集合、排成三维状或者(更加一般化的)N维状的集合都称为多维数组。下面我们就用NumPy来生成多维数组。

  1. 一维数组
import numpy as np
A = np.array([1, 2, 3, 4])
print(A)
print(np.ndim(A))
print(A.shape)

# 运行结果
[1 2 3 4]
1
(4,)

数组的维数可以通过np.dim()函数获得。数组的形状可以通过实例变量shape获得,上例中,A是一维数组,由4个元素构成,这里的A.shape的结果是个元组(tuple),二维数组时返回的是元组(4,3),三维数组时返回的是元组(4,3,2),…

  1. 二维数组
B = np.array([[1,2], [3,4], [5,6]])
print(B)
print(np.ndim(B))
print(B.shape)

# 运行结果
[[1 2]
[3 4]
[5 6]]
2
(3, 2)

这里生成了一个3×2的数组B。3×2的数组表示第一个维度有3个元素,第二个维度有2个元素。另外,第一个维度对应第0维,第二个维度对应第1维(Python的索引从0开始)。二维数组也称为矩阵(matrix)。数组的横向排列称为行(row),纵向排列称为列(column)

矩阵乘法

下面,我们来介绍矩阵(二维数组)的乘积。比如2×2的矩阵,其乘积可以像下图这样进行计算:

多输出层神经网络 神经网络多维输出_深度学习


矩阵的乘积是通过左边矩阵的行(横向)和右边矩阵的列(纵向)以对应元素的方式相乘后再求和而得到的。并且,运算的结果保存为新的多维数组的元素。比如,A的第1行和B的第1列的乘积结果是新数组的第1行第1列的元素,A的第2行和B的第1列的结果是新数组的第2行第1列的元素。这个运算在Python中可以用如下代码实现:

A = np.array([[1,2], [3,4]])
B = np.array([[5,6], [7,8]])
C = np.dot(A, B)
print(C)

# 运行结果
[[19, 22],[43, 50]]

这里,A和B都是2×2的矩阵,它们的乘积可以通过NumPy的np.dot()函数计算(乘积也称为点积)。np.dot()接收两个NumPy数组作为参数,并返回数组的乘积。
这里要注意的是,np.dot(A, B) 和np.dot(B, A) 的值可能不一样。和一般的运算(+或*等)不同,矩阵的乘积运算中,操作数A、B的顺序不同,结果也会不同。
我们再来看一个例子:

A = np.array([[1,2,3], [4,5,6]])
print(A.shape)

B = np.array([[1,2], [3,4], [5,6]])
print(B.shape)

C = np.dot(A, B)
print(C)

# 运行结果
(2, 3)
(3, 2)
[[22, 28],[49, 64]]

2×3 的矩阵A和3×2的矩阵B的乘积可按以上方式实现。这里需要注意的是矩阵的形状(shape)。具体地讲,矩阵A的第1维的元素个数(列数)必须和矩阵B的第0维的元素个数(行数)相等。在上面的例子中,矩阵A的形状是2×3,矩阵B的形状是3×2,矩阵A的第1维的元素个数“3”和矩阵B的第0维的元素个数“3”相等。如果这两个值不相等,则无法计算矩阵的乘积
如果对上面的表述不理解,可以参考下面图片:

多输出层神经网络 神经网络多维输出_深度学习_02


图中,3×2的矩阵A和2×4的矩阵B的乘积运算生成了3×4的矩阵C。如图所示,矩阵A和矩阵B的对应维度的元素个数必须保持一致。此外,可以注意到的是,运算结果的矩阵C的形状是由矩阵A的行数和矩阵B的列数构成的

另外,当有矩阵是一维的时候,比如当A是二维矩阵、B是一维数组时,对应维度的元素个数要保持一致的原则依然成立。代码如下:

A = np.array([[1,2], [3, 4], [5,6]])
print(A.shape)

B = np.array([7,8])
print(B.shape)

C = np.dot(A, B)
print(C)

# 运行结果
(3, 2)
(2,)
[23, 53, 83]

相应示意图如下:

多输出层神经网络 神经网络多维输出_多输出层神经网络_03

神经网络内积

下面我们使用NumPy矩阵来实现神经网络。这里我们以下图中的简单神经网络为对象。这个神经网络省略了偏置和激活函数,只有权重。

多输出层神经网络 神经网络多维输出_NumPy_04


实现该神经网络时,要注意X、W、Y的形状,特别是X和W的对应维度的元素个数是否一致,下面我们用代码来实现:

X = np.array([1, 2])
print(X.shape)

W = np.array([[1, 3, 5], [2, 4, 6]])
print(W.shape)

Y = np.dot(X, W)
print(Y)

# 运行结果
(2,)
(2, 3)
[5 11 17]

如上所示,使用np.dot(多维数组的点积),可以一次性计算出Y 的结果。这意味着,即便Y 的元素个数为100或1000,也可以通过一次运算就计算出结果!如果不使用np.dot,就必须单独计算Y 的每一个元素(或者说必须使用for语句),非常麻烦。因此,通过矩阵的乘积一次性完成计算的技巧,在实现的层面上可以说是非常重要的。

简单神经网络实现

现在我们来进行神经网络的实现。这里我们以下图的3层神经网络为对象,实现从输入到输出的(前向)处理。在代码实现方面,使用上一节介绍的NumPy多维数组。巧妙地使用NumPy数组,可以用很少的代码完成神经网络的前向处理。

多输出层神经网络 神经网络多维输出_神经网络_05

符号确认

在介绍神经网络中的处理之前,我们先导入w12(1)、a1(1)等符号。
我们先从定义符号开始。如下图所示,只突出显示了从输入层神经元x2到后一层的神经元的权重a1(1)

多输出层神经网络 神经网络多维输出_深度学习_06

如上图所示,权重和隐藏层的神经元的右上角有一个“(1)”,它表示权重和神经元的层号(即第1 层的权重、第1 层的神经元)。此外,权重的右下角有两个数字,它们是后一层的神经元和前一层的神经元的索引号。比如,w12(1)表示前一层的第2个神经元x2到后一层的第1个神经元a1(1)的权重。权重右下角按照“后一层的索引号、前一层的索引号”的顺序排列。

各层间信号传递

现在看一下从输入层到第1层的第1个神经元的信号传递过程:

多输出层神经网络 神经网络多维输出_python_07


上图中增加了表示偏置的神经元“1”。请注意,偏置的右下角的索引号只有一个。这是因为前一层的偏置神经元(神经元“1”)只有一个。为了确认前面的内容,现在用数学式表示。通过加权信号和偏置的和按如下方式进行计算:

多输出层神经网络 神经网络多维输出_多输出层神经网络_08

此外,如果使用矩阵的乘法运算,则可以将第1层的加权和表示成下面的式子:

多输出层神经网络 神经网络多维输出_神经网络_09

其中,A(1)、X、B(1)、W(1)如下所示:

多输出层神经网络 神经网络多维输出_python_10

下面我们用NumPy多维数组来实现上式,这里将输入信号、权重、偏置设置成任意值:

X = np.array([1.0, 0.5])
print(X.shape)

W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
print(W1.shape)

B1 = np.array([0.1, 0.2, 0.3])
print(B1.shape)

A1 = np.dot(X, W1) + B1
print(A1)

#运行结果
(2,)
(2, 3)
(3,)
[0.3, 0.7, 1.1]

这个运算和上一节进行的运算是一样的。W1是2×3的数组,X是元素个数为2的一维数组。这里,W1和X的对应维度的元素个数也保持了一致。接下来,我们观察第1层中激活函数的计算过程。如果把这个计算过程用图来表示:

多输出层神经网络 神经网络多维输出_python_11

如上图,隐藏层的加权和(加权信号和偏置的总和)用a表示,被激活函数转换后的信号用z表示。此外,图中h()表示激活函数,这里我们使用的是sigmoid函数。用Python来实现,代码如下所示:

print(A1)

Z1 = sigmoid(A1)
print(Z1)

# 运行结果
[0.3, 0.7, 1.1]
[0.57444252, 0.66818777, 0.75026011]

这个sigmoid()函数就是之前定义的那个函数。它会接收NumPy数组,并返回元素个数相同的NumPy数组。下面,我们来实现第1 层到第2 层的信号传递:

多输出层神经网络 神经网络多维输出_python_12

print(Z1.shape)

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
print(W2.shape) 

B2 = np.array([0.1, 0.2])
print(B2.shape)

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)

# 运行结果
(3,)
(3, 2)
(2,)

除了第1层的输出(Z1)变成了第2 层的输入这一点以外,这个实现和刚才的代码完全相同。由此可知,通过使用NumPy数组,可以将层到层的信号传递过程简单地写出来。
最后是第2层到输出层的信号传递:

多输出层神经网络 神经网络多维输出_多输出层神经网络_13


输出层的实现也和之前的实现基本相同。不过,最后的激活函数和之前的隐藏层有所不同:

def identity_function(x):
	return x

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])

A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) # 或者Y = A3

这里我们定义了identity_function()函数(也称为“恒等函数”),并将其作为输出层的激活函数。恒等函数会将输入按原样输出,因此,这个例子中没有必要特意定义identity_function()。这里这样实现只是为了和之前的流程保持统一。另外,上图输出层的激活函数用σ() 表示,不同于隐藏层的激活函数h()。
输出层所用的激活函数,要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用sigmoid 函数,多元分类问题可以使用softmax 函数

代码实现小结

至此,我们已经介绍完了3层神经网络的实现。现在我们把之前的代码实现全部整理一下。这里,我们按照神经网络的实现惯例,只把权重记为大写字母W1,其他的(偏置或中间结果等)都用小写字母表示:

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)

# 运行结果
[0.31682708 0.69627909]

这里定义了init_network() 和forward() 函数。init_network()函数会进行权重和偏置的初始化,并将它们保存在字典变量network中。这个字典变量network中保存了每一层所需的参数(权重和偏置)。forward()函数中则封装了将输入信号转换为输出信号的处理过程。
另外,这里出现了forward(前向)一词,它表示的是从输入到输出方向的传递处理。后面在进行神经网络的训练时,我们将介绍后向(backward,从输出到输入方向)的处理。
至此,神经网络的前向处理的实现就完成了。通过巧妙地使用NumPy多维数组,我们高效地实现了神经网络。

小结

  • NumPy可以生成一维、二维及多维数组
  • NumPy可以对多维数组运算,如矩阵乘法等
  • 矩阵乘法实现神经网络内积运算
  • 通过巧妙地使用NumPy多维数组及激活函数,可以高效地实现神经网络。