使用卷积神经网络进行图片分类 2

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

使用caffe构建卷积神经网络

一、实验介绍

1.1 实验内容

上一次实验我们介绍了卷积神经网络的基本原理,本次实验我们将学习如何使用深度学习框架caffe构建卷积神经网络,你将看到在深度学习框架上搭建和训练模型是一件非常简单快捷的事情(当然,是在你已经理解了基本原理的前提下)。如果上一次实验中的一些知识点你还理解的不够透彻,这次以及之后的实验正是通过实际操作加深对它们理解的好机会。

1.2 实验知识点

  • caffe 网络总体结构
  • caffe 训练数据的准备
  • caffe network定义文件的编写
  • SoftmaxWithLoss 损失层
  • Accuracy 准确率层
  • ReLU 激活函数层

1.3 实验环境

  • python 2.7
  • caffe 1.0.0 (实验楼环境已经预先安装)

二、实验步骤

2.1 项目的引入 & 网络总体结构

在正式介绍和使用caffe之前,我们先来大致确定我们的深度神经网络结构。和课程814一样,本课程也是识别图片中的英文字母。不过这次我们是使用caffe来训练我们的模型,并且我们会向网络模型中加入前面介绍的卷积层。我们的网络模型大致是这样的:

此处输入图片的描述

图中有一些你还没见过的东西,不过现在你只需要关注绿色的数据输入层、紫色的损失函数层、三个橙色的卷积层和两个红色的卷积层。
可以看到,我们的神经网络先用三个卷积层提取图片中的特征,然后是一个含有100个节点的全连接层ip1,最后是含有26个节点的用于分类的全连接层ip2和损失函数层。

和课程814相比,我们似乎只是多了卷积层。

2.2 caffe简要介绍

caffe(Convolutional Architecture for Fast Feature Embedding)和好多软件项目一样,它看起来复杂且有些让人摸不着头脑的英文全称很可能只是为了获得一个有意思的缩写名。所以不要被它的名字迷惑,不过名字里的Convolutional也暗示了caffe特别擅长构建卷积神经网络。目前,caffe是使用最多的深度学习框架之一。其作者Yangqing Jia(没错,他是中国人)在Facebook。caffe支持使用CPUGPU进行训练,当使用GPU时,能够获得更快的训练速度(实验楼环境中目前没有GPU资源可以使用,不过这不影响我们的实验)。

2.3 为caffe准备训练数据

2.3.1 获取数据

caffe支持多种训练数据输入方式,其中最常用的一种是先将训练图片存储到lmdb数据库中,在训练的过程中直接从lmdb数据库中读取数据。除非你有兴趣,不然你暂时不必关心如何掌握lmdb数据库,因为caffe已经为我们提供了将图片转化成lmdb数据的脚本。
我已经事先准备好了一些训练图片,过为了方便,我把图片的分辨率改成了16*16。你可以使用以下命令获取这些训练数据:

扫描二维码关注公众号,回复: 4904327 查看本文章
wget http://labfile.oss.aliyuncs.com/courses/820/cnndata.tar.gztar zxvf cnndata.tar.gz

注:cnndata.tar.gz中也包括之后我们会用到的network.prototxtsolver.prototxt

解压之后,你当前的目录结构应该如下:

.├── cnndata.tar.gz├── network.prototxt├── pic├── solver.prototxt├── test.txt├── train.txt└── validate.txt1 directory, 6 files

注意之后我们会编写的network.prototxtsolver.prototxt也包含在了里面,进行下面的实验时,你可以打开network.prototxt对照着查看完整的网络定义。

其实这个项目对于caffe来说有些太简单了,而且也无法完全体现出caffe的优势,本来我是想做人脸性别识别的项目的,但最后考虑到训练时间的问题,还是决定采用这个更加简单的项目。完成本课程后,你可以自己尝试其他更具有挑战性的项目。

2.3.2 生成lmdb数据库

实验楼环境中的caffe安装在/opt/caffe目录下,在/opt/caffe/build/tools目录下你可以找到一个名为convert_imageset的可执行程序,不过实验楼使用的环境,在~/.zshrc文件中已经将convert_imageset程序所在的目录添加到了环境变量PATH中,所以你可以直接在terminal中输入"convert_imageset"执行命令。如果你自己的电脑中安装有caffe而没有配置环境变量PATH, 可能无法直接执行convert_imageset命令。

~/.zshrc文件中,caffe(也包括torch)相关环境的配置如下:

此处输入图片的描述

输入“convert_imageset”命令,你可以看到这个命令有哪些选项:
此处输入图片的描述

你可以自己研究如何利用这些选项来自定义训练数据的生成过程,对于我们的训练数据,可以直接输入以下命令将图片转化成lmdb数据:

convert_imageset --check_size --gray -shuffle ./ train.txt trainconvert_imageset --check_size --gray -shuffle ./ validate.txt validateconvert_imageset --check_size --gray -shuffle ./ test.txt test

这三条命令的第一个参数--check_size检查每一张图片的尺寸是否相同。第二个参数--gray将图片转换为单通道灰度图片。第三个参数-shuffle将所有图片的顺序打乱。第三个参数./指明图片文件所在的父目录,由于这里的train.txt等文件中已经包含了前缀pic,所以这里的父目录就是当前目录./。第四个参数指明图片列表文件。第五个参数指明最后生成的lmdb数据库文件夹的位置。

这三条命令执行完毕后,你的目录结构应该是这样的:

.├── cnndata.tar.gz├── network.prototxt├── pic├── solver.prototxt├── test├── test.txt├── train├── train.txt├── validate└── validate.txt4 directories, 6 files

多出的三个文件夹train validate test包含我们生成的lmdb数据库。

2.3.3 计算图片像素均值

对于神经网络,我们希望输入的数据分布能够有正有负(具体原因这里不赘述,若有兴趣你可以查阅资料了解为什么希望数据有正有负)。而图片像素值都是大于0的,所以caffe为我们提供了另一个脚本compute_image_mean,运行这个命令获得训练数据在每个通道上的均值,在处理训练数据时减去这个均值就可以保证图片有正有负且其分布“以0为中心”。

需要注意的是,我们只对训练集train计算均值,训练和测试的时候都是减去这个均值,而不是对于测试集单独计算。因为如果对于训练集和测试集的预处理操作不一样的话,可能会影响模型在测试集上的实际效果。

运行以下命令计算训练集图片均值:

compute_image_mean train train.binaryproto

此处输入图片的描述

其中第一个参数train指定对在我们刚生成的lmdb数据库train中的数据计算均值,第二个参数train.binaryproto指定计算出的均值保存在train.binaryproto文件中。稍后我们会用到这个均值文件。

2.4 caffe如何定义网络结构

就像课程814中所说的,好多深度学习框架都提供层次化的网络结构,网络结构中的每一层为一个小的模块,将多个模块组合起来,就构成了一个神经网络模型,就像是搭积木一样。caffe正是如此。

2.4.1 通过protobuf文件定义网络结构

caffe通过编写probobuf文件来定义网络(network)结构, protobuf是一种数据交换格式。protobuf使用起来很简单,为了完成本课程的实验,你不需要专门去学习protobuf,只需要参照别人写好的protobuf文件照葫芦画瓢就行了。

使用编辑器创建一个network.prototxt(注意这里的后缀名是prototxt)文件, 这里使用的是vim编辑器,如果你不会使用vim, 也可以使用其他编辑器。

vim network.prototxt

在新建文件的第一行,你可以定义网络模型的名字:

name: "AlphaNet"

2.4.2 Blobs、bottom、top

在课程814中,我们直接使用numpy ndarray存储网络中的数据。在caffe中,网络中的数据使用Blobs存储,其实你可以直接把Blobs看成一个n*c*h*w的四维数组,其中n代表一个batch中图片的数量,c代表图片通道数(对于卷积层,代表特征个数),h代表图片高度,w代表图片宽度。

caffe中的每一个网络层可以有多个bottomtop,bottom其实就是一个网络层的数据流入口,top就是一个网络层的数据流出口。当层A的bottom和层B的top相同时,就代表层A以层B的输出作为输入。

2.4.3 定义数据输入层

我们已经准备好了训练和测试数据,为了让我们的卷积神经网络能够读取这些数据,需要在network.prototxt添加数据输入层。

数据层需要从lmdb中读取数据,然后产生两个输出top:一个data代表图片数据,一个label代表该图片的标签。

layer{    name:"data"     type:"Data"    top:"data"    top:"label"    data_param{        source: "train"        batch_size:16        backend:LMDB    }    transform_param{        scale: 0.00390625        mean_file: "train.binaryproto"    }    include{        phase:TRAIN    }}

我们逐个对这个数据层的各部分进行解释。

name代表这一层的名字。type代表类型,caffe中提供了很多种类型的网络层,你可以到这里查看有哪些层,这里的Data指定是从数据库中读取数据。
两个top代表了数据层有两个输出,一个是data(与层的名字相同),代表lmdb数据库中的图片数据,一个是label代表每张图片对应的标签。

data_param中的内容指定了Data类型数据层需要的参数,其中source指定数据库的位置,在这里就是我们之前生成的train文件夹;batch_size指定每一个batch一次性处理多少张图片(还记得课程814里说的batch吗);backend指定数据库的种类,我们使用的是lmdb数据库,所以这里为LMDB

接下来的transform_param指定对图片进行的预处理操作,这里的mean_file指定平均值文件的位置,同时,我们指定了对图片像素值的缩放比例scale为0.00390625(其实就是1/255), 将像素值的范围缩小到1。

include包含了其他一些信息,这里的phase指定这个数据层是在训练还是在测试阶段使用,TRAIN表明是在训练阶段。

对于测试阶段的数据,可以直接再增加一个数据层,同时设置phaseTEST就可以了,如下:

layer{    name:"data"    type:"Data"    top:"data"    top:"label"    data_param{        source: "validate"        batch_size:100        backend:LMDB    }    transform_param{        scale: 0.00390625        mean_file: "train.binaryproto"    }    include{        phase:TEST    }}

当进行训练时,caffe就调用phaseTRAIN的数据层,当测试时,caffe就调用phaseTEST的数据层。

除了phasesourcebatch_size,第二个数据层的设置与第一个数据层一模一样。注意这里的两个top名必须和第一个数据层一样,因为后面的网络层的输入bottom通过名称指定数据来源,所以两个数据层的输出top名设置成一样就可以保证在训练和测试时,后面的网络层都能读取到数据。

2.4.4 定义卷积层

我们一共有三个卷积层,让我们先来看看第一个卷积层的定义:

layer{    name: "conv1"    type: "Convolution"    bottom: "data"    top: "conv1"    param{        lr_mult: 1    }    param{        lr_mult: 2    }    convolution_param{        num_output: 32        kernel_size: 3        stride: 1        pad: 1        weight_filler{            type: "xavier"        }        bias_filler{            type:"constant"        }    }}

和数据层有一些类似(比如name,bottom,top的作用),但又有很多不一样的地方。首先这里的type变成了Convolution代表这一层是卷积层。

两个param中的lr_mult作用有些特殊,它们代表卷积层中对参数的学习速率乘以多少倍(就像它的名字暗示的那样--learning rate multiply),其中第一个lr_mult代表对卷积核中到的参数weight学习速率相乘的值,第二个lr_mult代表对偏移量bias学习速率相乘的值,一般我们总是把第一个设置为1(即学习速率不变),第二个设置为2。这里你可能对这两个参数的作用感到摸不着头脑,但以后你就会明白这两个参数非常有用(它们在caffe的迁移学习transfer learning中发挥作用)。
数据层中的参数在data_param中定义,类似的,卷积层中的参数在convolution_param中定义。这里kernel_sizestride pad你已经知道它们的作用了,分别代表卷积核的尺寸、卷积核移动步长、图片边缘填充的像素数。而num_output其实就是我们第一次实验所说的特征个数feature
最后剩下weight_fillerbias_filler,这两个参数指明weightbias使用什么方式初始化(填充),如果typeconstant,代表用常数0填充,而xavier所代表的填充算法就稍微有点复杂了。这里对xavier填充算法不做介绍,如果你有兴趣,可以自己查阅资料或者查看提出xavier算法的论文

这里需要注意的是,我们设置kernel_size=3 stride=1 pad=1可以保证卷积层输出的宽和高与输入相同,你可以代入第一次实验给出的公式计算验证。我们的输入图片尺寸为16x16,带入公式和卷积层的参数,得到:(16+2*1-3)/1+1=16

卷积层2和卷积层3的定义与卷积层1几乎一模一样,除了卷积层的num_output参数被设置成64。

2.4.5 卷积神经网络中的池化层

池化(Pooling)层是卷积神经网络中几乎必然出现的网络层,第一次实验为了突出卷积层,没有介绍池化层,放到了这里来介绍。

我们之前说过,合理的设置卷积层的参数,可以保证卷积层的输出和输入在宽和高上不变。但我们有时候又希望能减小训练数据的尺寸,这样可以降低模型的复杂度,减少参数的数量,让模型训练的更快,池化层就具有这样的作用。
池化层其实和卷积层非常相似,也是有一个“池化核”对整张图片中的所有可能位置进行计算,不同的是,池化层中没有参数,一般池化层会返回“池化核”中最大(或者最小,或者随机)的数字,且池化层的步长stride一般设置成与“池化核”的尺寸相同,返回“池化核”内最大数值的池化层效果如下:

此处输入图片的描述

在第一个卷积层之后,就有一个池化层。

layer{    name:"pool1"    type: "Pooling"    bottom: "conv1"    top: "pool1"    pooling_param{        pool: MAX        kernel_size:2        stride: 2    }}

其中参数的作用已经很明显了,注意pool设置为MAX代表这个“池化核”返回最大值。

注意

池化层的作用不止是降低模型复杂度,比如你可以把返回最大值池化层理解为只保留一个区域最明显的特征。如果你想弄清楚池化层更深层次的作用,请自行查阅资料理解。

2.4.5 caffe中的內积层

caffe将全连接层称为內积层(InnerProduct), 其计算方式大体上与课程814中的全连接层一样,內积层的定义如下:

layer{    name: "ip1"    type: "InnerProduct"    bottom: "conv3"    top: "ip1"    param{        lr_mult:1    }    param{        lr_mult:2    }    inner_product_param{        num_output: 100        weight_filler{            type: "xavier"        }        bias_filler{            type:"constant"        }    }}

其中的参数我们都已经见过,注意这里的typeInnerProduct代表是全连接层,num_output代表的是全连接层的输出节点的数量。

这里我们可以感受到深度学习框架带给我们的便利,课程814中我们为了实现全连接层绞尽脑汁,而这里只需要十几行的定义就可以了,其他一切都由caffe帮我们实现。

2.4.5 损失函数层

课程814中,我们介绍了QuadraticLossCrossEntropyLoss两种损失函数,但这里我们打算使用caffe提供的另一种损失函数:SoftmaxWithLoss。 为了弄清楚SoftmaxWithLoss损失函数是如何工作的,我们先要介绍什么是SoftmaxSoftmax函数的表达式如下:
此处输入图片的描述
不要被它的表达式吓到,其实Softmax的计算很简单,就是对一个数组中的每一个元素先求它对于自然数e的指数e^x,然后每一个元素的Softmax函数值就是e^xi除以所有元素对自然数e的指数的和。
Softmax函数的性质也很好分析,数组中原本最大的数的函数值最接近1,最小的数的函数值最接近0,同时,数组中所有元素的Softmax函数值加起来为1。这刚好可以作为概率来看待,实际上,caffe中有单独的Softmax层存在,我们可以直接用Softmax层的输出作为我们的模型对每个类别(比如这里是26个类别)的概率值的预测。

现在我们清楚了Softmax的作用,但你可能仍然会困惑,Softmax的名字从何而来,为什么把它叫做“软的最大”呢?其实,与Softmax对应的还有一种“硬的最大”函数,这里我把它叫做Hardmax, 它的表达式如下:

此处输入图片的描述
观察它的表达式,你会发现HardmaxSoftmax的性质非常相似,都是数组中原本最大的元素的函数值最大,但不同的是,对于Hardmax,数组中最大的元素的函数值固定为1,最小的元素的函数值固定为0。最大元素的函数值一定为1,所以我把它称为“硬最大”,而Softmax却相对更加“柔和”,更加的“软”。Softmax的这种特性恰恰适合于作为神经网络中的概率输出,而Hardmax则会总是把最大可能的类别概率值设置为1,这是不合理的。

搞清楚了Softmax的作用,理解SoftmaxWithLoss损失函数就非常简单了。caffe中的SoftmaxWithLoss损失函数层其实就是在Softmax层上增加了一些运算,SoftmaxWithLoss层的定义如下:

layer{    name: "loss"    type: "SoftmaxWithLoss"    bottom: "ip2"    bottom: "label"    top: "loss"}

SoftmaxWithLoss层有两个bottom, 一个ip2是我们模型对于每种类别可能性大小的预测,注意这个预测值在经过Softmax层之前是不能作为概率值的(可能有负值,和可能不为1)。另一个label就是我们数据层的输出label, 代表一个训练图片上实际英文字母的类别。

SoftmaxWithLoss层先用Softmax函数计算出模型对每种类别的预测概率。再根据label的值,选择出预测值中的对应概率。比如Softmax的输出在这里是一个长度为26的数组,而label中的值为0(代表图片上的字母实际为A),就选择出Softmax函数输出数组中的第一个概率值。显然,当这个概率值接近1的时候,说明我们的模型预测的比较准确,SoftmaxWithLoss的输出值应该接近于0,当这个概率值接近于0的时候,说明我们的模型预测的不太准确,SoftmaxWithLoss的输出值应该是一个很大的正值。

实际上,SoftmaxWithLoss层对概率值进行的运算很简单,就是对该值求负对数, 这样就满足了上面说的,预测越准损失函数值越小的要求。即SoftmaxWithLoss层的运算大致如下:

此处输入图片的描述

2.4.6 准确率层

之前我们说过,为了检验模型的泛化性能,需要在验证/测试集上检验模型的预测准确率。caffe为我们提供了Accuracy准确率层,其定义如下:

layer{    name: "accuracy"    type: "Accuracy"    bottom: "ip2"    bottom: "label"    top: "accuracy"    include{        phase:TEST    }}

bottom同样有两个,但注意这里的第一个bottomip2而不需要是概率值,因为Accuracy层只需要第一个bottom的最大值的下标与第二个bottom相同就认为预测是准确的。

2.4.7 ReLU 激活函数层

在课程814中我们说过,Sigmoid激活函数容易导致梯度消失问题,消失的梯度使得神经网络的训练变得非常困难。而这里我们将介绍的ReLU(Rectified Linear Unit)激活函数层则非常好的避免了梯度消失问题。

ReLU的函数表达式如下:
此处输入图片的描述
SigmoidReLU函数图像对比如下:

此处输入图片的描述
ReLU执行的运算非常简单,就是只让大于0的节点的值向前传递。你需要特别注意的是,当反向传播梯度的时候,大于0的节点的梯度是由之前的“部分梯度”乘以1得到的,而小于等于0的节点的梯度则为0。对于大于0的节点,ReLU不会导致梯度值减小,非常有效的避免了梯度消失问题。

同时,ReLU的计算非常简单,而Sigmoid涉及到的求指数运算对于计算机来说则非常复杂,ReLU激活函数具有更高的执行速度。

我们的网络中层与层之间都存在着ReLU激活函数层,其定义如下:

layer{    name: "relu1"    type: "ReLU"    bottom: "conv1"    top: "conv1"}

ReLU不需要参数,所以这里的定义非常简单,不过你需要注意的是,这里的bottomtop名字可以设置成一样的,当设置成一样的时候,ReLU的输出结果会保存到输入的Blobs里,这样能节省显存(或内存)。

2.5 caffe网络定义总结

至此我们的项目中会用到的各种网络层都已经介绍过了,完整的网络定义请你打开network.prototxt查看。我建议你仔细一行一行的查看这个文件,理解每个网络层的功能和它的特性,理解每一个参数的作用。这对你之后自己动手编写神经网络定义文件非常重要。

我们现在只写好了网络定义文件,但为了让模型开始训练,我们还有一些东西没有确定,比如超参数学习速率、测试间隔、最大训练周期(epoch)等,下次实验,我们将讲解如何编写solver.prototxt文件。

三、实验总结

虽然看起来我们的网络定义文件network.prototxt的行数有点多,但和我们之前自己动手实现神经网络比起来,这里的网络模型构建还是简单多了,熟练之后你可以在几分钟之内就搭建好一个神经网络。caffe还提供了很多其他种类的网络层,如果你有兴趣可以到caffe官网查看。理解这些层的原理是科学合理地使用这些网络层的基础。

本次实验,我们学习了:

  • caffe中的数据由Blobs承载,Blobs可以被看成一个四维数组(或者四维张量)。
  • caffe通过编写network.prototxt文件构建神经网络。
  • 池化层能够缩小图片尺寸,降低模型计算量。
  • SoftmaxWithLoss损失函数就是对概率值取负对数。
  • ReLU激活函数可以有效避免梯度消失。

四、课后作业

  1. [选做]如果你打算深入研究caffe,可能会发现caffe官网的文档并不是十分全面,你可以查看/opt/caffe/src/caffe/proto/caffe.proto文件,里面包含了caffe中所有参数的定义。
           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_43680030/article/details/84072374