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. { 

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

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

  12. { 

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

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

  15. cout << endl; 

  16. } 


  17. } 



  18. // sigmoid 函数 

  19. float sigmoid(float x) 

  20. { 

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

  22. } 

  23. CvMat* sigmoidM(CvMat* mat) 

  24. { 

  25. CvMat*mat2 = cvCloneMat(mat); 


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

  27. { 

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

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

  30. } 

  31. return mat2; 

  32. } 

  33. //sigmoid 函数的导数 

  34. float diffSigmoid(float x) 

  35. { 

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

  37. return x*(1 - x); 

  38. } 

  39. CvMat* diffSigmoidM(CvMat* mat) 

  40. { 

  41. CvMat* mat2 = cvCloneMat(mat); 


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

  43. { 

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

  45. { 

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

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

  48. } 


  49. } 

  50. return mat2; 


  51. } 



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

  53. * inputdim 整数的个数 

  54. * MAX 整数的最大范围 

  55. * Sample 存放整数 

  56. * 返回 整数和 

  57. **************************************************************/ 

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

  59. { 

  60. int sum = 0; 

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

  62. { 

  63. int t = random(MAX); 

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

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

  66. } 

  67. return sum; 

  68. } 

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

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

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

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

  73. CvMat* splitM( CvMat*Sample) 

  74. { 

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

  76. cvSetZero(mat); 

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

  78. { 

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

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

  81. { 

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

  83. x = x / 10; 

  84. } 

  85. } 

  86. return mat; 

  87. } 


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

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

  90. *返回 整合后的整数 

  91. *********************************************************************/ 

  92. int merge(CvMat* mat) 

  93. { 

  94. double d = 0; 

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

  96. { 

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


  98. } 

  99. return int(d); 

  100. } 

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

  102. * y 输出的数值 

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

  104. ***********************************************************************/ 

  105. CvMat* split(int y) 

  106. { 

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

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

  109. { 

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

  111. y = y / 10; 

  112. } 

  113. return mat; 


  114. } 


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

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

  117. * a, b, 区间 

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

  119. *****************************************************************/ 

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

  121. { 

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

  123. float* ptr; 

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

  125. { 

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

  127. { 

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

  129. } 

  130. } 

  131. return mat; 

  132. } 


  133. int main() 

  134. { 

  135. srand(time(NULL)); 

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

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

  138. int hiddendim = 16; 

  139. int outputdim = 1; 

  140. float eta = 0.1; 

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

  142. //初始化参数矩阵 

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

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

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

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

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


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

  149. cvSetZero(Sample); 

  150. CvMat* delta_V = cvCloneMat(V); 

  151. CvMat* delta_U = cvCloneMat(U); 

  152. CvMat* delta_W = cvCloneMat(W); 

  153. CvMat* delta_by = cvCloneMat(by); 

  154. CvMat* delta_bh = cvCloneMat(bh); 


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

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

  157. { 

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

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

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

  161. //正向计算 

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

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

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

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

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


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

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

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

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

  171. { 

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

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

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

  175. pre_h = sigmoidM(pre_h); 


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

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

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


  179. //保存隐层输出 

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

  181. { 

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

  183. } 

  184. } 

  185. cvReleaseMat(&temp1); 

  186. cvReleaseMat(&temp2); 


  187. //观察代码 

  188. int oy = merge(y); 

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

  190. cvSub(y, d, temp); 

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

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

  193. { 

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

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

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

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

  198. } 

  199. //反向传递误差 

  200. cvSetZero(delta_V); 

  201. cvSetZero(delta_U); 

  202. cvSetZero(delta_W); 

  203. cvSetZero(delta_bh); 

  204. cvSetZero(delta_by); 


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

  206. cvSetZero(delta_h); 

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

  208. cvSetZero(delta_y); 

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

  210. cvSetZero(next_delta_h); 


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

  212. { 

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

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

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

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

  217. //更新delta_y,delta_h 

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

  219. if (t>0) 

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

  221. cvGetCol(sampleM, xt, t); 

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

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

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


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


  226. } 

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

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

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


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

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


  232. cvReleaseMat(&sampleM); 

  233. cvReleaseMat(&d); 

  234. cvReleaseMat(&pre_h); 

  235. cvReleaseMat(&y); 

  236. cvReleaseMat(&h); 

  237. cvReleaseMat(&delta_h); 

  238. cvReleaseMat(&delta_y); 

  239. } 

  240. cvReleaseMat(&U); 

  241. cvReleaseMat(&V); 

  242. cvReleaseMat(&W); 

  243. cvReleaseMat(&by); 

  244. cvReleaseMat(&bh); 

  245. cvReleaseMat(&Sample); 

  246. cvReleaseMat(&delta_V); 

  247. cvReleaseMat(&delta_U); 

  248. cvReleaseMat(&delta_W); 

  249. cvReleaseMat(&delta_by); 

  250. cvReleaseMat(&delta_bh); 

  251. system("PAUSE"); 

  252. return 0; 

  253. } 

下面是代码结果,并没有完全一致。分析下主要原因可能是由于输出层是(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. Web获取客户端物理MAC地址(ocx插件)

    主要是通过ActiveX控件 从本地获取到MAC地址,传入到浏览器打开的网页中,再提交到服务器. 具体详解与步骤看文档中: 文件实例包下载 DotNetFX 文件夹附件文件:(可能安装时需用) dot ...

  2. 几个常用的Linux命令

    最近在学习Linux,记录了几个命令如下: 首先认识:关机命令,我喜欢用的是:shutdown -h now ,当然还有init 0等命令(用自己习惯的);重启命令:reboot;注销命令:logou ...

  3. oracle dump数据库

    最近正在看老白的<DBA的思想天空>,了解数据块结构,想通过dump data block验证oracle对于行尾的NULL,是不占用存储空间的. 我们先来看一下怎样dump数据块: 1. ...

  4. RubyOnRails local_assigns

    http://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns Returns a hash wi ...

  5. Interleaving String

    https://leetcode.com/problems/interleaving-string/ Given s1, s2, s3, find whether s3 is formed by th ...

  6. python note

    =和C一样,为赋值.==为判断,等于.但是,在python中是不支持行内赋值的,所以,这样避免了在判断的时候少写一个出错. dictionary 的key唯一,值可以为很多类型. list的exten ...

  7. linux文件对比命令——diff

    diff用于比较文件或目录内容,特别是比较两个版本不同的文件以找到改动的地方. 如果指定比较的是文件,则只有当输入为文本文件时才有效,以逐行的方式,比较文本文件的异同处. 如果指定比较的是目录的的时候 ...

  8. tengine/nginx-tomcat动静分离遇到的问题

    小站安装好tengine后,接下来的工作就是要配置好tengine让其和后端的tomcat正常的连接工作起来,tengine的配置文件本身比较简单,网上有大量的相关介绍说明文档,我这里只是摘出我配置过 ...

  9. Application package 'AndroidManifest.xml' must have a minimum of 2 segments.

    看了源码就是packagename里面必须包含一个. 源码在: ./sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/id ...

  10. $compile

    <html ng-app="compile"> <head> <script src="http://apps.bdimg.com/libs ...