1. 程式人生 > >神經網路學習(3)————BP神經網路以及python實現

神經網路學習(3)————BP神經網路以及python實現


一、BP神經網路結構模型       

        BP演算法的基本思想是,學習過程由訊號的正向傳播和誤差的反向傳播倆個過程組成,輸入從輸入層輸入,經隱層處理以後,傳向輸出層。如果輸出層的實際輸出和期望輸出不符合,就進入誤差的反向傳播階段。誤差反向傳播是將輸出誤差以某種形式通過隱層向輸入層反向傳播,並將誤差分攤給各層的所有單元,從而獲得各層單元的誤差訊號,這個誤差訊號就作為修正個單元權值的依據。知道輸出的誤差滿足一定條件或者迭代次數達到一定次數。

層與層之間為全連線,同一層之間沒有連線。結構模型如下圖所示。

        使用的傳遞函式sigmoid可微的特性使他可以使用梯度下降法。所以,在隱層函式中使用sigmoid函式作為傳遞函式,在輸出層採用線性函式作為傳遞函式。

輸入向量、隱層輸出向量、最終輸出向量、期望輸出向量:

X=(x1,x2,x3……xn),其中圖中x0是為隱層神經元引入閾值設定的;

Y=(y1,y2,y3……ym),其中圖中y0是為輸出神經元引入閾值設定的;

O=(o1,o2,o3……ol)

D=(d1,d2,d3……dl)

輸出層的輸入是隱層的輸出,隱層的輸入是輸入層的輸出,計算方法和單層感知器的計算方法一樣。

單極性Sigmoid函式:

雙極性sigmoid函式:

二、BP神經網路的學習演算法

        標準BP神經網路沿著誤差效能函式梯度的反方向修改權值,原理與LMS演算法比較類似,屬於最速下降法。此外還有以下改進演算法,如動量最速下降法,擬牛頓法等。

    最速下降法又稱為梯度下降法。LMS演算法就是最小均方誤差演算法。LMS演算法體現了糾錯原則,與梯度下降法本質上沒有區別,梯度下降法可以求目標函式的極小值,如果將目標函式取為均方誤差,就得到了LMS演算法。

       梯度下降法原理:對於實值函式F(x),如果函式在某點x0處有定義且可微,則函式在該點處沿著梯度相反的方向下降最快,因此,使用梯度下降法時,應首先計算函式在某點處的梯度,再沿著梯度的反方向以一定的步長調整自變數的值。其中實值函式指的是傳遞函式,自變數x指的是上一層權值和輸入值的點積作為的輸出值。

        網路誤差定義:

   

   

三層BP網路演算法推導:

1、變數定義

網路的實際輸出:

  1. 訊號正向傳播

  1. 誤差訊號反向傳播

首先誤差反向傳播首先經過輸出層,所以首先調整隱含層和輸出層之間的權值。

然後對輸入神經元和隱層神經元的誤差進行調整。

權值矩陣的調整可以總結為:

    權值調整量det(w)=學習率*區域性梯度*上一層輸出訊號。

BP神經網路的複雜之處在於隱層輸入層、隱層和隱層之間的權值調整時,區域性梯度的計算需要用到上一步計算的結果,前一層的區域性梯度是後一層區域性梯度的加權和。

 

訓練方式:

  1. 序列方式:網路每獲得一個新樣本,就計算一次誤差並更新權值,直到樣本輸入完畢。

  2. 批量方式:網路獲得所有的訓練樣本,計算所有樣本均方誤差的和作為總誤差;

在序列執行方式中,每個樣本依次輸入,需要的儲存空間更少,訓練樣本的選擇是隨機的,可以降低網路陷入區域性最優的可能性。

批量學習方式比序列方式更容易實現並行化。由於所有樣本同時參加運算,因此批量方式的學習速度往往遠優於序列方式。

 

BP神經網路的優點:

  1. 非線性對映能力
  2. 泛化能力  
  3. 容錯能力  允許輸入樣本中帶有較大誤差甚至個別錯誤。反應正確規律的知識來自全體樣本,個別樣本中的誤差不能左右對權矩陣的調整。

 

BP神經網路的侷限性:

    梯度下降法的缺陷:

  1. 目標函式必須可微;
  2. 如果一片區域比較平坦會花費較多時間進行訓練;
  3. 可能會陷入區域性極小值,而沒有到達全域性最小值;(求全域性極小值的目的是為了實現誤差的最小值)

BP神經網路的缺陷:

  1. 需要的引數過多,而且引數的選擇沒有有效的方法。確定一個BP神經網路需要知道:網路的層數、每一層神經元的個數和權值。權值可以通過學習得到,如果,隱層神經元數量太多會引起過學習,如果隱層神經元個數太少會引起欠學習。此外學習率的選擇也是需要考慮。目前來說,對於引數的確定缺少一個簡單有效的方法,所以導致演算法很不穩定;
  2. 屬於監督學習,對於樣本有較大依賴性,網路學習的逼近和推廣能力與樣本有很大關係,如果樣本集合代表性差,樣本矛盾多,存在冗餘樣本,網路就很難達到預期的效能;
  3. 由於權值是隨機給定的,所以BP神經網路具有不可重現性;

 

梯度下降法(最速下降法的改進):

    針對演算法的不足出現了幾種BP演算法的改進。

  1. 動量法

動量法是在標準BP演算法的權值更新階段引入動量因子α(0<α<1),使權值修正具有一定慣性,可以看出,在原有的權值調整公式中,加入了動量因子以及上一次的權值改變數。加入的動量項表示本次權值的更新方向和幅度,不但與本次計算所得的梯度有關,還與上一次更新的方向和幅度有關。動量項反映了以前積累的調整經驗,對於t時刻的調整起到了阻尼作用。當誤差曲面出現驟然起伏時,可減小震盪趨勢,提高訓練速度。

  1. 調節學習率法

在平緩區域希望學習率大一點減小迭代次數,在坑凹處希望學習率小一點,較小震盪。所以,為了加速收斂過程,希望自適應改變學習率,在該大的時候大,在該小的時候小。

學習率可變的BP演算法是通過觀察誤差的增減來判斷的,當誤差以減小的方式區域目標時,說明修正方向是正確的,可以增加學習率;當誤差增加超過一定範圍時,說明前一步修正進行的不正確,應減小步長,並撤銷前一步修正過程。學習率的增減通過乘以一個增量/減量因子實現:

  1. 引入陡度因子。

 

 

BP神經網路的設計:

   BP神經網路採用有監督學習。解決具體問題時,首先需要一個訓練集。然後神經網路的設計主要包括網路層數、輸入層節點數、隱層節點數、輸出層節點數、以及傳輸函式、訓練方法、訓練引數。

 

(一)輸入輸出資料的預處理:尺度變換。尺度變化也稱為歸一化或者標準化,是指變換處理將網路的輸入、輸出資料限制在[0,1]或者[-1,1]區間內。進行變換的原因是,(1)網路的各個輸入資料常常具有不同的物理意義和不同的量綱。尺度變換使所有分量都在一個區間內變化,從而使網路訓練一開始就給各輸入分量以同等重要的地位;(2)BP神經網路神經元均採用sigmoid函式,變換後可防止因淨輸入的絕對值過大而使神經元輸出飽和,繼而使權值調整進入誤差曲面的平坦區;(3)sigmoid函式輸出在區間[0,1]或者[-1,1]內,如果不對期望輸出資料進行變換處理,勢必使數值大的分量絕對誤差大,數值小的分量絕對誤差小。

(二)神經網路結構設計

1)網路層數  BP神經網路最多隻需要倆個隱層,在設計的時候一般先只考慮設一個隱層,當一個隱層的節點數很多但是依然不能改善網路情況時,才考慮增加一個隱層。經驗表明,如果在第一個隱層較多的節點數,第二個隱層較少的節點數,可以改善網路效能。

2)輸入層節點數  輸入層節點數取決於輸入向量的維數。應用神經網路解決實際問題時,首先應從問題中提煉出一個抽象模型。如果輸入的是64*64的影象,則輸入向量應為影象中左右的畫素形成的4096維向量。如果待解決的問題是二院函式擬合,則輸入向量應為二維向量。

3)隱層節點數設計  隱含節點數對BP神經網路的效能有很大影響,一般較多的隱含層節點數可以帶來更好的效能,但是導致訓練時間過長。通常是採用經驗公式給出估計值:

4)輸出層神經元個數  

5)傳遞函式的選擇  一般隱層選擇sigmoid函式,輸出層選擇線性函式。如果也使用sigmoid函式,則輸出值將會被限制在(0,1)或者(-1,1)之間。

6)訓練方法的選擇  一般來說,對於包含數百個權值的函式逼近網路,使用LM演算法收斂速度最快,均方誤差也小,但是LM演算法對於模式識別相關問題的處理能力較弱,且需要較大的儲存空間。對於模式識別問題,使用RPROP演算法能收到較好的效果。SCG演算法對於模式識別和函式逼近都有較好的效能表現。序列方式需要更小的儲存空間,且輸入樣本具有一定隨機性,可以避免陷入區域性最優。批量方式的誤差收斂條件非常簡單,訓練速度快。

 

三、python實現加動量法改進

# -*- coding: utf-8 -*-
"""
Created on Mon Oct  1 22:15:54 2018

@author: Heisenberg
"""
import numpy as np
import math
import random
import string
import matplotlib as mpl
import matplotlib.pyplot as plt

#random.seed(0)  #當我們設定相同的seed,每次生成的隨機數相同。如果不設定seed,則每次會生成不同的隨機數
                #參考https://blog.csdn.net/jiangjiang_jian/article/details/79031788

#生成區間[a,b]內的隨機數
def random_number(a,b):
    return (b-a)*random.random()+a

#生成一個矩陣,大小為m*n,並且設定預設零矩陣
def makematrix(m, n, fill=0.0):
    a = []
    for i in range(m):
        a.append([fill]*n)
    return a

#函式sigmoid(),這裡採用tanh,因為看起來要比標準的sigmoid函式好看
def sigmoid(x):
    return math.tanh(x)

#函式sigmoid的派生函式
def derived_sigmoid(x):
    return 1.0 - x**2

#構造三層BP網路架構
class BPNN:
    def __init__(self, num_in, num_hidden, num_out):
        #輸入層,隱藏層,輸出層的節點數
        self.num_in = num_in + 1  #增加一個偏置結點
        self.num_hidden = num_hidden + 1   #增加一個偏置結點
        self.num_out = num_out
        
        #啟用神經網路的所有節點(向量)
        self.active_in = [1.0]*self.num_in
        self.active_hidden = [1.0]*self.num_hidden
        self.active_out = [1.0]*self.num_out
        
        #建立權重矩陣
        self.wight_in = makematrix(self.num_in, self.num_hidden)
        self.wight_out = makematrix(self.num_hidden, self.num_out)
        
        #對權值矩陣賦初值
        for i in range(self.num_in):
            for j in range(self.num_hidden):
                self.wight_in[i][j] = random_number(-0.2, 0.2)
        for i in range(self.num_hidden):
            for j in range(self.num_out):
                self.wight_out[i][j] = random_number(-0.2, 0.2)
    
        #最後建立動量因子(矩陣)
        self.ci = makematrix(self.num_in, self.num_hidden)
        self.co = makematrix(self.num_hidden, self.num_out)        
        
    #訊號正向傳播
    def update(self, inputs):
        if len(inputs) != self.num_in-1:
            raise ValueError('與輸入層節點數不符')
            
        #資料輸入輸入層
        for i in range(self.num_in - 1):
            #self.active_in[i] = sigmoid(inputs[i])  #或者先在輸入層進行資料處理
            self.active_in[i] = inputs[i]  #active_in[]是輸入資料的矩陣
            
        #資料在隱藏層的處理
        for i in range(self.num_hidden - 1):
            sum = 0.0
            for j in range(self.num_in):
                sum = sum + self.active_in[i] * self.wight_in[j][i]
            self.active_hidden[i] = sigmoid(sum)   #active_hidden[]是處理完輸入資料之後儲存,作為輸出層的輸入資料
            
        #資料在輸出層的處理
        for i in range(self.num_out):
            sum = 0.0
            for j in range(self.num_hidden):
                sum = sum + self.active_hidden[j]*self.wight_out[j][i]
            self.active_out[i] = sigmoid(sum)   #與上同理
            
        return self.active_out[:]
    
    #誤差反向傳播
    def errorbackpropagate(self, targets, lr, m):   #lr是學習率, m是動量因子
        if len(targets) != self.num_out:
            raise ValueError('與輸出層節點數不符!')
            
        #首先計算輸出層的誤差
        out_deltas = [0.0]*self.num_out
        for i in range(self.num_out):
            error = targets[i] - self.active_out[i]
            out_deltas[i] = derived_sigmoid(self.active_out[i])*error
        
        #然後計算隱藏層誤差
        hidden_deltas = [0.0]*self.num_hidden
        for i in range(self.num_hidden):
            error = 0.0
            for j in range(self.num_out):
                error = error + out_deltas[j]* self.wight_out[i][j]
            hidden_deltas[i] = derived_sigmoid(self.active_hidden[i])*error
        
        #首先更新輸出層權值
        for i in range(self.num_hidden):
            for j in range(self.num_out):
                change = out_deltas[j]*self.active_hidden[i]
                self.wight_out[i][j] = self.wight_out[i][j] + lr*change + m*self.co[i][j]
                self.co[i][j] = change
                
        #然後更新輸入層權值
        for i in range(self.num_in):
            for i in range(self.num_hidden):
                change = hidden_deltas[j]*self.active_in[i]
                self.wight_in[i][j] = self.wight_in[i][j] + lr*change + m* self.ci[i][j]
                self.ci[i][j] = change
                
        #計算總誤差
        error = 0.0
        for i in range(len(targets)):
            error = error + 0.5*(targets[i] - self.active_out[i])**2
        return error
 
    #測試
    def test(self, patterns):
        for i in patterns:
            print(i[0], '->', self.update(i[0]))
    #權重
    def weights(self):
        print("輸入層權重")
        for i in range(self.num_in):
            print(self.wight_in[i])
        print("輸出層權重")
        for i in range(self.num_hidden):
            print(self.wight_out[i])
            
    def train(self, pattern, itera=100000, lr = 0.1, m=0.1):
        for i in range(itera):
            error = 0.0
            for j in pattern:
                inputs = j[0]
                targets = j[1]
                self.update(inputs)
                error = error + self.errorbackpropagate(targets, lr, m)
            if i % 100 == 0:
                print('誤差 %-.5f' % error)
    
#例項
def demo():
    patt = [
            [[1,2,5],[0]],
            [[1,3,4],[1]],
            [[1,6,2],[1]],
            [[1,5,1],[0]],
            [[1,8,4],[1]]
            ]
    #建立神經網路,3個輸入節點,3個隱藏層節點,1個輸出層節點
    n = BPNN(3, 3, 1)
    #訓練神經網路
    n.train(patt)
    #測試神經網路
    n.test(patt)
    #查閱權重值
    n.weights()

     
if __name__ == '__main__':
    demo()

計算結果: