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 ...
随机推荐
- JavaScript生成新标签的三个方法(摘抄自留)
<div id="d1"></div> <script> //HTML function a(){ document.getElementByI ...
- tail命令详解
搜索 纠正错误 添加实例 tail 在屏幕上显示指定文件的末尾若干行 补充说明 tail命令 用于输入文件中的尾部内容.tail命令默认在屏幕上显示指定文件的末尾10行.如果给定的文件不止一个,则在 ...
- bzoj2928: [Poi1999]飞弹
惨啊…… 被卡常是一种什么感受&…… 很明显的分治. 我们首先可以找到所有点中的最低点,然后对所有点进行一次极角排序,选取一个点使得他各侧飞弹和地堡一样多,并对两侧继续进行分治. 很容易证明这 ...
- Ubuntu Server 14.04 --secure-file-priv error in MySql 解决方案
在VPS部署Ubuntu 14.04服务器时,MySQL执行导出文件命令,报错: The MySQL server is running with the --secure-file-priv opt ...
- Windows Server 2008 双网卡同时上内外网 不能正常使用
Windows server 2008 32位下,双网卡同时上内外网,并提供VPN服务,遇见的奇怪问题 1.服务器配置 2.网络配置 以太网适配器 内部连接: 连接特定的 DNS 后缀 . . . . ...
- 应用商店后台MIS的一些思考
1.有些签名验证的工作应该在开发者上传APP的时候进行校验: 1)如果是更新新版本(包名packagename一致),那么需要验证两个APK的包的签名是否一致,不一致的,应该限制上传,除非先下架旧的A ...
- SQL(横表和纵表)行列转换,PIVOT与UNPIVOT的区别和使用方法举例,合并列的例子
使用过SQL Server 2000的人都知道,要想实现行列转换,必须综合利用聚合函数和动态SQL,具体实现起来需要一定的技巧,而在SQL Server 2005中,使用新引进的关键字PIVOT/UN ...
- 显示或隐藏div
代码来源于博客,如有侵权,请联系我! ASP.NET中TextBox控件设置ReadOnly="true"H或Enabled=false后台取不到值 当TextBox设置了Read ...
- PHP预定义接口之 ArrayAccess
最近这段时间回家过年了,博客也没有更新,感觉少学习了好多东西,也错失了好多的学习机会,就像大家在春节抢红包时常说的一句话:一不留神错过了好几亿.废话少说,这篇博客给大家说说关于PHP预定义接口中常用到 ...
- ShareSDK分享失败的原因
关于分享估计很多都用的是ShareSDK的社会化分享,简单方便,支持的种类很多,但是一般的话都还是QQ,微信,新浪微博,腾讯微博为主. 最近需要导入一个分享的模块,失败了几次之后最终成功,分享给大家, ...