title: 神经网络基础学习(2)
神经网络基础学习(2)
在本节中我们将学会最简单的三层神经网络搭建
一、矩阵乘法
在了解神经网络中数据传输方法之前,熟练掌握矩阵乘法的规则是必不可少的(学过线性代数的请自行忽略这一点),下面是两个简单的2*2矩阵间的乘法
矩阵的乘积是通过左边矩阵的行(横向)和右边矩阵的列(纵向)以对应元素的方式相乘而得到的。而且运算的结果保存为新的多维数组的元素,下图详细的表现了矩阵乘法的规则
这个运算在Python中可以用如下代码实现
>>>A = np.array([[1, 2], [3, 4]])
>>>A.shape
(2, 2)
>>>B = np.array([[5, 6], [7, 8]])
>>>B.shape
(2, 2)
>>>np.dot(A, B)
array([[19, 22],
[43, 50]])
在这里A和B都是2*2的矩阵,它们的乘积可以通过NumPy的np.dot()函数计算(严谨地来说是点积)。这里要注意的是np.dot(A, B)与np.dot(B, A)的值可能不一样,在矩阵的乘积运算中,矩阵的前后顺序不同会造成乘积结果的不同
二、搭建简单的三层神经网络
1.神经网络大体结构
上图就是一个简单的3层神经网络结构图。其中神经网络被分成三个层次:输入层、隐藏层和输出层,我们一般把出入层称为第0层。在代码实现方面,我们可以灵活地运用NumPy多维数组进行组建,用很少的代码完成神经网络的前向处理
2.各层间的信号传递实现
现在看一下从输出层第一个神经元x1传导到第1层第一层a1的过程
注意,我们在第一层加了一个数值为1的神经元,目的是为了把各个神经元计算所要加的偏置都提取出来,单独把各层权重设置为数值1的权重,这一步操作可以简化我们对神经网络传导函数的书写
现在我们可以把a1的加权信号和写出来
这是我们第一个神经元的信号加权值,但我们神经网络的传输是以层为单位的,也就是说同一层的神经元的计算是同步进行的,然后这一层所有神经元的信号加权和再往下一层传导,所以我们不可能把每一个神经元的计算公式都写出来,不然就太冗余了,所以我们可以写成下面矩阵乘法的形式
其中,A表示要传递到下一层的信号加权和,X为上一层输入值,W为权重,B为偏置,A、B、X、W如下所示
下面我们用NumPy多维数组来实现上面的计算公式
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
数组中的数值先随意设置,通过这一步函数我们成功的把这一层的信号加权和给算出来了,但这只并没有全部完成,我们上一章还讲到了激活函数这一概念,所以我们还需要激活函数sigmoid来激活数值再传导到下一层
Z1 = sigmoid(A1)
如果看到这里你还能保持大脑清醒并条理清晰,恭喜你,你已经会了神经网络的传输原理了,再往后的损失函数、梯度、正反向反馈等函数都是在此基础上的衍生
好的,我们按照上面第一层的操作,我们同理可以写出第二层和输出层的传递代码
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
A2 = np.dot(X, W1) + B1
Z2 = sigmoid(A2)
上面的代码与上面如出一辙,只是在数组的维度上有所变化,我们在神经网络传导的时候一定要搞清楚每一层数组的维数,不然很容易报错
在最后一层输出层的信号传导时,最后的激活函数会有所不同,我们会根据最后数据处理的要求选择不同的函数,我们这里就选择直接返回函数值,后面我们会提到不同的输出层的激活函数
def return_function(x):
return x
A3 = np.dot(Z2, W3) + B3
Y = return_function(A3) # Y为最后我们的输出值
三、总结代码
下面我们把上面所有的代码全部定义成函数来实现快速调用的功能
# 搭建3层神经网络(以sigmoid函数为激活函数)
import numpy as np
# 初始化各层的权重和偏置
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 sigmoid(x):
return 1 / (1 + np.exp(-x))
# 最后一层激活函数
def identity_function(x):
return x
# 输入到输出的传递函数
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)
在上面的代码里,我们首先定义了init_function(),在这个函数中我们创建了一个network的字典来储存各个权重和偏置,起到初始化的作用。然后我们还定义了forward()函数,存入了我们上面讲到的传输计算函数,起到了信息向前传输的作用。
到此为止我们已经成功搭建了一个简单的三层神经网络,你懂了吗?
四、输出层激活函数设计
我们一般用神经网络来解决分类问题和回归问题,现在我们来介绍分类问题的激活函数——softmax函数。
分类函数中使用softmax函数可以写成下面的式子
softmax函数中的分子时输入信号a的指数函数,分母是所有信号的指数函数的和,用Python我们可以写成下面这个样子
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
代码跑出来我们显而易见softmax函数得到的结果都是0.2478……或者0.7865……等小数(0.0到1.0的小数),实际上softmax输出的所有值全部加起来为1,所以我们可以把softmax函数的输出值当作该数据在数据集中出现的概率,所以我们可以把softmax函数当作分类函数的激活函数
但我们在这里要提到softmax函数有一个非常重要的注意点
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
上面我们通过减去输入信号中的最大值(上面的c),这样我们就可以很好的避免溢出的风险了。这里提一句,sigmoid函数也会有同样的风险,解决方法也如出一辙,就不过多赘述了
OK,现在你已经学会了如何搭建三层神经网络,下面我们会通过“手写数字识别”的项目来具体学习神经网络中的学习
大好きだよ
编辑者:Ice Man