BP神经网络手写数字识别的matlab实现
BP神经网络原理
BP神经网络是一种按误差逆传播算法训练的多层前馈网络。标准的前馈神经网络一般包括三层神经,即输入层,隐含层,输出层,每层神经包含的神经元个数不一。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。网络上有许多BP神经网络的原理,比较通俗易懂,读者可以自行搜索。这里推荐一篇BP神经网络权值迭代过程的博文和手写BP神经网络权值迭代公式的推导博文
链接1: BP神经网络权值迭代的直观过程.
链接2: BP神经网络权值迭代公式.
数据集选择
本次实验采用mmist数据集对搭建的网络进行训练和测试,下载网站如下:
链接3: MNIST数据集.
该数字集包含60000张手写数字的照片的训练集和包含10000张手写数字照片的测试集。每张图片为28*28像素,带有标签,满足训练网络所需要求。
实验代码
由于代码之中注释比较全面,所以直接上代码。
读取image数据
function y=loadmnistdata()
fp = fopen('文件保存路径', 'r');%打开文件句柄,路径之中不可以有汉字,否则打不开
assert(fp ~= -1, ['Could not open ',文件保存路径', '']);
magic = fread(fp, 1, 'int32', 0, 'ieee-be');
assert(magic == 2051, ['Bad magic number in ','文件保存路径', '']);
numImages = fread(fp, 1, 'int32', 0, 'ieee-be');%读取图片数
numRows = fread(fp, 1, 'int32', 0, 'ieee-be');%读取图片行数
numCols = fread(fp, 1, 'int32', 0, 'ieee-be');%读取图片列数
images = fread(fp, inf, 'unsigned char');%读取数据
images = reshape(images, numCols, numRows, numImages);%转化成28X28X6000
images = permute(images,[2 1 3]);%旋转
images = images/255;%让图片更加清晰
y=images;
fclose(fp);%关闭文件
end
读取label数据
function lables = loadmnistlabels()
fp = fopen('文件保存路径', 'r');
assert(fp ~= -1, ['Could not open ', '文件保存路径', '']);
magic = fread(fp, 1, 'int32', 0, 'ieee-be');
assert(magic == 2049, ['Bad magic number in ', '文件保存路径', '']);
numLabels = fread(fp, 1, 'int32', 0, 'ieee-be');
lables = fread(fp, inf, 'unsigned char');%
assert(size(lables,1) == numLabels, 'Mismatch in label count');
fclose(fp);
end
特征提取
虽然数据集中,每一张相片只有28x28像素,但是若是将每个像素的数值作为神经网络的输入,会导致神经元的数量过大,神经元特别是输入到输入层神经元的连接变得非常复杂,导致网络训练的速度变慢,推广能力变差。而且图片中并不是每一个像素都包含了识别该图片所必备的信息。因此要对照片进行特征提取,减少不必要信息输入,简化网络结构,增加训练速度和网络的推广性。特征提取的思路如下:在以4x4个像素为一个单位,原来的28x28像素图片分成49个4x4的单位,将里面的16个像素的数值相加,用这个数表示这个单位的特征。这样就把原本28x28=784个像素的照片变成了7x7=49的特征矩阵。
function y=takefeatures(images)
a=size(images);
b=a(3);
images=mat2cell(images,4*ones(1,7),4*ones(1,7),ones(1,b));%转为元胞数组,4X4X7x60000
images=cellfun(@(x) sum(x(:)),images);%4X4元胞求和,转换成 矩阵
images=reshape(images,[49,1,b]);%再转换成49X1X60000
y=0.1*images;%相当于归一化
end
BP神经网络代码
function [y1,y2,y3,y4]=bpnet_handwritednum(images,lables,innum,hdnum,otnum,learn_rate,traic)
in_w=double(rand(49,innum));%隐含层1权重
hd_w=double(rand(innum,hdnum));%隐含层2权重
ot_w=double(rand(hdnum,otnum));%输出层权重
in_incell(1,49)=(0);%保存隐含层1各个节点的输入值
in_outcell(1,innum)=(0);%保存隐含层1各个节点的输出值
hd_incell(1,hdnum)=(0);%保存隐含层2各个节点的输入值
hd_outcell(1,hdnum)=(0);%保存隐含层2各个节点的输出值
ot_incell(1,otnum)=(0);%保存输出层各个节点的输入值
ot_outcell(1,otnum)=(0);%保存输出层各个节点的输出值
ot_error(1,otnum)=(0);%保存输出各节点的偏差
hd_error(1,hdnum)=(0);%保存反向传后递隐含层各节点的误差
in_error(1,innum)=(0);%保存反向传播后隐含层1各节点的误差
bi=size(images);
count=0;
canout=0;
derror(traic)=0;%
tic
while(1)
count=count+1;
derror(count)=canout/bi(3);%误差计算,实际上没有用到
if count>=traic
break;
end
canout=0;
for i=1:1:bi(3)
in_incell=images(:,1,i)'*in_w;
if min(in_incell)<=-500
in_incell=mapminmax(in_incell,-400,20);
end
% in_incell=mapminmax(in_incell,-2,2); 原本用来归一化,但是导致训练过程变长,所以不用
%隐含层1各个节点的输出
in_outcell=1./(1+exp(-in_incell));
%隐含层2各个节点的输入
hd_incell=in_outcell*hd_w;
% hd_incell=mapminmax(hd_incell,0,1); 原本用来归一化,但是训练过程变长,所以不用
%隐含层2各个节点的输出
hd_outcell=1./(1+exp(-hd_incell));
%输出层各个节点的输入
ot_incell=hd_outcell*ot_w;
% ot_incell=mapminmax(ot_incell,0,1); 原本用来归一化,但是训练过程变长,所以不用
%输出层的输出
ot_outcell=1./(1+exp(-ot_incell));
% ot_outcell=mapminmax( ot_outcell,0,1);
switch lables(i)%判断图片对应的输出
case 0
ideal_out=[1,0,0,0,0,0,0,0,0,0];
case 1
ideal_out=[0,1,0,0,0,0,0,0,0,0];
case 2
ideal_out=[0,0,1,0,0,0,0,0,0,0];
case 3
ideal_out=[0,0,0,1,0,0,0,0,0,0];
case 4
ideal_out=[0,0,0,0,1,0,0,0,0,0];
case 5
ideal_out=[0,0,0,0,0,1,0,0,0,0];
case 6
ideal_out=[0,0,0,0,0,0,1,0,0,0];
case 7
ideal_out=[0,0,0,0,0,0,0,1,0,0];
case 8
ideal_out=[0,0,0,0,0,0,0,0,1,0];
case 9
ideal_out=[0,0,0,0,0,0,0,0,0,1];
end
%计算实际输出和理想输出之间的偏差
ot_error=ideal_out-ot_outcell;
%ot_error=0.5*(ot_error.*ot_error);
%原本是按照推导用欧氏几何距离作为误差计算,但是后来发现直接用差值效果更好
canout=canout+ot_error*ot_error';
%反向传播隐含层各个节点的误差
hd_error=ot_error*ot_w';
% hd_error= (exp(-ot_incell)./(exp(-ot_incell) + 1).^2).*ot_error*ot_w';
%原本是按照推导用应该用上述公式计算误差,但是后来发现直接用权值乘效果跟更好
%反向传播输入层各个节点的误差
in_error=hd_error*hd_w';
% %输入层权值更新
%in_w= in_w+learn_rate*images(:,1,i).*(exp(-images(:,1,i)')./(exp(-images(:,1,i)') + 1).^2)'.*in_error';
in_w= in_w+learn_rate*images(:,1,i)*(exp(-in_incell)./(exp(-in_incell) + 1).^2).*in_error;
%隐含层2权值更新
hd_w= hd_w+learn_rate*in_outcell'*(exp(-hd_incell)./(exp(-hd_incell) + 1).^2).*hd_error;
%隐含层1权值更新
ot_w=ot_w+learn_rate*hd_outcell'*(exp(-ot_incell)./(exp(-ot_incell) + 1).^2).*ot_error;
end
end
toc
y1=in_w;
y2=hd_w;
y3=ot_w;
y4=derror;
end
测试正确率代码
function y=bpnet_test(data,lables,in_w,hd_w,ot_w)
isright=0;
error(10)=0;
yes(10)=0;
a=1;
b=size(lables);
en=b(1);
for i=1:1:en
%隐含层1各个节点的输入
in_incell=data(:,1,i)'*in_w;
%隐含层1各个节点的输出
in_outcell=1./(1+exp(-in_incell));
%隐含层2各个节点的输入
hd_incell=in_outcell*hd_w;
%隐含层2各个节点的输出
hd_outcell=1./(1+exp(-hd_incell));
%输出层各个节点的输入
ot_incell=hd_outcell*ot_w;
%输出层的输出
ot_outcell=1./(1+exp(-ot_incell));
switch lables(i)%判断图片对应的输出
case 0
ideal_out=[1,0,0,0,0,0,0,0,0,0];
case 1
ideal_out=[0,1,0,0,0,0,0,0,0,0];
case 2
ideal_out=[0,0,1,0,0,0,0,0,0,0];
case 3
ideal_out=[0,0,0,1,0,0,0,0,0,0];
case 4
ideal_out=[0,0,0,0,1,0,0,0,0,0];
case 5
ideal_out=[0,0,0,0,0,1,0,0,0,0];
case 6
ideal_out=[0,0,0,0,0,0,1,0,0,0];
case 7
ideal_out=[0,0,0,0,0,0,0,1,0,0];
case 8
ideal_out=[0,0,0,0,0,0,0,0,1,0];
case 9
ideal_out=[0,0,0,0,0,0,0,0,0,1];
end
a=lables(i)+1;
if find(ideal_out==max(ideal_out))==find(ot_outcell==max(ot_outcell))
isright=isright+1;
yes(a)=yes(a)+1;
else
error(a)=error(a)+1;
end
end
error./yes
y=isright/en;
end
实验测试:
结论:可以看到,用训练集,训练400次以后,用测试集测试,得到的正确率为0.9404.正确率较高,符合预期。各个数据的错误率如图所示。可以看到5和8的错误率最高,但是正确率都在百分之九十左右。结果较为满意。
有兴趣的同学可以利用自己手写的数字进行测试。实际上本实验之中训练出来的 网络泛化性较差,用不在数据集之中的手写数字进行测试得出的正确率没有这么高,有待改进。
代码较多,阅读可能有点困难,见谅。若有错误,欢迎指正交流。