DeepLearnToolbox-master CNN源码拙见

DeepLearnToolbox-master CNN下载的源码,下面算是自己的一些注释。
刚刚接触CNN,希望能找到志同道合之人。
源码地址:https://github.com/rasmusbergpalm/DeepLearnToolbox

test_example_CNN.m

% function test_example_CNN
addpath('E:\deeplearningtoolbox\DeepLearnToolbox-master\data');
% addpath(genpath('E:\deeplearningtoolbox\DeepLearnToolbox-master'));
load mnist_uint8;
%load pi100;

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');

% train_x = double(reshape(train_x',28,28,900))/255;
% test_x = double(reshape(test_x',28,28,100))/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
% rng(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
};

%创建卷积神经网络
cnn = cnnsetup(cnn, train_x, train_y);

%设置学习率  小批量的数据集   迭代次数 
opts.alpha = 1;
opts.batchsize = 50;
opts.numepochs = 10;

%训练卷积神经网络
cnn = cnntrain(cnn, train_x, train_y, opts);

[er, bad] = cnntest(cnn, test_x, test_y);

disp(['error rate:' num2str(er)]);
%plot mean squared error
figure; plot(cnn.rL);

assert(er<0.12, 'Too big error');

cnnsetup.m 主要是初始化参数


%神经网络设置,主要是初始化参数
function net = cnnsetup(net, x, y)

    %其实6-9行就是struct('type', 'i')
    %默认为第一层输入的图片深度,并保存以后层的深度
    inputmaps = 1;
    %获取到图片的尺寸  28*28,并保存以后层的尺寸
    mapsize = size(squeeze(x(:, :, 1)));

    %遍历layers创建各个网络层
    for l = 1 : numel(net.layers)
        %如果是池化层
        if strcmp(net.layers{l}.type, 's')
            %池化后的尺寸
            mapsize = mapsize / net.layers{l}.scale;
            %防止不可除
            assert(all(floor(mapsize)==mapsize), ['Layer ' num2str(l) ' size must be integer. Actual: ' num2str(mapsize)]);
            %遍历前一层的深度(也就是当前层的输入maps数),并将当前层的偏置设置为0
            for j = 1 : inputmaps
                net.layers{l}.b{j} = 0;
            end
        end

        % 如果是卷积层
        if strcmp(net.layers{l}.type, 'c')
            % 计算卷积后的图片尺寸
            mapsize = mapsize - net.layers{l}.kernelsize + 1;
            % 当前层的未知参数W的个数
            fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;
            % 遍历卷积的feature map每一层
            for j = 1 : net.layers{l}.outputmaps 
                % 计算输入到输出层的连接   每个map对应的输入w参数(前一层深度*卷积核大小)
                fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;
                % 根据上层map数量来初始化核函数i,一共i*j个卷积核
                for i = 1 : inputmaps 
                    net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));
                end
                % 设置卷积层的偏置为0
                net.layers{l}.b{j} = 0;
            end
            % 更新inputmaps的,将当前卷积层的输出图作为下一层的输入maps数目(池化层不改变maps)
            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)

    %创建全连接层

    %最后一层的n*n尺寸*最后一层的maps,得到总的神经元的数目(以便后面展开全连接)
    fvnum = prod(mapsize) * inputmaps;
    %获取label类别数
    onum = size(y, 1);

    %将输出神经元的偏置为0
    net.ffb = zeros(onum, 1);
    %全连接的w值初始化
    net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
end

cnntrain.m数据训练

function net = cnntrain(net, x, y, opts)
    % size(x,3)获取第三个参数值  当前x=28*28*60000   size(x,3)=60000
    % m 为训练集大小
    m = size(x, 3);

    % 每个周期分多少次训练   opts.batchsize每次训练的数量
    numbatches = m / opts.batchsize;
    if rem(numbatches, 1) ~= 0
        error('numbatches not integer');
    end
    net.rL = [];

    % 循环周期
    for i = 1 : opts.numepochs
        % 输出训练的周期数
        disp(['ZhouQi: ' num2str(i) '/' num2str(opts.numepochs)]);
        tic;
        % 将m个训练实例打乱重排后赋值给kk
        kk = randperm(m);
        % 每个周期分多少次训练
        for l = 1 : numbatches
            % 小批量训练数据
            batch_x = x(:, :, kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));
            batch_y = y(:,    kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));

            % batchsize个训练数据的前向计算 
            net = cnnff(net, batch_x);
            % batchsize个训练数据的BP反向 
            net = cnnbp(net, batch_y);
            % 更新权重
            net = cnnapplygrads(net, opts);

            if isempty(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 前向计算

function net = cnnff(net, x)

    % 获取网络的层数(不包含全连接层)
    n = numel(net.layers);
    % 设置第一层(输入层)为a{1}  矩阵28*28*50
    net.layers{1}.a{1} = x;
    % 第一层的输入map为1
    inputmaps = 1;

    %从二层到全连接层的前一层进行前向计算
    for l = 2 : n   
        if strcmp(net.layers{l}.type, 'c')
            % 只需要对inputmaps进行outputmaps个卷积核计算。
            % 每个卷积核需要与inputmaps进行计算(或每个inputmaps需要与所有的核进行计算)
            for j = 1 : net.layers{l}.outputmaps  
                % 创建一个空的输出maps,z是卷积值  
                % 输入层的维度 - (net.layers{l}.kernelsize - 1)   
                % 这里是三维的,最后一维是批量数
                z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
                %  每次取出一个inputmaps与对应k{i}{j}卷积,
                %  再将卷积的值求和,最后加上偏置,
                %  从而更新出一个outputmaps的50个数据  ?*?*50
                for i = 1 : inputmaps
                    z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
                end
                %  为新的map加入偏置和非线性
                net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
            end
            %  将这层的输出设置为接下来的输入maps
            inputmaps = net.layers{l}.outputmaps;
        elseif strcmp(net.layers{l}.type, 's')
            %  对每一次进行池化
            for j = 1 : inputmaps
                %  卷积一个[0.25,0.25;0.25,0.25]的卷积核,但是需求是2*2选一个,所以第二步就每个两个取一个
                z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');
                net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
            end
        end
    end

    %  最后的池化层转成的矩阵
    net.fv = [];
    %  遍历最后一层的outputmaps,
    %  因为之前定义的outputmaps是公用的,所以只能用a来获取层数
    for j = 1 : numel(net.layers{n}.a)
        %  获取最后池化层的每个maps的大小   4*4*50
        sa = size(net.layers{n}.a{j});
        %  将fv中的数据,排成192*50的矩阵(每次添加16*50,添加outputmaps次)
        net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];
    end
    %  计算最后的输出 net.ffW:10*192  net.fv:192*50  
    %  repmat(a,b,c):将a这个整体复制b行c列。 net.ffb:10*1
    %  repmat(net.ffb, 1, size(net.fv, 2)):将net.ffb复制成1行50列,即:10*50
    net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));

end

cnnbp.m BP反向算法更新w,b(或者说k,b)


% BP反向算法更新w,b
function net = cnnbp(net, y)
    %  获取卷积和池化的层数
    n = numel(net.layers);

    %  全连接层的误差(对a) 10*50
    net.e = net.o - y;
    %  二次代价函数
    net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);

    %  全连接层第二层(10个神经元)的误差(对z)
    net.od = net.e .* (net.o .* (1 - net.o));
    %  全连接层第一层(192个神经元)的误差(对z)
    %  其实也就是最后的池化层的误差
    %  因为最后池化层到全连接层第一层只是展开了
    net.fvd = (net.ffW' * net.od);   
    %  如果最后一层是卷积层
    if strcmp(net.layers{n}.type, 'c') 
        net.fvd = net.fvd .* (net.fv .* (1 - net.fv));
    end

    %  将全连接层第一层的误差转换成4*4的maps的形状
    %  net.layers{n}.a{1}等于4*4*50   net.layers{n}.a是等于12
    sa = size(net.layers{n}.a{1});
    %  fvnum是每个多少个数据转成一个maps   4*4
    fvnum = sa(1) * sa(2);
    %  转成12张maps,每张50个图
    for j = 1 : numel(net.layers{n}.a)
        %  每次取出第(m-1)*fvnum+1~m*fvnum行*50列
        %  转成4*4*50的误差值,这里的误差也就是最后池化层的误差值
        net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum, :), sa(1), sa(2), sa(3));
    end

    %  反向求倒数第二层到正向第二层的误差值
    %  这里的第一层input层在下面没有对应的匹配
    for l = (n - 1) : -1 : 1
        %  如果当前层是卷积层
        if strcmp(net.layers{l}.type, 'c')
            %  获取当前层的outputmap数目
            for j = 1 : numel(net.layers{l}.a)
                %  d是三维的 ?*?*50
                %  σ的导数net.layers{l}.a{j} .* (1 - net.layers{l}.a{j}) 
                %  将pooling层展开到与输入层map相同大小
                %  注意这里的d{j}都是对z的导数
                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')
            %  循环遍历池化层的每一层进行maps计算
            for i = 1 : numel(net.layers{l}.a)
                %  创建l层大小的?*?*50的z值存储(只有卷积层才会有z值)
                z = zeros(size(net.layers{l}.a{1}));
                %  下一层的maps数量
                for j = 1 : numel(net.layers{l + 1}.a)
                    %  对于当前层的每一个maps都与下一层的所有maps进行卷积运算
                    %  所以求导时,需要反过来,将所有下层的d与当前层对应的下层的每个核卷积
                     z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}), 'full');
                end
                %  将所有的与第i个map的卷积之和保存到d{i}
                net.layers{l}.d{i} = z;
            end
        end
    end

    %  计算完误差后,计算梯度
    %  只有卷积层才有w,b,才需要计算梯度
    for l = 2 : n
        if strcmp(net.layers{l}.type, 'c')
            %  逐个outputmaps进行w,b的计算
            %  因为一个outputmaps对应一个w,b
            for j = 1 : numel(net.layers{l}.a)
                %  inputmaps的个数
                %  因为de/dw的需要 上一层的输出*当前层的delta
                for i = 1 : numel(net.layers{l - 1}.a)
                    %  dk{i}{j}  是上层的a{i}和d{j}的卷积  因为正向计算的时候a{i}*k{i}{j}只影响第j张输出maps
                    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
                %  b与i无关,至于j的层数中的每个元素相关,所以同意求和,再除以50组
                net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) / size(net.layers{l}.d{j}, 3);
            end
        end
    end

    %  计算全连接层的dw和db
    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')
            %  获取当前层的outputmaps
            for j = 1 : numel(net.layers{l}.a)
                %  获取输入maps
                for ii = 1 : numel(net.layers{l - 1}.a)
                    %  更新权重 w - u*delta
                    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

    %  更新全连接层w,b
    net.ffW = net.ffW - opts.alpha * net.dffW;
    net.ffb = net.ffb - opts.alpha * net.dffb;
end
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ListenAlone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值