【深度学习】神经网络与BP算法

前言

  本篇主要介绍神经网络的基本结构、激活函数以及学习算法(BP算法)

  神经网络 主要由三个组成部分,第一个是架构(architecture)或称为拓扑结构(topology),描述神经元的层次与连接神经元的结构。第二个组成部分是神经网络使用的激励/激活函数。第三个组成部分是找出最优权重值的学习算法

  为了能够解决感知机人工设定权重的工作,即确定合适的、能符合预期的输入与输出的权重,神经网络便出现了,神经网络的一个重要的性质是它可以自动地从数据中学习得到合适的权重参数。

一、神经网络的结构

在这里插入图片描述

  • 输入层 :通过输入层输入数据
  • 隐藏层 :通过隐藏的中间层对输入数据进行训练,训练过程中中间节点的真正数值无法通过训练集看到
  • 输出层 :输出模型的预测值

一般情况下,我们通常将输入层、隐藏层、输出层的总数减去1后的数量来表示神经网络的名称,上图即可称为两层神经网络

在这里插入图片描述

  • 上图是一个三层神经网络

二、激活函数

在这里插入图片描述

上面的激活函数 h()h() 是以阀值为界,一旦输入超过阀值,就切换输出。这样的激活函数为“阶跃函数”,因此,可以说感知机中使用了阶跃函数作为激活函数,那么感知机使用其他激活函数呢?实际上,如果将激活函数从阶跃函数换成其他的,就可以进入神经网络的世界了。

  激活函数 的主要作用是提供网络的非线性建模能力。如果没有激活函数,那么该网络仅能够表达线性映射,此时即便有再多的隐藏层,其整个网络跟单层神经网络也是等价的。因此也可以认为,只有加入了激活函数之后,深度神经网络才具备了分层的非线性映射学习能力。 激活函数的主要特性是:可微性、单调性、输出值的范围

  • 常见的激活函数:Sign函数Sigmoid函数Tanh函数ReLU函数P-ReLU函数Leaky-ReLU函数ELU函数Maxout函数

1、sigmoid 函数

数学表达式为:
h(x)=11+exh(x) = \frac{1}{1+e^{-x}}

python实现:

import numpy as np
import matplotlib.pylab 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()

在这里插入图片描述

2、阶跃函数

数学表达式:
h(x)={0(x0)1(x>0)h(x) = \begin{cases} 0 \quad (x \leqslant 0) \\ 1 \quad (x > 0) \end{cases}

python实现:

# 方法一
def step_function1(x):
	if x > 0:
		return 1
	else:
		return 0
# 方法二		
def step_function2(x):
    return np.array(x>0, dtype=np.int)

x = np.arange(-5.0, 5.0, 0.1) # 在-0.5到0.5的范围内,以0.1为单位,生成NumPy数组([-5.0,-4.9,...,4.9])
y = step_function2(x)

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

在这里插入图片描述

3、阶跃函数与sigmoid函数的比较

  • “平滑性”不同,sigmoid 函数是一条平滑的曲线,而阶跃函数在0处有突变,输出发生急剧性变化,sigmoid函数的平滑性对神经网络的学习具有重要意义;
  • 返回值的不同,阶跃函数只能返回0和1,sigmoid函数可以返回0到1的任意数,这和平滑性有关,也就是说,感知机中神经元之间流动的是0和1的二元信号,而神经网络中流动的是连续的实值信号;
  • 二者也有共同性质,输入较小时,输出接近0(或为0),随着输入增大,输出向1靠近(或变成1),也就是说,当输入信号为重要信息时,两者都会输出较大的值,当输入信号不重要的时,两者都会输出较小的值;
  • 二者输出值都在0到1之前

4、ReLU 函数

数学表达式:
h(x)={x(x>0)0(x0)h(x) = \begin{cases} x \quad (x > 0) \\ 0 \quad (x \leqslant 0) \end{cases}

python 实现:

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(-0.1, 5) # 指定y轴的范围
plt.show()

在这里插入图片描述

5、softmax函数

数学表达式为:
yk=eaki=1neaiy_k = \frac{e^{a_k}}{\sum_{i=1}^n e^{a_i}}

其中,yky_k表示假设输出层共有n个神经元,计算第k个神经元的输出yky_k
分子是输入信号aka_k的指数函数,分母是所有输入信号的指数函数之和

python实现:

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

exp_a = np.exp(a)

print(exp_a)
输出为:[ 1.34985881 18.17414537 54.59815003]
sum_exp_a = np.sum(exp_a)
print(sum_exp_a)

y = exp_a / sum_exp_a
print(y)
输出为:74.1221542101633
[0.01821127 0.24519181 0.73659691]
def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

6、神经网络为何要使用非线性函数

如果使用线性函数,那么不管如何加深层数,总是存在与子等效的“无隐藏层神经网络”

  • 假如我们使用 h(x)=cxh(x)=cx 作为激活函数,使用三层神经网络对应 y(x)=h(h(h(x)))y(x)=h(h(h(x))) 运算,即 y(x)=c×c×c×xy(x)= c\times c \times c \times x,我们取 a=c3a=c^3 同样可以经过一次乘法来处理(即没有隐藏层的神经网络),因此激活函数必须使用非线性函数。

三、三层神经网络的简单实现

在这里插入图片描述

图中,红色表示输入层到第一层的信号传递,蓝色表示第一层到第二层的信号传递,绿色表示第二层到输出层的信号传递

  • 其中x1x2x_1、x_2表示输入,灰色实心圆表示偏置项,
  • wab(c)w_{ab}^{(c)}中a表示后一层的第a个神经元,b表示前一层的第b个神经元,c表示第c层的权重
  • zd(c)z_d^{(c)}中c表示第c层的第d个输出,用于下一层的输入
  • yiy_i表示最终输出

1、从输入层到第一层的信号传递

由图,可得到:

a1(1)=w11(1)x1+w12(1)x2+b1(1)a_1^{(1)} = w_{11}^{(1)}x_1 + w_{12}^{(1)}x_2+b_1^{(1)}
a2(1)a3(1)a_2^{(1)},a_3^{(1)}也按此规律运算,写成矩阵的乘法运算可以表示为:
A(1)=XW(1)+B(1)A^{(1)} = XW^{(1)}+B^{(1)}
其中,A(1)=(a1(1)  a2(1)  a3(1))X=(x1  x2)B(1)=(b1(1)  b2(1)  b3(1))A^{(1)} =\big(a_1^{(1)}\ \ a_2^{(1)}\ \ a_3^{(1)}\big),X = \big(x_1 \ \ x_2\big),B^{(1)} = \big(b_1^{(1)}\ \ b_2^{(1)} \ \ b_3^{(1)}\big)
W(1)=(w11(1)w21(1)w31(1)w11(1)w12(1)w13(1))W^{(1)} = \left( \begin{matrix} w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)} \\ w_{11}^{(1)}& w_{12}^{(1)}& w_{13}^{(1)} \end{matrix} \right)

下面我们通过pythonNumPy 多维数组来实现以上步骤,这里将输入信号、权重、偏置设置成任意值。

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
Z1 = sigmoid(A1)   # 使用sigmoid函数转换

print(Z1)
输出为:[0.57444252 0.66818777 0.75026011]

2、从第一层到第二层的信号传递

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(Z1, W2) + B2
Z2 = sigmoid(A2)   # 使用sigmoid函数转换

print(Z2)
输出为:[0.62624937 0.7710107 ]

3、从第二层到输出层的信号传递

W3 = np.array([
    [0.1, 0.3],
    [0.2, 0.4]
])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
# 第二层到输出层的信号传递使用的激活函数不是sigmoid函数,而是恒等函数
def identity_function(x):
    return x
Y = identity_function(A3)

print(Y)
输出为:[0.31682708 0.69627909]

4、代码整合

# 恒等函数
def identity_function(x):
    return x

# 进行权重和偏置的初始化,并保存到字典network中    
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]

5、输出层的设计

\quad\quad神经网络可以用在分类问题回归问题上,不过需要改变输出层的激活函数,一般而言,回归问题用恒等函数,分类问题用softmax函数

\quad\quadsoftmax函数,它的分子和分母都涉及指数运算,因为指数运算的值很容易变得非常大,这在计算机的运算上有一定的缺陷,即溢出问题。

因此可对 softmax函数 进行改进,表达式如下:
yk=eaki=1neai=CeakCi=1neai=eak+logCi=1neai+logC=eak+Ci=1neai+Cy_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'}}

这里的 CC' 可以使用任何值,但是一般选择输入信号的最大值
分子、分母都乘上CC这个任意常数,值不变

不改进softmax函数

a = np.array([1010,1000,990])
print(np.exp(a) / np.sum(np.exp(a)))
输出为:array([nan, nan, nan])
很明显发生了溢出问题

通过减去最大值

c = np.max(a)
print(a - c)

print(np.exp(a-c) / np.sum(np.exp(a-c)))
输出为:array([  0, -10, -20])

array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
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)   # 输出是0.0到1.0之间的实数

print(np.sum(y))  # 和为1
输出为:[0.01821127 0.24519181 0.73659691]
1.0

四、BP 算法

  BP算法便是神经网络的一种求解W的算法,分为信号“正向传播(FP)”求损失,“反向传播(BP)”回传误差;根据误差值修改每层的权重,继续迭代。上面我们将输入信号、权重、偏置设置成任意值,然后经过了FP过程,而神经网络的一个重要的性质是它可以自动地从数据中学习得到合适的权重参数,这便是“反向传播(BP)”的作用。

下面我们通过一个具体的例子来说明BP 算法的 FPBP过程:

在这里插入图片描述
wb 的初始值:

  • w(0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65)w(0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65)
  • b(0.35,0.65)b(0.35,0.65)
  • 输出为:
  • O=(0.01,0.99)O = (0.01, 0.99)
  • 假设隐层和输出层都使用 sigmoid 激活函数
  • 学习率 η=0.5\eta=0.5

1、FP过程

h1=w1l1+w2l2+b11=0.15+0.1510+0.351=2.35h_1 = w_1 * l_1 + w_2 * l_2 + b_1 * 1 = 0.1 * 5 + 0.15*10+0.35*1=2.35

outh1=11+eh1=11+e2.35=0.912934out_{h_1} = \frac{1}{1+e^{-h_1}} = \frac{1}{1+e^{-2.35}} = 0.912934

同理可以得到:

outh2=0.979164out_{h_2} =0.979164

outh3=0.995275out_{h_3} =0.995275

o1=w7outh1+w9outh2+w10outh3+b21o_1 = w_7 * out_{h_1}+w_9 * out_{h_2}+w_{10} * out_{h_3}+b_2*1

o1=0.40.912934+0.50.979164+0.60.995275+0.651=2.1019206o_1 = 0.4*0.912934+0.5*0.979164+0.6*0.995275+0.65*1=2.1019206

outo1=11+eo1=11+e2.1019206=0.891090out_{o1} = \frac{1}{1+e^{-o_1}} = \frac{1}{1+e^{-2.1019206}}=0.891090
同理可以得到:

out02=0.904330out_{0_2} =0.904330

输出层误差表示如下:
E=12(dO)2=12k=1l(dkOk)2E = \frac{1}{2}(d - O)^2 = \frac{1}{2}\sum_{k=1}^l(d_k - O_k)^2

Eo1=12(targeto1outo1)2E_{o_1} = \frac{1}{2}(target_{o_1} - out_{o_1})^2

Etotal=Eo1+Eo2E_{total} = E_{o_1}+E_{o_2}

Etotal=12(0.010.891090)2+12(0.990.904330)2=0.391829E_{total} = \frac{1}{2}(0.01 - 0.891090)^2 + \frac{1}{2}(0.99 -0.904330 )^2 = 0.391829

2、BP 过程

输出层到第二层隐层,以求 w7w_7 为例:

Etotalw7=Etotalouto1outo1o1o1w7\frac{\partial E_{total}}{\partial w_7} =\frac{\partial E_{total}}{\partial out_{o_1}}*\frac{\partial out_{o_1}}{\partial o_1}*\frac{\partial o_1}{\partial w_7}

下面我们分别求上式的三个部分,其中第一部分

Etotalouto1=212(targeto1outo1)(1)+0=(0.010.891090)=0.88109\frac{\partial E_{total}}{\partial out_{o_1}} = 2 * \frac{1}{2}(target_{o_1}-out_{o_1}) * (-1) + 0 = -(0.01 - 0.891090) = 0.88109

又因为:

outo1=11+eo1out_{o1} = \frac{1}{1+e^{-o_1}}

outo1=eo1(1+eo1)2=1+eo11(1+eo1)2=11+eo11(1+eo1)2=outo1(1outo1)out_{o_1}' = \frac{e^{-o_1}}{(1+e^{-o_1})^2} = \frac{1+e^{-o_1}-1}{(1+e^{-o_1})^2} = \frac{1}{1+e^{-o_1}}-\frac{1}{(1+e^{-o_1})^2} = out_{o_1}(1 - out_{o_1})

第二分部:

outo1o1=outo1(1outo1)=0.891090(10.891090)=0.097049\frac{\partial out_{o_1}}{\partial o_1} = out_{o_1}(1 - out_{o_1}) = 0.891090(1 - 0.891090) = 0.097049

因为:
o1=w7outh1+w9outh2+w10outh3+b21o_1 = w_7 * out_{h_1}+w_9 * out_{h_2}+w_{10} * out_{h_3}+b_2*1

第三部分:
o1w7=outh1+0+0+0=0.912934\frac{\partial o_1}{\partial w_7} = out_{h_1} + 0 + 0+0=0.912934

最终得到:
Etotalw7=0.881090.0970490.912934=0.078064\frac{\partial E_{total}}{\partial w_7} =0.88109*0.097049*0.912934=0.078064

更新w7w_7的值:

w7^=w7+Δw7=w7ηEtotalw7=0.40.50.078064=0.360968\hat{w_7} = w_7 + \Delta w_7 = w_7 - \eta \frac{\partial E_{total}}{\partial w_7} =0.4 - 0.5 * 0.078064=0.360968

同理可以求出:

w8^=0.453383\hat{w_8} = 0.453383
w9^=0.458137\hat{w_9} = 0.458137
w10^=0.553629\hat{w_{10}} = 0.553629
w11^=0.557448\hat{w_{11}} = 0.557448
w12^=0.653688\hat{w_{12}} = 0.653688

第二层隐层到第一层隐层,以求 w1w_1 为例:

Etotalw1=Etotalouth1outh1h1h1w1\frac{\partial E_{total}}{\partial w_1} = \frac{\partial E_{total}}{\partial out_{h_1}}* \frac{\partial out_{h_1}}{\partial h_1}* \frac{\partial h_1}{\partial w_1}

Etotalw1=(Eo1outh1+Eo2outh1)outh1h1h1w1\frac{\partial E_{total}}{\partial w_1}=\Big(\frac{\partial E_{o_1}}{\partial out_{h_1}} + \frac{\partial E_{o_2}}{\partial out_{h_1}}\Big)* \frac{\partial out_{h_1}}{\partial h_1}* \frac{\partial h_1}{\partial w_1}

下面我们分别计算,第一部分()内的

Eo1outh1=Eo1outo1outo1o1o1outh1\frac{\partial E_{o_1}}{\partial out_{h_1}}=\frac{\partial E_{o_1}}{\partial out_{o_1}} * \frac{\partial out_{o_1}}{\partial o_1}*\frac{\partial o_1}{\partial out_{h_1}}

其中:

Eo1=12(targeto1outo1)2E_{o_1} = \frac{1}{2}(target_{o_1} - out_{o_1})^2

outo1=11+eo1out_{o1} = \frac{1}{1+e^{-o_1}}

o1=w7outh1+w9outh2+w10outh3+b21o_1 = w_7 * out_{h_1}+w_9 * out_{h_2}+w_{10} * out_{h_3}+b_2*1

Eo1outh1=(targeto1outo1)outo1(1outo1)w7^\frac{\partial E_{o_1}}{\partial out_{h_1}}=-(target_{o_1} - out_{o_1})*out_{o_1}*(1- out_{o_1})*\hat{w_7}

注意:这里由于是反向传播,此时要用到之前更新后的w7w_7的值

Eo1outh1=(0.010.891090)0.891090(10.891090)0.360968=0.030866\frac{\partial E_{o_1}}{\partial out_{h_1}}=-(0.01 - 0.891090)*0.891090*(1-0.891090)*0.360968 = 0.030866

Eo2outh1=Eo2outo2outo2o2o2outh1\frac{\partial E_{o_2}}{\partial out_{h_1}} = \frac{\partial E_{o_2}}{\partial out_{o_2}} * \frac{\partial out_{o_2}}{\partial o_2}*\frac{\partial o_2}{\partial out_{h_1}}

同理:
Eo2outh1=(targeto2outo2)outo2(1outo2)w8=(0.990.904330)0.904330(10.904330)0.453383=0.003360\frac{\partial E_{o_2}}{\partial out_{h_1}} = -(target_{o_2} -out_{o_2})*out_{o_2}(1-out_{o_2})*w_8=-(0.99-0.904330)*0.904330*(1-0.904330)*0.453383=-0.003360

第二部分:

outh1h1=outh1(1outh1)=0.912934(10.912934)=0.079486\frac{\partial out_{h_1}}{\partial h_1}=out_{h_1}*(1-out_{h_1}) =0.912934*(1-0.912934)=0.079486

第三部分:

h1w1=l1=5\frac{\partial h_1}{\partial w_1} = l_1 = 5

最终:

Etotalw1=(0.030866+(0.003360))0.0794865=0.010932\frac{\partial E_{total}}{\partial w_1} = (0.030866 + (-0.003360))*0.079486 *5=0.010932

于是:
w1^=w1+Δw1=w1ηEtotalw1=0.10.50.010932=0.094534\hat{w_1} = w_1 + \Delta w_1 = w_1 - \eta \frac{\partial E_{total}}{\partial w_1} = 0.1 - 0.5 *0.010932 =0.094534

同理求出:
w2,w3,w4,w5,w6w_2, w_3,w_4,w_5,w_6

以上是第一次迭代,经过多次迭代,最终的误差会越来越小

在这里插入图片描述

上图可以看出,当迭代1000次时,输出为 O=(0.022971,0.977675)O=(0.022971,0.977675) 和原本的 O=(0.01,0.99)O = (0.01, 0.99)以及比较接近了。

五、上例 Python 代码实现

import numpy as np

# 初始值
w = [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65]
# 偏置项b不进行更新
b = [0.35, 0.65]

l = [5, 10]

# sigmoid函数
def sigmoid(z):
    return 1.0 / (1 + np.exp(-z))


def f1(w, b, l):
    # 前向传播,计算结果值
    h1 = sigmoid(w[0] * l[0] + w[1] * l[1] + b[0])
    h2 = sigmoid(w[2] * l[0] + w[3] * l[1] + b[0])
    h3 = sigmoid(w[4] * l[0] + w[5] * l[1] + b[0])

    o1 = sigmoid(w[6] * h1 + w[8] * h2 + w[10] * h3 + b[1])
    o2 = sigmoid(w[7] * h1 + w[9] * h2 + w[11] * h3 + b[1])

    # 后向传播,更新w
    # 输出层到第二层隐层,前两项
    # 公式中的第一部分-(0.01 - o1),第二部分o1 * (l - o1)
    t1 = -(0.01 - o1) * o1 * (l - o1)
    # 第二层隐层到第一层隐层,前两项
    t2 = -(0.99 - o2) * o2 * (l - o2)
    # t1*第三部分,即输出层到第二层隐层的参数梯度
    w[6] = w[6] - 0.5 * (t1 * h1)
    w[8] = w[8] - 0.5 * (t1 * h2)
    w[10] = w[10] - 0.5 * (t1 * h3)
    w[7] = w[7] - 0.5 * (t2 * h1)
    w[9] = w[9] - 0.5 * (t2 * h2)
    w[11] = w[11] - 0.5 * (t2 * h3)

    # (t1 * w[6] + t2 * w[7])对于公式()中的两项,h1 * (1 - h1)对于第二项,l[0]对应第三项
    w[0] = w[0] - 0.5 * (t1 * w[6] + t2 * w[7]) * h1 * (1 - h1) * l[0]
    w[1] = w[1] - 0.5 * (t1 * w[6] + t2 * w[7]) * h1 * (1 - h1) * l[1]
    w[2] = w[2] - 0.5 * (t1 * w[8] + t2 * w[9]) * h2 * (1 - h2) * l[0]
    w[3] = w[3] - 0.5 * (t1 * w[6] + t2 * w[9]) * h2 * (1 - h2) * l[1]
    w[4] = w[4] - 0.5 * (t1 * w[10] + t2 * w[11]) * h3 * (1 - h3) * l[0]
    w[5] = w[5] - 0.5 * (t1 * w[10] + t2 * w[11]) * h3 * (1 - h3) * l[1]

    return o1, o2, w


for i in range(1000):
    r1, r2, w = f1(w, b, l)
    print("第{}次迭代后,结果值为:({},{}),权重更新为:{}".format(i+1, r1, r2, w))

运行结果:

第0次迭代,结果值为:(0.8910896614765176,0.9043299248500164),权重更新为:[0.09453429502265628, 0.13906859004531255, 0.1982111758493806, 0.2472699352051287, 0.29949648483800345, 0.34899296967600685, 0.3609680622498306, 0.4533833089635062, 0.4581364640581681, 0.5536287533891512, 0.5574476639638248, 0.653688458944847]

第1000次迭代,结果值为:(0.022971398121212325,0.9776750383779403),权重更新为:[0.21489448646234574, 0.3797889729246913, 0.26021215340481807, 0.3781845403360407, 0.3231871485490716, 0.3963742970981442, -1.4890558608085964, 0.9416395064631622, -1.5030558044874467, 1.0491054496597343, -1.4269056227390988, 1.151801999870186]

结果中标记的为第一次迭代 w1w_1w7w_7 更新后的值,可见和我们上面计算的结果是一致的

六、使用BP神经网络对公路货运量预测

使用数据 traffic_data.csv

人口数,机动车数,公路面积,客运量,货运量
20.55,0.6,0.09,5126.0,1237.0
22.44,0.75,0.11,6217.0,1379.0
25.37,0.85,0.11,7730.0,1385.0
27.13,0.9,0.14,9145.0,1399.0
29.45,1.05,0.2,10460.0,1663.0
30.1,1.35,0.23,11387.0,1714.0
30.96,1.45,0.23,12353.0,1834.0
34.06,1.6,0.32,15750.0,4322.0
36.42,1.7,0.32,18304.0,8132.0
38.09,1.85,0.34,19836.0,8936.0
39.13,2.15,0.36,21024.0,11099.0
39.99,2.2,0.36,19490.0,11203.0
41.93,2.25,0.38,20433.0,10524.0
44.59,2.35,0.49,22598.0,11115.0
47.3,2.5,0.56,25107.0,13320.0
52.89,2.6,0.59,33442.0,16762.0
55.73,2.7,0.59,36836.0,18673.0
56.76,2.85,0.67,40548.0,20724.0
59.17,2.95,0.69,42927.0,20803.0
60.63,3.1,0.79,43462.0,21804.0

代码:

import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False

# 1. 读取数据
df = pd.read_csv('traffic_data.csv', encoding='utf-8')
# print(df.head())

# 2. 读取特征属性X
x = df[['人口数', '机动车数', '公路面积']]
y = df[['客运量', '货运量']]

# 3. 因为x和y的数据取值范围太大了,所以做一个归一化操作(使用区间缩放法)
x_scaler = MinMaxScaler(feature_range=(-1, 1))
y_scaler = MinMaxScaler(feature_range=(-1, 1))
x = x_scaler.fit_transform(x)
y = y_scaler.fit_transform(y)

# 为了后面和w进行矩阵的乘法操作
sample_in = x.T
sample_out = y.T

# 超参数
max_epochs = 60000
learn_rate = 0.035
mse_final = 6.5e-4
sample_number = x.shape[0]
input_number = 3
out_number = 2
hidden_unit_number = 8

# 网络参数
# 8*3的矩阵
w1 = 0.5 * np.random.rand(hidden_unit_number, input_number) - 0.1
# 8*1的矩阵
b1 = 0.5 * np.random.rand(hidden_unit_number, 1) - 0.1
# 2*8的矩阵
w2 = 0.5 * np.random.rand(out_number, hidden_unit_number) - 0.1
# 2*1的矩阵
b2 = 0.5 * np.random.rand(out_number, 1) - 0.1


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


mse_history = []
# BP的计算
for i in range(max_epochs):
    # FP过程
    # 隐藏层的输出(8*20)
    hidden_out = sigmoid(np.dot(w1, sample_in) + b1)
    # 输出层的输出(为了简化我们的写法,输出层不进行sigmoid激活)(2*20)
    netword_out = np.dot(w2, hidden_out) + b2

    # 错误
    # 2*20
    err = sample_out - netword_out
    mse = np.average(np.square(err))
    mse_history.append(mse)
    if mse < mse_final:
        break

    # BP过程
    # delta2: 2*20
    delta2 = -err
    delta1 = np.dot(w2.transpose(), delta2) * hidden_out * (1 - hidden_out)
    dw2 = np.dot(delta2, hidden_out.transpose())
    db2 = np.dot(delta2, np.ones((sample_number, 1)))
    dw1 = np.dot(delta1, sample_in.transpose())
    db1 = np.dot(delta1, np.ones((sample_number, 1)))
    # w2: 2*8的矩阵, 那也就是要求dw2必须是2*8的一个矩阵
    w2 -= learn_rate * dw2
    b2 -= learn_rate * db2
    w1 -= learn_rate * dw1
    b1 -= learn_rate * db1

# 误差曲线图
mse_history10 = np.log10(mse_history)
min_mse = min(mse_history10)
plt.plot(mse_history10)
plt.plot([0, len(mse_history10)], [min_mse, min_mse])
ax = plt.gca()
ax.set_yticks([-2, -1, 0, 1, 2, min_mse])
ax.set_xlabel('iteration')
ax.set_ylabel('MSE')
ax.set_title('Log10 MSE History')
plt.show()

# 仿真输出和实际输出对比图
# 隐藏层输出
hidden_out = sigmoid((np.dot(w1, sample_in) + b1))
# 输出层输出
network_out = np.dot(w2, hidden_out) + b2
# 反转获取实际值
network_out = y_scaler.inverse_transform(network_out.T)
sample_out = y_scaler.inverse_transform(y)

fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(12, 10))
line1, = axes[0].plot(network_out[:, 0], 'k', marker='o')
line2, = axes[0].plot(sample_out[:, 0], 'r', markeredgecolor='b', marker='*', markersize=9)
axes[0].legend((line1, line2), ('预测值', '实际值'), loc='upper left')
axes[0].set_title('客流模拟')
line3, = axes[1].plot(network_out[:, 1], 'k', marker='o')
line4, = axes[1].plot(sample_out[:, 1], 'r', markeredgecolor='b', marker='*', markersize=9)
axes[1].legend((line3, line4), ('预测值', '实际值'), loc='upper left')
axes[1].set_title('货流模拟')
plt.show()

运行结果:

在这里插入图片描述

因为程序中 mse_history10 = np.log10(mse_history) 对MSE取了log10对数,所以会有负数的

在这里插入图片描述

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