循环神经网络RNN的基本组件(五)

RNN的基础知识和公式推导参考:
https://www.zybuluo.com/hanbingtao/note/541458

下面的部分实现代码也是基于上面的文章给出的,通过实现其中的组件来加深对RNN原理的认识,并且可以熟悉代码实现的框架。

这里强调下:循环神经网络的训练

循环神经网络的训练算法:BPTT

BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:

  1. 前向计算每个神经元的输出值;
  2. 反向计算每个神经元的误差项值,它是误差函数E对神经元j的加权输入的偏导数;
  3. 计算每个权重的梯度。

最后再用随机梯度下降算法更新权重。

这里直接贴出代码部分:
activators.py

# -*- coding: UTF-8 -*-

import numpy as np


class ReluActivator(object):
    def forward(self, weighted_input):
        #return weighted_input
        return max(0, weighted_input)

    def backward(self, output):
        return 1 if output > 0 else 0


class IdentityActivator(object):
    def forward(self, weighted_input):
        return weighted_input

    def backward(self, output):
        return 1


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

下面是代码的主要部分:

# -*- coding: UTF-8 -*-

import numpy as np
#from cnn import element_wise_op
from activators import ReluActivator, IdentityActivator


# 对numpy数组进行element wise操作
# element_wise_op函数实现了对numpy数组进行按元素操作,并将返回值写回到数组中
def element_wise_op(array, op):
    for i in np.nditer(array,op_flags=['readwrite']):
        i[...] = op(i)

# 用RecurrentLayer类来实现一个循环层       
class RecurrentLayer(object):
    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       # 当前时刻初始化为t0
        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)
        print 'state_list:\n',self.state_list

    def backward(self, sensitivity_array,activator):
        '''
        实现BPTT算法
        '''
        self.calc_delta(sensitivity_array, activator)
        self.calc_gradient()

    def update(self):
        '''
        按照梯度下降,更新权重
        '''
        self.W -= self.learning_rate * self.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)       
        print 'delta_list:\n',self.delta_list
        # 迭代计算每个时刻的误差项
        for k in range(self.times - 1, 0, -1): # [1]           
            self.calc_delta_k(k, activator)

    def calc_delta_k(self, k, activator):
        '''
        根据k+1时刻的delta计算k时刻的delta
        '''
        state = self.state_list[k+1].copy()
        print 'state_1:\n',state,np.shape(state)
        print 'state_2:\n',state[:,0],np.shape(state[:,0])
        print 'state_3:\n',np.diag(state[:,0]),np.shape(np.diag(state[:,0]))
        element_wise_op(state,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)
        # 实际的梯度是各个时刻梯度之和
        self.gradient = reduce(lambda a, b: a + b, self.gradient_list,self.gradient_list[0]) # [0]被初始化为0且没有被修改过

    def calc_gradient_t(self, t):
        '''
        计算每个时刻t权重的梯度
        '''
        print 'tt..\n',self.delta_list[t],'\n',self.state_list[t-1].T
        gradient = np.dot(self.delta_list[t],self.state_list[t-1].T)
        print 'np.shape(gradient):',np.shape(gradient)
        self.gradient_list[t] = gradient

# 初始化数据
def data_set():
    x = [np.array([[1], [2], [3]]),
         np.array([[2], [3], [4]])]
    d = np.array([[1], [2]])
    return x, d


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())

def test():
    # input_width, state_width,activator, learning_rate
    l = RecurrentLayer(3, 2, ReluActivator(), 1e-3)
    x, d = data_set()
    l.forward(x[0])
    l.forward(x[1])
    l.backward(d, ReluActivator())
    return l

if __name__ == '__main__':
    test()

运行结果:

state_list:
[array([[ 0.],
       [ 0.]]), array([[ 0.       ],
       [ 0.0001993]])]
state_list:
[array([[ 0.],
       [ 0.]]), array([[ 0.       ],
       [ 0.0001993]]), array([[ 0.        ],
       [ 0.00026813]])]
delta_list:
[array([[ 0.],
       [ 0.]]), array([[ 0.],
       [ 0.]]), array([[1],
       [2]])]
state_1:
[[ 0.        ]
 [ 0.00026813]] (2L, 1L)
state_2:
[ 0.          0.00026813] (2L,)
state_3:
[[ 0.          0.        ]
 [ 0.          0.00026813]] (2L, 2L)
tt..
[[1]
 [2]] 
[[ 0.         0.0001993]]
np.shape(gradient): (2L, 2L)
tt..
[[  0.00000000e+00]
 [ -3.08449714e-05]] 
[[ 0.  0.]]
np.shape(gradient): (2L, 2L)

在代码中添加打印输出,可以清楚的看到中间的运行细节。

注意:

  • 这里仅仅是计算了循环层权重矩阵W的梯度更新,没有对误差函数在t时刻对权重矩阵U的梯度进行更新计算,所以这里只是RNN实现过程中的一步。
  • 还有一点就是对作者的代码进行了一点改动
def calc_delta_k(self, k, activator):
        '''
        根据k+1时刻的delta计算k时刻的delta
        '''
        state = self.state_list[k+1].copy()
        element_wise_op(state,activator.backward)
        self.delta_list[k] = np.dot(
                np.dot(self.delta_list[k+1].T, self.W),np.diag(state[:,0])).T

个人认为这里的element_wise_op中的应用在self.state_list[k+1]的拷贝,而不是self.state_list[k+1],这样就和文章中的推导公式一致了。

猜你喜欢

转载自blog.csdn.net/lilong117194/article/details/80302417