本程序是来自deepLearnToolbox-master里面的经典cnn程序
看了前面2篇关于卷积神经网络的博客,应该对卷积神经网络有了大概的了解吧!如下,我将介绍我个人所学的东西。
首先CNN的整体网络结构为:
在了解卷积神经网络前,首先要理解卷积是如何进行计算的:
一.比如次博客的:http://blog.csdn.net/what_lei/article/details/49019049关于对convn讲解的就很好,
其中conv2就是convn为2维时的卷积。
语法格式:
w=convn(u,v);
计算矩阵u,v的卷积,w的尺寸为size(u)+size(v)-1;
w=convn(u,v,'shape');
返回卷积的一部分,这部分有参数shape决定:
full 返回完整的卷积(默认);
same 返回卷积的中心部分,与u有相同的大小;
valid 仅返回卷积中的那些被计算而没有填充零的部分,w的尺寸大小为max(size(u)-size(v)+1,0).
示例见:
http://blog.csdn.net/what_lei/article/details/49019049关于对convn讲解
CNN学习的代码注释:
1.主程序:
test_example_CNN.m
%function test_example_CNN
clc;
clear all;
load mnist_uint8;
train_x = double(reshape(train_x',28,28,60000))/255;%训练样本60000张,每张图片大小为28*28,并把数据归一化
test_x = double(reshape(test_x',28,28,10000))/255;%测试样本10000张,每张图片大小为28*28,并把数据归一化
train_y = double(train_y');%训练样本的标签
test_y = double(test_y');%测试样本的标签
% imshow(train_x(:,:,1)');
% [~,index]=max(train_y(:,1));
% num=index-1
%% ex1 Train a 6c-2s-12c-2s Convolutional neural network
%will run 1 epoch in about 200 second and get around 11% error.
%With 100 epochs you'll get around 1.2% error
cnn.layers = {
struct('type','i')%input layer
struct('type','c','outputmaps', 6,'kernelsize', 5)%convolution layer
struct('type','s','scale', 2)%sub sampling layer
struct('type','c','outputmaps', 12,'kernelsize', 5)%convolution layer
struct('type','s','scale', 2)%subsampling layer
};
cnn = cnnsetup(cnn, train_x, train_y); %初始化cnn网络
opts.alpha = 1;%学习率为1,及相当于不用
opts.batchsize = 50;%对数据批处理的样本数量
opts.numepochs = 10;%迭代次数1
cnn = cnntrain(cnn, train_x, train_y, opts);
[er, bad] = cnntest(cnn, test_x, test_y);
%plot mean squared error
figure;
plot(cnn.rL);
assert(er<0.12, 'Too big error');%0.111 0.1179 0.0436(迭代5次)
er
cnnsetup.m
function net = cnnsetup(net, x, y)%x为输入的数据,y为对应的标签,net为网络
inputmaps = 1;
mapsize = size(squeeze(x(:, :, 1)));%每张图片的尺寸为28*28,,squeeze(x(:, :, 1))移除单一维,具体看help squeeze
% mapsize 显示图片的尺寸
for l = 1 : numel(net.layers) % layer numel该语句返回数组中元素的总数numel(cnn.layers)=5
if strcmp(net.layers{l}.type,'s')%两个字符串相等的时候为1,否则为0 为s的往下执行,s表示的是降采样subsampling
mapsize = mapsize / net.layers{l}.scale;%scale=2,由28*28变为14*14
%如果不满足all(floor(mapsize)==mapsize),则会提示错误。
assert(all(floor(mapsize)==mapsize), ['Layer ' num2str(l)' size must be integer. Actual: ' num2str(mapsize)]);
for j = 1 : inputmaps
net.layers{l}.b{j} = 0;%偏置初始化为0
end
end
if strcmp(net.layers{l}.type,'c')%两个字符串相等的时候为1,否则为0 为c的往下执行,c表示的是卷积层
mapsize = mapsize - net.layers{l}.kernelsize + 1;%卷积层的特征映射尺寸为28-5+1即24*24
fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;%1*6*5*5
%fan_out该层的所有连接的数量=卷积核数*卷积核的尺寸(m*n)
%对于卷积核权重,输入输出为fan_in, fan_out
for j = 1 : net.layers{l}.outputmaps % output map
fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;%1*5*5
for i = 1 : inputmaps % input map
%产生0到1的随机数,减去0.5,即变成-0.5到0.5的随机数,再乘以2,再乘以sqrt(6/6*5*5+1*5*5),第二层卷积则为sqrt(6/(12*5*5+6*5*5))
net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));%权重 来自bengio的Understanding the difficulty of training deep feedforward neuralnetworks
end
net.layers{l}.b{j} = 0;%偏置依旧初始化为0
end
inputmaps = net.layers{l}.outputmaps;%运行一次后inputmaps变成6了,第二次变为12
end
end
% 'onum' is the number of labels, that's why it is calculated using size(y, 1). If you have 20 labels so the output of the network will be 20 neurons.
% 'fvnum' is the number of output neurons at the last layer, the layer just before the output layer.
% 'ffb' is the biases of the output neurons.
% 'ffW' is the weights between the last layer and the output neurons. Note that the last layer is fully connected to the output layer, that's why the size of the weights is (onum * fvnum)
fvnum = prod(mapsize) * inputmaps;%mapsize变成4*4了的inputmaps为12,fvnum是最后一层的输出神经元
% inputmaps
% mapsize
onum = size(y, 1);%标签数目
net.ffb = zeros(onum, 1);%偏置的输出神经元
net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));%最后一层与输出层是全连接,ffW代表的就是权重
end
cnntrain.m
function net = cnntrain(net, x, y, opts)
% net=cnn;
% x=train_x;
% y=train_y;
m = size(x, 3);%m表示训练样本的个数,为60000
numbatches = m / opts.batchsize;%批量处理的次数为1200,每次批量处理50张图片
if rem(numbatches, 1) ~= 0%如果不是整数,则会报错
error('numbatches not integer');
end
net.rL = [];
for i = 1 : opts.numepochs %迭代次数
disp(['epoch ' num2str(i)'/' num2str(opts.numepochs)]);
tic;
kk = randperm(m);
for l = 1 : numbatches%批量处理的次数为1200
%每次批量处理50张图片
batch_x = x(:, :, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));
batch_y = y(:, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));
%信号前向传导过程feedforward
net = cnnff(net, batch_x);
%计算误差反向传导,backpropagation
net = cnnbp(net, batch_y);
%更新模型
net = cnnapplygrads(net, opts);
if isempty(net.rL)
net.rL(1) = net.L;%% net.L为50个样本的误差函数
end
net.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.L;
end
toc;
end
end
cnnff.m
function net = cnnff(net, x)
n = numel(net.layers);%cnn网络的层数
% net.layers{1}.a{1}=batch_x;
net.layers{1}.a{1} = x;%第一层的输入,x包含有批量处理的50张图片
inputmaps = 1;%输入层只有一个特征map
for l = 2 : n % for each layer
if strcmp(net.layers{l}.type,'c') %如果是卷积层,则执行如下程序
% !!below can probably be handled by insane matrix operations
for j = 1 : net.layers{l}.outputmaps % for each output map
% create temp output map
z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);%[28 28 50]-[4 4 0]
for i = 1 : inputmaps % for each input map
% convolve with corresponding kernel and add to temp output map
z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j},'valid');%k{i}{j}表示的是k里的第i个,然后再里面找出第j个矩阵
end
% add bias, pass through nonlinearity
net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});%sigmoid(Wx+b) 6个a 24*24*50的卷积特征
end
% set number of input maps to this layers number of outputmaps
inputmaps = net.layers{l}.outputmaps;%第一次输出为6个卷积特征z
elseif strcmp(net.layers{l}.type,'s')
% downsample
for j = 1 : inputmaps
z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2),'valid'); % !! replace with variable 23*23*50
net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);%第一次降采样成12*12*50
end
end
end
% concatenate all end layer feature maps into vector
net.fv = [];%最后一层进行全连接
for j = 1 : numel(net.layers{n}.a) %numel(net.layers{n}.a)=12
sa = size(net.layers{n}.a{j});%[4 4 50]
net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))]; % net.fv相当于教程中的a,其中a=f(z)的
end
%size(net.fv)=[192 50] 12个 4*4 *50 12*16=192
% feedforward into output perceptrons
net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));%ffW为10*192 在cnnsetup里面进行的随机初始化
%net.ffW [10 192] net.fv为:[192 50]的矩阵
end
cnnbp.m
此程序参考的资料见:Notes on Convolutional Neural Networks, Jake Bouvrie
function net = cnnbp(net, y)
%具体推导过程参见:http://deeplearning.stanford.edu/wiki/index.php/反向传导算法
% y=batch_y;
n = numel(net.layers);%n=5
% error
net.e = net.o - y;%误差
% loss function
net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);%50个样本的误差函数
%% backprop deltas
net.od = net.e .* (net.o .* (1 - net.o)); % output delta (net.o .* (1 - net.o))表示的是f(zi)对zi求导的结果 net.e=-(yi-ai) 输出的delta,输出层室友sigmoid函数的,所以要求导
net.fvd = (net.ffW' * net.od); % feature vector delta 隐藏层的delta,不过还要乘以f'(z),但是cnn中网络的采样层没有sigmoid函数,所以不需要乘以f'(z)=(1-f(z))*f(z)
if strcmp(net.layers{n}.type,'c') % only conv layers has sigm function
net.fvd = net.fvd .* (net.fv .* (1 - net.fv));% net.fv相当于教程中的a,其中a=f(z),但是cnn中网络的卷积层每次输出都有sigmoid函数,所以需要乘以f'(z)=(1-f(z))*f(z)
haha=1%整个程序并没有执行就是因为最后一层是采样层,不是卷积层
end
% reshape feature vector deltas into output map style
sa = size(net.layers{n}.a{1});%sa[4 4 50],即最后的采样层的大小
fvnum = sa(1) * sa(2);
for j = 1 : numel(net.layers{n}.a) %numel(net.layers{n}.a)=12 共有12个不同权重
net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));%每一个都是16*50,即4*4*50 d保存的就是对应层(n层)的delta
end
for l = (n - 1) : -1 : 1
if strcmp(net.layers{l}.type,'c')%公式(9)
for j = 1 : numel(net.layers{l}.a) % numel(net.layers{4}.a)=12张8*8的图片 [net.layers{l + 1}.scale net.layers{l + 1}.scale 1]=[2 2 1] d{j}=12个4*4*50的矩阵 net.layers{l + 1}.scale ^ 2=4
% net.layers{l}.d{j}与采样层net.layers{l + 1}.d{j}有关
net.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);%
end
elseif strcmp(net.layers{l}.type,'s')
for i = 1 : numel(net.layers{l}.a) %numel(net.layers{l}.a)=6
z = zeros(size(net.layers{l}.a{1})); %a{1}为6个12*12*50的矩阵
for j = 1 : numel(net.layers{l + 1}.a)%numel(net.layers{l + 1}.a)=12
z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}),'full');
end
net.layers{l}.d{i} = z;
end
end
end
%% calc gradients
for l = 2 : n %hl为卷积层
if strcmp(net.layers{l}.type,'c')%l=2时
for j = 1 : numel(net.layers{l}.a)% numel(net.layers{l}.a)=6
for i = 1 : numel(net.layers{l - 1}.a)%numel(net.layers{l - 1}.a)=1
%flipall相当于rot180,将矩阵旋转180度
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);%公式12
end
net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);%size(net.layers{l}.d{j}, 3)=50,每张24*24*50小图片的平均误差
end
end
end
%参见机器学习20讲里面的第180面的最小面公式(hl为全连接)
net.dffW = net.od * (net.fv)' / size(net.od, 2);%size(net.od, 2)=50 net.fv:前向网络求出的参数
net.dffb = mean(net.od, 2);%对行取平均 net.od为输出的delta
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)
net.layers{l}.k{ii}{j} = net.layers{l}.k{ii}{j} - opts.alpha * net.layers{l}.dk{ii}{j};
end
net.layers{l}.b{j} = net.layers{l}.b{j} - opts.alpha * net.layers{l}.db{j};
end
end
end
net.ffW = net.ffW - opts.alpha * net.dffW;
net.ffb = net.ffb - opts.alpha * net.dffb;
end
一些小的子函数:
sigm.m
function X = sigm(P)
X = 1./(1+exp(-P));
end
rot180.m
function X = rot180(X)
X = flipdim(flipdim(X, 1), 2);
end
flipall.m
function X=flipall(X)
for i=1:ndims(X)
X = flipdim(X,i);
end
end
expand.m
function B = expand(A, S)
%EXPAND Replicate and tile each element of an array, similar to repmat.
% EXPAND(A,SZ), for array A and vector SZ replicates each element of A by
% SZ. The results are tiled into an array in the same order as the
% elements of A, so that the result is size: size(A).*SZ. Therefore the
% number of elements of SZ must equal the number of dimensions of A, or in
% MATLAB syntax: length(size(A))==length(SZ) must be true.
% The result will have the same number of dimensions as does A.
% There is no restriction on the number of dimensions for input A.
%
% Examples:
%
% A = [1 2; 3 4]; % 2x2
% SZ = [6 5];
% B = expand(A,[6 5]) % Creates a 12x10 array.
%
% The following demonstrates equivalence of EXPAND and expansion acheived
% through indexing the individual elements of the array:
%
% A = 1; B = 2; C = 3; D = 4; % Elements of the array to be expanded.
% Mat = [A B;C D]; % The array to expand.
% SZ = [2 3]; % The expansion vector.
% ONES = ones(SZ); % The index array.
% ExpMat1 = [A(ONES),B(ONES);C(ONES),D(ONES)]; % Element expansion.
% ExpMat2 = expand(Mat,SZ); % Calling EXPAND.
% isequal(ExpMat1,ExpMat2) % Yes
%
%
% See also, repmat, meshgrid, ones, zeros, kron
%
% Author: Matt Fig
% Date: 6/20/2009
% Contact: popkenai@yahoo.com
if nargin < 2
error('Size vector must be provided. See help.');
end
SA = size(A); % Get the size (and number of dimensions) of input.
if length(SA) ~= length(S)
error('Length of size vector must equal ndims(A). See help.')
elseif any(S ~= floor(S))
error('The size vector must contain integers only. See help.')
end
T = cell(length(SA), 1);
for ii = length(SA) : -1 : 1
H = zeros(SA(ii) * S(ii), 1); % One index vector into A for each dim.
H(1 : S(ii) : SA(ii) * S(ii)) = 1; % Put ones in correct places.
T{ii} = cumsum(H); % Cumsumming creates the correct order.
end
B = A(T{:}); % Feed the indices into A.