【神經網絡DNN】算法原理 公式推導 python編程實現

 

1.前言

如圖是一個神經網絡的簡化結構,隱藏層每一個節點都是一個神經元,比如下圖的a1,a2,a3。機器學習中的神經網絡是模擬生物神經網絡結構,每個神經元與其他神經元相連,當神經元的電位超過了一個‘閾值’,那麼它就會被激活,即‘興奮’起來。

   機器學習的神經網絡是怎麼模擬大腦神經元‘興奮’這個概念的?結合a1這個神經元做簡要的分析:

首先對於a1的定義,我們給出如下的公式..

                      \large {a_{1}}=g(h(x)))={w_{11}}*{x_{1}}+{w_{21}}*{x_{2}}\cdot \cdot \cdot \cdot {w_{n1}}*{x_{n}}+{w_{m1}}*{x_{m}}+{b_{01}}*{x_{0}}

    剖析神經元興奮的定義“當神經元的電位超過了一個‘閾值’,那麼它就會被激活”,對應與上面公式,怎麼使得上訴公式達到神經元的效果?

    當\small h(x)達到某一閥值時候\small {a_{1}}=g(h(x))=1,即興奮狀態,反之當未達到閥值時\small {a_{1}}=g(h(x))=0,即未興奮狀態。

所以理想情況下\small \small g(z)這個函數應該如下圖的紅色,因爲起特性就是大於零被激活,小於零未激活,所以稱之爲激活函數。 :

但是紅色函數存在不連續,不光滑等不太好的性質...所以在實際情況下,我們一般會使用sigmod函數來作爲激活函數(即圖中藍線):

                                                                                \large sigmoid(z)=\frac{1}{1+e^{-z}}

其實我們可以發現,對於神經網絡來說,每一個神經元我們都可以理解成一個邏輯迴歸(LR)模型。這樣去理解它或許對你之後的理解有所幫助。對於邏輯迴歸算法模型的推到及編程可以查看我之前的一篇文章:點擊此處跳轉

2.前向傳播的神經網絡運算

          由圖,輸入層的輸入矩陣(\large x_{i})可以表示爲:                 隱藏層的輸入矩陣(\large a_{j})可以表示爲:

                                   \large \begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}                                                                        \large \large \begin{bmatrix} a_{1}\\ a_{2} \end{bmatrix}                        

          輸入層的每個輸入\large x_{i}對應每個隱藏層\large a_{j}的權重爲\large w_{ij},所以權重的矩陣(\large w_{ij})可以表示爲:

                                                                   \large \begin{bmatrix} w_{11} &w_{12} \\ w_{21} &w_{22} \\ w_{31}&w_{32} \end{bmatrix}

          由神經元的興奮啓發可以將上式表示爲:

                                                                  \large h_{1}=\begin{bmatrix} w_{11} & w_{21}& w_{31} \end{bmatrix}*\begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}   

                                                                  \large a_{1}=g(h_{1})=\frac{1}{1+e^{-h_{1}}}

       由上式子可以整理得;

                                                                 \large h=w^{T}x+[b]    (b爲偏置單元,對應圖1的,x0,a0,在本節介紹中省略)

       將矩陣代入上式:

                                                               \large h_{j}=w^{T}x+[b]=\begin{bmatrix} w_{11} &w_{21} & w_{31} \\ w_{12} &w_{22}&w_{32} \end{bmatrix}*\begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}

                                                              \large a_{i}=g(h_{j})=\frac{1}{1+e^{-h_{j}}}

    同理,我們對於接下來的隱藏層和輸出層也用同樣的方法,那麼我們最終的輸出層就是一個在0,1之間分佈的值,當其大於0時,即輸入的x屬於1這個類別。當其小於0時,輸入的x屬於0這個類別。

 

3.梯度下降與代價函數

 

   你可能會因爲不清楚第二節的前向傳播算法中的權重矩陣(\large w_{ij})是怎麼來的,從而導致你對整個算法的過程有些不知所云。這一章節會爲你解決這個疑惑。

     需要明白的是,在設計一個神經網絡算法模型的時候,有些參數是提前人爲定義的。

#參數1:
隱藏層的層數
#參數2:
每層隱藏層的神經元個數
#參數3:
輸出層的神經元個數
#參數4
定義每次訓練的樣本數
#參數5
定義訓練的次數
#參數6
定義學習率

當上訴參數設定後,將在神經網絡中起到怎樣的作用呢?結合下圖來說明一下:

1.在定義了隱藏層的層數,每層神經元的個數,輸出層神經元個數後我們能確定上圖中w,b,v,b_,u,b__的矩陣大小。在神經網絡第一輪訓練時候,上訴矩陣的參數即w11,w12...等的值是人爲定義的一系列滿足正態分佈的隨機值。實質上之後你會發現,無論起先w,b的矩陣值如何,通過代價函數的反覆迭代都能讓其收斂到最低點。

2.定義每次訓練的樣本數即確定了輸入層輸入x的個數。

3.人爲定義不同的訓練的次數,學習率,通過觀察準確率的變化,來選擇最適合神經網絡的參數。

 

在前言部分爲大家介紹到,一個神經網絡的每個神經元可以看做一個邏輯迴歸算法模型。

邏輯迴歸的代價函數如下:

                                     \large j(w)=\frac{1}{m}\sum_{i=1}^{m}[-y^{i}(h_{w}(x^{i}))-(1-y^{i})log(1-h_{w}(x^{i}))]

關於邏輯迴歸代價函數的推到,我簡要說明一下:

1.在二分類問題中,對於每個觀察樣本:

                             \large p(x_{i},y_{i})=p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

這個公式很好理解,拆分開來可以這樣表示:

                             \large p(x_{i},y_{i})=\left\{\begin{matrix} p(y_{i}=1|x_{i})& y_{i}=1\\ (1-p(y_{i}=1|x_{i})) &y_{i}=0\end{matrix}\right.

2.對於n個出現的樣本,樣本間相互獨立,則n個出現的概率爲每一個出現的概率的乘積。

                             \large L(w)=\prod p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

3.爲了滿足凸函數求最優解的思想,我們對L(w)取對數,並化簡:

                              \large L(w)=\prod p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

                             \large j(w)=\sum_{i=1}^{n}[ y_{i}log(p(y_{i}=1|x_{i}))+(1-y_{i})log(1-p(y_{i}=1|x_{i}))]

4.由sigmod函數可以推到出如下結果:

                             \large p(y_{i}=1|x_{i})=\frac{1}{1+e^{-z}}               

                             \large 1-p(y_{i}=1|x_{i})=\frac{1}{1+e^{z}}

5.有次推導邏輯迴歸的代價函數:

                          \large j(w)=\frac{1}{m}\sum_{i=1}^{m}[-y^{i}(h_{w}(x^{i}))-(1-y^{i})log(1-h_{w}(x^{i}))]

    對於神經網絡來說,我們可以由上一章節的前向傳播的算法得到最後的輸出,之後我們算出這個輸出與真實值的誤差,然後用同樣的方式反向傳播回去迭代修正之前w,b,v,b_,u,b__矩陣的值。

   而對於每個隱藏層來說,其每個神經元都可以作爲一個邏輯迴歸單元,這樣神經網絡誤差算法的代價函數就可以做出如下表示:

                           \large j(w)=\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{k}[-y_{k}^{i}(h_{w}(x^{i})_{k})-(1-y_{k}^{i})log(1-h_{w}(x^{i})_{k})](k表示神經元的序號)

同理我們可以用邏輯迴歸算法模型的方式來對\large j(w)做梯度下降算法,即找出滿足:

                                                                 \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=0

時,的w和b的值。對\small \frac{\partial j(w)}{\partial w_{ij}^{l}}的求解,可以使用鏈式法則:

                                                                 \large \frac{\partial y}{\partial x}=\frac{\partial y}{\partial u}\frac{\partial u}{\partial x}

                                                               \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=\frac{\partial j(w)}{\partial z_{j}^{l+1}}\frac{\partial z_{j}^{l+1}}{\partial w_{ij}^{l}}

 對於神經元的求和:

                                                               \large z_{j}^{l+1}=\sum_{i=0}^{n}w_{ij}^{l}a_{j}^{l}(這個公式可由前向傳播算法矩陣運算中得出)

所以

         \large \frac{\partial z_{j}^{l+1}}{\partial w_{ij}^{l}}=(\frac{\partial\sum_{i=0}^{n} w_{ij}^{l}a_{j}^{l} }{\partial w_{ij}^{l}})=a_{j}^{l}

假設:

        \bg_white \large \large \delta_{i}^{l+1}=\frac{\partial j(w)}{\partial z_{j}^{l+1}} 

那麼:

         \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=\delta_{i}^{l+1}a_{j}^{l}

 

所以由梯度下降算法可以求得:

                                             \bg_white \large \dpi{120} \large w_{j}:=w_{j}+ \eta \delta_{i}^{l+1}a_{j}^{l}           (\eta爲人爲設定的學習率。)

4.BP反向傳播算法

通過第四節,我們對於神經網絡更新參數w,b有了一定的認識,而在上節最後,原來的更新w,b的問題轉爲了求解\bg_white \large \large \delta_{i}^{l+1}

 用一張動態圖表示前向(FP)和後向(BP)傳播的全過程:

對於輸出層來說:

\begin{align} \delta^{(n_l)}_i = \frac{\partial}{\partial z^{(n_l)}_i} \;\;         \frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2 = - (y_i - a^{(n_l)}_i) \cdot f'(z^{(n_l)}_i) \end{align}

\begin{align} \delta^{(n_l)}_i &= \frac{\partial}{\partial z^{n_l}_i}J(W,b;x,y)  = \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2 \\  &= \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}} (y_j-a_j^{(n_l)})^2  = \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}} (y_j-f(z_j^{(n_l)}))^2 \\  &= - (y_i - f(z_i^{(n_l)})) \cdot f'(z^{(n_l)}_i)  = - (y_i - a^{(n_l)}_i) \cdot f'(z^{(n_l)}_i) \end{align}

 

(用均方誤差來定義樣本的y值和通過算法模型求解出來的y值的距離差距) 

其中

f(z_{j}^{(n_{l})})=sigmod(z)=\frac{1}{1+e^{-z}}

 f^{'}(z_{j}^{(n_{l})})=sigmod(z)*(1-sigmod(z))=\frac{e^{-z}}{(1+e^{-z})^{2}}

對於隱藏層來說:

\delta^{(l)}_i = \left( \sum_{j=1}^{s_{l+1}} W^{(l)}_{ji} \delta^{(l+1)}_j \right) f'(z^{(l)}_i)

\begin{align} \delta^{(n_l-1)}_i &=\frac{\partial}{\partial z^{n_l-1}_i}J(W,b;x,y)  = \frac{\partial}{\partial z^{n_l-1}_i}\frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2   = \frac{\partial}{\partial z^{n_l-1}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}}(y_j-a_j^{(n_l)})^2 \\ &= \frac{1}{2} \sum_{j=1}^{S_{n_l}}\frac{\partial}{\partial z^{n_l-1}_i}(y_j-a_j^{(n_l)})^2  = \frac{1}{2} \sum_{j=1}^{S_{n_l}}\frac{\partial}{\partial z^{n_l-1}_i}(y_j-f(z_j^{(n_l)}))^2 \\ &= \sum_{j=1}^{S_{n_l}}-(y_j-f(z_j^{(n_l)})) \cdot \frac{\partial}{\partial z_i^{(n_l-1)}}f(z_j^{(n_l)})  = \sum_{j=1}^{S_{n_l}}-(y_j-f(z_j^{(n_l)})) \cdot  f'(z_j^{(n_l)}) \cdot \frac{\partial z_j^{(n_l)}}{\partial z_i^{(n_l-1)}} \\ &= \sum_{j=1}^{S_{n_l}} \delta_j^{(n_l)} \cdot \frac{\partial z_j^{(n_l)}}{\partial z_i^{n_l-1}}  = \sum_{j=1}^{S_{n_l}} \left(\delta_j^{(n_l)} \cdot \frac{\partial}{\partial z_i^{n_l-1}}\sum_{k=1}^{S_{n_l-1}}f(z_k^{n_l-1}) \cdot W_{jk}^{n_l-1}\right) \\ &= \sum_{j=1}^{S_{n_l}} \delta_j^{(n_l)} \cdot  W_{ji}^{n_l-1} \cdot f'(z_i^{n_l-1})  = \left(\sum_{j=1}^{S_{n_l}}W_{ji}^{n_l-1}\delta_j^{(n_l)}\right)f'(z_i^{n_l-1}) \end{align}

\large l替換調\large n_{l}-1,用\large l+1替換掉\large n_{l}

\delta^{(l)}_i = \left( \sum_{j=1}^{s_{l+1}} W^{(l)}_{ji} \delta^{(l+1)}_j \right) f'(z^{(l)}_i)

這個式子說明了反向傳播算法的本質,我們先求得輸入層\delta_{i}^{l},然後通過\delta_{i}^{l}不斷後向推到\delta_{i}^{l-1}....\delta_{i}^{1}

通過一輪一輪反覆迭代修正w,b。使得最終的w,b是最能完成分類的參數。

5.編程實現

 

如果需要更爲詳細的編程實現過程,點下面鏈接:

使用Python實現神經網絡

import numpy as np
import random
import os, struct
from array import array as pyarray
from numpy import append, array, int8, uint8, zeros
 
class NeuralNet(object):
 
    # 初始化神經網絡,sizes是神經網絡的層數和每層神經元個數
    def __init__(self, sizes):
        self.sizes_ = sizes
        self.num_layers_ = len(sizes)  # 層數
        self.w_ = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]  # w_、b_初始化爲正態分佈隨機數
        self.b_ = [np.random.randn(y, 1) for y in sizes[1:]]
 
    # Sigmoid函數,S型曲線,
    def sigmoid(self, z):
        return 1.0/(1.0+np.exp(-z))
    # Sigmoid函數的導函數
    def sigmoid_prime(self, z):
        return self.sigmoid(z)*(1-self.sigmoid(z))
 
    def feedforward(self, x):
        for b, w in zip(self.b_, self.w_):
            x = self.sigmoid(np.dot(w, x)+b)
        return x
 
    def backprop(self, x, y):
        nabla_b = [np.zeros(b.shape) for b in self.b_]
        nabla_w = [np.zeros(w.shape) for w in self.w_]
 
        activation = x
        activations = [x]
        zs = []
        for b, w in zip(self.b_, self.w_):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = self.sigmoid(z)
            activations.append(activation)
 
        delta = self.cost_derivative(activations[-1], y) * \
            self.sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
 
        for l in range(2, self.num_layers_):
            z = zs[-l]
            sp = self.sigmoid_prime(z)
            delta = np.dot(self.w_[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)
 
    def update_mini_batch(self, mini_batch, eta):
        nabla_b = [np.zeros(b.shape) for b in self.b_]
        nabla_w = [np.zeros(w.shape) for w in self.w_]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.w_ = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.w_, nabla_w)]
        self.b_ = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.b_, nabla_b)]
 
    # training_data是訓練數據(x, y);epochs是訓練次數;mini_batch_size是每次訓練樣本數;eta是learning rate
    def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None):
        if test_data:
            n_test = len(test_data)
 
        n = len(training_data)
        for j in range(epochs):
            random.shuffle(training_data)
            mini_batches = [training_data[k:k+mini_batch_size] for k in range(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print("Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test))
            else:
                print("Epoch {0} complete".format(j))
 
    def evaluate(self, test_data):
        test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)
 
    def cost_derivative(self, output_activations, y):
        return (output_activations-y)
 
    # 預測
    def predict(self, data):
        value = self.feedforward(data)
        return value.tolist().index(max(value))
 
    # 保存訓練模型
    def save(self):
        pass  # 把_w和_b保存到文件(pickle)
    def load(self):
        pass

打賞一下作者:

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