【深度學習】神經網絡與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對數,所以會有負數的

在這裏插入圖片描述

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