照旧,本练习的相关资料链接将会扔到评论区,大家自取
1. 前向传播
1.1 代价函数
本节练习的第一个任务,就是写出该神经网络的代价函数,实际上,该代价函数跟我们之前所学习的逻辑回归的代价函数几乎一样,下面来对比一下究竟有什么异同
- 逻辑回归
- 神经网络
这样一对比就很清楚了,显然不同之处是y,也就是输出值,根本原因在于在逻辑回归中我们仅有一个输出,而神经网络结构的最终输出是有多个的,需要把多个输出的误差值加在一起才是整个神经网络模型的最终误差
简单了,直接加个sum
求和一下就好了嘛!
你胸有成竹地打下这一堆代码
%计算各神经元的值
a1 = [ones(m,1),X]'; %401*5000
z2 = Theta1*a1; %25*5000
a2 = [ones(1,size(z2,2));sigmoid(z2)]; %26*5000
z3 = Theta2*a2; %10*5000
a3 = sigmoid(z3); %10*5000
%代价函数
J = -sum(sum(yy.*log(a3')+(1-yy).*log(1-a3')))/m;
运行,你会发现报错了,什么原因呢?这里就需要注意分析本次作业给我们的数据集,当你运行一次之后在octave左下角处可以看见X和y的维度
显然该数据集不是以向量的形式给出,因此我们需要进行转换,把数据集的每一个结果都转换为与神经网络对应格式的向量。显然根据该作业的神经网络结构(400*25*10),其输出应该是一个10*1的向量,所以我们应该先把结果集中的每一个结果转换为一个10*1的向量,事实上1*10也是可以的,具体原因就不多说应该很好理解
所以我们要把这堆代码加进去
yy = zeros(m,num_labels); %num_labels改为10会报错 原因不详 5000*10
for i = 1:m
yy(i,y(i)) = 1;
endfor
注意这里有个小坑,可能也不算坑,是我一开始做的时候不够严谨,在初始化矩阵时我尝试直接用10这个纯数字(yy = zeros(m,10)
),结果运行的结果是对的,但是submit
到吴恩达老师的课程网站上时候就报错,我猜应该是对这里也做了一个检查,大家要注意这个小小的细节
于是乎这个完整的代码就成了这个样子,把它扔进nnCostFunction.m
文件中
a1 = [ones(m,1),X]'; %401*5000
z2 = Theta1*a1; %25*5000
a2 = [ones(1,size(z2,2));sigmoid(z2)]; %26*5000
z3 = Theta2*a2; %10*5000
a3 = sigmoid(z3); %10*5000
yy = zeros(m,num_labels); %num_labels改为10会报错 原因不详 5000*10
for i = 1:m
yy(i,y(i)) = 1;
endfor
J = -sum(sum(yy.*log(a3')+(1-yy).*log(1-a3')))/m +;
运行过后你应该会得到这个结果
1.2 正则化代价函数
前面部分的代价函数是没有添加正则化项的,正则化项的作用是什么大家应该也很清楚了,下一步的工作就是把正则化项添加到代价函数中
来,再看一眼这里的正则化项,同样的与逻辑回归有所区别,这里需要对神经网络中每一层的权值都要进行正则化
看起来复杂,其实写起来不难,不过还要注意的是不需要对偏置项的权值进行正则化操作,把上一小节的代价函数改为如下代码
J = -sum(sum(yy.*log(a3')+(1-yy).*log(1-a3')))/m + ...
lambda*sum(sum(Theta1(:,2:end).^2))/2/m + ...
lambda*sum(sum(Theta2(:,2:end).^2))/2/m;
然后再运行一遍,你同样会看到正确的结果
2. 反向传播
2.1 sigmoid函数求导
在反向传播阶段进行权值的修正时,需要利用到激活函数的导数来进行相关的计算,因此在这一部分当中第一个小任务就是写出我们所使用的sigmoid函数的导数形式
就这?简单!下面这行代码快进去sigmoidGradient.m
文件中待着吧
g = sigmoid(z).*(1-sigmoid(z));
主程序文件中并没有提供该部分的测试内容,但是作业说明书中提示到,你可以单独运行这个函数进行检测
2.2 权值更新
重头戏来了重头戏来了重头戏来了,重要的事情说三遍,整个BP神经网络的精粹在于其反向传播的过程,而往往精粹的部分都是比较难以理解的
在作业说明书处,有整个很详细的计算公式,跟着这些公式来敲一遍,其实可以有一个比较清楚的理解,个人建议在敲的过程中,注意各个向量或矩阵的大小(是几乘几),你会更加容易理解整个反向传播算法的迭代过程
此外,在代码文件中,他也提示到了用for循环来进行整个反向传播的计算过程(对m个样本求和累加)
这时候你可能会想到,不是可以直接用矩阵运算来进行吗?对的,确实是可以,但以本人的亲身经验来说,我还是推荐你先使用for循环来实现一遍,再考虑用矩阵运算求解,为什么?因为当你一上来就直接用矩阵运算来做的时候,你思考的难度其实加大了,这里涉及到多个向量/矩阵的运算,在高维度状态时很难去理清楚其之间的联系,而使用for循环可以让你在一个更低的维度去思考,可以更容易有一种如沐春风的感觉
同样的,以下代码还是在nnCostFunction.m
文件中
for i = 1:m
a1 = X(i,:)';
a1 = [1;a1];
h2 = sigmoid(Theta1 * a1);
a2 = [1;h2];
h3 = sigmoid(Theta2 * a2);
a3 = h3;
c3 = a3 - yy(i,:)';
c2 = Theta2(:,2:end)'*c3.*sigmoidGradient(Theta1 * a1);
Theta2_grad = Theta2_grad + c3*a2';
Theta1_grad = Theta1_grad + c2*a1';
endfor
Theta1(:,1) = 0;
Theta2(:,1) = 0;
Theta2_grad = Theta2_grad/m + lambda*Theta2/m;
Theta1_grad = Theta1_grad/m + lambda*Theta1/m;
经过运算迭代过后,你可以看到其准确率为大概95%
这时候你应该会发现使用for循环进行这个迭代过程非常的慢,那么这时候就需要矩阵来出手了,我个人电脑测试两种形式的训练速度起码相差5倍以上
下面来看看利用矩阵运算的代码
c3 = a3 - yy'; %10*5000
c2 = Theta2(:,2:end)'*c3.*sigmoidGradient(z2); %25*5000
Theta2_grad = c3*a2'; %10*26
Theta1_grad = c2*a1'; %25*401
Theta1(:,1) = 0;
Theta2(:,1) = 0;
Theta2_grad = Theta2_grad/m + lambda*Theta2/m;
Theta1_grad = Theta1_grad/m + lambda*Theta1/m;
训练结果是一致的,这里就不贴了,大家可以好好研究一下矩阵实现的精妙之处
至此,BP神经网络的整个构建和训练过程我们已经有了初步的掌握了!