Deep Learning论文笔记之(五)CNN卷积神经网络代码理解
Deep Learning论文笔记之(五)CNN卷积神经网络代码理解
zouxy09@qq.com
http://blog.csdn.net/zouxy09
自己平时看了一些论文,但老感觉看完过后就会慢慢的淡忘,某一天重新拾起来的时候又好像没有看过一样。所以想习惯地把一些感觉有用的论文中的知识点总结整理一下,一方面在整理过程中,自己的理解也会更深,另一方面也方便未来自己的勘察。更好的还可以放到博客上面与大家交流。因为基础有限,所以对论文的一些理解可能不太正确,还望大家不吝指正交流,谢谢。
本文的代码来自githup的Deep Learning的toolbox,(在这里,先感谢该toolbox的作者)里面包含了很多Deep Learning方法的代码。是用Matlab编写的(另外,有人翻译成了C++和python的版本了)。本文中我们主要解读下CNN的代码。详细的注释见代码。
在读代码之前,最好先阅读下我的上一个博文:
Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现
http://blog.csdn.net/zouxy09/article/details/9993371
里面包含的是我对一个作者的CNN笔记的翻译性的理解,对CNN的推导和实现做了详细的介绍,看明白这个笔记对代码的理解非常重要,所以强烈建议先看懂上面这篇文章。
下面是自己对代码的注释:
cnnexamples.m
clear all; close all; clc;
addpath('../data');
addpath('../util');
load mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;
test_x = double(reshape(test_x',28,28,10000))/255;
train_y = double(train_y');
test_y = double(test_y');%% ex1
%will run 1 epoch in about 200 second and get around 11% error.
%With 100 epochs you'll get around 1.2% errorcnn.layers = {struct('type', 'i') %input layerstruct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layerstruct('type', 's', 'scale', 2) %sub sampling layerstruct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layerstruct('type', 's', 'scale', 2) %subsampling layer
};% 这里把cnn的设置给cnnsetup,它会据此构建一个完整的CNN网络,并返回
cnn = cnnsetup(cnn, train_x, train_y);% 学习率
opts.alpha = 1;
% 每次挑出一个batchsize的batch来训练,也就是每用batchsize个样本就调整一次权值,而不是
% 把所有样本都输入了,计算所有样本的误差了才调整一次权值
opts.batchsize = 50;
% 训练次数,用同样的样本集。我训练的时候:
% 1的时候 11.41% error
% 5的时候 4.2% error
% 10的时候 2.73% error
opts.numepochs = 10;% 然后开始把训练样本给它,开始训练这个CNN网络
cnn = cnntrain(cnn, train_x, train_y, opts);% 然后就用测试样本来测试
[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared error
plot(cnn.rL);
%show test error
disp([num2str(er*100) '% error']);
cnnsetup.m
function net = cnnsetup(net, x, y)inputmaps = 1;% B=squeeze(A) 返回和矩阵A相同元素但所有单一维都移除的矩阵B,单一维是满足size(A,dim)=1的维。% train_x中图像的存放方式是三维的reshape(train_x',28,28,60000),前面两维表示图像的行与列,% 第三维就表示有多少个图像。这样squeeze(x(:, :, 1))就相当于取第一个图像样本后,再把第三维% 移除,就变成了28x28的矩阵,也就是得到一幅图像,再size一下就得到了训练样本图像的行数与列数了mapsize = size(squeeze(x(:, :, 1)));% 下面通过传入net这个结构体来逐层构建CNN网络% n = numel(A)返回数组A中元素个数% net.layers中有五个struct类型的元素,实际上就表示CNN共有五层,这里范围的是5for l = 1 : numel(net.layers) % layerif strcmp(net.layers{l}.type, 's') % 如果这层是 子采样层% subsampling层的mapsize,最开始mapsize是每张图的大小28*28% 这里除以scale=2,就是pooling之后图的大小,pooling域之间没有重叠,所以pooling后的图像为14*14% 注意这里的右边的mapsize保存的都是上一层每张特征map的大小,它会随着循环进行不断更新mapsize = floor(mapsize / net.layers{l}.scale);for j = 1 : inputmaps % inputmap就是上一层有多少张特征图net.layers{l}.b{j} = 0; % 将偏置初始化为0endendif strcmp(net.layers{l}.type, 'c') % 如果这层是 卷积层% 旧的mapsize保存的是上一层的特征map的大小,那么如果卷积核的移动步长是1,那用% kernelsize*kernelsize大小的卷积核卷积上一层的特征map后,得到的新的map的大小就是下面这样mapsize = mapsize - net.layers{l}.kernelsize + 1;% 该层需要学习的参数个数。每张特征map是一个(后层特征图数量)*(用来卷积的patch图的大小)% 因为是通过用一个核窗口在上一个特征map层中移动(核窗口每次移动1个像素),遍历上一个特征map% 层的每个神经元。核窗口由kernelsize*kernelsize个元素组成,每个元素是一个独立的权值,所以% 就有kernelsize*kernelsize个需要学习的权值,再加一个偏置值。另外,由于是权值共享,也就是% 说同一个特征map层是用同一个具有相同权值元素的kernelsize*kernelsize的核窗口去感受输入上一% 个特征map层的每个神经元得到的,所以同一个特征map,它的权值是一样的,共享的,权值只取决于% 核窗口。然后,不同的特征map提取输入上一个特征map层不同的特征,所以采用的核窗口不一样,也% 就是权值不一样,所以outputmaps个特征map就有(kernelsize*kernelsize+1)* outputmaps那么多的权值了% 但这里fan_out只保存卷积核的权值W,偏置b在下面独立保存fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;for j = 1 : net.layers{l}.outputmaps % output map% fan_out保存的是对于上一层的一张特征map,我在这一层需要对这一张特征map提取outputmaps种特征,% 提取每种特征用到的卷积核不同,所以fan_out保存的是这一层输出新的特征需要学习的参数个数% 而,fan_in保存的是,我在这一层,要连接到上一层中所有的特征map,然后用fan_out保存的提取特征% 的权值来提取他们的特征。也即是对于每一个当前层特征图,有多少个参数链到前层fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;for i = 1 : inputmaps % input map% 随机初始化权值,也就是共有outputmaps个卷积核,对上层的每个特征map,都需要用这么多个卷积核% 去卷积提取特征。% rand(n)是产生n×n的 0-1之间均匀取值的数值的矩阵,再减去0.5就相当于产生-0.5到0.5之间的随机数% 再 *2 就放大到 [-1, 1]。然后再乘以后面那一数,why?% 反正就是将卷积核每个元素初始化为[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]% 之间的随机数。因为这里是权值共享的,也就是对于一张特征map,所有感受野位置的卷积核都是一样的% 所以只需要保存的是 inputmaps * outputmaps 个卷积核。net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));endnet.layers{l}.b{j} = 0; % 将偏置初始化为0end% 只有在卷积层的时候才会改变特征map的个数,pooling的时候不会改变个数。这层输出的特征map个数就是% 输入到下一层的特征map个数inputmaps = net.layers{l}.outputmaps; endend% fvnum 是输出层的前面一层的神经元个数。% 这一层的上一层是经过pooling后的层,包含有inputmaps个特征map。每个特征map的大小是mapsize。% 所以,该层的神经元个数是 inputmaps * (每个特征map的大小)% prod: Product of elements.% For vectors, prod(X) is the product of the elements of X% 在这里 mapsize = [特征map的行数 特征map的列数],所以prod后就是 特征map的行*列fvnum = prod(mapsize) * inputmaps;% onum 是标签的个数,也就是输出层神经元的个数。你要分多少个类,自然就有多少个输出神经元onum = size(y, 1);% 这里是最后一层神经网络的设定% ffb 是输出层每个神经元对应的基biasesnet.ffb = zeros(onum, 1);% ffW 输出层前一层 与 输出层 连接的权值,这两层之间是全连接的net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
end
cnntrain.m
function net = cnntrain(net, x, y, opts)m = size(x, 3); % m 保存的是 训练样本个数numbatches = m / opts.batchsize;% rem: Remainder after division. rem(x,y) is x - n.*y 相当于求余% rem(numbatches, 1) 就相当于取其小数部分,如果为0,就是整数if rem(numbatches, 1) ~= 0error('numbatches not integer');endnet.rL = [];for i = 1 : opts.numepochs% disp(X) 打印数组元素。如果X是个字符串,那就打印这个字符串disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);% tic 和 toc 是用来计时的,计算这两条语句之间所耗的时间tic;% P = randperm(N) 返回[1, N]之间所有整数的一个随机的序列,例如% randperm(6) 可能会返回 [2 4 5 6 1 3]% 这样就相当于把原来的样本排列打乱,再挑出一些样本来训练kk = randperm(m);for l = 1 : numbatches% 取出打乱顺序后的batchsize个样本和对应的标签batch_x = x(:, :, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));batch_y = y(:, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));% 在当前的网络权值和网络输入下计算网络的输出net = cnnff(net, batch_x); % Feedforward% 得到上面的网络输出后,通过对应的样本标签用bp算法来得到误差对网络权值%(也就是那些卷积核的元素)的导数net = cnnbp(net, batch_y); % Backpropagation% 得到误差对权值的导数后,就通过权值更新方法去更新权值net = cnnapplygrads(net, opts);if isempty(net.rL)net.rL(1) = net.L; % 代价函数值,也就是误差值endnet.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.L; % 保存历史的误差值,以便画图分析endtoc;endend
cnnff.m
function net = cnnff(net, x)n = numel(net.layers); % 层数net.layers{1}.a{1} = x; % 网络的第一层就是输入,但这里的输入包含了多个训练图像inputmaps = 1; % 输入层只有一个特征map,也就是原始的输入图像for l = 2 : n % for each layerif strcmp(net.layers{l}.type, 'c') % 卷积层% !!below can probably be handled by insane matrix operations% 对每一个输入map,或者说我们需要用outputmaps个不同的卷积核去卷积图像for j = 1 : net.layers{l}.outputmaps % for each output map% create temp output map% 对上一层的每一张特征map,卷积后的特征map的大小就是 % (输入map宽 - 卷积核的宽 + 1)* (输入map高 - 卷积核高 + 1)% 对于这里的层,因为每层都包含多张特征map,对应的索引保存在每层map的第三维% 所以,这里的z保存的就是该层中所有的特征map了z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);for i = 1 : inputmaps % for each input map% convolve with corresponding kernel and add to temp output map% 将上一层的每一个特征map(也就是这层的输入map)与该层的卷积核进行卷积% 然后将对上一层特征map的所有结果加起来。也就是说,当前层的一张特征map,是% 用一种卷积核去卷积上一层中所有的特征map,然后所有特征map对应位置的卷积值的和% 另外,有些论文或者实际应用中,并不是与全部的特征map链接的,有可能只与其中的某几个连接z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');end% add bias, pass through nonlinearity% 加上对应位置的基b,然后再用sigmoid函数算出特征map中每个位置的激活值,作为该层输出特征mapnet.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});end% set number of input maps to this layers number of outputmapsinputmaps = net.layers{l}.outputmaps;elseif strcmp(net.layers{l}.type, 's') % 下采样层% downsamplefor j = 1 : inputmaps% !! replace with variable% 例如我们要在scale=2的域上面执行mean pooling,那么可以卷积大小为2*2,每个元素都是1/4的卷积核z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid'); % 因为convn函数的默认卷积步长为1,而pooling操作的域是没有重叠的,所以对于上面的卷积结果% 最终pooling的结果需要从上面得到的卷积结果中以scale=2为步长,跳着把mean pooling的值读出来net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);endendend% concatenate all end layer feature maps into vector% 把最后一层得到的特征map拉成一条向量,作为最终提取到的特征向量net.fv = [];for j = 1 : numel(net.layers{n}.a) % 最后一层的特征map的个数sa = size(net.layers{n}.a{j}); % 第j个特征map的大小% 将所有的特征map拉成一条列向量。还有一维就是对应的样本索引。每个样本一列,每列为对应的特征向量net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];end% feedforward into output perceptrons% 计算网络的最终输出值。sigmoid(W*X + b),注意是同时计算了batchsize个样本的输出值net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));end
cnnbp.m
function net = cnnbp(net, y)n = numel(net.layers); % 网络层数% errornet.e = net.o - y; % loss function% 代价函数是 均方误差net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);%% backprop deltas% 这里可以参考 UFLDL 的 反向传导算法 的说明% 输出层的 灵敏度 或者 残差net.od = net.e .* (net.o .* (1 - net.o)); % output delta% 残差 反向传播回 前一层net.fvd = (net.ffW' * net.od); % feature vector deltaif strcmp(net.layers{n}.type, 'c') % only conv layers has sigm functionnet.fvd = net.fvd .* (net.fv .* (1 - net.fv));end% reshape feature vector deltas into output map stylesa = size(net.layers{n}.a{1}); % 最后一层特征map的大小。这里的最后一层都是指输出层的前一层fvnum = sa(1) * sa(2); % 因为是将最后一层特征map拉成一条向量,所以对于一个样本来说,特征维数是这样for j = 1 : numel(net.layers{n}.a) % 最后一层的特征map的个数% 在fvd里面保存的是所有样本的特征向量(在cnnff.m函数中用特征map拉成的),所以这里需要重新% 变换回来特征map的形式。d 保存的是 delta,也就是 灵敏度 或者 残差net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));end% 对于 输出层前面的层(与输出层计算残差的方式不同)for l = (n - 1) : -1 : 1if strcmp(net.layers{l}.type, 'c')for j = 1 : numel(net.layers{l}.a) % 该层特征map的个数% net.layers{l}.d{j} 保存的是 第l层 的 第j个 map 的 灵敏度map。 也就是每个神经元节点的delta的值% expand的操作相当于对l+1层的灵敏度map进行上采样。然后前面的操作相当于对该层的输入a进行sigmoid求导% 这条公式请参考 Notes on Convolutional Neural Networks% for k = 1:size(net.layers{l + 1}.d{j}, 3)% net.layers{l}.d{j}(:,:,k) = net.layers{l}.a{j}(:,:,k) .* (1 - net.layers{l}.a{j}(:,:,k)) .* kron(net.layers{l + 1}.d{j}(:,:,k), ones(net.layers{l + 1}.scale)) / net.layers{l + 1}.scale ^ 2;% endnet.layers{l}.d{j} = net.layers{l}.a{j} .* (1 - net.layers{l}.a{j}) .* (expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]) / net.layers{l + 1}.scale ^ 2);endelseif strcmp(net.layers{l}.type, 's')for i = 1 : numel(net.layers{l}.a) % 第l层特征map的个数z = zeros(size(net.layers{l}.a{1}));for j = 1 : numel(net.layers{l + 1}.a) % 第l+1层特征map的个数z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');endnet.layers{l}.d{i} = z;endendend%% calc gradients% 这里与 Notes on Convolutional Neural Networks 中不同,这里的 子采样 层没有参数,也没有% 激活函数,所以在子采样层是没有需要求解的参数的for l = 2 : nif strcmp(net.layers{l}.type, 'c')for j = 1 : numel(net.layers{l}.a)for i = 1 : numel(net.layers{l - 1}.a)% dk 保存的是 误差对卷积核 的导数net.layers{l}.dk{i}{j} = convn(flipall(net.layers{l - 1}.a{i}), net.layers{l}.d{j}, 'valid') / size(net.layers{l}.d{j}, 3);end% db 保存的是 误差对于bias基 的导数net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);endendend% 最后一层perceptron的gradient的计算net.dffW = net.od * (net.fv)' / size(net.od, 2);net.dffb = mean(net.od, 2);function X = rot180(X)X = flipdim(flipdim(X, 1), 2);end
end
cnnapplygrads.m
function net = cnnapplygrads(net, opts)for l = 2 : numel(net.layers)if strcmp(net.layers{l}.type, 'c')for j = 1 : numel(net.layers{l}.a)for ii = 1 : numel(net.layers{l - 1}.a)% 这里没什么好说的,就是普通的权值更新的公式:W_new = W_old - alpha * de/dW(误差对权值导数)net.layers{l}.k{ii}{j} = net.layers{l}.k{ii}{j} - opts.alpha * net.layers{l}.dk{ii}{j};endendnet.layers{l}.b{j} = net.layers{l}.b{j} - opts.alpha * net.layers{l}.db{j};endendnet.ffW = net.ffW - opts.alpha * net.dffW;net.ffb = net.ffb - opts.alpha * net.dffb;
end
cnntest.m
function [er, bad] = cnntest(net, x, y)% feedforwardnet = cnnff(net, x); % 前向传播得到输出% [Y,I] = max(X) returns the indices of the maximum values in vector I[~, h] = max(net.o); % 找到最大的输出对应的标签[~, a] = max(y); % 找到最大的期望输出对应的索引bad = find(h ~= a); % 找到他们不相同的个数,也就是错误的次数er = numel(bad) / size(y, 2); % 计算错误率
end
Deep Learning论文笔记之(五)CNN卷积神经网络代码理解相关推荐
- Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现
Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文, ...
- Deep Learning论文笔记之(七)深度网络高层特征可视化
Deep Learning论文笔记之(七)深度网络高层特征可视化 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文,但老感 ...
- Deep Learning论文笔记之(三)单层非监督学习网络分析
Deep Learning论文笔记之(三)单层非监督学习网络分析 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文,但老感 ...
- Deep Learning论文笔记之(八)Deep Learning最新综述
Deep Learning论文笔记之(八)Deep Learning最新综述 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文,但老感觉看完 ...
- Deep Learning论文笔记之(六)Multi-Stage多级架构分析
Deep Learning论文笔记之(六)Multi-Stage多级架构分析 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些 ...
- Deep Learning论文笔记之(二)Sparse Filtering稀疏滤波
Deep Learning论文笔记之(二)Sparse Filtering稀疏滤波 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看 ...
- Deep Learning论文笔记之(一)K-means特征学习
Deep Learning论文笔记之(一)K-means特征学习 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文,但老感 ...
- Deep Learning 论文笔记 (2): Neural network regularization via robust weight factorization
under review as a conference paper at ICLR 2015. Motivation: 本文提出来一种regularization的方法,叫做FaMe (Factor ...
- padding和卷积的区别_TensorFlow笔记1——20.CNN卷积神经网络padding两种模式SAME和VALID...
第1种解说:(核心最后一张图,两种填充方式输出的形状尺寸计算公式) 在用tensorflow写CNN的时候,调用卷积核api的时候,会有填padding方式的参数,找到源码中的函数定义如下(max p ...
最新文章
- domain,DNS,冗余,DNS劫持的大致原理
- Java程序设计语言(基础篇)机械工业出版社 原属第10版
- 苹果加入AOM联盟 AV1获全主流生态平台支持
- 实战:Redis 哨兵模式(下)
- numpy 索引多个_Numpy基础三,学习Python编程必备
- C++/C代码审查表
- 《机器人自动化:建模、仿真与控制》——第2章 仿真
- 1.业务层 、服务层、数据层、表现层
- Atitit 搜索蓝牙设备 powershell的实现 java noede.js python 先用脚本语言python nodejs,不好实现。。Java 也不好实现。。 Netcore可以,
- Win10 如何解决,应用商店打不开,错误代码0x80131500问题
- c语言自评报告怎么写,大学生学生自评报告怎么写
- TB,PB, Eb, Zb, YB
- 外贸邮箱能群发吗?用哪个外贸邮箱发开发信回复率高?
- 简述人工智能的应用领域
- SaaSpace:9种最好的免费幻灯片制作工具
- python 日期格式只取年月_python如何只获取日期
- 笔记本电脑C盘变红或变满,怎么处理?
- 宏定义 定义一年多少秒
- 【solr】solr介绍
- 微信实名认证怎么更改?更改微信实名认证只要3个小步骤
热门文章
- 电商双11已臻巅峰,众筹的双11才刚刚开始
- 深度学习的非主流应用
- 如何设置mysql让其他人能访问_怎么配置MySQL数据库让别人远程访问
- 白话Elasticsearch60-数据建模实战_Join datatype 父子关系数据建模
- Redis之字典(hashtable)
- python 顺序栈及基本操作
- java getselectedrow_Java swing(2)自己设计完善的独特功能图书管理系统,获取下拉框等的文字内容等...
- xshell 6和xftp 6的下载和使用
- python3 ftplib_ftplib — FTP protocol client
- session对象不创建会有吗_相亲对象突然不联系了,你知道原因吗?