原理就是這么簡(jiǎn)單 用Python搭建神經(jīng)網(wǎng)絡(luò)

Date: 11/22/2018

不做調(diào)包俠!

之前我在網(wǎng)易云課堂與稀牛學(xué)院的深度學(xué)習(xí)公開課上講述了“不調(diào)包,僅用Python如何四步搭建神經(jīng)網(wǎng)絡(luò)”。
我發(fā)現(xiàn)各位小伙伴們對(duì)原理的渴求非常強(qiáng)烈!說明大家在“不做調(diào)包俠”上非常重視,我也非常開心。
這篇文章也是“原理就是這么簡(jiǎn)單”的系列文章之一,希望通過文章的方式將原理描述的更加豐滿!幫助更多想學(xué)習(xí)深度學(xué)習(xí)的朋友!

對(duì)比Sklearn中的線性回歸

學(xué)過機(jī)器學(xué)習(xí)的朋友都知道Sklearn是非常重要的工具包,很多復(fù)雜的機(jī)器學(xué)習(xí)模型也許用sklearn幾行代碼就能搞定,
舉個(gè)例子比如線性回歸,我們可以構(gòu)建如下的線性回歸對(duì)象訓(xùn)練和預(yù)測(cè)一氣呵成:

linreg = LinearRegression()
linreg.fit(train_features,train_targets) 
y_pred = linreg.predict(val_features)

發(fā)現(xiàn)使用非常簡(jiǎn)單,僅僅三行代碼就能得到線性回歸的結(jié)果(y_pred
如果使用的案例是波士頓房價(jià)的數(shù)據(jù)集,那相應(yīng)可以得到如下的預(yù)測(cè)結(jié)果:

Boston_Housing_predict.PNG

預(yù)測(cè)結(jié)果還不錯(cuò)。

那如何構(gòu)建一個(gè)類似Skleran.LinearRegression 的神經(jīng)網(wǎng)絡(luò)工具包呢?
接下來我就要將數(shù)學(xué)原理轉(zhuǎn)換為代碼。

Python搭建神經(jīng)網(wǎng)絡(luò)完成回歸工作

終于到了本篇文章的重點(diǎn),我會(huì)用數(shù)學(xué)原理和python代碼詳細(xì)講解如何構(gòu)建如下結(jié)構(gòu)的神經(jīng)網(wǎng)絡(luò):


nn.png

介紹網(wǎng)絡(luò)

大家可以看到這個(gè)神經(jīng)網(wǎng)絡(luò)分別有

  • Input Layer
  • Hidden Layer
  • Output Layer

這個(gè)是這個(gè)網(wǎng)絡(luò)的基本結(jié)構(gòu),在進(jìn)行下面的講解之前需要在這里聲明一下,

既然是Layer 層 , 都會(huì)有這個(gè)層本身輸入和輸出,就好比一個(gè)凈水過濾器,傳進(jìn)去的是臟水,傳出來的是凈水,那么神經(jīng)網(wǎng)絡(luò)的各個(gè)層也有這個(gè)性質(zhì)。

1, Input Layer 值得注意的地方是Input Layer 只有輸出,傳入的就是數(shù)據(jù)特征,如果使用的是預(yù)測(cè)房價(jià)的案例傳入的就是(面積,朝向,地段,etc..)。
公式中會(huì)表示成X

2, Hidden Layer 有輸入和輸出兩個(gè)概念了,就好比凈水器。那么針對(duì)Hidden Layer凈水器的過濾網(wǎng)是什么呢? 其實(shí)就是激活函數(shù)(這個(gè)后面我會(huì)詳細(xì)講解)。
公式中會(huì)表示成h_input/h_out

3, Output Layer 也有輸入和輸出兩個(gè)概念,只不過這次我搭建的是處理回歸模型的網(wǎng)絡(luò),所以其輸入和輸出是一樣的值。
公式中會(huì)表示成O_input/O_out

4, 鏈接層與層之間的是權(quán)重矩陣。
公式中會(huì)表示成W_i_h/ W_h_o

BP算法數(shù)學(xué)原理和代碼實(shí)現(xiàn)

其實(shí) BP 算法的全名為"Error BackPropagation 誤差反向傳播"
但是其實(shí)BP算法的流程分為三個(gè)部分:
一為前向傳播求誤差
二為反向傳播求梯度
三為通過梯度更新權(quán)重

可能大家會(huì)對(duì)這三個(gè)部分晦澀難懂,其實(shí)我舉個(gè)不恰當(dāng)?shù)睦幽憔湍苊靼住?br> 比如小明想去學(xué)習(xí)自由泳,他沒有教練,不過好在有一個(gè)泳池他可以無限次的在里面嘗試。
然后小明開始自學(xué):

  • 步驟一,他腦子里想了A,B,C 三套動(dòng)作準(zhǔn)備在水中嘗試,然后直接跳入水中瞎撲騰,看看到底是落水還是前進(jìn)。
  • 步驟二,在水中他認(rèn)真體會(huì)各種動(dòng)作對(duì)于游泳的影響,發(fā)現(xiàn)使用C動(dòng)作他還能撲騰一會(huì),但是使用A,B動(dòng)作,瞬間落水。
  • 步驟三,他上了岸,然后仔細(xì)總結(jié)剛才的C動(dòng)作并加以改進(jìn),并且嘗試去除自己的A,B動(dòng)作。

然后再次跳入水中。

經(jīng)過n 次的刻苦訓(xùn)練,小明練成了自由泳。

其實(shí)這個(gè)流程就好比BP算法的流程。
步驟一就像前向傳播,帶著一些不靠譜的動(dòng)作相當(dāng)于初始權(quán)重矩陣
步驟二就像于反向傳播,仔細(xì)體驗(yàn)?zāi)膫€(gè)動(dòng)作更加有效這相當(dāng)于求權(quán)重的梯度
步驟三就像于梯度更新,更新自己的動(dòng)作相當(dāng)于更新權(quán)重矩陣

下面我就要仔細(xì)用數(shù)學(xué)推導(dǎo)BP算法:
首先來學(xué)習(xí)一下數(shù)學(xué)基礎(chǔ):


base.JPG

Loss function 不用過多解釋,了解機(jī)器學(xué)習(xí)的都知道這是MSE。
Sigmoid 就是剛才提到的過濾器也就是激活函數(shù)。

前向傳播:

前向傳播.JPG

代碼如下:
代碼中final_outputs 就是 O_out

hidden_inputs = np.dot(X,self.input_hidden_weight)
hidden_outputs = self.sigmoid(hidden_inputs)
final_inputs = np.dot(hidden_outputs,self.hidden_output_weight)
final_outputs = final_inputs

反向傳播:
基于鏈?zhǔn)角髮?dǎo)法則如下:


反向傳播.JPG

關(guān)注O_input_error_term, h_input_error_term
因?yàn)檫@兩項(xiàng)是關(guān)于權(quán)重矩陣W_i_h和W_h_o的函數(shù)。
代碼如下:

final_output_error = y-final_outputs
final_input_error_term = final_output_error*1
hidden_output_error = np.dot(self.hidden_output_weight, final_input_error_term)
hidden_input_error_term = hidden_output_error*hidden_outputs*(1-hidden_outputs)

梯度更新:

梯度更新.JPG

代碼如下:
代碼中update_x_x 就是權(quán)重的梯度

update_i_h += np.dot(X_item,hidden_error_term.T)
update_h_o += np.dot(hidden_outputs,output_error_term.T)

到了這里你已經(jīng)學(xué)會(huì)了BP算法的精華了,但是如果想構(gòu)造一個(gè)可以使用的神經(jīng)網(wǎng)絡(luò)還差一點(diǎn)點(diǎn),是什么呢?
下面我會(huì)根據(jù)代碼給你講解。

Python 四步構(gòu)造NeuralNetwork

想要構(gòu)造一個(gè)類似Sklearn的方便使用的神經(jīng)網(wǎng)絡(luò),我們需要四步

Step1:
初始化:

  • 構(gòu)造sigmoid 激活函數(shù)
  • 構(gòu)造mse 用于求損失
  • 設(shè)置學(xué)習(xí)率,以及網(wǎng)絡(luò)節(jié)點(diǎn)
  • 初始化兩個(gè)權(quán)重矩陣

Step2

  • 實(shí)現(xiàn)前向傳播
  • 實(shí)現(xiàn)predect函數(shù)基于前向傳播

Step3

  • 實(shí)現(xiàn)反向傳播

整個(gè)代碼如下:
···
class NeuralNetwork(object):
def init(self, input_units, hidden_units = 4, output_units = 1, learning_rate = 0.01):

    self.sigmoid = lambda x : 1/(1+np.exp(-x))
    self.mean_squared_error = lambda y_true, y_pred: np.mean((y_true-y_pred)**2)
    
    self.input_units = input_units
    self.hidden_units = hidden_units
    self.output_units = output_units
    
    self.learning_rate = learning_rate
    
    np.random.seed(1119)
    
    self.input_hidden_weight = np.random.randn(self.input_units,self.hidden_units)

    self.hidden_output_weight = np.random.randn(self.hidden_units,self.output_units)

def __forward__(self, X):
    ''' forward pass through the neural network with X 
    
        Arguments
        ---------
        X: 2D array
        features
        
        Returns
        -------
        hidden_outputs: 1D array 
        final_outputs: 1D array 
        
    '''
    hidden_inputs = np.dot(X,self.input_hidden_weight)
    
    hidden_outputs = self.sigmoid(hidden_inputs)
    
    final_inputs = np.dot(hidden_outputs,self.hidden_output_weight)
    
    final_outputs = final_inputs
    
    return hidden_outputs,final_outputs

def __backward__(self, y, hidden_outputs, final_outputs):
    ''' backward pass through the neural network with X 
    
        Arguments
        ---------
        X: 2D array
        features
        
        Returns
        -------
        hidden_input_error_term: 1D array 
        final_input_error_term: 1D array 
        
    '''
    final_output_error = y-final_outputs
    
    final_input_error_term = final_output_error*1
        
    hidden_output_error = np.dot(self.hidden_output_weight, final_input_error_term)

    hidden_input_error_term = hidden_output_error*hidden_outputs*(1-hidden_outputs)
    
    return hidden_input_error_term,final_input_error_term

def fit(self, X, y):
    ''' fit(X, y) method of NeuralNetwork
    
        Parameters
        ----------
        X : 2D array
        Training data

        y : 1D array
        Target values
        
        Returns
        -------
        void
    
    '''
    n_records = X.shape[0]
    for X_item, y_item in zip(X, y):
        hidden_outputs, final_outputs = self.__forward__(X_item)
        hidden_error_term, output_error_term = self.__backward__(y_item,hidden_outputs,final_outputs)
        update_i_h += np.dot(X_item,hidden_error_term.T)
        update_h_o += np.dot(hidden_outputs,output_error_term.T)

    self.hidden_output_weight += self.learning_rate * (update_h_o / n_records)
    self.input_hidden_weight += self.learning_rate * (update_i_h / n_records)
    
 
def predict(self, X):
    ''' predict(X) method of NeuralNetwork
    
        Arguments
        ---------
        X: 2D array
        features
        
        Returns
        -------
        outputs: 1D array 
        predicted values
    '''
    hidden_outputs, final_outputs = self.__forward__(X)
    
    return final_outputs

總結(jié)

對(duì)比了一下自己構(gòu)造的神經(jīng)網(wǎng)絡(luò)和線性回歸,預(yù)測(cè)波士頓房價(jià)的案例可以得到如下結(jié)果:


線性回歸VS神經(jīng)網(wǎng)絡(luò).PNG

可以明顯看出神經(jīng)網(wǎng)絡(luò)的性能更好一些。

并且神經(jīng)網(wǎng)絡(luò)可以做回歸和分類兩種任務(wù),只要將Output layer 加一個(gè)Sigmoid 即可實(shí)現(xiàn)二分類,加一個(gè)Softmax 又可以實(shí)現(xiàn)多分類(可參考另一篇 原理就是這么簡(jiǎn)單 Softmax 分類)

理論上來說神經(jīng)網(wǎng)絡(luò),有足夠都的數(shù)據(jù)有足夠多的層數(shù)和節(jié)點(diǎn)數(shù),可以擬合任何函數(shù),這也是神經(jīng)網(wǎng)絡(luò)的強(qiáng)大之處。
也是如今的AI 時(shí)代深度學(xué)習(xí)作為爆發(fā)技術(shù)的主要原因。

你也可以自己嘗試實(shí)現(xiàn),如果有問題,歡迎給我留言

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容