[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.
clearall;closeall;clc;addpath('../data');addpath('../util');loadmnist_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%willrun1epochinabout200secondandgetaround11%error.%With100epochsyou'llgetaround1.2%errorcnn.layers={struct('type','i')%inputlayerstruct('type','c','outputmaps',6,'kernelsize',5)%convolutionlayerstruct('type','s','scale',2)%subsamplinglayerstruct('type','c','outputmaps',12,'kernelsize',5)%convolutionlayerstruct('type','s','scale',2)%subsamplinglayer};%这里把cnn的设置给cnnsetup,它会据此构建一个完整的CNN网络,并返回cnn=cnnsetup(cnn,train_x,train_y);%学习率opts.alpha=1;%每次挑出一个batchsize的batch来训练,也就是每用batchsize个样本就调整一次权值,而不是%把所有样本都输入了,计算所有样本的误差了才调整一次权值opts.batchsize=50;%训练次数,用同样的样本集。我训练的时候:%1的时候11.41%error%5的时候4.2%error%10的时候2.73%erroropts.numepochs=10;%然后开始把训练样本给它,开始训练这个CNN网络20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.
38.39.40.41.42.43.44.45.46.
cnn=cnntrain(cnn,train_x,train_y,opts);%然后就用测试样本来测试[er,bad]=cnntest(cnn,test_x,test_y);%plotmeansquarederrorplot(cnn.rL);%showtesterrordisp([num2str(er*100)'%error']);cnnsetup.m
[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.10.11.
functionnet=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共有五层,这里范围的是5forl=1:numel(net.layers)%layer12.
13.14.15.16.17.18.19.20.
ifstrcmp(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);forj=1:inputmaps%inputmap就是上一层有多少张特征图net.layers{l}.b{j}=0;%将偏置初始化为0end21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.
2;endifstrcmp(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^forj=1:net.layers{l}.outputmaps特征map提取outputmaps种特征,%outputmap36.
37.38.39.40.
数链到前层%fan_out保存的是对于上一层的一张特征map,我在这一层需要对这一张%提取每种特征用到的卷积核不同,所以fan_out保存的是这一层输出新的特征需要学习的参数个数%而,fan_in保存的是,我在这一层,要连接到上一层中所有的特征map,然后用fan_out保存的提取特征%的权值来提取他们的特征。也即是对于每一个当前层特征图,有多少个参fan_in=inputmaps*net.layers{l}.kernelsize^2;fori=1:inputmaps特征map,都需要用这么多个卷积核%inputmap%随机初始化权值,也就是共有outputmaps个卷积核,对上层的每个%去卷积提取特征。%rand(n)是产生n×n的0-1之间均匀取值的数值的矩阵,再减去0.5就相当于产生-0.5到0.5之间的随机数%再*2就放大到[-1,1]。然后再乘以后面那一数,why?41.
42.43.44.45.46.
47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.
end%反正就是将卷积核每个元素初始化为[-sqrt(6/(fan_in+fan_out)),sqrt(6/(fan_in+fan_out))]%之间的随机数。因为这里是权值共享的,也就是对于一张特征map,所有感受野位置的卷积核都是一样的%所以只需要保存的是inputmaps*outputmaps个卷积核。net.layers{l}.k{i}{j}=(rand(net.layers{l}.kernelsize)-0.5)*2*sqrt(6/(fan_in+fan_out));net.layers{l}.b{j}=0;%将偏置初始化为0end%只有在卷积层的时候才会改变特征map的个数,pooling的时候不会改变个数。这层输出的特征map个数就是%输入到下一层的特征map个数inputmaps=net.layers{l}.outputmaps;endend%fvnum是输出层的前面一层的神经元个数。%这一层的上一层是经过pooling后的层,包含有inputmaps个特征map。每个特征map的大小是mapsize。%所以,该层的神经元个数是inputmaps*(每个特征map的大小)%prod:Productofelements.%Forvectors,prod(X)istheproductoftheelementsofX%在这里mapsize=[特征map的行数特征map的列数],所以prod后就是特征map的行*列fvnum=prod(mapsize)*inputmaps;%onum是标签的个数,也就是输出层神经元的个数。你要分多少个类,自然就有多少个输出神经元onum=size(y,1);%这里是最后一层神经网络的设定%ffb是输出层每个神经元对应的基biasesnet.ffb=zeros(onum,1);%ffW输出层前一层与输出层连接的权值,这两层之间是全连接的net.ffW=(rand(onum,fvnum)-0.5)*2*sqrt(6/(onum+fvnum));endcnntrain.m
[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
functionnet=cnntrain(net,x,y,opts)m=size(x,3);%m保存的是训练样本个数numbatches=m/opts.batchsize;%rem:Remainderafterdivision.rem(x,y)isx-n.*y相当于求余%rem(numbatches,1)就相当于取其小数部分,如果为0,就是整数ifrem(numbatches,1)~=0error('numbatchesnotinteger');endnet.rL=[];fori=1:opts.numepochs%disp(X)打印数组元素。如果X是个字符串,那就打印这个字符串disp(['epoch'num2str(i)'/'num2str(opts.numepochs)]);%tic和toc是用来计时的,计算这两条语句之间所耗的时间tic;%P=randperm(N)返回[1,N]之间所有整数的一个随机的序列,例如%randperm(6)可能会返回[245613]%这样就相当于把原来的样本排列打乱,再挑出一些样本来训练kk=randperm(m);forl=1:numbatches%取出打乱顺序后的batchsize个样本和对应的标签batch_x=x(:,:,kk((l-1)*opts.batchsize+1:l*opts.batchsize));batch_y=y(:,chsize));kk((l-1)*opts.batchsize+1:l*opts.bat24.25.26.27.
值%在当前的网络权值和网络输入下计算网络的输出net=cnnff(net,batch_x);%Feedforward%得到上面的网络输出后,通过对应的样本标签用bp算法来得到误差对网络权%(也就是那些卷积核的元素)的导数net=cnnbp(net,batch_y);%Backpropagation%得到误差对权值的导数后,就通过权值更新方法去更新权值net=cnnapplygrads(net,opts);ifisempty(net.rL)net.rL(1)=net.L;%代价函数值,也就是误差值endnet.rL(end+1)=0.99*net.rL(end)+0.01*net.L;%保存历史的误差值,以便画图分析endtoc;endend28.29.30.31.32.33.34.35.36.37.38.39.40.
cnnff.m
[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.
functionnet=cnnff(net,x)n=numel(net.layers);%层数net.layers{1}.a{1}=x;%网络的第一层就是输入,但这里的输入包含了多个训练图像inputmaps=1;%输入层只有一个特征map,也就是原始的输入图像forl=2:n%像%foreachlayerifstrcmp(net.layers{l}.type,'c')%卷积层!!belowcanprobablybehandledbyinsanematrixoperations%对每一个输入map,或者说我们需要用outputmaps个不同的卷积核去卷积图forj=1:net.layers{l}.outputmaps%createtempoutputmap%foreachoutputmap10.11.12.13.14.
的第三维%对上一层的每一张特征map,卷积后的特征map的大小就是%(输入map宽-卷积核的宽+1)*(输入map高-卷积核高+1)%对于这里的层,因为每层都包含多张特征map,对应的索引保存在每层map%所以,这里的z保存的就是该层中所有的特征map了z=zeros(size(net.layers{l-1}.a{1})-[net.layers{l}.kernelsize-1net.layers{l}.kernelsize-10]);fori=1:inputmaps%tputmap%foreachinputmap15.
16.17.18.19.
核进行卷积convolvewithcorrespondingkernelandaddtotempou%将上一层的每一个特征map(也就是这层的输入map)与该层的卷积%然后将对上一层特征map的所有结果加起来。也就是说,当前层的一张特征map,是%用一种卷积核去卷积上一层中所有的特征map,然后所有特征map对应位置的卷积值的和%另外,有些论文或者实际应用中,并不是与全部的特征map链接的,有可能只与其中的某几个连接z=z+convn(net.layers{l-1}.a{i},net.layers{l}.k{i}{j},'valid');end%addbias,passthroughnonlinearity%加上对应位置的基b,然后再用sigmoid函数算出特征map中每个位置的激活值,作为该层输出特征map20.21.22.23.24.25.26.
27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.
出值end%net.layers{l}.a{j}=sigm(z+net.layers{l}.b{j});end%setnumberofinputmapstothislayersnumberofoutputmapsinputmaps=net.layers{l}.outputmaps;elseifstrcmp(net.layers{l}.type,'s')%下采样层%downsample%!!replacewithvariableforj=1:inputmaps%例如我们要在scale=2的域上面执行meanpooling,那么可以卷积大小为2*2,每个元素都是1/4的卷积核z=convn(net.layers{l-1}.a{j},ones(net.layers{l}.scale)/(net.layers{l}.scale^2),'valid');%因为convn函数的默认卷积步长为1,而pooling操作的域是没有重叠的,所以对于上面的卷积结果%最终pooling的结果需要从上面得到的卷积结果中以scale=2为步长,跳着把meanpooling的值读出来net.layers{l}.a{j}=z(1:net.layers{l}.scale:end,1:net.layers{l}.scale:end,:);endendend%concatenateallendlayerfeaturemapsintovector%把最后一层得到的特征map拉成一条向量,作为最终提取到的特征向量net.fv=[];forj=1:numel(net.layers{n}.a)%最后一层的特征map的个数sa=size(net.layers{n}.a{j});%第j个特征map的大小%将所有的特征map拉成一条列向量。还有一维就是对应的样本索引。每个样本一列,每列为对应的特征向量net.fv=[net.fv;reshape(net.layers{n}.a{j},sa(1)*sa(2),sa(3))];feedforwardintooutputperceptrons%计算网络的最终输出值。sigmoid(W*X+b),注意是同时计算了batchsize个样本的输net.o=sigm(net.ffW*net.fv+repmat(net.ffb,1,size(net.fv,2)));end54.
55.56.
cnnbp.m
[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.
functionnet=cnnbp(net,y)n=numel(net.layers);%网络层数%%errorlossfunctionnet.e=net.o-y;%代价函数是均方误差net.L=1/2*sum(net.e(:).^2)/size(net.e,2);%%backpropdeltas%这里可以参考UFLDL的反向传导算法的说明%输出层的灵敏度或者残差net.od=net.e.*(net.o.*(1-net.o));%残差反向传播回前一层net.fvd=(net.ffW'*net.od);ifstrcmp(net.layers{n}.type,'c')functionnet.fvd=net.fvd.*(net.fv.*(1-net.fv));end%reshapefeaturevectordeltasintooutputmapstyle%%featurevectordeltaonlyconvlayershassigm%outputdeltasa=size(net.layers{n}.a{1});%最后一层特征map的大小。这里的最后一层都是指输出层的前一层fvnum=sa(1)*sa(2);%因为是将最后一层特征map拉成一条向量,所以对于一个样本来说,特征维数是这样forj=1:numel(net.layers{n}.a)%最后一层的特征map的个数%在fvd里面保存的是所有样本的特征向量(在cnnff.m函数中用特征map拉成的),所以这里需要重新%变换回来特征map的形式。d保存的是delta,也就是灵敏度或者残差net.layers{n}.d{j}=reshape(net.fvd(((j-1)*fvnum+1):j*fvnum,:),sa(1),sa(2),sa(3));end%对于输出层前面的层(与输出层计算残差的方式不同)forl=(n-1):-1:1ifstrcmp(net.layers{l}.type,'c')forj=1:numel(net.layers{l}.a)%该层特征map的个数%net.layers{l}.d{j}保存的是第l层的第j个map的灵敏度map。也就是每个神经元节点的delta的值%expand的操作相当于对l+1层的灵敏度map进行上采样。然后前面的操作相当于对该层的输入a进行sigmoid求导%这条公式请参考NotesonConvolutionalNeuralNetworks36.37.
%fork=1:size(net.layers{l+1}.d{j},3)%net.layers{l}.d{j}(:,:,k)=net.layers{l}.a{j}(:,:,k).*(1-net.layers{l}.a{j}(:,:,k)).*kron(net.layers{l+1}.d{j}(:,:,k),ones(net.layers{l+1}.scale))/net.layers{l+1}.scale^2;38.39.
%endnet.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}.scalenet.layers{l+1}.scale1])/net.layers{l+1}.scale^2);40.41.42.43.44.
数endelseifstrcmp(net.layers{l}.type,'s')fori=1:numel(net.layers{l}.a)%第l层特征map的个数z=zeros(size(net.layers{l}.a{1}));forj=1:numel(net.layers{l+1}.a)%第l+1层特征map的个z=z+convn(net.layers{l+1}.d{j},rot180(net.layers{l+1}.k{i}{j}),'full');endnet.layers{l}.d{i}=z;endendend%%calcgradients45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.
endendend%这里与NotesonConvolutionalNeuralNetworks中不同,这里的子采样层没有参数,也没有%激活函数,所以在子采样层是没有需要求解的参数的forl=2:nifstrcmp(net.layers{l}.type,'c')forj=1:numel(net.layers{l}.a)fori=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);%最后一层perceptron的gradient的计算net.dffW=net.od*(net.fv)'/size(net.od,2);net.dffb=mean(net.od,2);71.72.73.74.
functionX=rot180(X)X=flipdim(flipdim(X,1),2);endendcnnapplygrads.m
[plain]viewplaincopy
1.2.3.4.5.6.7.8.9.10.
functionnet=cnnapplygrads(net,opts)forl=2:numel(net.layers)ifstrcmp(net.layers{l}.type,'c')forj=1:numel(net.layers{l}.a)forii=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};endendnet.layers{l}.b{j}=net.layers{l}.b{j}-opts.alpha*net.layers{l}.db{j};endendnet.ffW=net.ffW-opts.alpha*net.dffW;net.ffb=net.ffb-opts.alpha*net.dffb;end11.12.13.14.15.16.
cnntest.m
[plain]viewplaincopy
1.2.3.4.5.6.
function[er,bad]=cnntest(net,x,y)%feedforwardnet=cnnff(net,x);%前向传播得到输出%[Y,I]=max(X)returnstheindicesofthemaximumvaluesinvectorI[~,h]=max(net.o);%找到最大的输出对应的标签[~,a]=max(y);%找到最大的期望输出对应的索引7.8.9.10.
bad=find(h~=a);%找到他们不相同的个数,也就是错误的次数er=numel(bad)/size(y,2);%计算错误率end
因篇幅问题不能全部显示,请点此查看更多更全内容