1. 程式人生 > >全連線神經網路的簡單python實現

全連線神經網路的簡單python實現

        學習深度學習很長一段時間了,平時專案中也使用過caffe,tensorflow等深度學習框架,但一直沒有真正動手實現過。雖然平時專案不建議自己重複造輪子,但作為時下這麼火的深度學習,與它強大無比的功能相比,它的基本原理卻是這麼簡潔,優雅,實現起來也不算多麼複雜。通過簡單實現神經網路,對深刻理解前向傳遞和反向傳遞都大有裨益,同時還能鍛鍊自己的程式碼實踐能力,何樂而不為呢?我之前看過一篇博文,按照原作者的思路,我用python簡單實現了一個toy級別的全連線神經網路,併成功測試了經典的mnist(手寫資料集),下面上程式碼。

啟用層函式的定義

首先是啟用層函式的定義, 先了解下啟用函式的概念。

Sigmoid函式

Sigmoid函式曾被廣泛地應用,但由於其自身的一些缺陷,現在很少被使用了。Sigmoid函式被定義為:

f(x)=11+ex 函式對應的影象是:

優點:
1.Sigmoid函式的輸出對映在(0,1)之間,單調連續,輸出範圍有限,優化穩定,可以用作輸出層。
2.求導容易。很容易求得f'(x) = f(x) * (1 - f(x))

缺點:
1.由於其軟飽和性,容易產生梯度消失,導致訓練出現問題。
2.其輸出並不是以0為中心的。

tanh函式

現在,比起Sigmoid函式我們通常更傾向於tanh函式。tanh函式被定義為

tanh(x)=1e2x1
+e2x
tanh(x)=1−e−2x1+e−2x

函式位於[-1, 1]區間上,對應的影象是:

優點:
1.比Sigmoid函式收斂速度更快,求導簡單。f'(x) = 1 - f(x)* f(x)
2.相比Sigmoid函式,其輸出以0為中心。

缺點:
還是沒有改變Sigmoid函式的最大問題——由於飽和性產生的梯度消失。

import numpy as np
class SigmoidActivator(object):
    def forward(self, weighted_input):
        return 1.0 / (1.0 + np.exp(-weighted_input))
    def backward(self, output):
        return output * (1 - output) 
    
class TanhActivator(object):
    def forward(self, weighted_input):
        return 2.0 / (1.0 + np.exp(-2 * weighted_input)) - 1.0

    def backward(self, output):
        return 1 - output * output

這裡面有我們熟悉的sigmoid和tanh啟用函式實現

全連線層的單層實現


class FullConnectedLayer():
    def __init__(self, input_size, output_size, activator):
        #self.input_size:輸入維度
        #self.output_size:輸出維度
        #self.activator:啟用函式
        self.input_size = input_size
        self.output_size = output_size
        self.activator = activator
        #權值矩陣self.w和偏置self.b
        self.w = np.random.uniform(-0.1, 0.1, (output_size, input_size))
        self.b = np.zeros((output_size, 1))
        
        self.w_grad_total = np.zeros((output_size, input_size))
        self.b_grad_total = np.zeros((output_size, 1))


    def forward(self, input_data):
        self.input_data = input_data
        self.output_data = self.activator.forward(np.dot(self.w, self.input_data) + self.b)


    def backward(self, input_delta):
        #input_delta_為後一層傳入的誤差
        #output_delta為傳入前一層的誤差
        self.sen = input_delta * self.activator.backward(self.output_data)
        output_delta = np.dot(self.w.T, self.sen)
        self.w_grad = np.dot(self.sen, self.input_data.T)
        self.b_grad = self.sen
        self.w_grad_total += self.w_grad
        self.b_grad_total += self.b_grad
        return output_delta




    def update(self, lr,MBGD_mode = 0):
        #梯度下降法進行權值更新,有幾種更新權值的演算法。
        #MBGD_mod==0指SGD模式,即隨機梯度下降
        #MBGD_mod==1指mnni_batch模式,即批量梯度下降, 當選取batch為整個訓練集時,為BGD模式,即批量梯度下降
        if MBGD_mode == 0:
            self.w -= lr * self.w_grad
            self.b -= lr * self.b_grad
        elif MBGD_mode == 1:
            self.w -= lr * self.w_grad_add
            self.b -= lr * self.b_grad_add
            self.w_grad_add = np.zeros((self.output_size, self.input_size))
            self.b_grad_add = np.zeros((self.output_size, 1))

程式碼中的FullConnectedLayer可以看成上圖中黑框裡的內容,這樣再去看程式碼是不是就清晰多了。前向計算forward比較簡單,一目瞭然。反向傳播涉及的公式網上一搜一大把,這裡不再贅述。點選開啟連結

關於梯度下降的區別,大家可以看這篇部落格點選開啟連結

全連線網路的實現

class Network():
    def __init__(self, params_array, activator):
        #params_array為層維度資訊超引數陣列
        #layers為網路的層集合
        self.layers = []
        for i in range(len(params_array) - 1):
            self.layers.append(FullConnectedLayer(params_array[i], params_array[i+1], activator))
      
    #網路前向迭代
    def predict(self, sample):
        #下面一行的output可以理解為輸入層輸出
        output = sample
        for layer in self.layers:
            layer.forward(output)
            output = layer.output_data
        return output
    
    #網路反向迭代
    def calc_gradient(self, label):
        delta = (self.layers[-1].output_data - label)
        for layer in self.layers[::-1]:
            delta = layer.backward(delta)
        return delta
    
    #一次訓練一個樣本 ,然後更新權值          
    def train_one_sample(self, sample, label, lr):
        self.predict(sample)
        Loss = self.loss(self.layers[-1].output_data, label)
        self.calc_gradient(label)
        self.update(lr)
        return Loss
    
    #一次訓練一批樣本 ,然後更新權值  
    def train_batch_sample(self, sample_set, label_set, lr, batch):
        Loss = 0.0
        for i in range(batch):
            self.predict(sample_set[i])
            Loss += self. loss(self.layers[-1].output, label_set[i])
            self.calc_gradient(label_set[i])
        self.update(lr, 1)
        return Loss
    
    def update(self, lr, MBGD_mode = 0):
        for layer in self.layers:
            layer.update(lr, MBGD_mode)
     
    def loss(self, pred, label):
        return 0.5 * ((pred - label) * (pred - label)) .sum()
    
                      
    
    def gradient_check(self, sample, label):
        self.predict(sample)
        self.calc_gradient(label)
        incre = 10e-4
        for layer in self.layers:
            for i in range(layer.w.shape[0]):
                for j in range(layer.w.shape[1]):
                    layer.w[i][j] += incre
                    pred = self.predict(sample)
                    err1 = self.loss(pred, label)
                    layer.w[i][j] -= 2 * incre
                    pred = self.predict(sample)
                    err2 = self.loss(pred, label)
                    layer.w[i][j] += incre
                    pred_grad = (err1 - err2) / (2 * incre) 
                    calc_grad = layer.w_grad[i][j]
                    print 'weights(%d,%d): expected - actural %.4e - %.4e' % (
                        i, j, pred_grad, calc_grad)

簡單測試

if __name__ == '__main__':
    params = [2, 2]      
    activator = SigmoidActivator()          
    net = Network(params, activator)
    
    data = np.array([[0.2], [0.1]])
    label = np.array([[0.3], [0.1]])
    for i in range(100):
        print 'iteration: %d'%i
        loss = net.train_one_sample(data, label, 2)
        print 'loss: %f'%loss
    print 'input: '
    print data
    print 'predict: ' 
    print net.predict(data)
    print 'true: '
    print label
    net.gradient_check(data, label)