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


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

其中
分别表示激活前对应的加权和,
表示激活函数。
然后看看误差如何传递。
假设真实的输出应该是
,那么误差可以定义为
,
是训练样本的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请求信息 ...