DeepLearnToolbox-master CNN

  没想到,又回来看这代码了,结合文章Notes on Convolutional Neural Networks看效果比较好。

  • 问题:

1)这代码在池化层没有参数,所以这和论文里面有点不一样。但感觉无法明白,池化层有参数(除了激活函数能够增加一些非线性之外,这有意义吗)?

2)对于bp和卷积之类的内容,理解不到位,其实理解还是不到位。

3)参数初始化理解不到位。

4)

5)

  • 创新点:

1)

2)

3)

4)

  • 全文总结:

1)

2)

3)

4)

5)

  • 想法:

1)看看caffe代码,再对比这个代码,以及论文Notes on Convolutional Neural Networks应该效果比较好。

2)

3)

4)

5)

  • 看懂本文,最好需要阅读的相关论文:

1)

2)

3)

4)

5)

6)

7)

  结合别人写的博客,看看效果更好。

先看代码:test_example_CNN.m(依次调用 cnnsetup.m,cnntrain.m,cnntest.m)

function test_example_CNN

clear;close all;clc;
disp('当前正在执行的程序是:');
disp([mfilename('fullpath'),'.m']);

isOctave=0;
addpath(genpath('../data/'));
addpath(genpath('../CNN/'));
addpath(genpath('../Util/'));
load mnist_uint8;%加载数据

%下面的数据train_x为训练数据样本,train_y为训练数据标签。
%下面的数据test_x为测试数据样本,test_y为测试数据标签。
%由于初始的训练和测试样本格式为 nx784 28x28=784,所以把784维改变维度为28x28
%样本都是28x28的图像,样本数据格式为28x28xn(n为样本个数)
%标签数据格式为10xn (10为总类别数,所以除了对应类别为1,其余为0),n为样本个数
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的值为5个单元数组,每个单元数组中都有1个结构体
%而每个结构体中都有字段和值,下面就为定义的不同层(单元数组)对应的字段和值
%这是一个比较老的工具箱,只定义了三种层:i 输入;c 卷积;s 下采样、池化
%'c'的outputmaps是convolution之后有多少张图,也就是有多少个卷积核的意思。(不知道这个卷积到底是遍历卷积还是怎么卷积的)?
%'c'的kernelsize应该就是卷积核的尺寸吧。
%'s'的scale就是池化的尺寸为scale*scale的区域
%cnn结构如下,从后面代码中看到的隐藏信息有:卷积层的步长为1,池化层位非重叠池化(步长就是池化scale的大小)
%cnn最后池化层的输出,拉成一个[192,1](这假定只输入了一个样本),然后输出为10类。192=4x4x12
%一个192x10的全连接网络,sigmoid为激活,值域刚好为[0,1]和标签1正好合适
%可以看出,这是一个非常古典的cnn,能看这代码,对于学习cnn很有帮助!

%特征图谱的尺寸变化为 28(i)24(c)12(s)8(c)4(s)
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的结构,输入层,卷积层,降采样,卷积,降采样


opts.alpha = 1;%学习率
opts.batchsize = 50;%每批训练数据的大小
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);%绘制均方误差曲线
disp(er);%显示误差
assert(er<0.12, 'Too big error');

  cnnsetup.m(初始化网络)

function net = cnnsetup(net, x, y)
    %assert(~isOctave() || compare_versions(OCTAVE_VERSION, '3.8.0', '>='), 
    %['Octave 3.8.0 or greater is required for CNNs as there is a bug in convolution in previous versions. 
    %See http://savannah.gnu.org/bugs/?39314. Your version is ' myOctaveVersion]);
    %x为训练集(28x28x60000),y为训练集对应的标签(10x60000)
    
    %每层输入的特征图谱的层数,也就是上一层的输出的特征图谱的层数,
    %在这个代码中,只有卷积层才会改变网络特征图谱的层数
    %所以在每个卷积层最后,都把inputmaps改为当前层的outputmaps
    %初始的特征图谱就是一张图像,而且只有一个通道,所以 inputmaps = 1
    inputmaps = 1;
    
    %squeeze为移除单一的维度
    %初始化mapsize,为训练集的样本的尺寸,由于取得是第一个样本,所以加入了squeeze来去除第三个维度
    %mapsize作为经过卷积或池化后特征图谱的尺寸,是一个非常重要的参数。
    mapsize = size(squeeze(x(:, :, 1)));
    
    %尤其注意这几个循环的参数的设定
    %net.layers为一个单元数组,按顺序每层一个单元,所以numel(net.layers)为总层数
    for l = 1 : numel(net.layers)   %  layer,numel(net.layers)  表示有多少层
        %对于不同的层,用不同的方法来处理
        %strcmp比较字符串,对于大小写敏感
        if strcmp(net.layers{l}.type, 's')%降采样层
            %subsampling层的mapsize,直接除以scale,非重叠池化
            mapsize = mapsize / net.layers{l}.scale;
            %floor趋向于负无穷,assert生成错误,当条件违反
            %这个语句,就是说明这个代码的卷积和池化每步的尺寸,都是严格设计的
            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;%bias统一设置为0
            end
        end
        %strcmp比较字符串,对于大小写敏感
        if strcmp(net.layers{l}.type, 'c')%卷积层
            %卷积层大小为上层特征图谱大小-核大小+1,就是步长为1的卷积处理,常规的尺寸
            mapsize = mapsize - net.layers{l}.kernelsize + 1; 
            %fan_out为当前层需要学习的参数个数,由于权值共享,每层都有 net.layers{l}.kernelsize ^ 2
            %个参数,乘以层数,就是当前层的参数个数
            fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;
            for j = 1 : net.layers{l}.outputmaps  %  output map
                %fan_out为假定,这层一个特征图谱对应于上层一个特征图谱,但是,这里假定的是这层的每一个特征图谱
                %都和上一层的多个特征图谱相连,所有就是inputmaps * net.layers{l}.kernelsize ^ 2
                %不过具体还是看后面的代码,这也不确定?
                fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;
                for i = 1 : inputmaps  %  input map,对于每一个后层特征图,有多少个参数链到前层
                    %rand:在(0,1)内的标准正态分布,减去0.5,就是(-0.5,0.5),然后乘以2,就是(-1,1)
                    %设置每层的权重,权重设置为:-1~1之间的随机数 * sqrt(6/(输入神经元数量+输出神经元数量))
                    
                    %MATLAB中单元数组的格式为嵌套,多维单元数组是1xn签到者1xm,和数组的mxn不一样,但是调用还是一样的。
                    net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));
                end
                %设置每层的偏置,就是每个输出层,都有一个偏置
                net.layers{l}.b{j} = 0;
            end
            inputmaps = net.layers{l}.outputmaps;%把上一层的输出变成下一层的输入
        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)
    
    %prod为数组中元素的乘积,inputmaps改为了最后一层输出的特征图谱的层数
    %因此这儿的作用就是计算输出层之前那层神经元的个数,fvnum=4×4×12=192
    %fvnum是最后一层输出神经元的个数
    fvnum = prod(mapsize) * inputmaps;
    %输出层的神经元个数,也就是标签数
    onum = size(y, 1);
    %输出层偏置,这里是最后一层神经网络的设定
    net.ffb = zeros(onum, 1);
    %输出层权重,这样定义也是由于最后一层和输出的标签是全连接的
    net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
end

  cnntrain.m(依次调用函数 cnnff.m,cnnbp.m,cnnapplygrads.m)

function net = cnntrain(net, x, y, opts)
%net为网络,x为训练数据,y为标签,opts为训练参数
    %m为样本数量,size(x)=[28*28*60000]
    m = size(x, 3);
    %总样本数除以训练时一批数据包含的图片数量,等于一次样本整体训练,需要的步数
    numbatches = m / opts.batchsize;
    %步数必须是可以整除的
    if rem(numbatches, 1) ~= 0
        error('numbatches not integer');
    end
    net.rL = [];%rL是最小均方差的平滑序列,绘图时使用
    for i = 1 : opts.numepochs%对于样本整体迭代的次数
        disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);%显示当前迭代的次数
        tic;%计开始
        %每次整体训练,都重新打乱样本的顺序
        kk = randperm(m);
        for l = 1 : numbatches%分成numbatches批,MNIST分了50批,训练每个batch
            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);%完成前向过程
            net = cnnbp(net, batch_y);%完成误差传导和梯度计算过程
            net = cnnapplygrads(net, opts);%应用梯度,模型更新
            if isempty(net.rL)%net.L为模型的costfunction,即最小均方误差,net.rL是平滑后的序列
                net.rL(1) = net.L;
            end
            net.rL(end + 1) = 0.99 * net.rL(end) + 0.01 * net.L;
        end
        toc;%计时结束
    end
    
end

  cnnff.m(CNN的前向传播)

function net = cnnff(net, x)
    %网络的层数
    n = numel(net.layers);
    %a是输入map,是一个[28,28,50]的序列,这里就是在每层增加一个单元数组a
    %作为当前层的激活?
    net.layers{1}.a{1} = x;
    %输入的层数,输入的是一个通道的图像
    inputmaps = 1;

    for l = 2 : n   %  for each layer
        %和初始化类似,先判断是c还是s,然后实施对应的处理
        %从第二层开始,所以用的是 if elseif 代码
        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
                %还是步长为1的卷积的基本操作,和初始化设置不同,这里是3维的处理,并且区分了输入和激活
                %对于第二层,也就是第一个卷积层:z=zeros([28,28,50]-[4,4,0]=zeros[24,24,50]
                z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
                for i = 1 : inputmaps   
                    %convolve with corresponding kernel and add to temp output map
                    %这里是后层的每个特征图谱,都和前一层的所有特征图谱相连,这也是方便规范化编程。
                    %虽然在LeNet5中不是这样搞的,但是好像AlexNet之后都是这样搞,好像是方便GPU计算。
                    %这也算搞懂了,为什么里面这两个for是这样编写的,先output后input
                    %一开始其实很不理解这两个for这样编排,从代码整体实验来说,还是这样比较好,LeNet5这样搞
                    %只能是针对某一特定的模型,可以每层都单独编写代码,但是现在的多层NN,这样编写太费劲了
                    %还是统一的实现比较方便。
                    z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
                end
                %add bias, pass through nonlinearity
                %对于上一层所有特征图谱累加的结果,再通过sigmoid,由于sigmoid是单调递增的函数
                %所以这样还是可以区分出不同输入的差异。
                net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
            end
            %  set number of input maps to this layers number of outputmaps
            %由于CNN,只会在卷积层会改变特征图谱的层数,所以在卷积层结尾都改变inputmaps为卷积层的输出
            inputmaps = net.layers{l}.outputmaps;%本层的输出作为下层的输入
        elseif strcmp(net.layers{l}.type, 's')
            %  downsample
            %就是平均池化,不改变输出的层数,所以就是遍历inputmaps
            for j = 1 : inputmaps
                %由于这里的平均池化,都是尺寸为2的平均池化。就等于2x2的卷积核,每个元素都是0.25。
                %所以才会出现下面这样的代码ones(net.layers{l}.scale)为2x2的单位矩阵(元素都为1)
                %net.layers{l}.scale ^ 2=4。所以结果为2x2的矩阵,元素都为0.25,这样也就是平静池化。
                z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');
                %由于上面的卷积操作的步长为1进行的平均池化,但是池化的步长为scale。
                %所以就把计算的结果,从1开始隔一个scale进行采样,就是平均池化的结果。这还是有点巧妙的
                net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
            end
        end
    end

    %concatenate all end layer feature maps into vector
    %下面这个net.fv 就把所有输出的所有特征图谱整合成一个向量
    net.fv = [];
    %numel(net.layers{n}.a) 是输出的特征图谱的层数
    for j = 1 : numel(net.layers{n}.a)
        %取出最后一层,对应的特征图谱的尺寸,[4,4,opts.batchsize]
        %opts.batchsize为每个小批次样本的个数
        sa = size(net.layers{n}.a{j});
        %reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3)) 
        %就是把最后一层对应的特征图谱按照样本拉成列向量
        %然后,由于;,循环下去,就是把每个样本输出的所有特征拉成了一个列向量net.fv
        net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];
    end
    %feedforward into output perceptrons,最后一层的perceptrons,数据识别的结果
    %输出乘以权值加上对应的偏置,再经过一个sigmoid成为了网络最后的输出 o,也就是数据识别的结果
    %net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));%计算输出
    %上面这个代码的效率还是不如下面的高,所以修改了一下。
    net.o = sigm(bsxfun(@plus,net.ffW * net.fv,net.ffb));
end

  cnnbp.m(计算CNN的bp,CNN反向传播的难点都在这,不过这代码里面池化层没有参数,所以简化了很多)

function net = cnnbp(net, y)
    %net为网络的整体参数,y为数据集的标签
    %获取网络的层数,n=5
    n = numel(net.layers);
    %误差,输出值和期望值之差,net.e尺寸为[10,50],50为样本数
    net.e = net.o - y;
    %  loss function,这就是基本的损失函数,均方差
    net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);

    %%  backprop deltas
    %从最后一层的error倒推回来deltas ,和神经网络的bp有些类似  
    %output delta 输出层的残差,由于是最后一层,所以误差是这样单独计算的,这层和bp完全一样
    %由于最后一层为一个简单的全连接的sigmoid,素以乘上了sigmoid的导数
    %变量命名规则,带有d的都为deltas
    %od:output deltas输出的残差
    %fvd:feature vector delta最后特征向量残差
    %其余就是对应层的残差,直接在结构体net对应层中增加单元数组d
    
    %net.od尺寸为[10,50]
    net.od = net.e .* (net.o .* (1 - net.o));
    %  feature vector delta,特征向量误差size=192×50,
    %net.fvd尺寸为[192,50],net.ffW尺寸为[10,192]
    net.fvd = (net.ffW' * net.od);    
    %  only conv layers has sigm function 
    %这个网络从函数cnnff.m可以看出,只有卷积层才有sigmoid处理
    %池化层,z就是a,直接没有经过sigmoid激活
    %所以加入了下面一个激活,但是由于这个网络的结构最后一层为下采样层
    %所以这段代码,应该是作者加入为了方便规划化后面的代码写的
    if strcmp(net.layers{n}.type, 'c')         
        net.fvd = net.fvd .* (net.fv .* (1 - net.fv));%卷积层的误差需要进行求导
    end
    
    %这是算delta的步骤  
    %这部分的计算参看Notes on Convolutional Neural Networks,其中的变化有些复杂  
    %和这篇文章里稍微有些不一样的是这个toolbox在subsampling(也就是pooling层)没有加sigmoid激活函数  
    %所以这地方还需仔细辨别  
    %这个toolbox里的subsampling是不用计算gradient的,而在上面那篇note里是计算了的  
    
    %net.layers{n}.a{1}为最后一层,激活的数据的尺寸
    %专业一点说,就是最后一层特征map的大小。这里的最后一层都是指输出层的前一层  
    %由于最后一层为池化层,而这个代码,池化的特征没有经过sigmoid
    %由于这个网络,最后一层为12层的池化层,所以这里面选择的是12个数据中的其中一个
    %由于小批次尺寸为50,所以这个sa尺寸为[4,4,50]
    sa = size(net.layers{n}.a{1});
    %把最后一层特征map拉成一条向量,对于单个样本来说,特征维数是这样  
    fvnum = sa(1) * sa(2);%fvnum =16
    
    % reshape feature vector deltas into output map style
    %遍历最后一层的所有特征图谱,numel(net.layers{n}.a)为最后一层特征图谱的层数
    %本网络最后一层为12层的池化层,所以 numel(net.layers{n}.a)=12
    for j = 1 : numel(net.layers{n}.a)
        %net.fvd为最后一层残差的反向累加,如果最后一层为池化层,由于没有经过sigmoid,所以直接导入
        %如果最后一层为卷积层,所以要乘以sigmoid的导数
        %net.fvd尺寸为[192,50],sa(1)=4, sa(2)=4, sa(3)=50,192=12x16,fvnum=16
        %一开始为了计算方便,所以反向传输的参数被整成向量的形式,但是对于不同层的特征图谱
        %反向传输计算的残差都是不一样的,所以把矩阵按照对应的层分割开来,这样就按照12层,每层都是4x4=16的特征图谱来reshape
        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 : 1
        %这判断是卷积还是池化层,分开处理
        if strcmp(net.layers{l}.type, 'c')%参见paper,注意这里只计算了'c'层的gradient,因为只有这层有参数
            %如果是卷积层,那么后面是池化层,只要对于池化层的残差进行扩展即可
            %而由于这个网络的设计,池化层不会改变特征图谱的层数,所以直接遍历卷积层的层数就可以
            for j = 1 : numel(net.layers{l}.a)
                %由于残差和激活值都是3维数组(前两维为特征图谱的维度,最后一维为每批次样本个数)
                %所以缩放池化层的前两层的尺寸和卷积层一样(而最后的样本层不变),然后除以一个缩放系数,使得这层整体的残差不变。
                %这样也符合池化的原理,不过这只是对于平均池化的,最大化池化是缩放到相同的尺寸,但是残差保留在对应的位置上
                %由于这个池化层没有激活,所以是这层残差比较简单
                %不知道 这个expand为util文件夹内,作者自己编写的函数,为对应维度的缩放
                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)
                %定义z为当前层(池化层)特征图谱中一层的尺寸,比如第3层位池化层,尺寸为[12,12,50]
                z = zeros(size(net.layers{l}.a{1}));
                %然后遍历后一层特征图谱的层数,潜在的意思,就是卷积过程,后面的每一层都是遍历前一层的所有特征图谱
                %所以bp传输,残差传输也是从后面所有的层传给前面某一层
                for j = 1 : numel(net.layers{l + 1}.a)
                    %卷积层的反向传输就是这样,卷积核先翻转180度,然后convn默认会翻转卷积核180度,这样就是一个相关操作
                    %从计算上,能够理解这样计算结果是正确的,但是还是不能够理解里面的数学原理,不懂反卷积呀。
                     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
    %上面计算残差,下面计算梯度
    %参见paper,注意这里只计算了'c'层的gradient,因为只有这层有参数  
    % 这里与 Notes on Convolutional Neural Networks 中不同,这里的 子采样 层没有参数,也没有激活函数,所以在子采样层是没有需要求解的参数的
    for l = 2 : n
        if strcmp(net.layers{l}.type, 'c')
            for j = 1 : numel(net.layers{l}.a)
                %计算卷积核的导数,卷积层对于前面层的每一个特征图谱都有一个对应的卷积核
                %所以遍历前面层的每一个特征图谱
                for i = 1 : numel(net.layers{l - 1}.a)
                    % dk 保存的是 误差对卷积核 的导数
                    %size(net.layers{l}.d{j}, 3)为每批的样本数
                    %convn(flipall(net.layers{l - 1}.a{i}), net.layers{l}.d{j}, 'valid')
                    %为对两个三维的数组卷积,得到了二维的结果,感觉对于第三维进行了累加。所以除以一个样本个数,得到均值
                    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);
            end
        end
    end
    %计算尾部单层感知器的参数,最后一层perceptron的gradient的计算
    %net.od尺寸为[10,50],net.fv尺寸为[192,50]
    %net.dffW尺寸为[10,192]为计算的最后全连接层权系数的偏差
    net.dffW = net.od * (net.fv)' / size(net.od, 2);%size(net.0d)=50,修改量,求和/50
    %net.dffb尺寸为[10,1],就是样本整体的残差,不过这没有取均值
    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)%枚举改层的每个输出%枚举所有卷积核的net.layers{l}.k{ii}{j}
                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};
                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

  cnntest.m

function [er, bad] = cnntest(net, x, y)
    %输出错误率,错误的索引;输入 训练好的网络,测试样本,测试样本的标签
    %  feedforward
    net = cnnff(net, x);%前向传播
    [~, h] = max(net.o);%找到输出的最大值
    [~, a] = max(y);%找到真实的标签
    bad = find(h ~= a);%找到标签不等的索引

    er = numel(bad) / size(y, 2);%计算错误率。其中y的第二维是测试样本的数量
end

  

posted @ 2015-11-28 17:53  菜鸡一枚  阅读(2251)  评论(0编辑  收藏  举报