这是UFLDL的编程练习,因为只看到第一章节,还没有看到向量化,所以本篇博客只注意对算法的理解,没有注意向量化。因为进入机器学习领域也只有一个多月,许多错误之处望指出。
传统的神经网络一般用于监督学习(supervised learning),一般而言,SVM比传统的backpropagation分类算法更有效。但是最近神经网络在无监督学习(unsupervised learning)领域开始大放异彩。
自编码神经网络是一种无监督学习算法,它使用了反向传播算法,并让目标值等于输入值值,比如 y(i) = x(i) 。
自编码神经网络尝试学习一个
hW,b(x)≈x
的函数。换句话说,它尝试逼近一个恒等函数,从而使得输出
x^
接近于输入x 。恒等函数虽然看上去不太有学习的意义,但是当我们为自编码神经网络加入某些限制,比如限定隐藏神经元的数量,我们就可以从输入数据中发现一些有趣的结构。举例来说,假设某个自编码神经网络的输入 x 是一张
10×10
图像(共100个像素)的像素灰度值,于是n=100 ,其隐藏层
L2
中有50个隐藏神经元。注意,输出也是100维的
y∈R100
。由于只有50个隐藏神经元,我们迫使自编码神经网络去学习输入数据的压缩表示,也就是说,它必须从50维的隐藏神经元激活度向量
a(2)∈R50
中重构出100维的像素灰度值输入 x 。如果网络的输入数据是完全随机的,比如每一个输入 x(i) 都是一个跟其它特征完全无关的独立同分布高斯随机变量,那么这一压缩表示将会非常难学习。但是如果输入数据中隐含着一些特定的结构,比如某些输入特征是彼此相关的,那么这一算法就可以发现输入数据中的这些相关性。事实上,这一简单的自编码神经网络通常可以学习出一个跟主元分析(PCA)结果非常相似的输入数据的低维表示。
我们刚才的论述是基于隐藏神经元数量较小的假设。但是即使隐藏神经元的数量较大(可能比输入像素的个数还要多),我们仍然通过给自编码神经网络施加一些其他的限制条件来发现输入数据中的结构。具体来说,如果我们给隐藏神经元加入稀疏性限制,那么自编码神经网络即使在隐藏神经元数量较多的情况下仍然可以发现输入数据中一些有趣的结构。
稀疏性可以被简单地解释如下。如果当神经元的输出接近于1的时候我们认为它被激活,而输出接近于0的时候认为它被抑制,那么使得神经元大部分的时间都是被抑制的限制则被称作稀疏性限制。这里我们假设的神经元的激活函数是sigmoid函数。如果你使用tanh作为激活函数的话,当神经元输出为-1的时候,我们认为神经元是被抑制的。
注意到
a(2)j
表示隐藏神经元 j 的激活度,但是这一表示方法中并未明确指出哪一个输入 x 带来了这一激活度。所以我们将使用
a(2)j(x)
来表示在给定输入为 x 情况下,自编码神经网络隐藏神经元 j 的激活度。 进一步,让
ρ^j=1m∑i=1m[a(2)j(x(i))]
表示隐藏神经元 \textstyle j 的平均活跃度(在训练集上取平均)。我们可以近似的加入一条限制
ρ^j=ρ,
其中,
ρ
是稀疏性参数,通常是一个接近于0的较小的值(比如 0.05 )。换句话说,我们想要让隐藏神经元 j 的平均活跃度接近0.05。为了满足这一条件,隐藏神经元的活跃度必须接近于0。
为了实现这一限制,我们将会在我们的优化目标函数中加入一个额外的惩罚因子,而这一惩罚因子将惩罚那些
ρ^j
和
ρ
有显著不同的情况从而使得隐藏神经元的平均活跃度保持在较小范围内。惩罚因子的具体形式有很多种合理的选择,我们将会选择以下这一种:
∑j=1s2ρlogρρ^j+(1−ρ)log1−ρ1−ρ^j.
BackPropagation实现步骤如下(这一步用于理解backpropagation算法比较好,先理解以下的每一步再去实现向量化):
- Perform a feedforward(参照UFLDL第一章第一节) pass, computing the activations for layers L2, L3, and so on up to the output layer L_{n_l}.
- For each output unit i in layer nl (the output layer), set
δ(nl)i=∂∂z(nl)i12∥∥y−hW,b(x)∥∥2=−(yi−a(nl)i)⋅f′(z(nl)i)
For l = n(l)-1, n(l)-2, n(l)-3, …, 2
For each node i in layer l, set
δ(l)i=⎛⎝∑j=1sl+1W(l)jiδ(l+1)j⎞⎠f′(z(l)i)
Compute the desired partial derivatives, which are given as:
∂∂W(l)ijJ(W,b;x,y)∂∂b(l)iJ(W,b;x,y)=a(l)jδ(l+1)i=δ(l+1)i.
下面给出完整的Sparese Autoencoder的实现流程(这些步骤你应该在练习的sparseAntoencoderCost.m中实现):
- Set
ΔW(l):=0,Δb(l):=0
(matrix/vector of zeros) for all \textstyle l.
- For i = 1 to m:
a.Use backpropagation(上面给出的步骤) to compute
∇W(l)J(W,b;x,y)
and
∇b(l)J(W,b;x,y).
b.Set
ΔW(l):=ΔW(l)+∇W(l)J(W,b;x,y).
c.Set
Δb(l):=Δb(l)+∇b(l)J(W,b;x,y)
- Update the parameters:
W(l)b(l)=W(l)−α[(1mΔW(l))+λW(l)]=b(l)−α[1mΔb(l)]
根据:
J(W,b)=[1m∑i=1mJ(W,b;x(i),y(i))]+λ2∑l=1nl−1∑i=1sl∑j=1sl+1(W(l)ji)2=[1m∑i=1m(12∥∥hW,b(x(i))−y(i)∥∥2)]+λ2∑l=1nl−1∑i=1sl∑j=1sl+1(W(l)ji)2
以及:
Jsparse(W,b)=J(W,b)+β∑j=1s2KL(ρ||ρ^j),
我们可以写出cost function,更具体一点的内容在UFLDL上都有说明,这里说明cost function的三个部分:
- squared-error term
- regularization term (also called a weight decay term)
- penalty term(BP神经网络没有这一项),which is for measuring how different two different distributions are
需要注意的是,在将BP神经网络转化为Sparse Autoencoder时,除了让y=x,在BackPropagation中计算第二层( \textstyle l=2 )更新的时候我们已经计算了
δ(2)i=⎛⎝∑j=1s2W(2)jiδ(3)j⎞⎠f′(z(2)i),
现在我们将其换成
δ(2)i=⎛⎝⎛⎝∑j=1s2W(2)jiδ(3)j⎞⎠−β(−ρρ^i+1−ρ1−ρ^i)⎞⎠f′(z(2)i).
就可以了。
本人实现的代码如下(可能存在诸多错误):
- sampleIMAGES.m
for index = 1:numpatches
rand_im=randperm(10);
rand_image=rand_im(3);
rand_vec=randperm(505);
rand_num=rand_vec(3);
patch=IMAGES(rand_num:rand_num+7,rand_num:rand_num+7,rand_image);
patches(:,index)=patch(:);
end
- computeNumericalGradient.m
EPSILON=0.0001;
for i=1:size(theta)
theta_plus=theta;
theta_minu=theta;
theta_plus(i)=theta_plus(i)+EPSILON;
theta_minu(i)=theta_minu(i)-EPSILON;
numgrad(i)=(J(theta_plus)-J(theta_minu))/(2*EPSILON);
end
Jcost = 0;
Jweight = 0;
Jsparse = 0;
[n m] = size(data);
z2 = W1*data+repmat(b1,1,m);
a2 = sigmoid(z2);
z3 = W2*a2+repmat(b2,1,m);
a3 = sigmoid(z3);
Jcost = (0.5/m)*sum(sum((a3-data).^2));
Jweight = (1/2)*(sum(sum(W1.^2))+sum(sum(W2.^2)));
rho = (1/m).*sum(a2,2);
Jsparse = sum(sparsityParam.*log(sparsityParam./rho)+ ...
(1-sparsityParam).*log((1-sparsityParam)./(1-rho)));
cost = Jcost+lambda*Jweight+beta*Jsparse;
d3 = -(data-a3).*sigmoidInv(z3);
sterm = beta*(-sparsityParam./rho+(1-sparsityParam)./(1-rho));
d2 = (W2'*d3+repmat(sterm,1,m)).*sigmoidInv(z2);
W1grad = W1grad+d2*data';
W1grad = (1/m)*W1grad+lambda*W1;
W2grad = W2grad+d3*a2';
W2grad = (1/m).*W2grad+lambda*W2;
b1grad = b1grad+sum(d2,2);
b1grad = (1/m)*b1grad;
b2grad = b2grad+sum(d3,2);
b2grad = (1/m)*b2grad;
其实Autoencoder算法类似PCA,都是找输入参数结构的联系,然后将之压缩。如果网络的输入数据是完全随机的,比如每一个输入 x(i) 都是一个跟其它特征完全无关的独立同分布高斯随机变量,那么这一压缩表示将会非常难学习。但是如果输入数据中隐含着一些特定的结构,比如某些输入特征是彼此相关的,那么这一算法就可以发现输入数据中的这些相关性。事实上,这一简单的自编码神经网络通常可以学习出一个跟主元分析(PCA)结果非常相似的输入数据的低维表示。
前向和反向算法其实是分步的,对于给定的minfun算法,只需将cost和grad传入计算即可。
如果我们给隐藏神经元加入稀疏性限制,那么自编码神经网络即使在隐藏神经元数量较多的情况下仍然可以发现输入数据中一些有趣的结构。