模式识别hw2-------基于matconvnet,用CNN实现人脸图片性别识别

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bizer_csdn/article/details/54755843

主要来源模式识别课程大作业,本文首先感谢当初的助教和一起完成作业的队友


matconvnet在matlab下封装了CNN常见算法,网址http://www.vlfeat.org/matconvnet/,本文采用matconvnet-1.0-beta16.tar.gz


第一题: 运用matconvet提供的mnist网络结构,

通过train文件夹中人脸图片训练网络(训练集),通过val文件夹调整网络结构(验证集),最后通过test文件夹测试网络性能(测试集)


第一题: 设计一个神经网络,并与第一题比较


第一题

1.利用train和val文件夹进行训练和调参:

    利用matconvnet工具包中对mnist手写数字体设计的cnn结构,识别人脸性别。主程序为“cnn_mnist.m”,而cnn结构初始化在“cnn_mnist_init.m”中完成,网络结构不需要重新设计,只需要在cnn_mnist.m中更改数据的输入,在cnn_mnist_init.m更改网络的输出。

    对于本题数据集,“train”中图片为训练集,“val”中为验证集,“test”中为测试集。首先利用“train”和“val”中人脸图片训练cnn网络权重,最后对训练好的网络用“test”中数据进行测试。

    本题主程序所在M文件为“cnn_mnist_new.m”;网络结构文件为“cnn_mnist_init_new.m”;“test_p1.m”为对训练好的网络进行测试的主程序。

   网络激活函数,在缺省情况下,默认为“sigmoid”。

   首先在“cnn_mnist_init_new.m”中先拷贝“cnn_mnist.m”中代码,然后主要更改其中“getMnistImdb”函数。由于mnist数据集中的图像数据28*28 ,而本次实验中,所用图片大小为100*100 ,因此,需要把这些数字图像压缩为28*28 。

   函数“getMnistImdb”中变量“x1”存储训练集中的图片,男性和女性的人脸图片给300张,其前300维存储男性,后300维存储女性;而变量“y1”存储训练集对应的标签,也是其前300维存储男性,后300维存储女性,这里需要注意的是,matconvnet不能把类别标签设置为“0”,否则对其他一些类别不能再识别;而本次作业为二分类问题,这里,男性对应于标签“1”,女性对应于标签“2”。同理,“x2”和“y2”对应于验证集中的数据,其中男性和女性图片各80张。

   作为说明,这里只给出读取训练集数据中女性的示例代码:

%x1(:,:,1:300) stores female images in the train set
%x1(:,:,301:600) stores male images in the train set
x1=zeros(28,28,600);
%y1(1:300) stores female labels(1) in the train set
%y1(301:600) stores male labels(2) in the train set
y1=cat(2,ones(1,300),ones(1,300)+1);
%读取训练集图片-female
img_file_path=fullfile(vl_rootnn,'examples\mnist\image\image\new\train\female');
img_dir=dir(img_file_path);
for i=1:length(img_dir)-2    filename=sprintf('%s\\%s\\fy.bmp',img_file_path,img_dir(i+2).name);
    I=imread(filename);
    I=imresize(I,[28,28]); %将图片压缩为28*28大小
    x1(:,:,i)=I;
end

另外,为了防止过拟合,“cnn_mnist_init_new.m”中加入dropout,所谓“dropout”就是在训练过程中,随机让一些节点不参与计算,加入方式为:

net.layers{end+1} = struct('type', 'dropout', 'rate', 0.5);

并且迭代次数设置为300,最重要的是,要把“softmaxloss”前一全连接层中神经元输出节点改2,即:

net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(1,1,500,2, 'single'), zeros(1,2,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;

执行“cnn_mnist_init_new.m”可以得到如下结果:




其中,“energy”训练过程中训练集和和验证集的总的误差能量之和;而“error”分别对应训练集和验证机的错误率,“top1err”是“softmax”层输出过程中最高得分项对应错误率,而由于是二分类问题,“top5err”(前5项得分最高项对应错误率)是没有意义的。

这里需要说明一下,由于是二分类问题,最后分类器一般“logistic regression”,而“softmax”是“logistic regression”升级版,支持多分类问题。由于,我们网络最后是全连接到2个神经元上,其中“1”代表女性,“2”代表男性(需要注意的是,运用“softmax”,matconvnet类别标签是不支持0的),并且通过“softmax”进行分类。

2.利用 test文件夹测试:

“test_p1.m”是对已经训练好的网络测试“test”文件夹下数据的主程序。测试集共265张图片,其中男性56张,女性209张。读取数据过程和“cnn_mnist_new.m”相似,这里不再赘述。而加载网络主要过程:

train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem1\problem-1-net-epoch\imdb.mat');
 load(net_path);
net.layers{end}.type = 'softmax';

其中,“net_path”为网络所在文件路径,并且要主要加载网络之后,要把原来的“softmaxloss”改为“softmax”。并且处理测试集数据时候,要把测试集的数据减去训练集的均值(因为训练时候,预处理过程含有去均值的过程),其中,均值存储在训练集中的“imdb”结构体中,

示例代码如下:

test_set=single(test_set);
train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem1\problem-1-net-epoch\imdb.mat');
train_imdb=load(train_imdb_path);
train_data_mean=train_imdb.images.data_mean;
test_set=bsxfun(@minus,test_set,train_data_mean);

而这个前向过程的输出主要利用函数“vl_simplenn”,示例如下:

err_cnt=0; 
best_scores=zeros(1,female_num+male_num);
err_id=zeros(1,female_num+male_num);
for i=1:(female_num+male_num)
    res=vl_simplenn(net,test_set(:,:,i));
    scores=squeeze(gather(res(end).x));
    [best_score, best_id] = max(scores);
    best_scores(i)=best_score;
    if abs(str2double(net.meta.classes.name{best_id})-test_label(i))>1e-1;
        err_cnt=err_cnt+1;
        err_id(i)=1;
    end
    
end
err_rate=err_cnt/(female_num+male_num);
实验结果,最低错误率为6.79%

运行test_p1.m文件对网络进行测试。本文件共重复10次实验,实验结果分布如上所示。其中平均错误率为8.26%,最低错误率为6.79%。

第二题

1.利用train和val文件夹进行训练和调参:

与第一题类似,“my_cnn.m”是训练过程的主程序,“my_cnn_init.m”对应网络结构,“test_my_cnn.m” 为对训练好的网络进行测试的主程序。读取图片数据与第一题类似,这里不再赘述。

而“my_cnn_init.m”中网络结构对应为:

该网络通过卷积和池化降低维度,并且网络最后全连接到两个神经元上,通过“softmax”进行分类。然后“relu”作为激活函数,训练时候一般需要"batch normalization"

核心代码如下:
net.layers = {} ;
%得到96*96
net.layers{end+1} = struct('type', 'conv', ...   
                           'weights', {{f*randn(5,5,1,20, 'single'), zeros(1, 20, 'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ; 
%得到48*48
net.layers{end+1} = struct('type', 'pool', ...
                           'method', 'avg', ...
                           'pool', [2 2], ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到44*44
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,10,20, 'single'),zeros(1,20,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
%得到22*22
net.layers{end+1} = struct('type', 'pool', ...
                           'method', 'avg', ...
                           'pool', [2 2], ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到18*18
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,20,40, 'single'),zeros(1,40,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'dropout', 'rate', 0.2);
%得到9*9
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,40,100, 'single'), zeros(1,100,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ;
%得到5*5
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,100,100, 'single'), zeros(1,100,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'relu') ;
%得到1*1
net.layers{end+1} = struct('type', 'conv', ...
                           'weights', {{f*randn(5,5,100,2, 'single'), zeros(1,2,'single')}}, ...
                           'stride', 1, ...
                           'pad', 0) ;
net.layers{end+1} = struct('type', 'softmaxloss') ;

执行“my_cnn.m”可以得到如下结果:



观察上图可知,当训练次数为第222次时,该网络对val集达到了最低预测错误率,大约为7%。其后有细微的上升趋势。故在下文中选择第222次网络结果进行test集的测试。


2.利用 test文件夹测试:

“test_p2.m”是对已经训练好的网络测试“test”文件夹下数据的主程序。测试集共265张图片,其中男性56张,女性209张。加载网络主要过程:

train_imdb_path=fullfile(vl_rootnn,'\examples\mnist\problem2\problem-2-net-epoch\imdb.mat');
train_imdb=load(train_imdb_path);
net_path=fullfile(vl_rootnn, '\examples\mnist\problem2\problem-2-net-epoch\','net-epoch-222.mat') ;
load(net_path);
net.layers{end}.type = 'softmax';

利用函数“vl_simplenn”进行预测,本部分同问题一,故不再复述。

实验结果,最低错误率为4.53%

运行test_p2.m文件对网络进行测试。本文件共重复10次实验,实验结果分布如上所示。其中平均错误率为7.85%,最低错误率为4.53%。




一些思考:

为何不能分块:

在完成第一题的时候,除了利用imresize函数对图像进行缩放外,我们还试图对图像进行分块处理,如将100*100的图像分割为16张28*28的子图,如下所示:


然后再对子图进行cnn训练,但是效果并不理想,甚至训练过程都不收敛:



分析这个现象的原因,我们认为,人脸图像并不能分割为子块。当分割为子块时,人脸不再是人脸,而只是各个部分离散的多张图片,其中男女图像各个部分高度相似。甚至我们人类在判别的时候也不能分清一个子图属于男还是属于女。如下图第1排的第1张图片,我们很难判断该子图属于女性。
图像的分割,造成了信息的丢失,部分之和再也不能代替整体!



为何28*28与100*100的精度近似相等



观察下图,左为第一题训练结果,右为第二题训练结果。可以看到,28*28与100*100大小的图片经过cnn训练后,对男女性别的判断,在精度上近似相等。

究其原因,我们认为,图片大小的改变对人类来说,并不影响判断结果,丢失的信息不足以影响二值化判断。





代码已上传:http://download.csdn.net/detail/bizer_csdn/9827175
使用时候替换安装matconvnet文件夹下的examples文件夹


展开阅读全文

没有更多推荐了,返回首页