RNN求解过程推导与实现
RNN求解过程推导与实现
BPTT,Back Propagation Through Time.
首先来看看怎么处理RNN。
RNN展开网络如下图


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

其中 分别表示激活前对应的加权和,
分别表示激活前对应的加权和, 表示激活函数。
表示激活函数。
然后看看误差如何传递。
假设真实的输出应该是 ,那么误差可以定义为
,那么误差可以定义为 ,
, 是训练样本的index。整个网络的误差
是训练样本的index。整个网络的误差
我们将RNN再放大一些,看看细节

令 则
则

矩阵向量化表示

所以梯度为:

其中 是点乘符号,即对应元素乘。
是点乘符号,即对应元素乘。
代码实现:
我们可以注意到在计算梯度时需要用到的之前计算过的量,即需要保存的量包括,前向计算时隐层节点和输出节点的输出值,以及由 时刻累积的
时刻累积的 。
。
人人都能用Python写出LSTM-RNN的代码![你的神经网络学习最佳起步]这篇文章里使用python实现了基本的RNN过程。代码功能是模拟二进制相加过程中的依次进位过程,代码很容易明白。
这里改写成matlab代码
- function error = binaryRNN( ) 
 
- largestNumber=256; 
 
- T=8; 
 
- dic=dec2bin(0:largestNumber-1)-'0';% 将uint8表示成二进制数组,这是一个查找表 
 
- %% 初始化参数 
 
- eta=0.1;% 学习步长 
 
- inputDim=2;% 输入维度 
 
- hiddenDim=16; %隐层节点个数 
 
- outputDim=1; % 输出层节点个数 
 
- W=rand(hiddenDim,outputDim)*2-1;% (-1,1)参数矩阵 
 
- U=rand(hiddenDim,hiddenDim)*2-1;% (-1,1)参数矩阵 
 
- V=rand(inputDim,hiddenDim)*2-1; % (-1,1)参数矩阵 
 
- delta_W=zeros(hiddenDim,outputDim); % 时刻间中间变量 
 
- delta_U=zeros(hiddenDim,hiddenDim); 
 
- delta_V=zeros(inputDim,hiddenDim); 
 
- error=0; 
 
- for p=1:10000 
 
- aInt=randi(largestNumber/2); 
 
- bInt=randi(largestNumber/2); 
 
- a=dic(aInt+1,:); 
 
- b=dic(bInt+1,:); 
 
- cInt=aInt+bInt; 
 
- c=dic(cInt+1,:); 
 
- y=zeros(1,T); 
 
- preh=zeros(1,hiddenDim); 
 
- hDic=zeros(T,hiddenDim); 
 
- %% 前向计算 
 
- for t=T:-1:1 % 注意应该从最低位计算,也就是二进制数组最右端开始计算 
 
- x=[a(t),b(t)];        
 
- h=sigmoid(x*V+preh*U); 
 
- y(t)=sigmoid(h*W);    
 
- hDic(t,:)=h; 
 
- preh=h; 
 
- end 
 
- err=y-c; 
 
- error=error+norm(err,2)/2; 
 
- next_delta_h=zeros(1,hiddenDim); 
 
- %% 反馈 
 
- for t=1:T 
 
- delta_y = err(t).*sigmoidOutput2d(y(t)); 
 
- delta_h=(delta_y*W'+next_delta_h*U').*sigmoidOutput2d(hDic(t,:)); 
 
- delta_W=delta_W+hDic(t,:)'*delta_y; 
 
- if t<T 
 
- delta_U=delta_U+hDic(t+1,:)'*delta_h; 
 
- end 
 
- delta_V=delta_V+[a(t),b(t)]'*delta_h; 
 
- next_delta_h=delta_h;          
 
- end 
 
- % 梯度下降    
 
- W=W-eta*delta_W; 
 
- U=U-eta*delta_U; 
 
- V=V-eta*delta_V; 
 
- delta_W=zeros(hiddenDim,outputDim); 
 
- delta_U=zeros(hiddenDim,hiddenDim); 
 
- delta_V=zeros(inputDim,hiddenDim); 
 
- if mod(p,1000)==0 
 
- fprintf('Samples:%d\n',p); 
 
- fprintf('True:%d\n',cInt); 
 
- fprintf('Predict:%d\n',bin2dec(int2str(round(y)))); 
 
- fprintf('Error:%f\n',norm(err,2)/2); 
 
- end 
 
- end 
 
- end 
 
- function sx=sigmoid(x) 
 
- sx=1./(1+exp(-x)); 
 
- end 
 
- function dx=sigmoidOutput2d(output) 
 
- dx=output.*(1-output); 
 
- end 
 
为了更深入理解RNN过程,这里我想用OpenCV和C++实现自己的RNN,简单的单隐层网络。同样类似的功能,模拟多个十进制数的加法进位过程。
- # include "highgui.h" 
 
- # include "cv.h" 
 
- # include <iostream> 
 
- #include "math.h" 
 
- #include<cstdlib> 
 
- using namespace std; 
 
- # define random(x) ((rand()*rand())%x) //生成0-x的随机数 
 
- void Display(CvMat* mat) 
 
- { 
 
- cout << setiosflags(ios::fixed); 
 
- for (int i = 0; i < mat->rows; i++) 
 
- { 
 
- for (int j = 0; j < mat->cols; j++) 
 
- cout << cvmGet(mat, i, j) << "  "; 
 
- cout << endl; 
 
- } 
 
- } 
 
- // sigmoid 函数 
 
- float sigmoid(float x) 
 
- { 
 
- return 1 / (1 + exp(-x)); 
 
- } 
 
- CvMat* sigmoidM(CvMat* mat) 
 
- { 
 
- CvMat*mat2 = cvCloneMat(mat); 
 
- for (int i = 0; i < mat2->rows; i++) 
 
- { 
 
- for (int j = 0; j < mat2->cols; j++) 
 
- cvmSet(mat2, i, j, sigmoid(cvmGet(mat, i, j))); 
 
- } 
 
- return mat2; 
 
- } 
 
- //sigmoid 函数的导数 
 
- float diffSigmoid(float x) 
 
- { 
 
- //注意,这里的x已经是sigmoid的结果 
 
- return x*(1 - x); 
 
- } 
 
- CvMat* diffSigmoidM(CvMat* mat) 
 
- { 
 
- CvMat* mat2 = cvCloneMat(mat); 
 
- for (int i = 0; i < mat2->rows; i++) 
 
- { 
 
- for (int j = 0; j < mat2->cols; j++) 
 
- { 
 
- float t = cvmGet(mat, i, j); 
 
- cvmSet(mat2, i, j, t*(1 - t)); 
 
- } 
 
- } 
 
- return mat2; 
 
- } 
 
- /**************随机生成inputdim个整数,并求和****************** 
 
- * inputdim      整数的个数 
 
- * MAX           整数的最大范围 
 
- * Sample        存放整数 
 
- * 返回            整数和 
 
- **************************************************************/ 
 
- int sample(int inputdim, CvMat* Sample,int MAX) 
 
- { 
 
- int sum = 0; 
 
- for (int i = 0; i < inputdim; i++) 
 
- { 
 
- int t = random(MAX); 
 
- cvmSet(Sample, 0, i, t); 
 
- sum += cvmGet(Sample,0,i); 
 
- } 
 
- return sum; 
 
- } 
 
- /********将整数拆分成10以内的数,作为每个时刻的输入************* 
 
- * Sample        存放的整数 大小 1*inputdim 
 
- * 返回            拆分后的输入数据 大小 inputdim*9 
 
- ****************************************************************/ 
 
- CvMat* splitM( CvMat*Sample) 
 
- { 
 
- CvMat* mat = cvCreateMat(Sample->cols, 8, CV_32F); 
 
- cvSetZero(mat); 
 
- for (int i = 0; i < mat->rows; i++) 
 
- { 
 
- int x = cvmGet(Sample,0,i); 
 
- for (int j = 0; j < 8; ++j) 
 
- { 
 
- cvmSet(mat,i,j, x % 10); 
 
- x = x / 10; 
 
- } 
 
- } 
 
- return mat; 
 
- } 
 
- /***************将数字数组整合成一个整数****************************** 
 
- *mat            数字数组,即每个元素是十以内的整数,大小1*9 
 
- *返回         整合后的整数 
 
- *********************************************************************/ 
 
- int merge(CvMat* mat) 
 
- { 
 
- double d = 0; 
 
- for (int i = mat->cols; i >0; i--) 
 
- { 
 
- d = 10 * d + round(10*(cvmGet(mat,0,i-1))); 
 
- } 
 
- return int(d); 
 
- } 
 
- /*****************将输出的数值拆分************************************** 
 
- * y             输出的数值 
 
- * 返回            长度为9的数组,这里转换成了0,1之间的数 
 
- ***********************************************************************/ 
 
- CvMat* split(int y) 
 
- { 
 
- CvMat* mat = cvCreateMat(1, 8, CV_32F); 
 
- for (int i = 0; i < 8; i++) 
 
- { 
 
- cvmSet(mat,0,i, (y % 10) / 10.0); 
 
- y = y / 10; 
 
- } 
 
- return mat; 
 
- } 
 
- /**********************产生随机矩阵****************************** 
 
- * rows, cols,           矩阵的规模 
 
- * a, b,                 区间 
 
- * 返回                    返回[a,b]之间的随机矩阵 
 
- *****************************************************************/ 
 
- CvMat*randM(int rows,int cols, float a,float b) 
 
- { 
 
- CvMat* mat = cvCreateMat(rows, cols, CV_32FC1); 
 
- float* ptr; 
 
- for (int i = 0; i < mat->rows; i++) 
 
- { 
 
- for (int j = 0; j < mat->cols; j++) 
 
- { 
 
- cvmSet(mat, i, j, random(1000) / 1000.0*(b - a) + a); 
 
- } 
 
- } 
 
- return mat; 
 
- } 
 
- int main() 
 
- { 
 
- srand(time(NULL)); 
 
- //首先,先定义网络 
 
- int inputdim = 2;//不超过10 
 
- int hiddendim = 16; 
 
- int outputdim = 1; 
 
- float eta = 0.1; 
 
- int MAX = 100000000;//令整数最多八位 
 
- //初始化参数矩阵 
 
- CvMat* V = randM(inputdim, hiddendim,-1,1); 
 
- CvMat* U = randM(hiddendim, hiddendim, -1, 1); 
 
- CvMat* W = randM(hiddendim, outputdim, -1, 1); 
 
- CvMat* bh = randM(1, hiddendim, -1, 1); 
 
- CvMat* by = randM(1, outputdim, -1, 1);//偏置 
 
- CvMat*Sample = cvCreateMat(1, inputdim, CV_32F); 
 
- cvSetZero(Sample); 
 
- CvMat* delta_V = cvCloneMat(V); 
 
- CvMat* delta_U = cvCloneMat(U); 
 
- CvMat* delta_W = cvCloneMat(W); 
 
- CvMat* delta_by = cvCloneMat(by); 
 
- CvMat* delta_bh = cvCloneMat(bh); 
 
- //开始训练,训练集大小10000 
 
- for (int p = 0; p < 20000; p++) 
 
- { 
 
- int sum = sample(inputdim,Sample,MAX); 
 
- CvMat* sampleM = splitM(Sample);//每一行对应着一个整数的拆分,个位在前 
 
- CvMat* d = split(sum);//真实结果拆分,每位存放的是除以10后的小数 
 
- //正向计算 
 
- CvMat* pre_h = cvCreateMat(1, hiddendim, CV_32F); 
 
- cvSetZero(pre_h);//初始化最开始的h_{-1} 
 
- CvMat* y = cvCreateMat(1, 8, CV_32F); 
 
- cvSetZero(y);//定义输出量 
 
- CvMat* h = cvCreateMat(8, hiddendim, CV_32F);//每一行存储一个时刻的隐变量输出 
 
- CvMat* temp1 = cvCreateMat(1, hiddendim, CV_32F); 
 
- CvMat* temp2 = cvCreateMat(1, outputdim, CV_32F); 
 
- CvMat* xt = cvCreateMatHeader(inputdim, 1, CV_32S); 
 
- for (int t = 0; t < 8; t++) 
 
- { 
 
- cvGetCol(sampleM, xt, t);//获取第t时刻输入值 
 
- cvGEMM(xt, V, 1,bh, 1, temp1, CV_GEMM_A_T); 
 
- cvGEMM(pre_h, U, 1, temp1, 1, pre_h);// t时刻隐层输出 
 
- pre_h = sigmoidM(pre_h); 
 
- cvGEMM(pre_h, W, 1, by, 1, temp2); 
 
- float yvalue = sigmoid(cvmGet(temp2, 0, 0)); 
 
- cvmSet(y, 0, t, yvalue);//t时刻的输出 
 
- //保存隐层输出 
 
- for (int j = 0; j < hiddendim; j++) 
 
- { 
 
- cvmSet(h, t, j, cvmGet(pre_h, 0, j)); 
 
- } 
 
- } 
 
- cvReleaseMat(&temp1); 
 
- cvReleaseMat(&temp2); 
 
- //观察代码 
 
- int oy = merge(y); 
 
- CvMat* temp = cvCreateMat(1, 8, CV_32F); 
 
- cvSub(y, d, temp); 
 
- double error = 0.5*cvDotProduct(temp, temp); 
 
- if ((p+1)%1000==0) 
 
- { 
 
- cout << "************************第" << p + 1 << "个样本***********" << endl; 
 
- cout << "真实值:" << sum%MAX << endl; 
 
- cout << "预测值:" << oy << endl; 
 
- cout << "误差:" << error << endl; 
 
- } 
 
- //反向传递误差 
 
- cvSetZero(delta_V); 
 
- cvSetZero(delta_U); 
 
- cvSetZero(delta_W); 
 
- cvSetZero(delta_bh); 
 
- cvSetZero(delta_by); 
 
- CvMat* delta_h = cvCreateMat(1, hiddendim, CV_32F); 
 
- cvSetZero(delta_h); 
 
- CvMat* delta_y = cvCreateMat(1, outputdim, CV_32F); 
 
- cvSetZero(delta_y); 
 
- CvMat* next_delta_h = cvCreateMat(1, hiddendim, CV_32F); 
 
- cvSetZero(next_delta_h); 
 
- for (int t = 7; t > 0; t--) 
 
- { 
 
- cvmSet(delta_y, 0, 0, (cvmGet(y, 0, t) - cvmGet(d, 0, t))*diffSigmoid(cvmGet(y, 0, t))); 
 
- cvGEMM(delta_y, W, 1, delta_h, 0, delta_h, CV_GEMM_B_T); 
 
- cvGEMM(next_delta_h, U, 1, delta_h, 1, delta_h, CV_GEMM_B_T); 
 
- cvMul(delta_h, diffSigmoidM(cvGetRow(h, temp, t)), delta_h); 
 
- //更新delta_y,delta_h 
 
- cvGEMM(cvGetRow(h, temp, t), delta_y, 1, delta_W, 1, delta_W, CV_GEMM_A_T); 
 
- if (t>0) 
 
- cvGEMM(cvGetRow(h, temp, t - 1), delta_h, 1, delta_U, 1, delta_U, CV_GEMM_A_T); 
 
- cvGetCol(sampleM, xt, t); 
 
- cvGEMM(xt, delta_h, 1, delta_V, 1, delta_V); 
 
- cvAddWeighted(delta_by, 1, delta_y, 1, 0, delta_by); 
 
- cvAddWeighted(delta_bh, 1, delta_h, 1, 0, delta_bh); 
 
- cvAddWeighted(delta_h, 1, next_delta_h, 0, 0, next_delta_h); 
 
- } 
 
- cvAddWeighted(W, 1, delta_W, -eta, 0, W); 
 
- cvAddWeighted(V, 1, delta_V, -eta, 0, V); 
 
- cvAddWeighted(U, 1, delta_U, -eta, 0, U); 
 
- cvAddWeighted(by, 1, delta_by, -eta, 0, by); 
 
- cvAddWeighted(bh, 1, delta_bh, -eta, 0, bh); 
 
- cvReleaseMat(&sampleM); 
 
- cvReleaseMat(&d); 
 
- cvReleaseMat(&pre_h); 
 
- cvReleaseMat(&y); 
 
- cvReleaseMat(&h); 
 
- cvReleaseMat(&delta_h); 
 
- cvReleaseMat(&delta_y); 
 
- } 
 
- cvReleaseMat(&U); 
 
- cvReleaseMat(&V); 
 
- cvReleaseMat(&W); 
 
- cvReleaseMat(&by); 
 
- cvReleaseMat(&bh); 
 
- cvReleaseMat(&Sample); 
 
- cvReleaseMat(&delta_V); 
 
- cvReleaseMat(&delta_U); 
 
- cvReleaseMat(&delta_W); 
 
- cvReleaseMat(&delta_by); 
 
- cvReleaseMat(&delta_bh); 
 
- system("PAUSE"); 
 
- return 0; 
 
- } 
 
下面是代码结果,并没有完全一致。分析下主要原因可能是由于输出层是(0,1)的小数,但我们希望得到的是[0,10]的整数,而通过round或者强制类型转换总会带来较大误差,所以会出现预测值和真实值差别很大,这时候其实比较值的差异意义不大,应该对比每一位上数字的差异。

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

PS. 作为opencv新手,觉得matlab半小时搞定的东西,opencv要捣鼓两个小时。。。
RNN求解过程推导与实现的更多相关文章
- h.264 mvp求解过程
		h.264标准中由于分为宏块分割块(8x8),子宏块分割块(4x4),所以各种各样的求解过程比较繁琐 下面整理出标准中mvp的求解过程 8.4.1.3 已知条件有当前块的属性:位置.块类型需要得到当前 ... 
- 深度学习(二)BP求解过程和梯度下降
		一.原理 重点:明白偏导数含义,是该函数在该点的切线,就是变化率,一定要理解变化率. 1)什么是梯度 梯度本意是一个向量(矢量),当某一函数在某点处沿着该方向的方向导数取得该点处的最大值,即函数在该点 ... 
- 一种3位sar adc工作过程推导(二)
		3位sar adc采用下图的电容阵列,需要23个电容,它的基本单元有二进制加权的电容阵列.1个与LSB电容等值的电容:它利用电容上的初始电荷再分配完成二进制搜索算法,因此功耗一般比较小,而且不需要额外 ... 
- TSP旅行商问题的Hopfield求解过程
		连续型Hopfield在matlab中没有直接的工具箱,所以我们们根据Hopfield给出的连续行算法自行编写程序.本文中,以求解旅行商 问题来建立Hopfield网络,并得到解,但是该解不一定是 ... 
- 推荐系统 BPR 算法求解过程
		数据假设: 每个用户之间的偏好行为相互独立 同一用户对不同物品的偏序相互独立 则优化问题为极大化如下目标: [Reference] 1.论文翻译:BPR:面向隐偏好数据的贝叶斯个性化排序学习模型 2. ... 
- RNN推导
		http://www.cnblogs.com/YiXiaoZhou/p/6058890.html RNN求解过程推导与实现 RNN LSTM BPTT matlab code opencv code ... 
- EM算法求高斯混合模型參数预计——Python实现
		EM算法一般表述: 当有部分数据缺失或者无法观察到时,EM算法提供了一个高效的迭代程序用来计算这些数据的最大似然预计.在每一步迭代分为两个步骤:期望(Expectation)步骤和最大化( ... 
- Logistic回归计算过程的推导
		https://blog.csdn.net/ligang_csdn/article/details/53838743 https://blog.csdn.net/weixin_30014549/art ... 
- 坐标下降法(coordinate descent method)求解LASSO的推导
		坐标下降法(coordinate descent method)求解LASSO推导 LASSO在尖点是singular的,因此传统的梯度下降法.牛顿法等无法使用.常用的求解算法有最小角回归法.coor ... 
随机推荐
- VB操作EXCEL文件
			用VB操作Excel(VB6.0)(整理) 首先创建Excel对象,使用ComObj:Dim ExcelID as Excel.ApplicationSet ExcelID as new Excel. ... 
- MySQL 权限与安全
			一.MySQL权限系统通过两个阶段进行认证: (A) 对用户进行身份认证,IP地址和用户名联合, (B) 对合法用户赋予相应权限,权限表在数据库启动的时候载入内存中. 二.在权限的存取过程中,会用到& ... 
- How to use Bundle&Minifier  and bundleconfig.json in ASP.NET Core
			引言 我们在ASP.NET MVC 中经常会用到 bundleConfig.cs 文件来进行我们 css 和 js 的绑定, 那么在ASP.NET Core 中我们应该如何使用呢? 步骤一 在 Vis ... 
- IIS网站或系统验证码不显示问题——"使用了托管的处理程序,但是未安装或未完整安装 ASP.NET"
			在IIS上发布了一个系统,但是登陆页面的验证码图片一直出不来,尝试了各种办法,权限.路径.继承父类路径等都不管用,进入Login.html,对着无验证码图片的图标,右键复制图片的网址,粘贴到地址栏,出 ... 
- Android插件化框架研究-DroidPlugin
			直接贴上我做的ppt. 
- Libvlc  API 简单说明 [转]
			Libvlc API 简单说明 原文来自http://www.xuebuyuan.com/1519616.html libvlc_instance_t* libvlc_new(int argc, co ... 
- 修复Linux Mint损坏的依赖
			第一种: sudo apt-get install -f 第二种 sudo aptitude install -f 注: 要是某软件xxx依赖损坏了,可以这样 sudo aptitude instal ... 
- Qt 二维码
			1.生成二维码 利用第三方库qrencode ,将qrencode源码添加到自己的程序中,直接调用使用. 参考http://blog.csdn.net/zhangxufei/article/detai ... 
- C语言移位算符">>"
			右移算符>>是将二进制数的每一位右移.如:a=32,a>>2,就是将32的二进制数100000每一个二进制位向右移动两位.得到的是二进制数1000,也就是8. #include ... 
- Servlet调用过程
			(1)在浏览器输入地址,浏览器先去查找hosts文件,将主机名翻译为ip地址,如果找不到就再去查询dns服务器将主机名翻译成ip地址. (2)浏览器根据ip地址和端口号访问服务器,组织http请求信息 ... 
