零基础入门深度学习(6) - 长短时记忆网络(LSTM)4

规划数据分析助手2019-03-13 14:17:33

点击上方蓝字

关注我们

规划数据分析助手!

专注规划数据分析与可视化方法

零基础入门深度学习(6) - 长短时记忆网络(LSTM)4


反向传播算法的实现


backward方法实现了LSTM的反向传播算法。需要注意的是,与backword相关的内部状态变量是在调用backward方法之后才初始化的。这种延迟初始化的一个好处是,如果LSTM只是用来推理,那么就不需要初始化这些变量,节省了很多内存。


def backward(self, x, delta_h, activator):

       '''

       实现LSTM训练算法

       '''

       self.calc_delta(delta_h, activator)

       self.calc_gradient(x)


算法主要分成两个部分,一部分使计算误差项:

def calc_delta(self, delta_h, activator):

       # 初始化各个时刻的误差项

       self.delta_h_list = self.init_delta()  # 输出误差项

       self.delta_o_list = self.init_delta()  # 输出门误差项

       self.delta_i_list = self.init_delta()  # 输入门误差项

       self.delta_f_list = self.init_delta()  # 遗忘门误差项

       self.delta_ct_list = self.init_delta() # 即时输出误差项

       # 保存从上一层传递下来的当前时刻的误差项

       self.delta_h_list[-1] = delta_h

       # 迭代计算每个时刻的误差项

       for k in range(self.times, 0, -1):

           self.calc_delta_k(k)

   def init_delta(self):

       '''

       初始化误差项

       '''

       delta_list = []

       for i in range(self.times + 1):

           delta_list.append(np.zeros(

               (self.state_width, 1)))

       return delta_list

   def calc_delta_k(self, k):

       '''

       根据k时刻的delta_h,计算k时刻的delta_f、

       delta_i、delta_o、delta_ct,以及k-1时刻的delta_h

       '''

       # 获得k时刻前向计算的值

       ig = self.i_list[k]

       og = self.o_list[k]

       fg = self.f_list[k]

       ct = self.ct_list[k]

       c = self.c_list[k]

       c_prev = self.c_list[k-1]

       tanh_c = self.output_activator.forward(c)

       delta_k = self.delta_h_list[k]

       # 根据式9计算delta_o

       delta_o = (delta_k * tanh_c *

           self.gate_activator.backward(og))

       delta_f = (delta_k * og *

           (1 - tanh_c * tanh_c) * c_prev *

           self.gate_activator.backward(fg))

       delta_i = (delta_k * og *

           (1 - tanh_c * tanh_c) * ct *

           self.gate_activator.backward(ig))

       delta_ct = (delta_k * og *

           (1 - tanh_c * tanh_c) * ig *

           self.output_activator.backward(ct))

       delta_h_prev = (

               np.dot(delta_o.transpose(), self.Woh) +

               np.dot(delta_i.transpose(), self.Wih) +

               np.dot(delta_f.transpose(), self.Wfh) +

               np.dot(delta_ct.transpose(), self.Wch)

           ).transpose()

       # 保存全部delta值

       self.delta_h_list[k-1] = delta_h_prev

       self.delta_f_list[k] = delta_f

       self.delta_i_list[k] = delta_i

       self.delta_o_list[k] = delta_o

       self.delta_ct_list[k] = delta_ct

另一部分是计算梯度:

def calc_gradient(self, x):

       # 初始化遗忘门权重梯度矩阵和偏置项

       self.Wfh_grad, self.Wfx_grad, self.bf_grad = (

           self.init_weight_gradient_mat())

       # 初始化输入门权重梯度矩阵和偏置项

       self.Wih_grad, self.Wix_grad, self.bi_grad = (

           self.init_weight_gradient_mat())

       # 初始化输出门权重梯度矩阵和偏置项

       self.Woh_grad, self.Wox_grad, self.bo_grad = (

           self.init_weight_gradient_mat())

       # 初始化单元状态权重梯度矩阵和偏置项

       self.Wch_grad, self.Wcx_grad, self.bc_grad = (

           self.init_weight_gradient_mat())

      # 计算对上一次输出h的权重梯度

       for t in range(self.times, 0, -1):

           # 计算各个时刻的梯度

           (Wfh_grad, bf_grad,

           Wih_grad, bi_grad,

           Woh_grad, bo_grad,

           Wch_grad, bc_grad) = (

               self.calc_gradient_t(t))

           # 实际梯度是各时刻梯度之和

           self.Wfh_grad += Wfh_grad

           self.bf_grad += bf_grad

           self.Wih_grad += Wih_grad

           self.bi_grad += bi_grad

           self.Woh_grad += Woh_grad

           self.bo_grad += bo_grad

           self.Wch_grad += Wch_grad

           self.bc_grad += bc_grad

           print '-----%d-----' % t

           print Wfh_grad

           print self.Wfh_grad

       # 计算对本次输入x的权重梯度

       xt = x.transpose()

       self.Wfx_grad = np.dot(self.delta_f_list[-1], xt)

       self.Wix_grad = np.dot(self.delta_i_list[-1], xt)

       self.Wox_grad = np.dot(self.delta_o_list[-1], xt)

       self.Wcx_grad = np.dot(self.delta_ct_list[-1], xt)

   def init_weight_gradient_mat(self):

       '''

       初始化权重矩阵

       '''

       Wh_grad = np.zeros((self.state_width,

           self.state_width))

       Wx_grad = np.zeros((self.state_width,

           self.input_width))

       b_grad = np.zeros((self.state_width, 1))

       return Wh_grad, Wx_grad, b_grad

   def calc_gradient_t(self, t):

       '''

       计算每个时刻t权重的梯度

       '''

       h_prev = self.h_list[t-1].transpose()

       Wfh_grad = np.dot(self.delta_f_list[t], h_prev)

       bf_grad = self.delta_f_list[t]

       Wih_grad = np.dot(self.delta_i_list[t], h_prev)

       bi_grad = self.delta_f_list[t]

       Woh_grad = np.dot(self.delta_o_list[t], h_prev)

       bo_grad = self.delta_f_list[t]

       Wch_grad = np.dot(self.delta_ct_list[t], h_prev)

       bc_grad = self.delta_ct_list[t]

       return Wfh_grad, bf_grad, Wih_grad, bi_grad, \

              Woh_grad, bo_grad, Wch_grad, bc_grad

梯度下降算法的实现


下面是用梯度下降算法来更新权重:

def update(self):

       '''

       按照梯度下降,更新权重

       '''

       self.Wfh -= self.learning_rate * self.Whf_grad

       self.Wfx -= self.learning_rate * self.Whx_grad

       self.bf -= self.learning_rate * self.bf_grad

       self.Wih -= self.learning_rate * self.Whi_grad

       self.Wix -= self.learning_rate * self.Whi_grad

       self.bi -= self.learning_rate * self.bi_grad

       self.Woh -= self.learning_rate * self.Wof_grad

       self.Wox -= self.learning_rate * self.Wox_grad

       self.bo -= self.learning_rate * self.bo_grad

       self.Wch -= self.learning_rate * self.Wcf_grad

       self.Wcx -= self.learning_rate * self.Wcx_grad

       self.bc -= self.learning_rate * self.bc_grad


梯度检查的实现


和RecurrentLayer一样,为了支持梯度检查,我们需要支持重置内部状态:


def reset_state(self):

       # 当前时刻初始化为t0

       self.times = 0      

       # 各个时刻的单元状态向量c

       self.c_list = self.init_state_vec()

       # 各个时刻的输出向量h

       self.h_list = self.init_state_vec()

       # 各个时刻的遗忘门f

       self.f_list = self.init_state_vec()

       # 各个时刻的输入门i

       self.i_list = self.init_state_vec()

       # 各个时刻的输出门o

       self.o_list = self.init_state_vec()

       # 各个时刻的即时状态c~

       self.ct_list = self.init_state_vec()


最后,是梯度检查的代码:


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

   lstm = LstmLayer(3, 2, 1e-3)

   # 计算forward值

   x, d = data_set()

   lstm.forward(x[0])

   lstm.forward(x[1])

   # 求取sensitivity map

   sensitivity_array = np.ones(lstm.h_list[-1].shape,

                               dtype=np.float64)

   # 计算梯度

   lstm.backward(x[1], sensitivity_array, IdentityActivator())

   # 检查梯度

   epsilon = 10e-4

   for i in range(lstm.Wfh.shape[0]):

       for j in range(lstm.Wfh.shape[1]):

           lstm.Wfh[i,j] += epsilon

           lstm.reset_state()

           lstm.forward(x[0])

           lstm.forward(x[1])

           err1 = error_function(lstm.h_list[-1])

           lstm.Wfh[i,j] -= 2*epsilon

           lstm.reset_state()

           lstm.forward(x[0])

           lstm.forward(x[1])

           err2 = error_function(lstm.h_list[-1])

           expect_grad = (err1 - err2) / (2 * epsilon)

           lstm.Wfh[i,j] += epsilon

           print 'weights(%d,%d): expected - actural %.4e - %.4e' % (

               i, j, expect_grad, lstm.Wfh_grad[i,j])

return lstm

下图是GRU的示意图:

GRU的训练算法比LSTM简单一些,留给读者自行推导,本文就不再赘述了。


小结


至此,LSTM——也许是结构最复杂的一类神经网络——就讲完了,相信拿下前几篇文章的读者们搞定这篇文章也不在话下吧!现在我们已经了解循环神经网络和它最流行的变体——LSTM,它们都可以用来处理序列。但是,有时候仅仅拥有处理序列的能力还不够,还需要处理比序列更为复杂的结构(比如树结构),这时候就需要用到另外一类网络:递归神经网络(Recursive Neural Network),巧合的是,它的缩写也是RNN。在下一篇文章中,我们将介绍递归神经网络和它的训练算法。现在,漫长的烧脑暂告一段落,休息一下吧:)

规划数据分析助手

微信公众号

DSJGHZNT

专注规划数据分析与可视化方法

方法|案例|交流

记得扫描上方二维码关注我们哟 

点击

阅读原文

了解更多详情