在前面的几篇文章中试着实现了CNN,RNN的一些组件,这里继续学习LSTM,也是是实现部分组件,旨在学习其LSTM的原理。

具体参考:
https://www.zybuluo.com/hanbingtao/note/581764

LSTM训练算法框架

LSTM的训练算法仍然是反向传播算法,对于这个算法,我们已经非常熟悉了。主要有下面三个步骤:

  • 前向计算每个神经元的输出值,对于LSTM来说,即ft,it,ct,ot,htft,it,ct,ot,htf_t,i_t,c_t,o_t,h_t五个向量的值。计算方法已经在上一节中描述过了。
  • 反向计算每个神经元的误差项δδ\delta值。与循环神经网络一样,LSTM误差项的反向传播也是包括两个方向:一个是沿时间的反向传播,即从当前t时刻开始,计算每个时刻的误差项;一个是将误差项向上一层传播。
  • 根据相应的误差项,计算每个权重的梯度。

下面是代码实现:
activators.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-import numpy as npclass ReluActivator(object):def forward(self, weighted_input):#return weighted_inputreturn max(0, weighted_input)def backward(self, output):return 1 if output > 0 else 0class IdentityActivator(object):def forward(self, weighted_input):return weighted_inputdef backward(self, output):return 1class 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.0def backward(self, output):return 1 - output * output

下面是lstm.py的代码:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-import matplotlib.pyplot as plt
import numpy as np
from activators import SigmoidActivator, TanhActivator, IdentityActivator# element_wise_op函数实现了对numpy数组进行按元素操作,并将返回值写回到数组中
def element_wise_op(array, op):for i in np.nditer(array,op_flags=['readwrite']):i[...] = op(i)# 在构造函数的初始化中,只初始化了与forward计算相关的变量,与backward相关的变量没有初始化。
# 这是因为构造LSTM对象的时候,我们还不知道它未来是用于训练(既有forward又有backward)
# 还是推理(只有forward)
class LstmLayer(object):def __init__(self, input_width, state_width,learning_rate):self.input_width = input_widthself.state_width = state_widthself.learning_rate = learning_rate# 门的激活函数self.gate_activator = SigmoidActivator()# 输出的激活函数self.output_activator = TanhActivator()# 当前时刻初始化为t0self.times = 0       # 各个时刻的单元状态向量cself.c_list = self.init_state_vec()# 各个时刻的输出向量hself.h_list = self.init_state_vec()# 各个时刻的遗忘门fself.f_list = self.init_state_vec()# 各个时刻的输入门iself.i_list = self.init_state_vec()# 各个时刻的输出门oself.o_list = self.init_state_vec()# 各个时刻的即时状态c~self.ct_list = self.init_state_vec()# 遗忘门权重矩阵Wfh, Wfx, 偏置项bfself.Wfh, self.Wfx, self.bf = (self.init_weight_mat())# 输入门权重矩阵Wfh, Wfx, 偏置项bfself.Wih, self.Wix, self.bi = (self.init_weight_mat())# 输出门权重矩阵Wfh, Wfx, 偏置项bfself.Woh, self.Wox, self.bo = ( self.init_weight_mat())# 单元状态权重矩阵Wfh, Wfx, 偏置项bfself.Wch, self.Wcx, self.bc = (self.init_weight_mat())def init_state_vec(self):'''初始化保存状态的向量'''state_vec_list = []state_vec_list.append(np.zeros((self.state_width, 1)))return state_vec_listdef init_weight_mat(self):'''初始化权重矩阵'''Wh = np.random.uniform(-1e-4, 1e-4,(self.state_width, self.state_width))Wx = np.random.uniform(-1e-4, 1e-4,(self.state_width, self.input_width))b = np.zeros((self.state_width, 1))return Wh, Wx, b# forward方法实现了LSTM的前向计算:每次输入不同的向量,其中权重矩阵是相同的。def forward(self, x):'''根据式1-式6进行前向计算'''self.times += 1# 遗忘门fg = self.calc_gate(x, self.Wfx, self.Wfh, self.bf, self.gate_activator)self.f_list.append(fg)# 输入门ig = self.calc_gate(x, self.Wix, self.Wih,self.bi, self.gate_activator)self.i_list.append(ig)# 输出门og = self.calc_gate(x, self.Wox, self.Woh,self.bo, self.gate_activator)self.o_list.append(og)# 即时状态ct = self.calc_gate(x, self.Wcx, self.Wch,self.bc, self.output_activator)self.ct_list.append(ct)# 单元状态c = fg * self.c_list[self.times - 1] + ig * ct # *是对应元素相乘self.c_list.append(c)# 输出h = og * self.output_activator.forward(c)self.h_list.append(h)# 门的计算方法都相同,这里的calc_gate方法可以复用def calc_gate(self, x, Wx, Wh, b, activator):'''计算门'''h = self.h_list[self.times - 1] # 上次的LSTM输出net = np.dot(Wh, h) + np.dot(Wx, x) + bgate = activator.forward(net)return gate# backward方法实现了LSTM的反向传播算法def backward(self, x, delta_h, activator):'''实现LSTM训练算法'''self.calc_delta(delta_h, activator)self.calc_gradient(x)def update(self):'''按照梯度下降,更新权重'''self.Wfh -= self.learning_rate * self.Whf_gradself.Wfx -= self.learning_rate * self.Whx_gradself.bf -= self.learning_rate * self.bf_gradself.Wih -= self.learning_rate * self.Whi_gradself.Wix -= self.learning_rate * self.Whi_gradself.bi -= self.learning_rate * self.bi_gradself.Woh -= self.learning_rate * self.Wof_gradself.Wox -= self.learning_rate * self.Wox_gradself.bo -= self.learning_rate * self.bo_gradself.Wch -= self.learning_rate * self.Wcf_gradself.Wcx -= self.learning_rate * self.Wcx_gradself.bc -= self.learning_rate * self.bc_grad# 计算误差项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_listdef 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,10,11,12计算delta_f、delta_i、delta_o、delta_ctdelta_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))# k-1时刻的delta_hdelta_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_prevself.delta_f_list[k] = delta_fself.delta_i_list[k] = delta_iself.delta_o_list[k] = delta_oself.delta_ct_list[k] = delta_ctdef 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_gradself.bf_grad += bf_gradself.Wih_grad += Wih_gradself.bi_grad += bi_gradself.Woh_grad += Woh_gradself.bo_grad += bo_gradself.Wch_grad += Wch_gradself.bc_grad += bc_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_graddef 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_graddef reset_state(self):# 当前时刻初始化为t0self.times = 0       # 各个时刻的单元状态向量cself.c_list = self.init_state_vec()# 各个时刻的输出向量hself.h_list = self.init_state_vec()# 各个时刻的遗忘门fself.f_list = self.init_state_vec()# 各个时刻的输入门iself.i_list = self.init_state_vec()# 各个时刻的输出门oself.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是作为最后一个时刻的误差项d = np.array([[1], [2]])return x, ddef 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 mapsensitivity_array = np.ones(lstm.h_list[-1].shape,dtype=np.float64)# 计算梯度lstm.backward(x[1], sensitivity_array, IdentityActivator())# 检查梯度epsilon = 10e-4for i in range(lstm.Wfh.shape[0]):for j in range(lstm.Wfh.shape[1]):lstm.Wfh[i,j] += epsilonlstm.reset_state()lstm.forward(x[0])lstm.forward(x[1])err1 = error_function(lstm.h_list[-1])lstm.Wfh[i,j] -= 2*epsilonlstm.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] += epsilonprint 'weights(%d,%d): expected - actural %.4e - %.4e' % (i, j, expect_grad, lstm.Wfh_grad[i,j])return lstmdef test():# input_width, state_width,learning_ratel = LstmLayer(3, 2, 1e-3)x, d = data_set()l.forward(x[0])#print 'Wfx..1:',l.Wfxl.forward(x[1])#print 'Wfx..2:',l.Wfxl.backward(x[1], d, IdentityActivator())return lif __name__ == '__main__':test()gradient_check()

运行结果:

weights(0,0): expected - actural 1.2801e-09 - 1.2802e-09
weights(0,1): expected - actural -2.0909e-09 - -2.0909e-09
weights(1,0): expected - actural -2.0909e-09 - -2.0909e-09
weights(1,1): expected - actural 3.4151e-09 - 3.4150e-09

这里多了一个梯度检查的步骤,原理参考:
https://www.zybuluo.com/hanbingtao/note/476663,这里有其代码原理。

lstm有几点需要注意的地方:

  • 这里仅是组件,不是完整的实现lstm的所有流程,比如仅仅反向传播了关于时间的梯度,也即是只更新了Wfh,Wih,Wch,WohWfh,Wih,Wch,WohW_{fh},W_{ih},W_{ch},W_{oh}的权重,还有Wfx,Wix,Wcx,WoxWfx,Wix,Wcx,WoxW_{fx},W_{ix},W_{cx},W_{ox}也需要更新,这里的代码没有给出。
  • 公式推到中的矩阵元素对应相乘需要注意下,还有文章中的后续对其求全导也需要留意。
  • 这里的运行结果是对所求梯度的验证

总之,通过代码和理论公式推到相结合,对lstm的原理和运行机制有了更进一步的理解,当然这只是最基础的架构,还有其他的很多变体,后面继续努力。

长短时记忆网络(LSTM)部分组件(六)相关推荐

  1. 小常识10: 循环神经网络(RNN)与长短时记忆网络LSTM简介。

    小常识10:  循环神经网络(RNN)与长短时记忆网络LSTM简介. 本文目的:在计算机视觉(CV)中,CNN 通过局部连接/权值共享/池化操作/多层次结构逐层自动的提取特征,适应于处理如图片类的网格 ...

  2. 【深度学习理论】(7) 长短时记忆网络 LSTM

    大家好,今天和各位分享一下长短时记忆网络 LSTM 的原理,并使用 Pytorch 从公式上实现 LSTM 层 上一节介绍了循环神经网络 RNN,感兴趣的可以看一下:https://blog.csdn ...

  3. 深度学习(7) - 长短时记忆网络(LSTM)

    长短时记忆网络是啥 我们首先了解一下长短时记忆网络产生的背景.回顾一下零基础入门深度学习(5) - 循环神经网络中推导的,误差项沿时间反向传播的公式: 我们可以根据下面的不等式,来获取的模的上界(模可 ...

  4. 长短时记忆神经网络python代码_零基础入门深度学习(6) - 长短时记忆网络(LSTM)

    无论即将到来的是大数据时代还是人工智能时代,亦或是传统行业使用人工智能在云上处理大数据的时代,作为一个有理想有追求的程序员,不懂深度学习(Deep Learning)这个超热的技术,会不会感觉马上就o ...

  5. 深度学习之长短时记忆网络(LSTM)

    本文转自<零基础入门深度学习>系列文章,阅读原文请移步这里 之前我们介绍了循环神经网络以及它的训练算法.我们也介绍了循环神经网络很难训练的原因,这导致了它在实际应用中,很难处理长距离的依赖 ...

  6. 长短时记忆网络(LSTM)的训练

    长短时记忆网络的训练 熟悉我们这个系列文章的同学都清楚,训练部分往往比前向计算部分复杂多了.LSTM的前向计算都这么复杂,那么,可想而知,它的训练算法一定是非常非常复杂的.现在只有做几次深呼吸,再一头 ...

  7. 长短时记忆网络LSTM

    网络介绍 长短时记忆网络(Long short time memory network, LSTM)是RNN的重要变体,解决了RNN无法长距离依赖的问题,同时缓了RNN的梯度爆炸问题.LSTM由遗忘门 ...

  8. 深度学习代码实战演示_Tensorflow_卷积神经网络CNN_循环神经网络RNN_长短时记忆网络LSTM_对抗生成网络GAN

    前言 经过大半年断断续续的学习和实践,终于将深度学习的基础知识看完了,虽然还有很多比较深入的内容没有涉及到,但也是感觉收获满满.因为是断断续续的学习做笔记写代码跑实验,所以笔记也零零散散的散落在每个角 ...

  9. 多元经验模态分解_交通运输|基于小波分解和长短时记忆网络的地铁进站量短时预测...

    山东科学 ›› 2019, Vol. 32 ›› Issue (4): 56-63.doi: 10.3976/j.issn.1002-4026.2019.04.008 摘要: 针对城市地铁车站进站客流 ...

最新文章

  1. usaco Prime Palindromes
  2. 声智科技完成2亿元B轮融资,将持续拓展语音交互产品的规模化落地
  3. 分子偶极矩大小如何判断_润滑油粘度大小该如何判断你知道吗?
  4. 马斯克即将解锁海上发射!SpaceX海洋太空港最早明年可投入使用
  5. 树莓派python3.6 tensorflow_raspberry pi 3B 树莓派 安装tensorflow
  6. Ubuntu 13.10 软件更新源列表
  7. MySQL数据库8(二十二)变量
  8. yum 配置详解(转发)
  9. 找出数组中只出现过一次的数
  10. CTF中遇到不知道文件类型_道家文化中你不知道的尖端科学
  11. 图像处理——Haar特征
  12. NetXray使用说明之(6)----捕捉oicq message报文
  13. 微软官方知识:了解 Office 二进制文件格式
  14. Visio中如何自定义图形旋转角度
  15. 什么样的照明灯对眼睛好?分享光线柔和的LED护眼灯
  16. java poi excel 图表_java poi导出带图表的excel表格
  17. 互联网晚报 | 3月16日 星期三 |​ 罗永浩即将重返科技行业;财政部:今年内不扩大房地产税改革试点城市...
  18. HTML和HTML5常用标签
  19. word流程图擦除_word画流程图 Word中绘制流程图的正确姿势,这招大多数人不知道...
  20. autohotkey 常见语法

热门文章

  1. OpenCV背景扣除Background subtraction的实例(附完整代码)
  2. QT的QTableWidget类的使用
  3. QT的QQmlExpression类的使用
  4. C++多态案例二-制作饮品
  5. c++队列queue
  6. c++预处理命令#pragma 用法
  7. C语言与C++优缺点
  8. java重新初始化吗_Java中为何已经重新赋值的变量在输出后会初始化?
  9. Docker私有仓库管理,删除本地仓库中的镜像
  10. mongodb中的副本集搭建实践(转自:http://www.cnblogs.com/visionwang/p/3290435.html)