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

实验测试:

bp神经网络 Sigmoid BP神经网络数字识别_权值

bp神经网络 Sigmoid BP神经网络数字识别_权值_02

结论:可以看到,用训练集,训练400次以后,用测试集测试,得到的正确率为0.9404.正确率较高,符合预期。各个数据的错误率如图所示。可以看到5和8的错误率最高,但是正确率都在百分之九十左右。结果较为满意。

有兴趣的同学可以利用自己手写的数字进行测试。实际上本实验之中训练出来的 网络泛化性较差,用不在数据集之中的手写数字进行测试得出的正确率没有这么高,有待改进。

代码较多,阅读可能有点困难,见谅。若有错误,欢迎指正交流。