UFLDL深度学习笔记 (一)反向传播与稀疏自编码
UFLDL深度学习笔记 (一)基本知识与稀疏自编码
前言
近来正在系统研究一下深度学习,作为新入门者,为了更好地理解、交流,准备把学习过程总结记录下来。最开始的规划是先学习理论推导;然后学习一两种开源框架;第三是进阶调优、加速技巧。越往后越要带着工作中的实际问题去做,而不能是空中楼阁式沉迷在理论资料的旧数据中。深度学习领域大牛吴恩达(Andrew Ng)老师的UFLDL教程 (Unsupervised Feature Learning and Deep Learning)提供了很好的基础理论推导,文辞既系统完备,又明白晓畅,最可贵的是能把在斯坦福大学中的教学推广给全世界,尽心力而广育人,实教授之典范。
这里的学习笔记不是为了重复UFLDL中的推导过程,而是为了一方面补充我没有很快看明白的理论要点,另一方面基于我对课后练习的matlab实现,记录讨论卡壳时间较长的地方。也方便其他学习者,学习过程中UFLDL所有留白的代码模块全是自己编写的,没有查网上的代码,经过一番调试结果都达到了练习给出的参考标准,而且都按照矩阵化实现(UFLDL称为矢量化 vectorization),代码链接见此https://github.com/codgeek/deeplearning,所以各种matlab实现细节、缘由也会比较清楚,此外从实现每次练习的代码commit中可以清楚看到修改了那些代码,欢迎大家一起讨论共同进步。
理论推导中的要点
先上一个经典的自编码神经网络结构图。推导中常用的符号表征见于此
上图的神经网络结构中,输入单个向量$\ x\ \(,逐层根据网络参数矩阵\)\ W, b\ \(计算[**前向传播**](http://deeplearning.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C),最后输出向量\) h_{W,b}(x)$, 这样单个向量的误差代价函数是
\]
对所有数据代价之和即
\]
偏导与反向传导的前因后果
神经网络的最基础的理论是反向传导算法,反向传导的主语是误差代价函数$\ J_{W,b}\ $对网络系数(W,b)的偏导数。那为什么要求偏导呢? 这里就引出了最优化理论,具体的问题一般被建模成许多自变量决定的代价函数,最优化的目标就是找到使代价函数最小化的自变量取值。数学方法告诉我们令偏导为0对应的自变量取值就是最小化代价的点,不幸的是多数实际问题对用偏导为0方程求不出闭合的公式解,神经网络也不例外。为了解决这类问题,可以使用优化领域的梯度下降法,梯度是对每个自变量的偏导组成的向量或矩阵,梯度下降直白地说就是沿着对每个自变量偏导的负方向改变自变量,下降指的是负方向,直到梯度值接近0或代价函数变化很小,即停止查找认为找到了达到最优化的所有自变量数值。 所以为了最小化稀疏自编码误差代价函数,我们需要求出J(W,b)对神经网络各层系数W、b的偏导数组成的梯度矩阵。
接下来在反向传导算法文章中给出了详细的推导过程,推导的结果便是残差、梯度值从输出层到隐藏层的反向传导公式
输出层的对输出值$\ z\ \(第i个分量\)\ z_i^{n_l}\ $的偏导为
\]
后向传导时,第l+1层传导给第l层偏导值为 $$\delta_i^{(l)} = (\sum_{j=1}{s_l+1}W_{ij}{(l)}\delta_j^{(l+1)}) f'( z_i^{(l)}) $$
J对输出值z的偏导怎么得到对网络权值参数的偏导?
我们知道$\ z_i{l+1} $与$ W,b $的关系$ z_i{(l+1)}=\sum_{j=1}^{n_l} ({W_{i,j}{(l)}\cdot{a_j{(l)}+b}})\ $,运用求导链式法则可得:
\]
原文中跳过了这一步,基于此,才能得到误差函数对参数$\ W, b\ $每一项的偏导数。
\]
\]
结合稀疏自编码问题
前面的反向传导是神经网络的一般形式,具体到稀疏自编码,有两个关键字“稀疏”、“自”。“自”是指目标值$\ y\ \(和输入向量\)\ x\ \(相等,“稀疏”是指过程要使隐藏层L1的大部分激活值分量接近0,个别分量显著大于0.所以稀疏自编码的代价函数中\)\ y\ \(直接使用\)\ x\ $的值。同时加上稀疏性惩罚项,详见稀疏编码一节。
\]
惩罚项只施加在隐藏层上,对偏导项做对应项添加,也只有隐藏层的偏导增加一项。
\]
后向传导以及对网络权值$\ W, b\ $的梯度公式不变。
在反向传导的前因后果段落,我们已经知道了要用梯度下降法搜索使代价函数最小的自变量$\ W, b\ \(。实际上梯度下降也不是简单减梯度,而是有下降速率参数\)\ \alpha\ $的L-BFGS,又牵扯到一个专门的优化。为了简化要做的工作量,稀疏自编码练习中已经帮我们集成了一个第三方实现,在minFunc文件夹中,我们只需要提供代价函数、梯度计算函数就可以调用minFunc实现梯度下降,得到最优化参数。后续的工作也就是要只需要补上sampleIMAGES.m, sparseAutoencoderCost.m, computeNumericalGradient.m的"YOUR CODE HERE"的代码段。
exercise代码实现难点
UFLDL给大家的学习模式很到家,把周边的结构性代码都写好了matlab代码与注释,尽量给学习者减负。系数自编码中主m文件是train.m。先用实现好的代价、梯度模块调用梯度检验,然后将上述代价、梯度函数传入梯度下降优化minFunc。满足迭代次数后退出,得到训练好的神经网络系数矩阵,补全全部待实现模块的完整代码见此,https://github.com/codgeek/deeplearning.
其中主要过程是
训练数据
训练数据来源是图片,第一步要在sampleIMAGES.m中将其转化为神经网络$\ x\ $向量。先看一下训练数据的样子吧
sampleIMAGES.m模块是可以被后续课程复用的,尽量写的精简通用些。从一幅图上取patchsize大小的子矩阵,要避免每个图上取的位置相同,也要考虑并行化,循环每张图片取patch效率较低。下文给出的方法是先随机确定行起始行列坐标,计算出numpatches个这样的坐标值,然后就能每个patch不关联地取出来,才能运用parfor做并行循环。
sampleIMAGES.m
function patches = sampleIMAGES(patchsize, numpatches)
load IMAGES; % load images from disk dataSet=IMAGES;
dataSet = IMAGES;
patches = zeros(patchsize*patchsize, numpatches); % 初始化数组大小,为并行循环开辟空间
[row, colum, numPic] = size(dataSet);
rowStart = randi(row - patchsize + 1, numpatches, 1);% 从一幅图上取patchsize大小子矩阵,只需要确定随机行起始点,范围是[1,row - patchsize + 1]
columStart = randi(colum - patchsize + 1, numpatches, 1);% 确定随机列起始点,范围是colum - patchsize + 1
randIdx = randperm(numPic); % 确定从哪一张图上取子矩阵,打乱排列,防止生成的patch顺序排列。
parfor r=1:numpatches % 确定了起始坐标后,每个patch不关联,可以并行循环取
patches(:,r) = reshape(dataSet(rowStart(r):rowStart(r) + patchsize - 1, columStart(r):columStart(r) + patchsize - 1, randIdx(floor((r-1)*numPic/numpatches)+1)),[],1);
end
patches = normalizeData(patches)
end
后向传播模型的矩阵实现
稀疏自编码最重要模块是计算代价、梯度矩阵的sparseAutoencoderCost.m。输入$\ W, b\ \(与训练数据data,外加稀疏性因子、稀疏项系数、权重\)\ W\ \(系数,值得关注的是后向传导时隐藏层输出的残差delta2是由隐藏层与输出层的网络参数\)\ W2\ \(和输出层的残差delta3决定的,不是输入层与隐藏层的网络参数\)\ W1\ $,这里最开始写错了,耽误了一些时间才调试正确。下文结合代码用注释的形式解释每一步具体作用。
sparseAutoencoderCost.m
function [cost,grad] = sparseAutoencoderCost(theta, visibleSize, hiddenSize, ...
lambda, sparsityParam, beta, data)
%% 一维向量重组为便于矩阵计算的神经网络系数矩阵。
W1 = reshape(theta(1:hiddenSizevisibleSize), hiddenSize, visibleSize);
W2 = reshape(theta(hiddenSizevisibleSize+1:2hiddenSizevisibleSize), visibleSize, hiddenSize);
b1 = theta(2hiddenSizevisibleSize+1:2hiddenSizevisibleSize+hiddenSize);
b2 = theta(2hiddenSizevisibleSize+hiddenSize+1:end);
%% 前向传导,W1矩阵为hiddenSize×visibleSize,训练数据data为visibleSize×N_samples,训练所有数据的过程正好是矩阵相乘$\ W1*data\ $。注意所有训练数据都共用系数$\ b\ $,而单个向量的每个分量对用使用$\ b\ $的对应分量,b1*ones(1,m)是将列向量复制m遍,组成和$\ W1*data\ $相同维度的矩阵。
[~, m] = size(data); % visibleSize×N_samples, m=N_samples
a2 = sigmoid(W1*data + b1*ones(1,m));% active value of hiddenlayer: hiddenSize×N_samples
a3 = sigmoid(W2*a2 + b2*ones(1,m));% output result: visibleSize×N_samples
diff = a3 - data; % 自编码也就意味着将激活值和原始训练数据做差值。
penalty = mean(a2, 2); % measure of hiddenlayer active: hiddenSize×1
residualPenalty = (-sparsityParam./penalty + (1 - sparsityParam)./(1 - penalty)).*beta; % penalty factor in residual error delta2
% size(residualPenalty)
cost = sum(sum((diff.*diff)))./(2*m) + ...
(sum(sum(W1.*W1)) + sum(sum(W2.*W2))).*lambda./2 + ...
beta.*sum(KLdivergence(sparsityParam, penalty));
% 后向传导过程,隐藏层残差需要考虑稀疏性惩罚项,公式比较清晰。
delta3 = -(data-a3).*(a3.*(1-a3)); % visibleSize×N_samples
delta2 = (W2'*delta3 + residualPenalty*ones(1, m)).*(a2.*(1-a2)); % hiddenSize×N_samples. !!! => W2'*delta3 not W1'*delta3
% 前面已经推导出代价函数对W2的偏导,矩阵乘法里包含了公式中l层激活值a向量与1+1层残差delta向量的点乘。
W2grad = delta3*a2'; % ▽J(L)=delta(L+1,i)*a(l,j). sum of grade value from N_samples is got by matrix product visibleSize×N_samples * N_samples× hiddenSize. so mean value is caculated by "/N_samples"
W1grad = delta2*data';% matrix product visibleSize×N_samples * N_samples×hiddenSize
b1grad = sum(delta2, 2);
b2grad = sum(delta3, 2);
% 对m个训练数据取平均
W1grad=W1grad./m + lambda.*W1;
W2grad=W2grad./m + lambda.*W2;
b1grad=b1grad./m;
b2grad=b2grad./m;% mean value across N_sample: visibleSize ×1
% 矩阵转列向量
grad = [W1grad(:) ; W2grad(:) ; b1grad(:) ; b2grad(:)];
end
最终结果
在tarin.m中,将sparseAutoencoderCost.m传入梯度优化函数minFunc,经过迭代训练出网络参数,通常见到的各个方向的边缘检测图表示的是权重矩阵对每个隐藏层激活值的特征,当输入值为对应特征值时,每个激活值会有最大响应,同时的其余隐藏节点处于抑制状态。所以只需要把W1矩阵每一行的64个向量还原成8*8的图片patch,也就是特征值了,每个隐藏层对应一个,总共100个。结果如下图。
接下来我们验证一下隐藏层对应的输出是否满足激活、抑制的要求,将上述输入值用参数矩阵传导到隐藏层,也就是$\ W1*W1'\ $.可见,每个输入对应的隐藏层只有一个像素是白色,表示接近1,其余是暗色调接近0,满足了稀疏性要求,而且激活的像素位置是顺序排列的。
继续改变不同的输入层单元个数,隐藏层单元个数,可以得到更多有意思的结果!
UFLDL深度学习笔记 (一)反向传播与稀疏自编码的更多相关文章
- UFLDL深度学习笔记 (七)拓扑稀疏编码与矩阵化
UFLDL深度学习笔记 (七)拓扑稀疏编码与矩阵化 主要思路 前面几篇所讲的都是围绕神经网络展开的,一个标志就是激活函数非线性:在前人的研究中,也存在线性激活函数的稀疏编码,该方法试图直接学习数据的特 ...
- UFLDL深度学习笔记 (五)自编码线性解码器
UFLDL深度学习笔记 (五)自编码线性解码器 1. 基本问题 在第一篇 UFLDL深度学习笔记 (一)基本知识与稀疏自编码中讨论了激活函数为\(sigmoid\)函数的系数自编码网络,本文要讨论&q ...
- UFLDL深度学习笔记 (二)SoftMax 回归(矩阵化推导)
UFLDL深度学习笔记 (二)Softmax 回归 本文为学习"UFLDL Softmax回归"的笔记与代码实现,文中略过了对代价函数求偏导的过程,本篇笔记主要补充求偏导步骤的详细 ...
- UFLDL深度学习笔记 (六)卷积神经网络
UFLDL深度学习笔记 (六)卷积神经网络 1. 主要思路 "UFLDL 卷积神经网络"主要讲解了对大尺寸图像应用前面所讨论神经网络学习的方法,其中的变化有两条,第一,对大尺寸图像 ...
- UFLDL深度学习笔记 (四)用于分类的深度网络
UFLDL深度学习笔记 (四)用于分类的深度网络 1. 主要思路 本文要讨论的"UFLDL 建立分类用深度网络"基本原理基于前2节的softmax回归和 无监督特征学习,区别在于使 ...
- tensorflow学习笔记(2)-反向传播
tensorflow学习笔记(2)-反向传播 反向传播是为了训练模型参数,在所有参数上使用梯度下降,让NN模型在的损失函数最小 损失函数:学过机器学习logistic回归都知道损失函数-就是预测值和真 ...
- UFLDL深度学习笔记 (三)无监督特征学习
UFLDL深度学习笔记 (三)无监督特征学习 1. 主题思路 "UFLDL 无监督特征学习"本节全称为自我学习与无监督特征学习,和前一节softmax回归很类似,所以本篇笔记会比较 ...
- 【深度学习】BP反向传播算法Python简单实现
转载:火烫火烫的 个人觉得BP反向传播是深度学习的一个基础,所以很有必要把反向传播算法好好学一下 得益于一步一步弄懂反向传播的例子这篇文章,给出一个例子来说明反向传播 不过是英文的,如果你感觉不好阅读 ...
- 深度学习笔记:优化方法总结(BGD,SGD,Momentum,AdaGrad,RMSProp,Adam)
深度学习笔记:优化方法总结(BGD,SGD,Momentum,AdaGrad,RMSProp,Adam) 深度学习笔记(一):logistic分类 深度学习笔记(二):简单神经网络,后向传播算法及实现 ...
随机推荐
- vs生成命令和属性的宏
在vs属性页面中编辑后期生成事件... 下面是vs中宏的描述信息. http://i.cnblogs.com/EditPosts.aspx?opt=1 高级用法: 磨刀不误砍柴工——VS生成事件
- JS版汉字与拼音互转终极方案,附简单的JS拼音输入法
原文:http://www.cnblogs.com/liuxianan/p/pinyinjs.html 前言 网上关于JS实现汉字和拼音互转的文章很多,但是比较杂乱,都是互相抄来抄去,而且有的不支持多 ...
- MySQL5.6 怎样优化慢查询的SQL语句 -- SQL优化
上篇:MySQL5.6 怎样优化慢查询的SQL语句 -- 慢日志介绍 在实际的日志分析中,通常慢日志的log数量不少,同一时候同样的查询被记录的条数也会非常多.这里就须要怎样从慢日志查询中找到最有问题 ...
- Linux文件夹、分区
必须明确,硬盘分区的存在,是由硬盘的物理特性决定的,不会因为操作系统的不同而有所改变 所以不用对为根目录/挂载分区的同时还为/usr挂载分区感到惊讶 ====分区的概念==== 可以把一个硬盘比成 ...
- 一个简单的JS函数,用于判断文本是否数字
/****************************************************** 判断是否是数字(整数,小数均可,不包括负数)* 2014年10月10日22:38:19* ...
- iOS-国家代码选择功能github开源分享
三行代码集成国家区号选择功能 功能执行效果如图: 开源链接: https://github.com/qxuewei/XWCountryCode 用法: 1.导入XWCountryCode类 2.在须要 ...
- 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-Switch Case语句是否会自动跳转到下一个
在C#中,每一个case后面必须有break,所以输出1,也就是如果a=0,则只会执行case=0的那一段,当等于1之后不会继续. 在TwinCAT中,虽然CASE语句没有break,但是实际上不 ...
- mac 上多版本python 共存
Mac上自带了Python2.x的版本,有时需要使用Python3.x版本做开发,但不能删了Python2.x,可能引起系统不稳定,那么就需要安装多个版本的Python. 1.安装Python3.x版 ...
- 数据结构之---C语言实现最小生成树之prim(普里姆)算法
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/ ...
- jQuery 获取DOM节点的两种方式
jQuery中包裹后的DOM对象实际上是一个数组,要获得纯粹的DOM对象可以有两种方式: 1.使用数组索引方式访问,例如: var dom = $(dom)[0]; 如: $("#id&qu ...