代码来自github上的一个DeepLearning Toolbox,地址:https://github.com/rasmusbergpalm/DeepLearnToolbox
主要参考这篇博客中的代码注释:
http://blog.csdn.net/zouxy09/article/details/9993743
单步调试后自己添加了一部分注释
如下:
test_example_CNN
function test_example_CNN
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 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
rand('state',0)
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
};
% learning rate 学习率,决定收敛的速度
opts.alpha = 1;
% 把样本split为每50个一份,每用batchsize个样本训练一次,调整一次权值
opts.batchsize = 50;
% split次数
opts.numepochs = 1;
cnn = cnnsetup(cnn, train_x, train_y);
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');
cnnsetup
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共有五层,这里的范围是5
for l = 1 : numel(net.layers) % layer
if 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; % 将偏置初始化为0
end
end
if 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 =