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

浙公网安备 33010602011771号