1. 程式人生 > >深度學習筆記——卷積神經網路

深度學習筆記——卷積神經網路

程式碼參考了零基礎入門深度學習(4) - 卷積神經網路這篇文章,我只對程式碼裡可能存在的一些小錯誤進行了更改。至於卷積神經網路的原理以及程式碼裡不清楚的地方可以結合該文章理解,十分淺顯易懂。

import numpy as np
from functools import reduce

from DL.cnn import ReluActivator,IdentityActivator,element_wise_op

class RecurrentLayer():
    def __init__(self,input_width,state_width,activator,learning_rate):
        self.input_width = input_width
        self.state_width = state_width
        self.activator = activator
        self.learning_rate = learning_rate
        self.times = 0  # 當前時刻初始化為0
        self.state_list = []    # 儲存各個時刻的state
        self.state_list.append(np.zeros(state_width,1)) # 初始化s0
        self.U = np.random.uniform(-1e-4,1e-4,(state_width,input_width))    #初始化U
        self.W = np.random.uniform(-1e-4,1e-4,(state_width,state_width))    #初始化W

    def forward(self,input_array):
        '''
        根據式2進行前向計算
        '''
        self.times += 1
        state = (np.dot(self.U,input_array)+np.dot(self.W,self.state_list[-1]))
        element_wise_op(state,self.activator.forward)
        self.state_list.append(state)

    def backward(self,sensitivity_array,activator):
        '''
        實現BPTT演算法
        '''
        self.calc_delta(sensitivity_array,activator)
        self.calc_gradient()

    def calc_delta(self, sensitivity_array, activator):
        # 用來儲存各個時刻的誤差項
        self.delta_list = []
        for i in range(self.times):
            self.delta_list.append(np.zeros(self.state_width,1))
        self.delta_list.append(sensitivity_array)
        # 迭代計算每個時刻的誤差項
        for k in range(self.times-1,0,-1):
            self.calc_delta_k(k,activator)

    def calc_delta_k(self, k, activator):
        '''
        根據t+1時刻的delta計算k時刻的delta
        f’(neti)怎麼成了f(neti)
        '''
        state = self.state_list[k+1].copy()
        element_wise_op(self.state_list[k+1],activator.backward)
        self.delta_list[k] = np.dot(np.dot(self.delta_list[k+1].T,self.W),np.diag(state[:,0])).T

    def calc_gradient(self):
        # 儲存各個時刻的權重梯度
        self.gradient_list = []
        for t in range(self.times+1):
            self.gradient_list.append(np.zeros((self.state_width,self.state_width)))
        for t in range(self.times,0,-1):
            self.calc_gradient_t(t)
        # 實際的梯度是各個時刻的梯度之和
        # [0]被初始化為0且沒有被修改過
        self.gradient = reduce(lambda a,b:a+b,self.gradient_list,self.gradient_list[0])

    def calc_gradient_t(self, t):
        '''
        計算每個時刻t權重的梯度
        '''
        gradient = np.dot(self.delta_list[t],self.state_list[t-1].T)
        self.gradient_list[t] = gradient

    def update(self):
        '''
        按照梯度下降,更新權重
        '''
        self.W -= self.learning_rate*self.gradient

    # 上面的程式碼不包含權重U的更新。這部分實際上和全連線神經網路是一樣的.

    # 迴圈層是一個帶狀態的層,每次forword都會改變迴圈層的內部狀態,這給梯度檢查帶來了麻煩。
    # 因此,我們需要一個reset_state方法,來重置迴圈層的內部狀態。
    def reset_state(self):
        self.times = 0
        self.state_list = []
        self.state_list.append((np.zeros((self.state_width,1))))


def data_set():
    pass


def gradient_check():
    '''
    梯度檢查
    '''
    # 設計一個誤差函式,取所有節點輸出項之和
    error_function = lambda o:o.sum()

    rl = RecurrentLayer(3,2,IdentityActivator(),1e-3)

    # 計算forward值
    x,d = data_set()
    rl.forward(x[0])
    rl.forward(x[1])
    # 求取sensitivity map
    sensitivity_array = np.ones(rl.state_list[-1].shape,dtype=np.float64)
    # 計算梯度
    rl.backward(sensitivity_array,IdentityActivator())
    # 檢查梯度
    epsilon = 1e-4
    for i in range(rl.W.shape[0]):
        for j in range(rl.W.shape[1]):
            rl.W[i][j] += epsilon
            rl.reset_state()
            rl.forward(x[0])
            rl.forward(x[1])
            err1 = error_function(rl.state_list[-1])
            rl.W[i][j] -= 2*epsilon
            rl.reset_state()
            rl.forward(x[0])
            rl.forward(x[1])
            err2 = error_function(rl.state_list[-1])
            expected_grad = (err1-err2)/(2*epsilon)
            rl.W[i][j] += epsilon
            print('weights(%d%d):expected-actural%f-%f'%(i,j,expected_grad,rl.gradient[i][j]))