DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)python
@author:wepongit
@blog:http://blog.csdn.net/u012162613/article/details/43277187github
本文代码下载地址:个人github算法
本文主要讲解将CNN应用于人脸识别的流程,程序基于python+numpy+theano+PIL开发,采用相似LeNet5的CNN模型,应用于olivettifaces人脸数据库,实现人脸识别的功能,模型的偏差降到了5%如下。本程序只是我的学习过程的一个toy implement,样本很小,模型随时都会过拟合。数据库
可是,本文意在理清程序开发CNN模型的具体步骤,特别是针对图像识别,从拿到图像数据库,到实现一个针对这个图像数据库的CNN模型,我以为本文对这些流程的实现具备参考意义。数组
《本文目录》网络
1、olivettifaces人脸数据库介绍框架
2、CNN的基本“构件”(LogisticRegression、HiddenLayer、LeNetConvPoolLayer)机器学习
3、组建CNN模型,设置优化算法,应用于Olivetti Faces进行人脸识别函数
4、训练结果以及参数设置的讨论
5、利用训练好的参数初始化模型
6、一些须要说明的
1、olivettifaces人脸数据库介绍
Olivetti Faces是纽约大学的一个比较小的人脸库,由40我的的400张图片构成,即每一个人的人脸图片为10张。每张图片的灰度级为8位,每一个像素的灰度大小位于0-255之间,每张图片大小为64×64。以下图,这个图片大小是1190*942,一共有20*20张人脸,故每张人脸大小是(1190/20)*(942/20)即57*47=2679:

本文所用的训练数据就是这张图片,400个样本,40个类别,乍一看样本好像比较小,用CNN效果会好吗?先别下结论,请往下看。
要运行CNN算法,这张图片必须先转化为数组(或者说矩阵),这个用到python的图像库PIL,几行代码就能够搞定,具体的方法我以前恰好写过一篇文章,也是用这张图,考虑到文章冗长,就不复制过来了,连接在此:《利用Python PIL、cPickle读取和保存图像数据库》。
训练机器学习算法,咱们通常将原始数据分红训练数据(training_set)、验证数据(validation_set)、测试数据(testing_set)。本程序将training_set、validation_set、testing_set分别设置为320、40、40个样本。它们的label为0~39,对应40个不一样的人。这部分的代码以下:
- def load_data(dataset_path):
- img = Image.open(dataset_path)
- img_ndarray = numpy.asarray(img, dtype='float64')/256
- faces=numpy.empty((400,2679))
- for row in range(20):
- for column in range(20):
- faces[row*20+column]=numpy.ndarray.flatten(img_ndarray [row*57:(row+1)*57,column*47:(column+1)*47])
-
- label=numpy.empty(400)
- for i in range(40):
- label[i*10:i*10+10]=i
- label=label.astype(numpy.int)
-
-
- train_data=numpy.empty((320,2679))
- train_label=numpy.empty(320)
- valid_data=numpy.empty((40,2679))
- valid_label=numpy.empty(40)
- test_data=numpy.empty((40,2679))
- test_label=numpy.empty(40)
-
- for i in range(40):
- train_data[i*8:i*8+8]=faces[i*10:i*10+8]
- train_label[i*8:i*8+8]=label[i*10:i*10+8]
- valid_data[i]=faces[i*10+8]
- valid_label[i]=label[i*10+8]
- test_data[i]=faces[i*10+9]
- test_label[i]=label[i*10+9]
-
-
- def shared_dataset(data_x, data_y, borrow=True):
- shared_x = theano.shared(numpy.asarray(data_x,
- dtype=theano.config.floatX),
- borrow=borrow)
- shared_y = theano.shared(numpy.asarray(data_y,
- dtype=theano.config.floatX),
- borrow=borrow)
- return shared_x, T.cast(shared_y, 'int32')
-
-
-
- train_set_x, train_set_y = shared_dataset(train_data,train_label)
- valid_set_x, valid_set_y = shared_dataset(valid_data,valid_label)
- test_set_x, test_set_y = shared_dataset(test_data,test_label)
- rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y),
- (test_set_x, test_set_y)]
- return rval
2、CNN的基本“构件”(LogisticRegression、HiddenLayer、LeNetConvPoolLayer)
卷积神经网络(CNN)的基本结构就是输入层、卷积层(conv)、子采样层(pooling)、全链接层、输出层(分类器)。 卷积层+子采样层通常都会有若干个,本程序实现的CNN模型参考LeNet5,有两个“卷积+子采样层”LeNetConvPoolLayer。全链接层至关于MLP(多层感知机)中的隐含层HiddenLayer。输出层即分类器,通常采用softmax回归(也有人直接叫逻辑回归,其实就是多类别的logistics regression),本程序也直接用LogisticRegression表示。
代码太长,就不贴具体的了,只给出框架,具体能够下载个人代码看看:
- class LogisticRegression(object):
- def __init__(self, input, n_in, n_out):
- self.W = ....
- self.b = ....
- self.p_y_given_x = ...
- self.y_pred = ...
- self.params = ...
- def negative_log_likelihood(self, y):
- def errors(self, y):
-
- class HiddenLayer(object):
- def __init__(self, rng, input, n_in, n_out, W=None, b=None,activation=T.tanh):
- self.input = input
- self.W = ...
- self.b = ...
- lin_output = ...
- self.params = [self.W, self.b]
-
- class LeNetConvPoolLayer(object):
- def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
- self.input = input
- self.W = ...
- self.b = ...
-
- conv_out = ...
-
- pooled_out =...
- self.output = ...
- self.params = [self.W, self.b]
3、组建CNN模型,设置优化算法,应用于Olivetti Faces进行人脸识别
上面定义好了CNN的几个基本“构件”,如今咱们使用这些构件来组建CNN模型,本程序的CNN模型参考LeNet5,具体为:input+layer0(LeNetConvPoolLayer)+layer1(LeNetConvPoolLayer)+layer2(HiddenLayer)+layer3(LogisticRegression)
这是一个串联结构,代码也很好写,直接用第二部分定义好的各类layer去组建就好了,上一layer的输出接下一layer的输入,具体能够看看代码evaluate_olivettifaces函数中的“创建CNN模型”部分。
CNN模型组建好了,就剩下用优化算法求解了,优化算法采用批量随机梯度降低算法(MSGD),因此要先定义MSGD的一些要素,主要包括:代价函数,训练、验证、测试model、参数更新规则(即梯度降低)。这部分的代码在evaluate_olivettifaces函数中的“定义优化算法的一些基本要素”部分。
优化算法的基本要素也定义好了,接下来就要运用人脸图像数据集来训练这个模型了,训练过程有训练步数(n_epoch)的设置,每一个epoch会遍历全部的训练数据(training_set),本程序中也就是320我的脸图。还有迭代次数iter,一次迭代遍历一个batch里的全部样本,具体为多少要看所设置的batch_size。关于参数的设定我在下面会讨论。这一部分的代码在evaluate_olivettifaces函数中的“训练CNN阶段”部分。
代码很长,只贴框架,具体能够下载个人代码看看:
- def evaluate_olivettifaces(learning_rate=0.05, n_epochs=200,
- dataset='olivettifaces.gif',
- nkerns=[5, 10], batch_size=40):
-
-
-
-
-
-
-
-
-
-
- ...
- ....
- ......
-
-
-
-
- ...
- ....
- ......
-
-
-
-
- ...
- .....
- .......
另外,值得一提的是,在训练CNN阶段,咱们必须定时地保存模型的参数,这是在训练机器学习算法时一个常常会作的事情,这一部分的详细介绍我以前写过一篇文章《DeepLearning tutorial(2)机器学习算法在训练过程当中保存参数》。简单来讲,咱们要保存CNN模型中layer0、layer一、layer二、layer3的参数,因此在“训练CNN阶段”这部分下面,有一句代码:
- save_params(layer0.params,layer1.params,layer2.params,layer3.params)
这个函数具体定义为:
- def save_params(param1,param2,param3,param4):
- import cPickle
- write_file = open('params.pkl', 'wb')
- cPickle.dump(param1, write_file, -1)
- cPickle.dump(param2, write_file, -1)
- cPickle.dump(param3, write_file, -1)
- cPickle.dump(param4, write_file, -1)
- write_file.close()
若是在其余算法中,你要保存的参数有五个六个甚至更多,那么改一下这个函数的参数就行啦。
4、训练结果以及参数设置的讨论
ok,上面基本介绍完了CNN模型的构建,以及模型的训练,我将它们的代码都放在train_CNN_olivettifaces.py这个源文件中,将
Olivetti Faces这张图片跟这个代码文件放在同个目录下,运行这个文件,几分钟就能够训练完模型,而且在同个目录下获得一个params.pkl文件,这个文件保存的就是最后的模型的参数,方便你之后直接使用这个模型。
好了,如今讨论一下怎么设置参数,具体来讲,程序中能够设置的参数包括:学习速率learning_rate、batch_size、n_epochs、nkerns、poolsize。下面逐一讨论调节它们时对模型的影响。
学习速率learning_rate就是运用SGD算法时梯度前面的系数,很是重要,设得太大的话算法可能永远都优化不了,设得过小会使算法优化得太慢,并且可能还会掉入局部最优。能够形象地将learning_rate比喻成走路时步子的大小,想象一下要从一个U形的山谷的一边走到山谷最低点,若是步子特别大,像巨人那么大,那会直接从一边跨到另外一边,而后又跨回这边,如此往复。若是过小了,可能你走着走着就掉入了某些小坑,由于山路老是凹凸不平的(局部最优),掉入这些小坑后,若是步子仍是不变,就永远走不出那个坑。
好,回到本文的模型,下面是我使用时的记录,固定其余参数,调节learning_rate:
(1)kerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.1时,validation-error一直是97.5%,没降下来,分析了一下,以为应该是学习速率太大,跳过了最优。
(2)nkerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.01时,训练到epoch 60多时,validation-error降到5%,test-error降到15%
(3)nkerns=[20, 50], batch_size=40,poolsize=(2,2),learning_rate=0.05时,训练到epoch 36时,validation-error降到2.5%,test-error降到5%
注意,验证集和测试集都只有40张图片,也就是说只有一两张识别错了,仍是不错的,数据集再大点,偏差率能够降到更小。最后我将learning_rate设置为0.05。
PS:学习速率应该自适应地减少,是有专门的一些算法的,本程序没有实现这个功能,有时间再研究一下。
由于咱们采用minibatch SGD算法来优化,因此是一个batch一个batch地将数据输入CNN模型中,而后计算这个batch的全部样本的平均损失,即代价函数是全部样本的平均。而batch_size就是一个batch的所包含的样本数,显然batch_size将影响到模型的优化程度和速度。
回到本文的模型,首先由于咱们train_dataset是320,valid_dataset和test_dataset都是40,因此batch_size最好都是40的因子,也就是能让40整除,好比40、20、十、五、二、1,不然会浪费一些样本,好比设置为30,则320/30=10,余数时20,这20个样本是没被利用的。而且,若是batch_size设置为30,则得出的validation-error和test-error只是30个样本的错误率,并非所有40个样本的错误率。这是设置batch_size要注意的。特别是样本比较少的时候。
下面是我实验时的记录,固定其余参数,改变batch_size:
batch_size=一、二、五、十、20时,validation-error一直是97.5%,没降下来。我以为多是样本类别覆盖率太小,由于咱们的数据是按类别排的,每一个类别10个样本是连续排在一块儿的,batch_size等于20时其实只包含了两个类别,这样优化会很慢。
所以最后我将batch_size设为40,也就是valid_dataset和test_dataset的大小了,没办法,原始数据集样本太少了。通常咱们都不会让batch_size达到valid_dataset和test_dataset的大小的。
n_epochs也就是最大的训练步数,好比设为200,那训练过程最多遍历你的数据集200遍,当遍历了200遍你的dataset时,程序会中止。n_epochs就至关于一个中止程序的控制参数,并不会影响CNN模型的优化程度和速度,只是一个控制程序结束的参数。
20表示第一个卷积层的卷积核的个数,50表示第二个卷积层的卷积核的个数。这个我也是瞎调的,暂时没什么经验能够总结。
不过从理论上来讲,卷积核的个数其实就表明了特征的个数,你提取的特征越多,可能最后分类就越准。可是,特征太多(卷积核太多),会增长参数的规模,加大了计算复杂度,并且有时候卷积核也不是越多越好,应根据具体的应用对象来肯定。因此我以为,CNN虽号称自动提取特征,免去复杂的特征工程,可是不少参数好比这里的nkerns仍是须要去调节的,仍是须要一些“人工”的。
下面是个人实验记录,固定batch_size=40,learning_rate=0.05,poolsize=(2,2):
(1)nkerns=[20, 50]时,训练到epoch 36时,validation-error降到2.5%,test-error降到5%
(2)nkerns=[10, 30]时,训练到epoch 46时,validation-error降到5%,test-error降到5%
(3)nkerns=[5, 10]时,训练到epoch 38时,validation-error降到5%,test-error降到7.5%
poolzize在本程序中是设置为(2,2),即从一个2*2的区域里maxpooling出1个像素,说白了就算4和像素保留成1个像素。本例程中人脸图像大小是57*47,对这种小图像来讲,(2,2)时比较合理的。若是你用的图像比较大,能够把poolsize设的大一点。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++分割线+++++++++++++++++++++++++++++++++++++++++++
上面部分介绍完了CNN模型构建以及模型训练的过程,代码都在train_CNN_olivettifaces.py里面,训练完能够获得一个params.pkl文件,这个文件保存的就是最后的模型的参数,方便你之后直接使用这个模型。之后只需利用这些保存下来的参数来初始化CNN模型,就获得一个可使用的CNN系统,将人脸图输入这个CNN系统,预测人脸图的类别。
接下来就介绍怎么使用训练好的参数的方法,这部分的代码放在use_CNN_olivettifaces.py文件中。
5、利用训练好的参数初始化模型
在train_CNN_olivettifaces.py中的LeNetConvPoolLayer、HiddenLayer、LogisticRegression是用随机数生成器去随机初始化的,咱们将它们定义为能够用参数来初始化的版本,其实很简单,代码只须要作稍微的改动,只须要在LogisticRegression、HiddenLayer、LeNetConvPoolLayer这三个class的__init__()函数中加两个参数params_W,params_b,而后将params_W,params_b赋值给这三个class里的W和b:
- self.W = params_W
- self.b = params_b
params_W,params_b就是从params.pkl文件中读取来的,读取的函数:
- def load_params(params_file):
- f=open(params_file,'rb')
- layer0_params=cPickle.load(f)
- layer1_params=cPickle.load(f)
- layer2_params=cPickle.load(f)
- layer3_params=cPickle.load(f)
- f.close()
- return layer0_params,layer1_params,layer2_params,layer3_params
ok,能够用参数初始化的CNN定义好了,那如今就将须要测试的人脸图输入该CNN,测试其类别。一样的,须要写一个读图像的函数load_data(),代码就不贴了。将图像数据输入,CNN的输出即是该图像的类别,这一部分的代码在use_CNN()函数中,代码很容易看懂。
这一部分还涉及到theano.function的使用,我把一些笔记记在了use_CNN_olivettifaces.py代码的最后,由于跟代码相关,结合代码来看会比较好,因此下面就不讲这部分,有兴趣的看看代码。
最后说说测试的结果,我仍然以整副olivettifaces.gif做为输入,得出其类别后,跟真正的label对比,程序输出被错分的那些图像,运行结果以下:
错了五张,我标了三张:
6、一些须要说明的
首先是本文的严谨性:在文章开头我就说这只是一个toy implement,400张图片根本不适合用DL来作。
固然我写这篇文章,只是为了总结一下这个实现流程,这一点但愿对读者也有参考意义。
欢迎留言交流,有任何错误请不吝指出!