全連接神經網絡的概念我就不介紹了,對這個不是很了解的朋友,可以移步其他博主的關於神經網絡的文章,這里只介紹我使用基本工具實現全連接神經網絡的方法。
所用工具:
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
到這里,我們已經具備了搭建神經網絡的基本板塊了,我們可以用這些基本模塊來搭建一個簡單的神經網絡模型。下一章,我會繼續詳述一下這個過程。