课程作业记录8:基于CNN的MNIST数据集手写数字识别/Matlab仿真

前情提要:
数据集的读取方法写在 -> https://blog.csdn.net/Carriefuu/article/details/102702770

本文章中涉及的大部分代码来源于 -> https://github.com/FenHua/CNN
cnnff注释来源于 -> https://blog.csdn.net/u013088062/article/details/48662303
cnnbp注释来源于 -> https://blog.csdn.net/u013088062/article/details/48662349
cnngrads注释来源于 -> https://blog.csdn.net/u013088062/article/details/48662397
感谢以上大佬,感恩的心,感谢大佬。
我在做这个设计时,主要还是整理了代码的结构,自己写了测试函数,现在写个博客记录一下,哈哈。

  1. CNN结构
    我们做的是LeNet-5卷积神经网络。LeNet-5卷积神经网络,包括输入层,卷积层,池化层和全连接层共有6 层卷积核大小为 5×5,采用均方误差作为loss,sigmod作为激活函数。其网络模型如图1所示。输入层负责接收输入,它是由28×28的矩阵组成。接着是卷积层 C1,它由5个大小为 24×24 的特征图组成,特征图是由卷积核对图像进行卷积运算后,通过激活函数作用形成的。采样层 S2是由5个12×12的特征图组成,然后采用mean-pooling方式对 C1 区域内2×2个像素求取均值,然后通过激活函数输出结果。卷积层C2,它是由10个大小为8×8的特征图组成,每个特征图接收采样层S2若干个特征连接。 FC层由160个大小为1×1的特征图组成。输出层是0-9共10个字符组成的神经元。
    LetNet-5神经网络结构具体的概念大家自己查查资料,我这里主要记代码。

  2. 代码

2.1 CNNmain

% 基于CNN网络实现分类
% 本函数为主函数
% 建议代码阅读顺序:readDATA -> transformDATA -> cnnsetup -> cnntrain
% -> cnnff -> cnnbp -> cnngrads -> cnntest -> CNNmain -> analyze/imagetest
% 参考资料:https://github.com/FenHua/CNN

clear all
close all

[train_data, train_labels, test_data, test_labels] = readDATA();
[train_x,train_y,test_x,test_y]=transformDATA();

cnn.layers={struct('type','i')% input layer 输入层
    struct('type','c','outputmaps',5,'kernelsize',5)% convolution layer 卷积层
    struct('type','s','scale',2)%sub sampling layer  下采样层
    struct('type','c','outputmaps',10,'kernelsize',5)%convolution layer 卷积层
    struct('type','s','scale',2)%subsampling layer  下采样层
    };

opts.numepochs=1;% numepochs迭代次数
opts.batchsize=10;% 每批训练中样本的数量
% Attention:
% opts.batchsize这个数据指代的是我们跑这总共60000个数据,多少个数据为一组,每一组我们都会更新一次卷积核
% 后面测试自己的手写数字时,batchsize需要和我们的输入量保持一致
% opts.numepochs这个数据指代的是我们跑这60000个数据迭代多少次,即每一次我们都是跑了60000个数据
% 但它与卷积核更新频率没有直接联系
% 分析正确率 -> 主opts.batchsize,次要opts.numepochs
% 注意过拟合现象,改善过拟合方法可参考:https://blog.csdn.net/taoyanqi8932/article/details/71101699

opts.alpha=1;  % alpha学习率,一般设定为1
rng('default');
% 恢复matlab启动时默认的全局随机流
% 即matlab启动时,会用一个默认的随机数生成器产生很多0到1之间的伪随机数
% 全局随机流,rand等函数产生的随机数均来源于此
% 在matlab启动期间,任何分布的随机数组都是该全局随机流中的数据,当然也可使用其他随机数生成器

% errr = analyze(cnn,train_x,train_y,test_x,test_y,opts);
% 该行代码用于优化错误率
cnn=cnnsetup(cnn,train_x,train_y);
disp('开始训练CNN')
[cnn,loss]=cnntrain(cnn,train_x,train_y,opts);
disp('开始测试CNN')
[err_rate,err_num]=cnntest(cnn,test_x,test_y);
disp(['最终分类错误率:' num2str(err_rate*100),'%']);
answer = imagetest(cnn);
disp(['输入的手写数字识别结果为:',num2str(answer)]);

2.2 cnntrain

% 本函数功能为:
% 通过卷积神经网络训练数据

% 本函数涉及的参数为: 
% 输入:x -> 训练图像数据;y -> 训练图像数据标签
% net -> 训练前的卷积神经网络;opts -> 卷积神经网络相关参数
% 输出:net -> 训练后的卷积神经网络;L -> 代价

% 参考资料:https://blog.csdn.net/u013088062/article/details/48662303

function [net,L]=cnntrain(net,x,y,opts)

m=size(x,3); % 返回训练数据第三个维度的长度,训练数据维度小于3则返回1
% numbatches -> 将总训练个数分成的批数
% opts.batchsize -> 每批训练中样本数量
numbatches=m/opts.batchsize; 
if rem(numbatches,1)~=0
    % 返回余数
    error('给定的每批训练样本数量不符合要求');
end

% numepochs -> 迭代次数
L=zeros(opts.numepochs*numbatches,1); % 大小为每批样本数量乘以迭代次数
n=1;

for i=1:opts.numepochs % 迭代循环
    
    tic; % 记录当前时间
    kk=randperm(m); % 返回一个行向量,其中包含从1到n的整数的随机排列
    
    for k=1:numbatches % 每批循环
        
        batch_x=x(:,:,kk((k-1)*opts.batchsize+1:k*opts.batchsize));
        batch_y=y(:,kk((k-1)*opts.batchsize+1:k*opts.batchsize));
        % 按顺序选取的一个28*28的图像数据,及其对应的标签
        
        net=cnnff(net,batch_x);% 使用当前的神经网络进行训练
        net=cnnbp(net,batch_y);% bp算法训练神经网络
        net=cnngrads(net,opts);% 权值更新
        
        L(n)=net.loss; % 代价
        n=n+1;
        
    end
    
    t=toc;% 记录程序完成时间
    
    str_perf=sprintf('; 本批训练数据 err= %f',net.loss);
    disp(['CNN train:epoch ' num2str(i) '/' num2str(opts.numepochs) '.Took' num2str(t) ' second''. 本批训练集的均方误差为 ' num2str(mean(L((n-numbatches):(n-1)))) str_perf]);

end
end

% 1.sigm函数
% 功能:一个计算公式
function X=sigm(P)
X=1./(1+exp(-P));
end

% 2.rot180函数
% 功能:返回一个相同大小的矩阵,但是每个维度的数据都在各自维度上翻转
function X=rot180(X)
X=flip(flip(X,1),2);
end

% 3.expand函数
% 功能:相当于对l+1层的灵敏度map进行上采样,然后前面的操作相当于对该层的输入a进行sigmoid求导
function B=expand(A,S)

SA=size(A);% 记录矩阵A的大小

if length(SA)~=length(S)
    error('Err:SIZE向量的长度必须与矩阵维度相等。');
elseif any(S~=floor(S))
    error('Err:SIZE向量只能包含整数');
end

T=cell(length(SA),1); % 生成与矩阵相同维度长度的列向量,每一个为0x0double的cell型数据

for i=length(SA):-1:1
    
    H=zeros(SA(i)*S(i),1);
    H(1:S(i):SA(i)*S(i))=1;
    % 相应位置置1
    T{i}=cumsum(H);
    % 累加函数,按列进行计算
end

B=A(T{:});% 将索引输入A

end

% 4.flipall函数
% 功能:将每一个维度的数据均反转
function X=flipall(X)
for i=1:ndims(X)% ndims -> X的维度
    X=flip(X,i);
    % 如果A是矩阵,则flip(A,1)反转每一列中的元素,而flip(A,2)反转每一行中的元素。
end
end

2.3 cnnsetup

% 本函数功能为:
% 建立卷积层和池化层

% 本函数涉及的参数为: 
% 输入:net -> 初始设定的卷积神经网络参数
% x -> 训练图像数据;y -> 训练图像数据标签
% 输出:net -> 建立卷积层和池化层后的卷积神经网络

function net=cnnsetup(net,x,y)

inputmaps=1;  % 输入图片数量
mapsize=size(squeeze(x(:,:,1))); % 获取训练图像的大小  

% squeeze可以去除x中只有一个的维度,即将x(:,:,1)变为x(:,:)
% size函数返回矩阵大小,即mapsize:x(:,:)的行数,列数(为一个行向量)

% 下面通过传入net这个结构体来逐层构建CNN网络
for l=1:numel(net.layers)%layer  返回层数
    
    if strcmp(net.layers{l}.type,'s')% 如果这层是下采样层 
       
        %比较字符串,若net.layers{l}.type与S完全相等则返回1,否则返回0  
        
        mapsize=mapsize/net.layers{l}.scale;  %除以尺度
        assert(all(floor(mapsize)==mapsize),['层' num2str(l) '的大小必须为整数。实际:' num2str(mapsize)]);
        % 层大小必须为整数
        % floor函数:朝负无穷大方向取整  
        % all(A):A为行向量,如果值均不为0,则返回1,反之,返0
        % assert函数:如果all(floor(mapsize)==mapsize)不为1,
        % 则输出['层' num2str(l) '的大小必须为整数。实际:' num2str(mapsize)]
        
        for j=1:inputmaps
            net.layers{l}.b{j}=0;  %将偏置初始化为0
        end
    end
    
    if strcmp(net.layers{l}.type,'c')  % 如果这层是卷积层,且步长为1  
        
        % outputmaps表示卷积核的个数
        mapsize=mapsize-net.layers{l}.kernelsize+1;
        fan_out=net.layers{l}.outputmaps*net.layers{l}.kernelsize^2;
        
        % outputmaps表示卷积核的个数,fan_out表示卷积层需要的总参数个数
        for j=1:net.layers{l}.outputmaps % 遍历每个卷积核
            fan_in=inputmaps*net.layers{l}.kernelsize^2; % 所有输入图片每个卷积核需要的参数个数
            
            for i=1:inputmaps % inputmaps为每张图片的每个卷积核随机初始化权值
                % 每个卷积核的权值是一个kernelsize*kernelsize的矩阵
                % rand(n)是产生n×n的 0-1之间均匀取值的数值的矩阵,再减去0.5就相当于产生-0.5到0.5之间的随机数  
                % 再 *2 就放大到 [-1, 1] 
                % 反正就是将卷积核每个元素初始化为[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))]  
                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; % 只有在卷积层的时候才会改变特征map的个数,pooling的时候不会改变个数只改变大小。
        %这层输出的特征map个数就是输入到下一层的特征map个数 
        
    end
end

% fvnum 是输出层的前面一层的神经元个数
% 这一层的上一层是经过pooling后的层,包含有inputmaps个特征map
% 每个特征map的大小是mapsize
% 所以,该层的神经元个数是 inputmaps * (每个特征map的大小)  
% 在这里 mapsize = [特征map的行数 特征map的列数],所以prod后就是特征map的行*列  
fvnum=prod(mapsize)* inputmaps;

% onum 是标签的个数,也就是输出层神经元的个数。你要分多少个类,自然就有多少个输出神经元 
onum=size(y,1);

% 这里是最后一层神经网络的设定,即全连接层
% ffb 是输出层每个神经元对应的基biases 
net.ffb=zeros(onum,1);
net.ffW=(rand(onum,fvnum)-0.5)*2*sqrt(6/(onum+fvnum));
% ffW 输出层前一层与输出层连接的权值,这两层之间是全连接的

end

2.4 cnnff

% 本函数的功能为:
% 使用当前的神经网络对输入的向量进行预测

% 本函数涉及的参数为:
% 输入:net -> 待训练的神经网络;x -> 训练数据矩阵(我们期望得到的数据)
% 输出:net -> 训练好的神经网络

% 参考资料:https://blog.csdn.net/u013088062/article/details/48662303

function net =cnnff(net,x)

n=numel(net.layers);% 层数
net.layers{1}.a{1}=x;% 网络的第一层就是输入,但这里的输入包含了多个训练图像
inputmaps=1;% 输入层只有一个特征map,也就是原始的输入图像

for l=2:n
    % 每层循环
    if strcmp(net.layers{l}.type,'c')
        % 如果当前是卷积层
        
        for j=1:net.layers{l}.outputmaps
            % 对每一个输入map,需要用outputmaps个不同的卷积核去卷积图像
            z=zeros(size(net.layers{l-1}.a{1})-[net.layers{l}.kernelsize-1 net.layers{l}.kernelsize-1 0]);
            % 用这个公式生成一个零矩阵,作为特征map
            % 对于上一层的每一张特征map,卷积后的特征map的大小是:(输入map宽 - 卷积核的宽 + 1)* (输入map高 - 卷积核高 + 1)
            % 由于每层都包含多张特征map,则对应的索引则保存在每层map的第三维,及变量Z中
            
            for i=1:inputmaps
                % 对每个输入的特征map
                % 将上一层的每一个特征map(也就是这层的输入map)与该层的卷积核进行卷积
                % 进行卷积
                % 加上对应位置的基b,然后再用sigmoid函数算出特征map中每个位置的激活值,作为该层输出特征map
                z=z+convn(net.layers{l-1}.a{i},net.layers{l}.k{i}{j},'valid');
            end
            
            net.layers{l}.a{j}=sigm(z+net.layers{l}.b{j});% 加基(加上加性偏置b)
            
        end
        
        inputmaps=net.layers{l}.outputmaps; % 更新当前层的map数量
        
    elseif strcmp(net.layers{l}.type,'s')% 如果当前层是下采样层
        
        for j=1:inputmaps
            % 对特征map进行下采样
            % 进行卷积
            % 最终pooling的结果需要从上面得到的卷积结果中以scale=2为步长,跳着把mean pooling的值读出来
            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

% 输出层,将最后一层得到的特征变成一条向量,作为最终提取得到的特征向量
% 获取倒数第二层中每个特征map的尺寸
% 用reshape函数将map转换为向量的形式
% 使用sigmoid(W*X + b)函数计算样本输出值,放到net成员o中

net.fv=[];% net.fv为神经网络倒数第二层的输出map

for j=1:numel(net.layers{n}.a)% 最后一层的特征map的个数
    sa=size(net.layers{n}.a{j}); % 第j个特征map的大小
    net.fv=[net.fv;reshape(net.layers{n}.a{j},sa(1)*sa(2),sa(3))];
end

net.o=sigm(net.ffW*net.fv+repmat(net.ffb,1,size(net.fv,2)));% 通过全连接层的映射得到网络的最终预测结果输出

end

% 1.sigm函数
% 功能:一个计算公式
function X=sigm(P)
X=1./(1+exp(-P));
end

% 2.rot180函数
% 功能:返回一个相同大小的矩阵,但是每个维度的数据都在各自维度上翻转
function X=rot180(X)
X=flip(flip(X,1),2);
end

% 3.expand函数
% 功能:相当于对l+1层的灵敏度map进行上采样,然后前面的操作相当于对该层的输入a进行sigmoid求导
function B=expand(A,S)

SA=size(A);% 记录矩阵A的大小

if length(SA)~=length(S)
    error('Err:SIZE向量的长度必须与矩阵维度相等。');
elseif any(S~=floor(S))
    error('Err:SIZE向量只能包含整数');
end

T=cell(length(SA),1); % 生成与矩阵相同维度长度的列向量,每一个为0x0double的cell型数据

for i=length(SA):-1:1
    
    H=zeros(SA(i)*S(i),1);
    H(1:S(i):SA(i)*S(i))=1;
    % 相应位置置1
    T{i}=cumsum(H);
    % 累加函数,按列进行计算
end

B=A(T{:});% 将索引输入A

end

% 4.flipall函数
% 功能:将每一个维度的数据均反转
function X=flipall(X)
for i=1:ndims(X)% ndims -> X的维度
    X=flip(X,i);
    % 如果A是矩阵,则flip(A,1)反转每一列中的元素,而flip(A,2)反转每一行中的元素。
end
end

2.5 cnnbp

% 本函数的功能为:
% 通过bp算法训练神经网络函数

% 本函数涉及到的参数为:
% 输入:net -> 待训练的神经网络;y -> 训练数据标签(我们期望得到的数据)
% 输出:net -> 经bp训练后的神经网络

% 参考资料:https://blog.csdn.net/u013088062/article/details/48662349

function net=cnnbp(net,y)

n=numel(net.layers);
net.e=net.o-y;% 实际输出与期望输出之间的误差
net.loss=0.5*sum(net.e(:).^2)/size(net.e,2);% 代价函数,采用均方误差函数作为代价函数

net.od=net.e .*(net.o .*(1-net.o));% 输出层的灵敏度或者残差,(net.o .* (1 - net.o))代表输出层的激活函数的导数
net.fvd=(net.ffW'*net.od);% 残差反向传播回前一层,net.fvd保存的是残差
if strcmp(net.layers{n}.type,'c')
    % sigm这个公式只针对卷积层
    net.fvd=net.fvd.*(net.fv .*(1-net.fv));% net.fv是前一层的输出(未经过sigm函数),作为输出层的输入
end

% 将输出的残差扩展成与最后一层的特征map相同的尺寸形式
sa=size(net.layers{n}.a{1}); % 最后一层特征map的大小。这里的最后一层都是指输出层的前一层
fvnum=sa(1)*sa(2);% 因为是将最后一层特征map拉成一条向量,所以对于一个样本来说,特征维数是这样

for j=1:numel(net.layers{n}.a)% 最后一层的特征map的个数
    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')% 如果是卷积层,则进行上采样
        for j=1:numel(net.layers{l}.a)% 该层特征map的个数
            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);
            % net.layers{l}.d{j} 保存的是 第l层 的 第j个 map 的 灵敏度map。 也就是每个神经元节点的delta的值
            % expand的操作相当于对l+1层的灵敏度map进行上采样。然后前面的操作相当于对该层的输入a进行sigmoid求导
            % 这条公式请参考 Notes on Convolutional Neural Networks
        end
        
    elseif strcmp(net.layers{l}.type,'s')% 如果是下采样层,则进行下采样
        % 功能:下采样层的灵敏度误差传递
        for i=1:numel(net.layers{l}.a)
            z=zeros(size(net.layers{1}));
            for j=1:numel(net.layers{l+1}.a)
                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

% 计算梯度
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保存的是误差对卷积核的导数
                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.dffW=net.od*(net.fv)'/size(net.od,2);
net.dffb=mean(net.od,2);
end

% 1.sigm函数
% 功能:一个计算公式
function X=sigm(P)
X=1./(1+exp(-P));
end

% 2.rot180函数
% 功能:返回一个相同大小的矩阵,但是每个维度的数据都在各自维度上翻转
function X=rot180(X)
X=flip(flip(X,1),2);
end

% 3.expand函数
% 功能:相当于对l+1层的灵敏度map进行上采样,然后前面的操作相当于对该层的输入a进行sigmoid求导
function B=expand(A,S)

SA=size(A);% 记录矩阵A的大小

if length(SA)~=length(S)
    error('Err:SIZE向量的长度必须与矩阵维度相等。');
elseif any(S~=floor(S))
    error('Err:SIZE向量只能包含整数');
end

T=cell(length(SA),1); % 生成与矩阵相同维度长度的列向量,每一个为0x0double的cell型数据

for i=length(SA):-1:1
    
    H=zeros(SA(i)*S(i),1);
    H(1:S(i):SA(i)*S(i))=1;
    % 相应位置置1
    T{i}=cumsum(H);
    % 累加函数,按列进行计算
end

B=A(T{:});% 将索引输入A

end

% 4.flipall函数
% 功能:将每一个维度的数据均反转
function X=flipall(X)
for i=1:ndims(X)% ndims -> X的维度
    X=flip(X,i);
    % 如果A是矩阵,则flip(A,1)反转每一列中的元素,而flip(A,2)反转每一行中的元素。
end
end

2.6 cnngrads

% 本函数的功能为:
% 权值更新函数
% 先更新卷积层的参数,再更新全连接层参数

% 本函数涉及到的参数为:
% 输入:net -> 权值待更新的卷积神经网络;opts -> 卷积神经网络相关参数
% 输出:net -> 权值更新后的卷积神经网络

% 参考资料:https://blog.csdn.net/u013088062/article/details/48662397

function net=cnngrads(net,opts)

for l=2:numel(net.layers)
    if strcmp(net.layers{l}.type,'c')
        for j=1:numel(net.layers{l}.a)
            for i=1:numel(net.layers{l-1}.a)
                % 权值更新的公式:W_new = W_old - alpha * de/dW(误差对权值导数)
                net.layers{l}.k{i}{j}=net.layers{l}.k{i}{j}-opts.alpha*net.layers{l}.dk{i}{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

% 1.sigm函数
% 功能:一个计算公式
function X=sigm(P)
X=1./(1+exp(-P));
end

% 2.rot180函数
% 功能:返回一个相同大小的矩阵,但是每个维度的数据都在各自维度上翻转
function X=rot180(X)
X=flip(flip(X,1),2);
end

% 3.expand函数
% 功能:相当于对l+1层的灵敏度map进行上采样,然后前面的操作相当于对该层的输入a进行sigmoid求导
function B=expand(A,S)

SA=size(A);% 记录矩阵A的大小

if length(SA)~=length(S)
    error('Err:SIZE向量的长度必须与矩阵维度相等。');
elseif any(S~=floor(S))
    error('Err:SIZE向量只能包含整数');
end

T=cell(length(SA),1); % 生成与矩阵相同维度长度的列向量,每一个为0x0double的cell型数据

for i=length(SA):-1:1
    
    H=zeros(SA(i)*S(i),1);
    H(1:S(i):SA(i)*S(i))=1;
    % 相应位置置1
    T{i}=cumsum(H);
    % 累加函数,按列进行计算
end

B=A(T{:});% 将索引输入A

end

% 4.flipall函数
% 功能:将每一个维度的数据均反转
function X=flipall(X)
for i=1:ndims(X)% ndims -> X的维度
    X=flip(X,i);
    % 如果A是矩阵,则flip(A,1)反转每一列中的元素,而flip(A,2)反转每一行中的元素。
end
end

2.7 cnntest

% 本函数功能为:
% 用测试数据来测试我们经过训练后的卷积神经网络

% 本函数涉及的参数为: 
% 输入:net -> 训练好的卷积神经网络
% x -> 测试图像数据;y -> 测试图像数据标签
% 输出:er -> 测试错误率;bad -> 出错的位置 

function[er,bad]=cnntest(net,x,y)

net=cnnff(net,x);
[~,h]=max(net.o);
[~,a]=max(y);% max(A) -> 返回矩阵中每一列的最大元素
bad=find(h~=a);
er=numel(bad)/size(y,2); % numbel(A) -> 输出A数组元素

end

% 1.sigm函数
% 功能:一个计算公式
function X=sigm(P)
X=1./(1+exp(-P));
end

后面两个自己写的测试函数写在 -> https://blog.csdn.net/Carriefuu/article/details/102720122
跑的时候把主函数的测试函数注释掉也是ok的,这里给出的就是完整的一个流程了。

你可能感兴趣的:(课程作业记录8:基于CNN的MNIST数据集手写数字识别/Matlab仿真)