首页 > 技术文章 > 第12章 训练你的第一个CNN

paladinzxl 2018-09-21 12:47 原文

  第12章 训练你的第一个CNN

      既然熟悉了CNN基础,我们将用python和keras实现我们的第一个CNN。我们通过快速的回顾当构建和训练你的CNNs时应当注意的keras配置开始本章。之后将实现ShallowNet,它是一个仅有单个CONV层的非常浅的CNN。但是,不要被这个网络的简洁性蒙蔽了你,ShallowNet在CIFAR-10和Animals数据集上,与我们到目前为止本书所回顾的任何其它方法相比,它将获得高分类精确度的能力。

1         Keras配置和将图像转为数组

在我们实现ShallowNet之前,我们首先需要查看keras.json配置文件,以及该文件中的设置将如何影响您如何实现自己的CNNs。我们也将实现第二个名为ImageToArrayPreprocessor的图像预处理功能,它接收一副输入图像并且将它转换为keras可以工作的NumPy数组。

1.1  理解keras.json配置文件

在我们第一次将keras库导入python shell或执行import keras的python脚本时,keras将在home目录下产生一个keras.json的文件,现在我们打开该文件查看,vim ~/.keras/keras.json:

 

      我们注意到这个json格式文件有4个key以及对应的4个值,epsilon值在keras库的不同位置使用用于防止除以0操作错误,默认值为1e-7是固定的且不应该被改变。之后我们有一个floatx的值表示浮点精度,设置为float32是精度安全的。

      还有两个极其重要的配置为image_data_format和backend。默认keras库使用TensorFlow数值计算后端,我们也可以仅仅通过替换“tensorflow”成“theano”变成Theano计算后端。Keras库允许你编写与这些后端兼容的代码。最后这个image_data_format可配置两个值:channels_last和channels_first。我们知道图像在OpenCV中是按照(rows,columns,channels)的顺序表示,这也是keras默认配置的channels_last方式,即通道作为数组的最后的维度。两种设置,是因为在Theano社区,用户倾向于channels_first顺序,但是在TensorFlow发行版中教程和示例都是channels_last顺序,为了保持兼容,keras使用两种顺序。并且keras引入一个img_to_array的函数用于接收图像输入参数,并按照这个默认配置顺序来返回一个数组。

1.2  图像到数组预处理器

本节我们通过引入一个新的ImageToArrayPreprocessor预处理器用于将输入图像变为keras库支持的数组顺序的数组格式。我们首先将github上的chapter7/pyimagesearch文件夹拷贝到chapter12下,然后在preprocessing/下创建imagetoarraypreprocessor.py文件,此时的目录架构如下所示:

     

       我们打开刚创建的imagetoarraypreprocessor.py文件,开始写代码,具体代码见https://github.com/shengqishi8787/Starter_Bundle.git

      

       首先from keras.preprocessing.image import img_to_array导入图像转为数组的包,这里我们可以明确的指定通道在前或在后,但是最好是让keras根据keras的json配置文件自行决定。而且我们在这里定义一个类来处理预处理而不是直接调用img_to_array方法,是因为我们这样做可以方便的对图像通过预处理列表进行一系列预处理,即可以通过构建一个预处理链条来对图像一步步处理。基于我们在之前对图像缩放的预处理,我们现在有了两个预处理,那么我们可以构建一个预处理链条,首先应用SimplePreprocessor对图像缩放到32×32尺寸,然后调用ImageToArrayPreprocessor对图像按默认通道变为数组格式,如图1所示:

              

                            图1 预处理链条示例
       用这种方式将简单的预处理器串在一起,每个预处理器负责一个小任务,这是一种简单的构建一个可扩展的专门用于图像分类的深度学习库的方式。后续预处理中我们也将采用这种方式。

2         ShallowNet

       ShallowNet网络架构仅包含很少层,可概括为:INPUT => CONV => RELU => FC。

2.1  实现ShallowNet

       为了保持简洁,我们在上述目录结构中创建一个nn,然后在nn下创建一个conv的文件夹,里面包含我们的CNN架构:

        

       在conv下的shallownet.py包含我们的ShallowNet架构实现,现在让我们实现它:

        


       2-9行我们导入必要的类,其中Conv2D为keras的卷积层实现,之后的Activation将以激活函数作为输入实现激活功能。Flatten类将以多维卷作为输入,然后在将这个输入送入Dense层之前flatten成1D数组。
       我们定义了一个ShallowNet的类以及一个build方法,该方法在本书中都是用来构建CNN网络并返回给调用函数。该方法接收4个参数,即输入图像的宽、高、深度、以及类别数目(如Animals数据集为3,CIFAR-10为10)。
       我们在第15-19行对输入图像根据配置的深度优先顺序进行数组格式转换,在所有构建中都建议使用这些操作。
       在形状shape定义好后,下面就可以根据CNN模型架构来顺序构建模型了,首先在Conv2D中构建卷积层如第22行,其中参数分别为核数目、感受野大小F×F,然后设定padding=”name”保持输出尺寸与输入尺寸一致。最后通过26-29行完成softmax分类器的设置完成该模型的配置。
       现在我们完成了ShallowNet的定义,就可以编写真正的驱动脚本,进行加载数据集、预处理、训练网络。我们将在Animals和CIFAR-10数据集上演示。

2.2  在Animals上演示ShallowNet

       我们首先创建一个shallownet_animals.py文件,确保该文件与我们的pyimagesearch模块在同一个目录下。注意如果我们想按照下面的导入进行模块导入,我们需要将nn/conv/以及preprocessing/下的__init__.py文件配置,即将对应文件夹下的类包含到里面即可,如:

          

       代码见GitHub,在导入必要的库之后。我们首先通过解析参数,来加载需要的数据集。加载后,在26-33行通过链表预处理依次对图像缩放、转换并且进行归一化到[0, 1]。
       然后在第37-42行对数据集进行划分,分成75%的训练集和25%的测试集。并对标签进行向量化。
       准备好数据集,我们就可以在第46-54行对模型进行初始化并且进行训练网络。训练完成后,在57-61进行评估网络。最后进行画图显示评估曲线。
       运行代码python shallownet_animals.py –dataset ../datasets/animals后可显示训练结果:

          

       以及训练与测试曲线图:

                    

       我们看到在测试数据集上我们ShallowNet获得了71%的分类正确率,比我们之前的前馈神经网络59%提高了很多。使用更高级的训练网络以及更强大的网络架构,我们还可以获得更高的分类正确率。
       由上述图曲线可以看出,在epoch为20或60时学习有一点大的波动——这很可能由于学习率太高了,可在第16章阐述克服。我们还看到在epoch为30之后,训练和测试损失出现分歧,暗示我们的网络对训练数据的建模太过接近和过拟合了。我们可以通过获得更多的数据或采用像数据增加(data augmentation)这样的技术(在Practitioner Bundle中)来纠正这个问题。
       在大约epoch为60时,我们的测试正确率饱和了——不能再超过70%的正确率了,但是我们的训练正确率却能够达到超过85%。那么,在将来通过收集更多的数据、应用数据增加、更仔细的调整学习率将能够提高这个结果。
       我们这里的使用极其简单的CNN网络就获得了71%的正确率。
 

2.3  在CIFAR-10上演示ShallowNet

       该数据集的验证见github的chapter12/下的shallownet_cifar10.py。基本处理过程与在digital数据集上过程类似。计算完成后,损失与精确度结果如下:
   

            

       可看到该图示的结果与上一例子基本一致,都出现了过拟合,由于我们使用了有限的样本数据。

推荐阅读