如何使用numpy實現一個全連接神經網絡?(上)


  全連接神經網絡的概念我就不介紹了,對這個不是很了解的朋友,可以移步其他博主的關於神經網絡的文章,這里只介紹我使用基本工具實現全連接神經網絡的方法。

  所用工具:

    numpy == 1.16.4

    matplotlib 最新版

  我的思路是定義一個layer類,在這個類里邊構建傳播的前向傳播的邏輯,以及反向傳播的邏輯,然后在構建一個model類,在model類里邊,將layer類中的對象拼接,就可以得到我們想要的模型。

  在Layers類的傳播中,在Dense層中,我是按照公式output = X*w+b,來計算輸出。X 是 (m,n)的矩陣,表示有m行數據,每一個數據是n維的向量。w是一個(n,1)的矩陣,是我們要優化的參數。b是一個(m,1)的矩陣,是偏置。在Activation層中,我是按照公式 output = f激活(X) 來計算輸出的。f激活是激活函數,是逐元素函數。將Dense層與Activation層疊加,就能實現output = f激活(X*w+b)的效果,如果多次交替疊加,就相當於在計算output = f激活( f激活(f激活(X*w+b)*w+b)*w+b),這里只演示了三層,實際上這個就是全連接神經網絡的基本數學表達式。

  構建這個模型的難點在於梯度的計算以及反向傳播邏輯。中間層的每一層的輸出對輸入以及偏置求導,都是一個矩陣對另一個矩陣的求導。而矩陣的求導不同於高數中所學的導數,鏈式法則也有一些不同。關於這部分內容可參考:矩陣求導術(上)矩陣求導術(下),這里不再講述。筆者正是在參考了這兩篇文章的前提下實現這個過程的。

  導入工具包:

  

import numpy as np
import matplotlib.pyplot as plt

 

  定義Layer類中的Dense類中類:(這里可以把layers類單獨拿出來作為一個父類,其余的層可以繼承layers,然后釘子自己的反向傳播邏輯,可以減少重復代碼,這里為了方便展示,沒有那么做)

class Layers:
    class Dense:
        '''
        全連接層
        '''

        def __init__(self, output_category):
            '''
            接收並初始化一個輸出維度,用於確定這一層w的維度,以及用於梯度計算
            :param output_category:
            '''
            self.output_category = output_category

        def __call__(self, Input):
            '''
            使用魔法方法,實例化對象后,隨機的方式初始化w參數,
            實例化輸入數據,計算本層前向傳播方式
            :param Input:
            :return:
            '''
            w_shape = (Input.shape[1], self.output_category)
            b_shape = (Input.shape[0], self.output_category)
            self.w = np.mat(np.random.random(size=w_shape))
            self.b = np.mat(np.random.random(size=b_shape))
            self.Input = Input
            result = self.Input * self.w +self.b
            self.result = result
            return result

        def backpropagation(self, w_grad_from_next_layer=None, learning_rate=None, use_bias=False):
            '''
            反向傳播算法的數學描述,公式參考
            https://zhuanlan.zhihu.com/p/24863977
            公式 Y = X * w +b ,
            dY = dX * w + X * dw + db
               = I * dX * w + X * dw * I + I * db * I   (I是單位矩陣,
               公式里每個I都不一樣維度,具體是多少要參考它與誰相乘)
            vec(dY) = np.kron(w,I)*vec(dX)  + np.kron(I,w)*vec(dw) + np.kron(I,I)*vec(db)
            :param w_grad_from_next_layer:從下一層傳過來的梯度
            :param learning_rate:學習率
            :return:
            '''
            mid_w_grad = np.mat(np.kron(np.eye(self.output_category), self.Input))
            self.w_grad = w_grad_from_next_layer * mid_w_grad


            mid_x_grad = np.kron(self.w.T, np.eye(self.Input.shape[0]))
            self.x_grad = w_grad_from_next_layer * mid_x_grad

            if use_bias == True:
                mid_b_grad = np.kron(np.eye(self.output_category), np.eye(self.Input.shape[0]))
                self.b_grad = w_grad_from_next_layer * mid_b_grad

            if learning_rate is not None:
                self.w = self.w - learning_rate * self.w_grad.T
                if use_bias == True:
                    self.b = self.b - learning_rate * self.b_grad
            return self.x_grad

 

  定義Layers 中的Activation類:(這里初始化的時候必須傳入一個激活函數,這個激活函數應該是一個類,應該有一個call方法來定義它的前向邏輯,應該有一個grad方法來計算梯度)

class Layers:
  class Activation:
        '''
        激活層
        '''
        def __init__(self, activate_func):
            '''
            傳入激活方程類
            :param output_category:
            '''
            self.activate_func = activate_func

        def __call__(self, Input):
            '''
            使用魔法方法,實例化對象后,隨機的方式初始化w參數,
            實例化輸入數據,計算本層前向傳播方式
            :param Input:
            :return:
            '''
            self.Input = Input
       self.activate_func_obj = self.activate_func(self.Input) result
= self.activate_func_obj.call() self.result = result return result def backpropagation(self, w_grad_from_next_layer=None, learning_rate=None, ): ''' 反向傳播算法的數學描述,公式參考 https://zhuanlan.zhihu.com/p/24863977 公式 Y = f(X) (f是逐元素函數) dY = df(X) = f'(X) ⊙ dX (⊙表示出逐元素相乘,也就是通縮意義上的對應位置相乘) 所以 vec(dY) = np.diagflat(f'(X))* vec(dX) :param w_grad_from_next_layer:從下一層傳過來的梯度 :param learning_rate:學習率 :return: ''' mid_x_grad = self.activate_func_obj.grad() self.x_grad = w_grad_from_next_layer * mid_x_grad return self.x_grad

  定義了Activation后,但這個方程需要傳入一個激活函數類,現在我們順從定義一個函數類,用來給激活層提供激活函數類。這個類有兩個功能,一是定義正向傳播方法,一種是定義梯度計算。

class Funcs:
    '''
    方程
    '''
    class Relu:
        '''
        Relu函數
        '''

        def __init__(self,Input):
            self.Input = Input
            pass

        def call(self):
            '''
            計算結果,並返回
            :return:
            '''

            result = np.multiply((self.Input>0),self.Input)
            return result

        def grad(self):
            '''
            梯度計算
            :return:
            '''

            mid_grad = (self.Input>0)*1.0 #乘1 講bollen類型轉化為float類型
            result = np.diagflat(mid_grad)
            return result

  類似的,我們可以定義更多的種類的激活函數,比如selu,sigmoid等等。到目前為止,如果我們可以定義一個常用的損失函數,那么我們就具備了搭建簡單的神經網絡的基本要求了。這里我們的定義一個平方差損失函數。

class Funcs:
    '''
    方程
    '''

    class SqureLossError:
        '''
        平方誤差類
        '''
        def __init__(self,y,pred):
            self.y=y
            self.pred = pred

        def call(self):
            '''
            計算損失,並返回
            :return:
            '''
            result = np.linalg.norm(self.y-self.pred)
            return result

        def grad(self):
            '''
            梯度計算
            :return:
            '''
            result = np.mat(2*(self.pred-self.y).T)
            return result

  到這里,我們已經具備了搭建神經網絡的基本板塊了,我們可以用這些基本模塊來搭建一個簡單的神經網絡模型。下一章,我會繼續詳述一下這個過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM