【深度學習】BP反向傳播算法Python簡單實現

  個人覺得BP反向傳播是深度學習的一個基礎,所以很有必要把反向傳播算法好好學一下
  得益於一步一步弄懂反向傳播的例子這篇文章,給出一個例子來說明反向傳播
  不過是英文的,如果你感覺不好閱讀的話,優秀的國人已經把它翻譯出來了。
  一步一步弄懂反向傳播的例子(中文翻譯)
神經網絡
  然後我使用了那個博客的圖片。這次的目的主要是對那個博客的一個補充。但是首先我覺得先用面向過程的思想來實現一遍感覺會好一點。隨便把文中省略的公式給大家給寫出來。大家可以先看那篇博文。

(1)Etotalw5=Etotalouto1×outo1neto1×neto1w5(2)Etotalw6=Etotalouto1×outo1neto1×neto1w6(3)Etotalw7=Etotalouto2×outo2neto2×neto2w7(4)Etotalw8=Etotalouto2×outo2neto2×neto2w8(5)Etotalw1=Etotalouth1×outh1neth1×neth1w1(6)Etotalw2=Etotalouth1×outh1neth1×neth1w2(7)Etotalw3=Etotalouth2×outh2neth2×neth2w3(8)Etotalw4=Etotalouth2×outh2neth2×neth2w4
import numpy as np

# "pd" 偏導
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def sigmoidDerivationx(y):
    return y * (1 - y)

if __name__ == "__main__":
    #初始化
    bias = [0.35, 0.60]
    weight = [0.15, 0.2, 0.25, 0.3, 0.4, 0.45, 0.5, 0.55]
    output_layer_weights = [0.4, 0.45, 0.5, 0.55]
    i1 = 0.05
    i2 = 0.10
    target1 = 0.01
    target2 = 0.99
    alpha = 0.5 #學習速率
    numIter = 90000 #迭代次數
    for i in range(numIter):
        #正向傳播
        neth1 = i1*weight[1-1] + i2*weight[2-1] + bias[0]
        neth2 = i1*weight[3-1] + i2*weight[4-1] + bias[0]
        outh1 = sigmoid(neth1)
        outh2 = sigmoid(neth2)
        neto1 = outh1*weight[5-1] + outh2*weight[6-1] + bias[1]
        neto2 = outh2*weight[7-1] + outh2*weight[8-1] + bias[1]
        outo1 = sigmoid(neto1)
        outo2 = sigmoid(neto2)
        print(str(i) + ", target1 : " + str(target1-outo1) + ", target2 : " + str(target2-outo2))
        if i == numIter-1:
            print("lastst result : " + str(outo1) + " " + str(outo2))
        #反向傳播
        #計算w5-w8(輸出層權重)的誤差
        pdEOuto1 = - (target1 - outo1)
        pdOuto1Neto1 = sigmoidDerivationx(outo1)
        pdNeto1W5 = outh1
        pdEW5 = pdEOuto1 * pdOuto1Neto1 * pdNeto1W5
        pdNeto1W6 = outh2
        pdEW6 = pdEOuto1 * pdOuto1Neto1 * pdNeto1W6
        pdEOuto2 = - (target2 - outo2)
        pdOuto2Neto2 = sigmoidDerivationx(outo2)
        pdNeto1W7 = outh1
        pdEW7 = pdEOuto2 * pdOuto2Neto2 * pdNeto1W7
        pdNeto1W8 = outh2
        pdEW8 = pdEOuto2 * pdOuto2Neto2 * pdNeto1W8
        # 計算w1-w4(輸出層權重)的誤差
        pdEOuto1 = - (target1 - outo1) #之前算過
        pdEOuto2 = - (target2 - outo2)  #之前算過
        pdOuto1Neto1 = sigmoidDerivationx(outo1)    #之前算過
        pdOuto2Neto2 = sigmoidDerivationx(outo2)    #之前算過
        pdNeto1Outh1 = weight[5-1]
        pdNeto1Outh2 = weight[7-1]
        pdENeth1 = pdEOuto1 * pdOuto1Neto1 * pdNeto1Outh1 + pdEOuto2 * pdOuto2Neto2 * pdNeto1Outh2
        pdOuth1Neth1 = sigmoidDerivationx(outh1)
        pdNeth1W1 = i1
        pdNeth1W2 = i2
        pdEW1 = pdENeth1 * pdOuth1Neth1 * pdNeth1W1
        pdEW2 = pdENeth1 * pdOuth1Neth1 * pdNeth1W2
        pdNeto1Outh2 = weight[6-1]
        pdNeto2Outh2 = weight[8-1]
        pdOuth2Neth2 = sigmoidDerivationx(outh2)
        pdNeth1W3 = i1
        pdNeth1W4 = i2
        pdENeth2 = pdEOuto1 * pdOuto1Neto1 * pdNeto1Outh2 + pdEOuto2 * pdOuto2Neto2 * pdNeto2Outh2
        pdEW3 = pdENeth2 * pdOuth2Neth2 * pdNeth1W3
        pdEW4 = pdENeth2 * pdOuth2Neth2 * pdNeth1W4
        #權重更新
        weight[1-1] = weight[1-1] - alpha * pdEW1
        weight[2-1] = weight[2-1] - alpha * pdEW2
        weight[3-1] = weight[3-1] - alpha * pdEW3
        weight[4-1] = weight[4-1] - alpha * pdEW4
        weight[5-1] = weight[5-1] - alpha * pdEW5
        weight[6-1] = weight[6-1] - alpha * pdEW6
        weight[7-1] = weight[7-1] - alpha * pdEW7
        weight[8-1] = weight[8-1] - alpha * pdEW8
        # print(weight[1-1])
        # print(weight[2-1])
        # print(weight[3-1])
        # print(weight[4-1])
        # print(weight[5-1])
        # print(weight[6-1])
        # print(weight[7-1])
        # print(weight[8-1])

  不知道你是否對此感到熟悉一點了呢?反正我按照公式實現一遍之後深有體會,然後用向量的又寫了一次代碼。
  接下來我們要用向量來存儲這些權重,輸出結果等,因爲如果我們不這樣做,你看上面的例子就知道我們需要寫很多w1,w2等,這要是參數一多就很可怕。
  這些格式我是參考吳恩達的格式,相關課程資料->吳恩達深度學習視頻
神經網絡
我將原文的圖片的變量名改成如上
然後正向傳播的公式如下:

(9)z1[1]=w1[1]Tx+b1,a1[1]=σ(z1[1])(10)z2[1]=w2[1]Tx+b1,a2[1]=σ(z2[1])(11)z1[2]=w1[2]Ta1+b2,a1[2]=σ(z1[2])(12)z2[2]=w2[2]Ta1+b2,a2[2]=σ(z2[2])(13)

其中
(14)w1[1]T=(w1,w2)(15)w2[1]T=(w3,w4)(16)w1[2]T=(w5,w6)(17)w2[2]T=(w7,w8)

然後反向傳播的公式如下:
(18)Ew1[2]=Ea1[2]a1[2]z1[2]z1[2]w1[2](19)Ew1[2]=Ea2[2]a2[2]z2[2]z2[2]w2[2](20)Ew1[1]=Ea1[1]a1[1]z1[1]z1[1]w1[1](21)Ew2[1]=Ea2[1]a2[1]z2[1]z2[1]w2[1]

具體地
(22)Ea1[2]=(y1a1[2])(23)a1[2]z1[2]=a1[2](1a1[2])(24)z1[2]w1[2]=a1[2](25)Ea2[2]=(y2a2[2])(26)a1[2]z1[2]=a2[2](1a2[2])(27)z1[2]w2[2]=a2[2](28)Ea1[1]=w1[2]Tδ2(29)a1[1]z1[1]=a1[1](1a1[1])(30)z1[1]w1[1]=a1[1](31)Ea1[1]=w2[2]Tδ2(32)a2[1]z1[1]=a2[1](1a2[1])(33)z1[1]w2[1]=a2[1]

其中
δ2=((34)Ea1[2]a1[2]z1[2](35)Ea2[2]a2[2]z2[2])=(Ea[2]a[2]z[2])

爲啥這樣寫呢,一開始我也沒明白,後來看到Ea1[2]a1[2]z1[2] 有好幾次重複,且也便於梯度公式的書寫。
import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def sigmoidDerivationx(y):
    return y * (1 - y)

if __name__ == '__main__':
    #初始化一些參數
    alpha = 0.5
    w1 = [[0.15, 0.20], [0.25, 0.30]] #Weight of input layer
    w2 = [[0.40, 0.45], [0.50, 0.55]]
    b1 = 0.35
    b2 = 0.60
    x = [0.05, 0.10]
    y = [0.01, 0.99]
    #前向傳播
    z1 = np.dot(w1, x) + b1
    a1 = sigmoid(z1)
    z2 = np.dot(w2, a1) + b2
    a2 = sigmoid(z2)
    for n in range(10000):
        #反向傳播 使用代價函數爲C=1 / (2n) * sum[(y-a2)^2]
        #分爲兩次
        # 一次是最後一層對前面一層的錯誤
        delta2 = np.multiply(-(y-a2), np.multiply(a2, 1-a2))
        # for i in range(len(w2)):
        #     print(w2[i] - alpha * delta2[i] * a1)
        #計算非最後一層的錯誤
        # print(delta2)
        delta1 = np.multiply(np.dot(w1, delta2), np.multiply(a1, 1-a1))
        # print(delta1)
        # for i in range(len(w1)):
            # print(w1[i] - alpha * delta1[i] * np.array(x))
        #更新權重
        for i in range(len(w2)):
            w2[i] = w2[i] - alpha * delta2[i] * a1
        for i in range(len(w1)):
            w1[i] - alpha * delta1[i] * np.array(x)
        #繼續前向傳播,算出誤差值
        z1 = np.dot(w1, x) + b1
        a1 = sigmoid(z1)
        z2 = np.dot(w2, a1) + b2
        a2 = sigmoid(z2)
        print(str(n) + " result:" + str(a2[0]) + ", result:" +str(a2[1]))
        # print(str(n) + "  error1:" + str(y[0] - a2[0]) + ", error2:" +str(y[1] - a2[1]))

可以看到,用向量來表示的話代碼就簡短了非常多。但是用了向量化等的方法,如果不太熟,去看吳恩達深度學習的第一部分,再返過來看就能懂了。
下面,來看一個例子。用神經網絡實現XOR(01=1,10=1,00=0,11=0)。我們都知道感知機是沒法實現異或的,原因是線性不可分。
接下里的這個例子,我是用2個輸入結點,3個隱層結點,1個輸出結點來實現的。
異或
讓我們以一個輸入爲例。
前向傳播:

(w11[1]w12[1]w21[1]w22[1]w31[1]w32[1])(x1x2)=(z1[1]z2[1]z3[1])

(a1[1]a2[1]a3[1])=(σ(z1[1])σ(z2[1])σ(z3[1]))

(w11[2]w12[2]w13[2])(a1[1]a2[1]a3[1])=(z1[2])

(a1[2])=(σ(z1[2]))

反向傳播:
主要是有2個公式比較重要
δL=aCσ(aL)=(yaL)(aL(1aL))

原理同上
δl=((wl+1)T)σ(al)

wl=wlηδl(al1)T

這次省略了偏導,代碼如下
import numpy as np
# sigmoid function
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def sigmoidDerivationx(y):
    return y * (1 - y)

if __name__ == '__main__':
    alpha = 1
    input_dim = 2
    hidden_dim = 3
    output_dim = 1
    synapse_0 = 2 * np.random.random((hidden_dim, input_dim)) - 1  #(2, 3)
    # synapse_0 = np.ones((hidden_dim, input_dim)) * 0.5
    synapse_1 = 2 * np.random.random((output_dim, hidden_dim)) - 1  #(2, 2)
    # synapse_1 = np.ones((output_dim, hidden_dim)) * 0.5
    x = np.array([[0, 1], [1, 0], [0, 0], [1, 1]]).T #(2, 4)
    # x = np.array([[0, 1]]).T #(3, 1)
    y = np.array([[1], [1], [0], [0]]).T    #(1, 4)
    # y = np.array([[1]]).T    #(2, 1)
    for i in range(2000000):
        z1 = np.dot(synapse_0, x)   #(3, 4)
        a1 = sigmoid(z1)    #(3, 4)
        z2 = np.dot(synapse_1, a1)  #(1, 4)
        a2 = sigmoid(z2)    #(1, 4)
        error = -(y - a2) #(1, 4)
        delta2 = np.multiply(-(y - a2) / x.shape[1], sigmoidDerivationx(a2))  #(1, 4)
        delta1 = np.multiply(np.dot(synapse_1.T, delta2), sigmoidDerivationx(a1))  #(3, 4)
        synapse_1 = synapse_1 - alpha * np.dot(delta2, a1.T) #(1, 3)
        synapse_0 = synapse_0 - alpha * np.dot(delta1, x.T) #(3, 2)
        print(str(i) + ":", end=' ')
        print(a2)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章