RNN求解过程推导与实现

RNN
LSTM
BPTT
matlab code
opencv code

BPTT,Back Propagation Through Time.

首先来看看怎么处理RNN。

RNN展开网络如下图

RNN展开结构.jpg

RNN节点结构.jpg

现令第t时刻的输入表示为,隐层节点的输出为,输出层的预测值,输入到隐层的权重矩阵,隐层自循环的权重矩阵,隐层到输出层的权重矩阵,对应的偏执向量分别表示为,输入层的某一个节点使用i标识,如,类似的隐层和输出层某一节点表示为。这里我们仅以三层网络为例。

那么首先正向计算

其中分别表示激活前对应的加权和,表示激活函数。

然后看看误差如何传递。

假设真实的输出应该是,那么误差可以定义为,是训练样本的index。整个网络的误差

我们将RNN再放大一些,看看细节

RNN节点内部连接.jpg

矩阵向量化表示

所以梯度为:

其中是点乘符号,即对应元素乘。

代码实现:

我们可以注意到在计算梯度时需要用到的之前计算过的量,即需要保存的量包括,前向计算时隐层节点和输出节点的输出值,以及由时刻累积的

人人都能用Python写出LSTM-RNN的代码![你的神经网络学习最佳起步]这篇文章里使用python实现了基本的RNN过程。代码功能是模拟二进制相加过程中的依次进位过程,代码很容易明白。

这里改写成matlab代码

  1. function error = binaryRNN( ) 

  2. largestNumber=256; 

  3. T=8; 

  4. dic=dec2bin(0:largestNumber-1)-'0';% 将uint8表示成二进制数组,这是一个查找表 

  5. %% 初始化参数 

  6. eta=0.1;% 学习步长 

  7. inputDim=2;% 输入维度 

  8. hiddenDim=16; %隐层节点个数 

  9. outputDim=1; % 输出层节点个数 


  10. W=rand(hiddenDim,outputDim)*2-1;% (-1,1)参数矩阵 

  11. U=rand(hiddenDim,hiddenDim)*2-1;% (-1,1)参数矩阵 

  12. V=rand(inputDim,hiddenDim)*2-1; % (-1,1)参数矩阵 


  13. delta_W=zeros(hiddenDim,outputDim); % 时刻间中间变量 

  14. delta_U=zeros(hiddenDim,hiddenDim); 

  15. delta_V=zeros(inputDim,hiddenDim); 

  16. error=0; 

  17. for p=1:10000 

  18. aInt=randi(largestNumber/2); 

  19. bInt=randi(largestNumber/2); 

  20. a=dic(aInt+1,:); 

  21. b=dic(bInt+1,:); 

  22. cInt=aInt+bInt; 

  23. c=dic(cInt+1,:); 

  24. y=zeros(1,T); 


  25. preh=zeros(1,hiddenDim); 

  26. hDic=zeros(T,hiddenDim); 

  27. %% 前向计算 

  28. for t=T:-1:1 % 注意应该从最低位计算,也就是二进制数组最右端开始计算 

  29. x=[a(t),b(t)];  

  30. h=sigmoid(x*V+preh*U); 

  31. y(t)=sigmoid(h*W);  

  32. hDic(t,:)=h; 

  33. preh=h; 

  34. end 


  35. err=y-c; 

  36. error=error+norm(err,2)/2; 

  37. next_delta_h=zeros(1,hiddenDim); 

  38. %% 反馈 

  39. for t=1:T 

  40. delta_y = err(t).*sigmoidOutput2d(y(t)); 

  41. delta_h=(delta_y*W'+next_delta_h*U').*sigmoidOutput2d(hDic(t,:)); 


  42. delta_W=delta_W+hDic(t,:)'*delta_y; 

  43. if t<T 

  44. delta_U=delta_U+hDic(t+1,:)'*delta_h; 

  45. end 

  46. delta_V=delta_V+[a(t),b(t)]'*delta_h; 

  47. next_delta_h=delta_h;  

  48. end 

  49. % 梯度下降  

  50. W=W-eta*delta_W; 

  51. U=U-eta*delta_U; 

  52. V=V-eta*delta_V; 


  53. delta_W=zeros(hiddenDim,outputDim); 

  54. delta_U=zeros(hiddenDim,hiddenDim); 

  55. delta_V=zeros(inputDim,hiddenDim); 


  56. if mod(p,1000)==0 

  57. fprintf('Samples:%d\n',p); 

  58. fprintf('True:%d\n',cInt); 

  59. fprintf('Predict:%d\n',bin2dec(int2str(round(y)))); 

  60. fprintf('Error:%f\n',norm(err,2)/2); 

  61. end 

  62. end 

  63. end 


  64. function sx=sigmoid(x) 

  65. sx=1./(1+exp(-x)); 

  66. end 


  67. function dx=sigmoidOutput2d(output) 

  68. dx=output.*(1-output); 

  69. end 

为了更深入理解RNN过程,这里我想用OpenCV和C++实现自己的RNN,简单的单隐层网络。同样类似的功能,模拟多个十进制数的加法进位过程。

  1. # include "highgui.h" 

  2. # include "cv.h" 

  3. # include <iostream> 

  4. #include "math.h" 

  5. #include<cstdlib> 

  6. using namespace std; 


  7. # define random(x) ((rand()*rand())%x) //生成0-x的随机数 


  8. void Display(CvMat* mat) 



  9. cout << setiosflags(ios::fixed); 

  10. for (int i = 0; i < mat->rows; i++) 



  11. for (int j = 0; j < mat->cols; j++) 

  12. cout << cvmGet(mat, i, j) << " "; 

  13. cout << endl; 








  14. // sigmoid 函数 

  15. float sigmoid(float x) 



  16. return 1 / (1 + exp(-x)); 



  17. CvMat* sigmoidM(CvMat* mat) 



  18. CvMat*mat2 = cvCloneMat(mat); 


  19. for (int i = 0; i < mat2->rows; i++) 



  20. for (int j = 0; j < mat2->cols; j++) 

  21. cvmSet(mat2, i, j, sigmoid(cvmGet(mat, i, j))); 



  22. return mat2; 



  23. //sigmoid 函数的导数 

  24. float diffSigmoid(float x) 



  25. //注意,这里的x已经是sigmoid的结果 

  26. return x*(1 - x); 



  27. CvMat* diffSigmoidM(CvMat* mat) 



  28. CvMat* mat2 = cvCloneMat(mat); 


  29. for (int i = 0; i < mat2->rows; i++) 



  30. for (int j = 0; j < mat2->cols; j++) 



  31. float t = cvmGet(mat, i, j); 

  32. cvmSet(mat2, i, j, t*(1 - t)); 






  33. return mat2; 






  34. /**************随机生成inputdim个整数,并求和****************** 

  35. * inputdim 整数的个数 

  36. * MAX 整数的最大范围 

  37. * Sample 存放整数 

  38. * 返回 整数和 

  39. **************************************************************/ 

  40. int sample(int inputdim, CvMat* Sample,int MAX) 



  41. int sum = 0; 

  42. for (int i = 0; i < inputdim; i++) 



  43. int t = random(MAX); 

  44. cvmSet(Sample, 0, i, t); 

  45. sum += cvmGet(Sample,0,i); 



  46. return sum; 



  47. /********将整数拆分成10以内的数,作为每个时刻的输入************* 

  48. * Sample 存放的整数 大小 1*inputdim 

  49. * 返回 拆分后的输入数据 大小 inputdim*9 

  50. ****************************************************************/ 

  51. CvMat* splitM( CvMat*Sample) 



  52. CvMat* mat = cvCreateMat(Sample->cols, 8, CV_32F); 

  53. cvSetZero(mat); 

  54. for (int i = 0; i < mat->rows; i++) 



  55. int x = cvmGet(Sample,0,i); 

  56. for (int j = 0; j < 8; ++j) 



  57. cvmSet(mat,i,j, x % 10); 

  58. x = x / 10; 





  59. return mat; 




  60. /***************将数字数组整合成一个整数****************************** 

  61. *mat 数字数组,即每个元素是十以内的整数,大小1*9 

  62. *返回 整合后的整数 

  63. *********************************************************************/ 

  64. int merge(CvMat* mat) 



  65. double d = 0; 

  66. for (int i = mat->cols; i >0; i--) 



  67. d = 10 * d + round(10*(cvmGet(mat,0,i-1))); 




  68. return int(d); 



  69. /*****************将输出的数值拆分************************************** 

  70. * y 输出的数值 

  71. * 返回 长度为9的数组,这里转换成了0,1之间的数 

  72. ***********************************************************************/ 

  73. CvMat* split(int y) 



  74. CvMat* mat = cvCreateMat(1, 8, CV_32F); 

  75. for (int i = 0; i < 8; i++) 



  76. cvmSet(mat,0,i, (y % 10) / 10.0); 

  77. y = y / 10; 



  78. return mat; 





  79. /**********************产生随机矩阵****************************** 

  80. * rows, cols, 矩阵的规模 

  81. * a, b, 区间 

  82. * 返回 返回[a,b]之间的随机矩阵 

  83. *****************************************************************/ 

  84. CvMat*randM(int rows,int cols, float a,float b) 



  85. CvMat* mat = cvCreateMat(rows, cols, CV_32FC1); 

  86. float* ptr; 

  87. for (int i = 0; i < mat->rows; i++) 



  88. for (int j = 0; j < mat->cols; j++) 



  89. cvmSet(mat, i, j, random(1000) / 1000.0*(b - a) + a); 





  90. return mat; 




  91. int main() 



  92. srand(time(NULL)); 

  93. //首先,先定义网络 

  94. int inputdim = 2;//不超过10 

  95. int hiddendim = 16; 

  96. int outputdim = 1; 

  97. float eta = 0.1; 

  98. int MAX = 100000000;//令整数最多八位 

  99. //初始化参数矩阵 

  100. CvMat* V = randM(inputdim, hiddendim,-1,1); 

  101. CvMat* U = randM(hiddendim, hiddendim, -1, 1); 

  102. CvMat* W = randM(hiddendim, outputdim, -1, 1); 

  103. CvMat* bh = randM(1, hiddendim, -1, 1); 

  104. CvMat* by = randM(1, outputdim, -1, 1);//偏置 


  105. CvMat*Sample = cvCreateMat(1, inputdim, CV_32F); 

  106. cvSetZero(Sample); 

  107. CvMat* delta_V = cvCloneMat(V); 

  108. CvMat* delta_U = cvCloneMat(U); 

  109. CvMat* delta_W = cvCloneMat(W); 

  110. CvMat* delta_by = cvCloneMat(by); 

  111. CvMat* delta_bh = cvCloneMat(bh); 


  112. //开始训练,训练集大小10000 

  113. for (int p = 0; p < 20000; p++) 



  114. int sum = sample(inputdim,Sample,MAX); 

  115. CvMat* sampleM = splitM(Sample);//每一行对应着一个整数的拆分,个位在前 

  116. CvMat* d = split(sum);//真实结果拆分,每位存放的是除以10后的小数 

  117. //正向计算 

  118. CvMat* pre_h = cvCreateMat(1, hiddendim, CV_32F); 

  119. cvSetZero(pre_h);//初始化最开始的h_{-1} 

  120. CvMat* y = cvCreateMat(1, 8, CV_32F); 

  121. cvSetZero(y);//定义输出量 

  122. CvMat* h = cvCreateMat(8, hiddendim, CV_32F);//每一行存储一个时刻的隐变量输出 


  123. CvMat* temp1 = cvCreateMat(1, hiddendim, CV_32F); 

  124. CvMat* temp2 = cvCreateMat(1, outputdim, CV_32F); 

  125. CvMat* xt = cvCreateMatHeader(inputdim, 1, CV_32S); 

  126. for (int t = 0; t < 8; t++) 



  127. cvGetCol(sampleM, xt, t);//获取第t时刻输入值 

  128. cvGEMM(xt, V, 1,bh, 1, temp1, CV_GEMM_A_T); 

  129. cvGEMM(pre_h, U, 1, temp1, 1, pre_h);// t时刻隐层输出 

  130. pre_h = sigmoidM(pre_h); 


  131. cvGEMM(pre_h, W, 1, by, 1, temp2); 

  132. float yvalue = sigmoid(cvmGet(temp2, 0, 0)); 

  133. cvmSet(y, 0, t, yvalue);//t时刻的输出 


  134. //保存隐层输出 

  135. for (int j = 0; j < hiddendim; j++) 



  136. cvmSet(h, t, j, cvmGet(pre_h, 0, j)); 





  137. cvReleaseMat(&temp1); 

  138. cvReleaseMat(&temp2); 


  139. //观察代码 

  140. int oy = merge(y); 

  141. CvMat* temp = cvCreateMat(1, 8, CV_32F); 

  142. cvSub(y, d, temp); 

  143. double error = 0.5*cvDotProduct(temp, temp); 

  144. if ((p+1)%1000==0) 



  145. cout << "************************第" << p + 1 << "个样本***********" << endl; 

  146. cout << "真实值:" << sum%MAX << endl; 

  147. cout << "预测值:" << oy << endl; 

  148. cout << "误差:" << error << endl; 



  149. //反向传递误差 

  150. cvSetZero(delta_V); 

  151. cvSetZero(delta_U); 

  152. cvSetZero(delta_W); 

  153. cvSetZero(delta_bh); 

  154. cvSetZero(delta_by); 


  155. CvMat* delta_h = cvCreateMat(1, hiddendim, CV_32F); 

  156. cvSetZero(delta_h); 

  157. CvMat* delta_y = cvCreateMat(1, outputdim, CV_32F); 

  158. cvSetZero(delta_y); 

  159. CvMat* next_delta_h = cvCreateMat(1, hiddendim, CV_32F); 

  160. cvSetZero(next_delta_h); 


  161. for (int t = 7; t > 0; t--) 



  162. cvmSet(delta_y, 0, 0, (cvmGet(y, 0, t) - cvmGet(d, 0, t))*diffSigmoid(cvmGet(y, 0, t))); 

  163. cvGEMM(delta_y, W, 1, delta_h, 0, delta_h, CV_GEMM_B_T); 

  164. cvGEMM(next_delta_h, U, 1, delta_h, 1, delta_h, CV_GEMM_B_T); 

  165. cvMul(delta_h, diffSigmoidM(cvGetRow(h, temp, t)), delta_h); 

  166. //更新delta_y,delta_h 

  167. cvGEMM(cvGetRow(h, temp, t), delta_y, 1, delta_W, 1, delta_W, CV_GEMM_A_T); 

  168. if (t>0) 

  169. cvGEMM(cvGetRow(h, temp, t - 1), delta_h, 1, delta_U, 1, delta_U, CV_GEMM_A_T); 

  170. cvGetCol(sampleM, xt, t); 

  171. cvGEMM(xt, delta_h, 1, delta_V, 1, delta_V); 

  172. cvAddWeighted(delta_by, 1, delta_y, 1, 0, delta_by); 

  173. cvAddWeighted(delta_bh, 1, delta_h, 1, 0, delta_bh); 


  174. cvAddWeighted(delta_h, 1, next_delta_h, 0, 0, next_delta_h); 




  175. cvAddWeighted(W, 1, delta_W, -eta, 0, W); 

  176. cvAddWeighted(V, 1, delta_V, -eta, 0, V); 

  177. cvAddWeighted(U, 1, delta_U, -eta, 0, U); 


  178. cvAddWeighted(by, 1, delta_by, -eta, 0, by); 

  179. cvAddWeighted(bh, 1, delta_bh, -eta, 0, bh); 


  180. cvReleaseMat(&sampleM); 

  181. cvReleaseMat(&d); 

  182. cvReleaseMat(&pre_h); 

  183. cvReleaseMat(&y); 

  184. cvReleaseMat(&h); 

  185. cvReleaseMat(&delta_h); 

  186. cvReleaseMat(&delta_y); 



  187. cvReleaseMat(&U); 

  188. cvReleaseMat(&V); 

  189. cvReleaseMat(&W); 

  190. cvReleaseMat(&by); 

  191. cvReleaseMat(&bh); 

  192. cvReleaseMat(&Sample); 

  193. cvReleaseMat(&delta_V); 

  194. cvReleaseMat(&delta_U); 

  195. cvReleaseMat(&delta_W); 

  196. cvReleaseMat(&delta_by); 

  197. cvReleaseMat(&delta_bh); 

  198. system("PAUSE"); 

  199. return 0; 



下面是代码结果,并没有完全一致。分析下主要原因可能是由于输出层是(0,1)的小数,但我们希望得到的是[0,10]的整数,而通过round或者强制类型转换总会带来较大误差,所以会出现预测值和真实值差别很大,这时候其实比较值的差异意义不大,应该对比每一位上数字的差异。

1479024804302.jpg

再下面是3个输入,32个隐层节点的结果

1479024912622.jpg

PS. 作为opencv新手,觉得matlab半小时搞定的东西,opencv要捣鼓两个小时。。。

RNN求解过程推导与实现的更多相关文章

  1. h.264 mvp求解过程

    h.264标准中由于分为宏块分割块(8x8),子宏块分割块(4x4),所以各种各样的求解过程比较繁琐 下面整理出标准中mvp的求解过程 8.4.1.3 已知条件有当前块的属性:位置.块类型需要得到当前 ...

  2. 深度学习(二)BP求解过程和梯度下降

    一.原理 重点:明白偏导数含义,是该函数在该点的切线,就是变化率,一定要理解变化率. 1)什么是梯度 梯度本意是一个向量(矢量),当某一函数在某点处沿着该方向的方向导数取得该点处的最大值,即函数在该点 ...

  3. 一种3位sar adc工作过程推导(二)

    3位sar adc采用下图的电容阵列,需要23个电容,它的基本单元有二进制加权的电容阵列.1个与LSB电容等值的电容:它利用电容上的初始电荷再分配完成二进制搜索算法,因此功耗一般比较小,而且不需要额外 ...

  4. TSP旅行商问题的Hopfield求解过程

      连续型Hopfield在matlab中没有直接的工具箱,所以我们们根据Hopfield给出的连续行算法自行编写程序.本文中,以求解旅行商 问题来建立Hopfield网络,并得到解,但是该解不一定是 ...

  5. 推荐系统 BPR 算法求解过程

    数据假设: 每个用户之间的偏好行为相互独立 同一用户对不同物品的偏序相互独立 则优化问题为极大化如下目标: [Reference] 1.论文翻译:BPR:面向隐偏好数据的贝叶斯个性化排序学习模型 2. ...

  6. RNN推导

    http://www.cnblogs.com/YiXiaoZhou/p/6058890.html RNN求解过程推导与实现 RNN LSTM BPTT matlab code opencv code ...

  7. EM算法求高斯混合模型參数预计——Python实现

    EM算法一般表述:       当有部分数据缺失或者无法观察到时,EM算法提供了一个高效的迭代程序用来计算这些数据的最大似然预计.在每一步迭代分为两个步骤:期望(Expectation)步骤和最大化( ...

  8. Logistic回归计算过程的推导

    https://blog.csdn.net/ligang_csdn/article/details/53838743 https://blog.csdn.net/weixin_30014549/art ...

  9. 坐标下降法(coordinate descent method)求解LASSO的推导

    坐标下降法(coordinate descent method)求解LASSO推导 LASSO在尖点是singular的,因此传统的梯度下降法.牛顿法等无法使用.常用的求解算法有最小角回归法.coor ...

随机推荐

  1. Open Xml 读取Excel中的图片

      在我的一个项目中,需要分析客户提供的Excel, 读出其中的图片信息(显示在Excel的第几行,第几列,以及图片本身). 网络上有许多使用Open Xml插入图片到Word,Excel的文章, 但 ...

  2. EXCEL 2010学习笔记 —— VLOOKUP函数 嵌套 MATCH 函数

    match index vlookup 等函数都是查找引用类函数,需要查找的时候关键变量只有两个,区域+位置,区域的选择注意是否需要锁定,位置的确定可以通过输入特定的行号和列号. match() ma ...

  3. Ubuntu14.04源

    Ubuntu14.04源:   来源: http://wiki.ubuntu.org.cn/Qref/Source (包含15.04.14.10.14.04.12.04.10.04的源)     Ub ...

  4. JAVA基础学习——1.0 Java概述

    Java语言 SUN公司  1995年推出的高级编程语言 ■  主要应用方向 Web开发和Android开发 ■  主要特点 平台无关性:能运行于不同的平台上    安全性:去掉了指针操作,内存由操作 ...

  5. Fiddler学习笔记

    1. [HTTP]Fiddler(一) - Fiddler简介 Fiddler使用代理(127.0.0.1:8888), 打开Fiddler会自动设置该代理. 2.[HTTP]Fiddler(二) - ...

  6. 父类div高度适应子类div

    父类div高度适应子类div 通常有许多div的高度由子类的高度决定父类的高度,所以需要父类div要适应子类div的高度,一般情况父类的高度可以直接设置成“auto”即可. 在有的情况下,子类div会 ...

  7. google tensorflow guide

    # For CPU-only version $ pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow- ...

  8. Java字节流和字符流区别

    1.字节流:直接操作文件本身. 2.字符流:通过缓冲区来操作文件. 所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节 ...

  9. tomcat虚拟路径

    目的:减小项目大小,方便部署和资源共享 tomcat/conf/server.xml host 标签中添加 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: ...

  10. Web服务器与数据库服务器分离 导入 Excel数据至数据库

    一般情况一般项目WEB服务器与数据库均部署在一台服务器,文件上传,数据导入在一台服务器完成.web服务器与数据库服务器分离,文件上传与数据导入将分布在两台服务器或多台服务器之间.本案例为两台服务器,具 ...