1. 程式人生 > >反向傳播(BP演算法)python實現

反向傳播(BP演算法)python實現

反向傳播(BP演算法)python實現

1、BP演算法描述

BP演算法就是反向傳播,要輸入的資料經過一個前向傳播會得到一個輸出,但是由於權重的原因,所以其輸出會和你想要的輸出有差距,這個時候就需要進行反向傳播,利用梯度下降,對所有的權重進行更新,這樣的話在進行前向傳播就會發現其輸出和你想要的輸出越來越接近了。

上面只是其簡單的原理,具體實現起來其實就是利用了鏈式法則,逐步的用誤差對所有權重求導,這樣便反向得到了誤差對每個權重的梯度,然後再把所有的梯度更新一下即可。

其中每一層的w的梯度可以用下面的公式計算,下面的公式是摘自吳恩達老師的筆記,這個是一個兩層的網路, d

w [ 2 ] dw^{[2]} 代表誤差對最後一層的w的導數, d
w [ 1 ] dw^{[1]}
代表第一層,其實除了最後一層,前面的可以抽象成一個公式,比如誤差對第 l
l
層( l l 為非最後一層)的權重的導數為 d w [ l ] = d z l a [ l 1 ] T dw^{[l]} =dz^{l} a^{[l-1]^T} 其中 a [ 0 ] = x a^{[0]} = x ,這樣的話我們就可以求出一個對任意層的權重的梯度了。( z [ l ] l z^{[l]} 代表第l層未啟用的輸出 a [ l ] l a^{[l]} 代表第l層啟用的輸出 ,這些基本符號的含義可以參考吳恩達筆記的定義)

在這裡插入圖片描述

並且我們從公式也可以看出來,如果計算權重的梯度,我們需要 z a w z、a、w ,所以我們在編制程式碼的時候需要注意,在進行前向傳播的時候要把z和a暫存起來,這樣在進行反向傳播的時候就可以直接拿來用了。

Notice: 在圖片中的乘分為兩種,如果是ab則在python中用點乘就是a.dot(b),而ab則在python中用ab 二者去唄是a.dot(b)就是矩陣運算,而a*b則是對應元素乘。

2、基於梯度下降的反向傳播的程式碼實現

# 生成權重以及偏執項layers_dim代表每層的神經元個數,
#比如[2,3,1]代表一個三成的網路,輸入為2層,中間為3層輸出為1層
def init_parameters(layers_dim):
    
    L = len(layers_dim)
    parameters ={}
    for i in range(1,L):
        parameters["w"+str(i)] = np.random.random([layers_dim[i],layers_dim[i-1]])
        parameters["b"+str(i)] = np.zeros((layers_dim[i],1))
    return parameters
def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

# sigmoid的導函式
def sigmoid_prime(z):
        return sigmoid(z) * (1-sigmoid(z))
# 前向傳播,需要用到一個輸入x以及所有的權重以及偏執項,都在parameters這個字典裡面儲存
# 最後返回會返回一個caches裡面包含的 是各層的a和z,a[layers]就是最終的輸出
def forward(x,parameters):
    a = []
    z = []
    caches = {}
    a.append(x)
    z.append(x)
    layers = len(parameters)//2
    # 前面都要用sigmoid
    for i in range(1,layers):
        z_temp =parameters["w"+str(i)].dot(x) + parameters["b"+str(i)]
        z.append(z_temp)
        a.append(sigmoid(z_temp))
    # 最後一層不用sigmoid
    z_temp = parameters["w"+str(layers)].dot(a[layers-1]) + parameters["b"+str(layers)]
    z.append(z_temp)
    a.append(z_temp)
    
    caches["z"] = z
    caches["a"] = a    
    return  caches,a[layers]
# 反向傳播,parameters裡面儲存的是所有的各層的權重以及偏執,caches裡面儲存各層的a和z
# al是經過反向傳播後最後一層的輸出,y代表真實值
# 返回的grades代表著誤差對所有的w以及b的導數
def backward(parameters,caches,al,y):
    layers = len(parameters)//2
    grades = {}
    m = y.shape[1]
    # 假設最後一層不經歷啟用函式
    # 就是按照上面的圖片中的公式寫的
    grades["dz"+str(layers)] = al - y
    grades["dw"+str(layers)] = grades["dz"+str(layers)].dot(caches["a"][layers-1].T) /m
    grades["db"+str(layers)] = np.sum(grades["dz"+str(layers)],axis = 1,keepdims = True) /m
    # 前面全部都是sigmoid啟用
    for i in reversed(range(1,layers)):
        grades["dz"+str(i)] = parameters["w"+str(i+1)].T.dot(grades["dz"+str(i+1)]) * sigmoid_prime(caches["z"][i])
        grades["dw"+str(i)] = grades["dz"+str(i)].dot(caches["a"][i-1].T)/m
        grades["db"+str(i)] = np.sum(grades["dz"+str(i)],axis = 1,keepdims = True) /m
    return grades   
# 就是把其所有的權重以及偏執都更新一下
def update_grades(parameters,grades,learning_rate):
    layers = len(parameters)//2
    for i in range(1,layers+1):
        parameters["w"+str(i)] -= learning_rate * grades["dw"+str(i)]
        parameters["b"+str(i)] -= learning_rate * grades["db"+str(i)]
    return parameters
# 計算誤差值
def compute_loss(al,y):
    return np.mean(np.square(al-y))
# 載入資料
def load_data():
    """
    載入資料集
    """
    x = np.arange(0.0,1.0,0.01)
    y =20* np.sin(2*np.pi*x)
    # 資料視覺化
    plt.scatter(x,y)
    return x,y
#進行測試
x,y = load_data()
x = x.reshape(1,100)
y = y.reshape(1,100)
plt.scatter(x,y)
parameters = init_parameters([1,25,1])
al = 0
for i in range(4000):
    caches,al = forward(x, parameters)
    grades = backward(parameters, caches, al, y)
    parameters = update_grades(parameters, grades, learning_rate= 0.3)
    if i %100 ==0:
        print(compute_loss(al, y))
plt.scatter(x,al)
plt.show()

結果顯示:

在這裡插入圖片描述

從結果可以看出來幾乎能對一條非線性線進行完全擬合了。

參考:

https://blog.csdn.net/qq_28888837/article/details/82901011
https://mooc.study.163.com/learn/2001281002?tid=2001392029#/learn/content?type=detail&id=2001702020&cid=2001700045