CNN算法与程序研究
1) 深度学习基本理论方法
http://wenku.baidu.com/view/2e630ddfc5da50e2524d7ff3
特征多,给出的信息多,识别准确性会提升。
但是,计算复杂度增加,搜索的空间大,可以用来训练的数据在每个特征上就会稀疏。
采用层次网络结构,
BP一层隐层节点的浅层模型,带有一层隐层节点(如SVM、Boosting)
对复杂函数的表示能力和泛化能力有限
深度学习指的是:多隐层的人工神经网络具有优异的特征学习能力,学习得到的特征对数据有更本质的刻画,从而有利于可视化或分类;深度神经网络在训练上的难度,可以通过“逐层初始化”(layer-wise pre-training)来有效克服,逐层初始化可通过无监督学习实现的。
与浅层学习区别:
1)强调了模型结构的深度,通常有5-10多层的隐层节点;
2)明确突出了特征学习的重要性,通过逐层特征变换,将样本在原空间的特征表示变换到一个新特征空间,从而使分类或预测更加容易。与人工规则构造特征的方法相比,利用大数据来学习特征,更能够刻画数据的丰富内在信息。
可通过学习一种深层非线性网络结构,实现复杂函数逼近,表征输入数据分布式表示
不同点:
神经网络:采用BP算法调整参数,即采用迭代算法来训练整个网络。随机设定初值,计算当前网络的输出,然后根据当前输出和样本真实标签之间的差去改变前面各层的参数,直到收敛;
深度学习:采用逐层训练机制。采用该机制的原因在于如果采用BP机制,对于一个deep network(7层以上),残差传播到最前面的层将变得很小,出现所谓的gradient diffusion(梯度扩散)----【梯度扩散问题。因为当网络层次比较深时,在计算损失函数的偏导时一般需要使用BP算法,但是这些梯度值随着深度慢慢靠前而显著下降,这样导致前面的网络对最终的损失函数的贡献很小。这样的话前面的权值更新速度就非常非常慢了】
不采用BP算法的原因
(1)反馈调整时,梯度越来越稀疏,从顶层越往下,误差校正信号越来越小;
(2)收敛易至局部最小,由于是采用随机值初始化,当初值是远离最优区域时易导致这一情况;
(3)BP算法需要有标签数据来训练,但大部分数据是无标签的
深度学习训练过程
第一步:采用自下而上的无监督学习
1)逐层构建单层神经元。
2)每层采用wake-sleep算法进行调优。每次仅调整一层,逐层调整。
这个过程可以看作是一个featurelearning的过程,是和传统神经网络区别最大的部分。
wake-sleep算法:
1)wake阶段:
认知过程,通过下层的输入特征(Input)和向上的认知(Encoder)权重产生每一层的抽象表示(Code),再通过当前的生成(Decoder)权重产生一个重建信息(Reconstruction),计算输入特征和重建信息残差,使用梯度下降修改层间的下行生成(Decoder)权重。也就是“如果现实跟我想象的不一样,改变我的生成权重使得我想象的东西变得与现实一样”。
2)sleep阶段:
生成过程,通过上层概念(Code)和向下的生成(Decoder)权重,生成下层的状态,再利用认知(Encoder)权重产生一个抽象景象。利用初始上层概念和新建抽象景象的残差,利用梯度下降修改层间向上的认知(Encoder)权重。也就是“如果梦中的景象不是我脑中的相应概念,改变我的认知权重使得这种景象在我看来就是这个概念”。
第二步:自顶向下的监督学习
这一步是在第一步学习获得各层参数进的基础上,在最顶的编码层添加一个分类器(例如罗杰斯特回归、SVM等),而后通过带标签数据的监督学习,利用梯度下降法去微调整个网络参数。
深度学习的第一步实质上是一个网络参数初始化过程。区别于传统神经网络初值随机初始化,深度学习模型是通过无监督学习输入数据的结构得到的,因而这个初值更接近全局最优,从而能够取得更好的效果。
自动编码器( AutoEncoder )
稀疏自动编码器
稀疏自动编码器(SparseAutoEncoder)
1)Training阶段:给定一系列的样本图片[x1, x 2, …],我们需要学习得到一组基[Φ1, Φ2, …],也就是字典。
可使用K-SVD方法交替迭代调整a [k],Φ [k],直至收敛,从而可以获得一组可以良好表示这一系列x的字典。
2)Coding阶段:给定一个新的图片x,由上面得到的字典,利用OMP算法求解一个LASSO问题得到稀疏向量a。这个稀疏向量就是这个输入向量x的一个稀疏表达。
降噪自动编码器(Denoising AutoEncoders)
在自动编码器的基础上,对训练数据加入噪声,自动编码器必须学习去去除这种噪声而获得真正的没有被噪声污染过的输入。因此,这就迫使编码器去学习输入信号的更加鲁棒的表达,这也是它的泛化能力比一般编码器强的原因
限制波尔兹曼机(RestrictedBoltzmann Machine)
假设有一个二部图,同层节点之间没有链接,一层是可视层,即输入数据层(v),一层是隐藏层(h),如果假设所有的节点都是随机二值( 0,1值)变量节点,同时假设全概率分布p(v,h)满足Boltzmann 分布,我们称这个模型是Restricted BoltzmannMachine (RBM)。
4.主函数代码:test_sae.m
clear all; closeall; clc;
%% //导入数据
loadmnist_uint8;
train_x =double(train_x)/255;
test_x =double(test_x)/255;
train_y =double(train_y);
test_y =double(test_y);
%%一:采用autoencoder进行预训练
rng(0);%高版本的matlab可以使用这个语句,低版本会出错
sae =saesetup([784 200 100]);
sae.ae{1}.activation_function ='sigm';
sae.ae{1}.learningRate =1;
sae.ae{1}.inputZeroMaskedFraction =0.;
sae.ae{2}.activation_function ='sigm';
sae.ae{2}.learningRate =1;
sae.ae{2}.inputZeroMaskedFraction =0.;
opts.numepochs= 1;
opts.batchsize =100;
visualize(sae.ae{1}.W{1}(:,2:end)')
%二:fine_tuning过程
% Use the SDAEto initialize a FFNN
nn =nnsetup([784 200 100 10]);
nn.activation_function ='sigm';
nn.learningRate =1;
%add pretrained weights
nn.W{1} =sae.ae{1}.W{1};
nn.W{2} =sae.ae{2}.W{1};
% Train the FFNN
opts.numepochs= 1;
opts.batchsize =100;
nn = nntrain(nn,train_x, train_y, opts);
[er, bad] =nntest(nn, test_x, test_y);
str =sprintf('testing error rate is: %f',er);
disp(str)
三:pre_training阶段代码详解
3.1:网络结构建立
Sae网络就是堆叠多个autoencoder网络,所以此网络结构由2个autoencoder构成,分别是:v—h1—v和h1—h2—h1.
此处主要利用saesetup函数来实现,
3.1.1 Saesetup函数说明
输入参数:size为构建网络的节点向量,由于此处pre_training阶段的网络为784-200-100;所以size=[784 200 100]
输出参数:sae元包矩阵,每一个元包矩阵对应一个autoencoder网络;例如sae.ae{1}中“存放”的就是v——h1——v这个autoencoder;
函数体说明:通过for循环2次,调用nnsetup函数两次,生成两个autoencoder
function sae=saesetup(size)
for u = 2:numel(size)
sae.ae{u-1}= nnsetup([size(u-1) size(u) size(u-1)]);
end
end
autoencoder构建:我们知道autoencoder就是一个v——h——v 的3层神经网络;所以主要通过nnsetup来构建。
3.1.2 nnsetup函数说明
输入参数:nnsetup(architecture),architecture,顾名思义,就是构建网络的结构,也就是每层网络的神经元节点个数,例如本题为第一个autoencoder结构为v—h1—v [784 200 784]
输出参数nn:一个元包矩阵,元包矩阵中存放着一个autoencoder网络的各种配置参数
函数体说明:
Encoder过程:y=f(xw'+b)
激活函数nn.activation_function:f(),一般有sigmoid和tanh两种;
学习率 nn.learningRate:控制学习的速度,
动量项 nn.momentum:调节在应用minibatch方法训练网络时,每次新权值更新值和上一次权值更行值的相对比例;
权值惩罚项 nn.weightPenaltyL2:防止权值过大,发生过拟合现象;
稀疏目标 nn.sparsityTarget:通过控制隐层激活值的平均值,来控制隐层激活值的稀疏性
Decoder过程:x=g(wy+b)
解码激活函数设置:sigmoid和tanh两种
其他参数:例如dae中的加噪比例参数,dropout中的隐层节点的忽略比例等
权值W初始化:
W结构初始化:权值矩阵W的行列数,例如若使用:h=f(x*w'+b),本例中第一个autoencoder中,权值矩阵w1为200*785 ,(785=784+1,1为偏置项b,后面介绍)
W元素初始化:W的元素初始值为一个0均值的随机矩阵,权值系数很小,这样可以防止过拟合
nn.W{i - 1} =(a*b);
a=rand(nn.size(i),nn.size(i - 1)+1) - 0.5)%生产一个0均值,范围元素值在[-0.5 0.5]范围内的矩阵
b=2 * 4 * sqrt(6 /(nn.size(i) + nn.size(i - 1))%缩减矩阵元素值到一个更小的范围
3.2:网络训练阶段
此阶段主要调用saetrain函数来训练网络;
3.2.1 Seatrain函数说明
输入参数:saetrain(sae, x, opts)
Sae为step1阶段设置好的网络结构;x为输入数据;opts中有两个参数;其中opts.numepochs为训练次数,所有样本一共训练多少次;opts.batchsize参数,为在对所有样本进行minibatch训练时,每个batch的容量,即每个batch的样本个数。
输出参数:训练好的元包矩阵sae
函数体说明:通过for循环,调用nntrain函数,依次训练每个autoencoder。
3.2.2 Nntrain函数说明:
输入参数:nntrain(nn, train_x, train_y, opts, val_x, val_y)
其中nn为一个元包矩阵,nn中存放的是网络的配置参数;train_x,train_y就是输入数据,和目标数据;在autoencoder网络中,都是输入数据x,opts参数,已经说过;至于val_x和val_y做什么的不清楚,训练中暂时用不到。
输出参数:[nn, L],已经训练好的网络参数,主要训练W和b,L为损失函数值,在autoencoder中是重构误差。
函数体说明:
1.minibatch部分
所谓的minibatch训练方法就是把所有训练样本分成多个batch,然后依次训练每个batch中的样本。
将训练集事先分成包含几十或几百个样本的小批量数据进行计算将更高效,这主要是可以利用图形处理器Gpu和matlab中矩阵之间相乘运算的优势。
m = size(train_x,1);%提取样本总数
batchsize =opts.batchsize; %每个batch中样本个数
numepochs =opts.numepochs; %所有样本训练次数
numbatches = m /batchsize; %所有样本分成多少个batch
2.训练过程
for i = 1 : numepochs%所有样本训练次数
kk=randperm(m); %形成样本编号的随机向量
for l = 1 : numbatches%每次提取batchsize个样本,一共提取numbatches次
batch_x =train_x(kk((l - 1) * batchsize + 1 : l * batchsize), :);
batch_y =train_y(kk((l - 1) * batchsize + 1 : l * batchsize), :);
nn =nnff(nn, batch_x, batch_y); %前馈计算网络
nn =nnbp(nn);%计算误差和权值梯度
nn =nnapplygrads(nn);%参数更新
L(n) =nn.L;
n = n + 1;
end
end
在nntrain末尾还可以缩减学习率,使更新步长逐渐变小。
nn.learningRate =nn.learningRate *nn.scaling_learningRate;
3.2.3 前馈函数nnff说明
输入参数:nnff(nn, batch_x, batch_y),这个就不说了,和上面一样
输出参数:元包矩阵nn,主要计算了隐层的激活函数值。
函数体说明:
1 添加偏置项
x = [ones(m,1)x];
nn.a{1} = x;
一般前馈计算过程为,h=f(xw’+b);其中参数b即为偏置项;此时在计算过程中,需要“复制”b维数,是的复制后的b可以和wx乘积结果可以相加(详见SparseAutoEncoder稀疏编码详解));这样每次复制b,会增大计算量;本代码中,把偏置项,直接添加到输入数据x中,在x前添加一列0元素,作为偏置项b;这是样输入数据矩阵,就变成了60000*785,这也就和前面为什么要把权值矩阵w1定义为为200*785,而不是200*784了。
所以最后权值矩阵W1为200*785;W2为100*201 ;W3为10*101。
这样前馈计算过程,由h=f(xw’+b),变成了h=f(x*w’)
2 encoder和decoder过程
Encoder阶段:
for i = 2 : n-1
switchnn.activation_function
case 'sigm'
% Calculate theunit's outputs (including the bias term)
nn.a{i}= sigm(nn.a{i - 1} * nn.W{i - 1}');
case 'tanh_opt'
nn.a{i}= tanh_opt(nn.a{i - 1} * nn.W{i - 1}');
end
nn.a{i} =[ones(m,1) nn.a{i}];%给下一个网络的输入添加偏置项
end
decoder阶段:
根据选择的decoder函数,来计算;由于此处是autoencoder,通过 nn.a{n} = sigm(nn.a{n - 1} * nn.W{n - 1}');来生成输入数据的近似估计,以便用来求重构误差。
switch nn.output
case 'sigm'
nn.a{n}= sigm(nn.a{n - 1} * nn.W{n - 1}');
case 'linear'
nn.a{n}= nn.a{n - 1} * nn.W{n - 1}';
case 'softmax'
nn.a{n}= nn.a{n - 1} * nn.W{n - 1}';
nn.a{n}= exp(bsxfun(@minus, nn.a{n}, max(nn.a{n},[],2)));
nn.a{n}= bsxfun(@rdivide, nn.a{n}, sum(nn.a{n}, 2));
end
3.计算损失函数
nn.e = y -nn.a{n};
switch nn.output
case {'sigm', 'linear'}
nn.L =1/2 * sum(sum(nn.e .^ 2)) / m; %均方误差
case 'softmax'
nn.L =-sum(sum(y .* log(nn.a{n}))) / m;
end
3.2.4反向函数nnbp说明:
输入,输出参数:nn = nnbp(nn),都是元胞矩阵nn
这里首先要复习一下,bp算法的误差传播方法。
误差反向传播:
最后一层误差:nl为最后一层的层数,z为最后一层的输入,上一层的输出值
Sigmoid函数的导数为f’(a)=a(1-a)
输出层误差代码:
switch nn.output
case 'sigm'
d{n} =- nn.e .* (nn.a{n} .* (1 - nn.a{n}));
case {'softmax','linear'}
d{n} =- nn.e;
end
中间层误差:本层和下一层的连接权值,乘以,上一层误差,在乘以,本层输入的导数。
for i = (n -1) : -1 :2
% 各层激活函数求导
switchnn.activation_function
case 'sigm'
d_act= nn.a{i} .* (1 - nn.a{i});
case 'tanh_opt'
d_act= 1.7159 * 2/3 * (1 - 1/(1.7159)^2 * nn.a{i}.^2);
end
% 计算误差
if i+1==n
d{i} =(d{i + 1} * nn.W{i} + sparsityError) .* d_act;
%倒数第二层的残杀d{nl-1},是由最后一层残差d{nl}传播得到的,而最后一层的残差没有添加偏置项,也就是输出层节点是10,而不是11;所以误差可以直接通过连接的权值矩阵向前传递。
else
d{i} =(d{i + 1}(:,2:end) * nn.W{i} + sparsityError) .* d_act;
倒数第二层以前的残差d{i}是由上一层残差d{i+1}传递过来的;而中间层再向前传递的时候每层都加了偏置项,而这里我们要计算的是连接权值矩阵W的残差,所以应该把添加的偏置项去掉,所以去挑d{i+1}的第一列,使用d{i + 1}(:,2:end),而不是整个d{i+1}。
end
end
权值更新量计算:
l层的权值更新量delta_W=下一层网络的误差 * 本层输入
l层偏置项更新量delta_b=残差
为了避免在小批量数据的样本容量发生改变时,学习率也必须做相应的修改;通常的做法是在参数的更新过程中昬使用参数的平均梯度;即总梯度除以数据容量。
for i = 1 : (n- 1)
if i+1==n
nn.dW{i}= (d{i + 1}' * nn.a{i}) / size(d{i + 1}, 1);
else
nn.dW{i}= (d{i + 1}(:,2:end)' * nn.a{i}) / size(d{i + 1},1);
end
end
权值更新:
基本的梯度下降更新:无动量项,无权值惩罚项
w=w-alpha*delta_W
for i = 1 : (nn.n - 1)
if(nn.weightPenaltyL2>0)
dW =nn.dW{i} + nn.weightPenaltyL2 * [zeros(size(nn.W{i},1),1) nn.W{i}(:,2:end)];
else
dW =nn.dW{i};
end
dW =nn.learningRate * dW;
if(nn.momentum>0)
nn.vW{i}= nn.momentum*nn.vW{i} + dW;
dW =nn.vW{i};
end
nn.W{i} =nn.W{i} - dW;
end
四、fine_tuning阶段
4.1初始化网络结构
1.首先通过nnsetup(784,200,100,10)函数来构建网络;此时不是通过saesetup函数来构建;
2.设置网络激活函数,学习率等参数
3.把Pre_training阶段,学习到的权值矩阵,作为网络权值的初始值;
nn.W{1} =sae.ae{1}.W{1};
nn.W{2}= sae.ae{2}.W{1};
4.2 fine_tuning
利用nntrain函数,使用真实的标签数据train_y来微调整个网络
nn= nntrain(nn, train_x, train_y, opts);
4.3:预测阶段 (此部分代码详解见博文:Denosing Autoencoder训练过程代码详解)
[er,bad] =nntest(nn, test_x, test_y);
7.普通deep autoencoder训练过程
本文主要参考Deeplearn toolbox中代码
matlab的Deep Learning toolbox,见:https://github.com/rasmusbergpalm/DeepLearnToolbox
一:加载数据
二:pre_training阶段
2.1初始化DAE网络框架
sae = saesetup([784 100 100]);%建立一个3层网络
在函数saesetup函数内部,循环调用nnsetup函数,此处2次调用nnsetup函数
最终saesetup([784 100 100]);建立一个2个autoencoder网络
分别是:784 ——100——784;100 ——100——100
2.1.1 nnsetup函数说明;
nn.size =architecture; nn.n=numel(nn.size)=3;
初始化激活函数类型,学习率,动量项,稀疏项等参数。
初始化网络结构:
for i= 2 : nn.n=3 %第一次循环调用:输入nnsetup([784 100 784])
经过2次循环,初始化一个784 ——100——784 的autoencoder网络。
2.2初始化SAE网络的训练参数
初始化激活函数类型(此处默认为tanh_opt函数),学习率,噪声比例等参数。
2.3开始训练SAE网络
sae = saetrain(sae, train_x,opts);%输入网络结构,样本数据,批处理的个数
saetrain内部调用nntrain函数来训练sae的每个autoencoder子网络.
sae.ae{i} = nntrain(sae.ae{i},x, x, opts);%循环两次
2.3.1 nntrain函数说明
输入参数:nntrain(nn, train_x, train_y, opts,val_x, val_y)
由于是autoencoder网络,此处的train_y是输入数据train_x
整个数据循环训练numepochs次,每次将整个数据分成numbatches个组,进行minibatch训练。
每次minibatch训练过程:
从整个数据中,“随机提取”minibatch个batch_x和batch_y数据;
调用nnff函数前馈计算 损失函数nn.L,和误差向量 nn.e
调用nnbp函数,反向计算误差dw{i}
调用nnapplygrads函数,来更新权值
三:fine_tuning 阶段
3.1初始化前馈网络
nn = nnsetup([784 100 100 10]);
把pre_training阶段训练的权值矩阵赋值给前馈网络。
nn.W{1} = sae.ae{1}.W{1};
nn.W{2} = sae.ae{2}.W{1};
3.2标签数据来fine_tuning整个网络
nn = nntrain(nn, train_x,train_y, opts);
四:测试数据
[er, bad] = nntest(nn, test_x,test_y);
4.1 预测样本分类
labels = nnpredict(nn, x);
nnpredict函数首先实现一个网络的前馈计算,计算样本属于每个分类的“概率”
nn = nnff(nn, x,zeros(size(x,1), nn.size(end)));
根据最大化原则,确定样本的分类,并提取类别标签
[~, i] = max(nn.a{end},[],2);%max(a,[],2)提取矩阵a每行的最大值
labels = i;
4.2计算错误率
[~, expected] = max(y,[],2);
bad = find(labels ~= expected);
er = numel(bad) / size(x, 1);
Denosing Autoencoder训练过程
Denoising autoencode主要通过在nntrain函数中对输入数据加入噪声,其他训练部分相同
一:Pre_training阶段
数据加噪:
sae.ae{1}.inputZeroMaskedFraction = 0.5;
sae.ae{2}.inputZeroMaskedFraction = 0.5;
由于是使用saetrain函数来分别训练每个autoencoder网络,所以这里对每个网络的输入数据都加入50%的随机噪声。
加噪代码:
if(nn.inputZeroMaskedFraction~= 0)
batch_x=batch_x.*(rand(size(batch_x))>nn.inputZeroMaskedFraction);
end
加入50%随机噪声后,原始数据
二:fine_tuning阶段
由于此处直接使用的是nntrain,微调整个网络;所以只在在调用nntrain函数时,把原始输入数据train_x加入噪声
代码的一些困惑:
在pre_training阶段,在nnsetup中,初始化每个autoencoder的前馈计算的encoder阶段使用的是tanh函数,decoder阶段使用的是sigmoid函数,这两个阶段为什么不一样呢?其次就是最后fine_tuning过程,前馈计算过程都使用的是sigmoid函数,这个也和pre_training过程的不一样。这个木有想明白