一样平常来说,图像分类通过手工提取特色或特色学习方法对全体图像进行全部描述,然后利用分类器判别物体种别,因此如何提取图像的特色至关主要。基于深度学习的图像分类方法,可以通过有监督或无监督的办法学习层次化的特色描述,从而取代了手工设计或选择图像特色的事情。
深度学习模型中的卷积神经网络(Convolution Neural Network, CNN) 直策应用图像像素信息作为输入,最大程度上保留了输入图像的所有信息,通过卷积操作进行特色的提取和高层抽象,模型输出直接是图像识别的结果。这种基于\"大众输入-输出\"大众直接端到真个学习方法取得了非常好的效果。
本教程紧张先容图像分类的深度学习模型,以及如何利用PaddlePaddle在CIFAR10数据集上快速实现CNN模型。
项目地址:
http://paddlepaddle.org/documentation/docs/zh/1.3/beginners_guide/basics/image_classification/index.html
基于ImageNet数据集演习的更多图像分类模型,及对应的预演习模型、finetune操作详情请参照Github:
https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/README_cn.md
效果图像分类包括通用图像分类、细粒度图像分类等。图1展示了通用图像分类效果,即模型可以精确识别图像上的紧张物体。
图1. 通用图像分类展示
图2展示了细粒度图像分类-花卉识别的效果,哀求模型可以精确识别花的种别。
图2. 细粒度图像分类展示
一个好的模型既要对不同种别识别精确,同时也该当能够对不同视角、光照、背景、变形或部分遮挡的图像精确识别(这里我们统一称作图像扰动)。图3展示了一些图像的扰动,较好的模型会像聪明的人类一样能够精确识别。
图3. 扰动图片展示[7]
模型概览:CNN传统CNN包含卷积层、全连接层等组件,并采取softmax多种别分类器和多类交叉熵丢失函数,一个范例的卷积神经网络如图4所示,我们先先容用来布局CNN的常见组件。
图4. CNN网络示例[5]
• 卷积层(convolution layer): 实行卷积操作提取底层到高层的特色,发掘出图片局部关联性子和空间不变性子。
• 池化层(pooling layer): 实行降采样操作。通过取卷积输出特色图中局部区块的最大值(max-pooling)或者均值(avg-pooling)。降采样也是图像处理中常见的一种操作,可以过滤掉一些不主要的高频信息。
• 全连接层(fully-connected layer,或者fc layer): 输入层到隐蔽层的神经元是全部连接的。
• 非线性变革: 卷积层、全连接层后面一样平常都会接非线性变革函数,例如Sigmoid、Tanh、ReLu等来增强网络的表达能力,在CNN里最常利用的为ReLu激活函数。
• Dropout [1] : 在模型演习阶段随机让一些隐层节点权重不事情,提高网络的泛化能力,一定程度上防止过拟合。
接下来我们紧张先容VGG,ResNet网络构造。
1、VGG
牛津大学VGG(Visual Geometry Group)组在2014年ILSVRC提出的模型被称作VGG模型[2] 。该模型比较以往模型进一步加宽和加深了网络构造,它的核心是五组卷积操作,每两组之间做Max-Pooling空间降维。同一组内采取多次连续的3X3卷积,卷积核的数目由较浅组的64增多到最深组的512,同一组内的卷积核数目是一样的。卷积之后接两层全连接层,之后是分类层。由于每组内卷积层的不同,有11、13、16、19层这几种模型,下图展示一个16层的网络构造。
VGG模型构造相对简洁,提出之后也有很多文章基于此模型进行研究,如在ImageNet上首次公开超过人眼识别的模型[4]便是借鉴VGG模型的构造。
图5. 基于ImageNet的VGG16模型
2、ResNet
ResNet(Residual Network) [3] 是2015年ImageNet图像分类、图像物体定位和图像物体检测比赛的冠军。针对随着网络演习加深导致准确度低落的问题,ResNet提出了残差学习方法来减轻演习深层网络的困难。在已有设计思路(BN, 小卷积核,全卷积网络)的根本上,引入了残差模块。每个残差模块包含两条路径,个中一条路径是输入特色的直连通路,另一条路径对该特色做两到三次卷积操作得到该特色的残差,末了再将两条路径上的特色相加。
残差模块如图7所示,左边是基本模块连接办法,由两个输出通道数相同的3x3卷积组成。右边是瓶颈模块(Bottleneck)连接办法,之以是称为瓶颈,是由于上面的1x1卷积用来降维(图示例即256->64),下面的1x1卷积用来升维(图示例即64->256),这样中间3x3卷积的输入和输出通道数都较小(图示例即64->64)。
图7. 残差模块
3、数据准备
由于ImageNet数据集较大,下载和演习较慢,为了方便大家学习,我们利用CIFAR10数据集。CIFAR10数据集包含60,000张32x32的彩色图片,10个种别,每个类包含6,000张。个中50,000张图片作为演习集,10000张作为测试集。图11从每个种别中随机抽取了10张图片,展示了所有的种别。
图11. CIFAR10数据集[6]
Paddle API供应了自动加载cifar数据集模块paddle.dataset.cifar。
通过输入python train.py,就可以开始演习模型了,以下小节将详细先容train.py的干系内容。
模型构造1、Paddle 初始化
让我们从导入Paddle Fluid API 和赞助模块开始。
from __future__ import print_functionimport osimport paddleimport paddle.fluidas fluidimport numpyimport sysfrom vgg import vgg_bn_dropfrom resnet import resnet_cifar10
本教程中我们供应了VGG和ResNet两个模型的配置。
2、VGG
首先先容VGG模型构造,由于CIFAR10图片大小和数量比较ImageNet数据小很多,因此这里的模型针对CIFAR10数据做了一定的适配。卷积部分引入了BN和Dropout操作。VGG核心模块的输入是数据层,vgg_bn_drop定义了16层VGG构造,每层卷积后面引入BN层和Dropout层,详细的定义如下:
def vgg_bn_drop(input): def conv_block(ipt, num_filter, groups, dropouts): return fluid.nets.img_conv_group( input=ipt, pool_size=2, pool_stride=2, conv_num_filter=[num_filter] groups, conv_filter_size=3, conv_act='relu', conv_with_batchnorm=True, conv_batchnorm_drop_rate=dropouts, pool_type='max') conv1= conv_block(input, 64, 2, [0.3, 0]) conv2= conv_block(conv1, 128, 2, [0.4, 0]) conv3= conv_block(conv2, 256, 3, [0.4, 0.4, 0]) conv4= conv_block(conv3, 512, 3, [0.4, 0.4, 0]) conv5= conv_block(conv4, 512, 3, [0.4, 0.4, 0]) drop= fluid.layers.dropout(x=conv5, dropout_prob=0.5) fc1= fluid.layers.fc(input=drop, size=512, act=None) bn= fluid.layers.batch_norm(input=fc1, act='relu') drop2= fluid.layers.dropout(x=bn, dropout_prob=0.5) fc2= fluid.layers.fc(input=drop2, size=512, act=None) predict= fluid.layers.fc(input=fc2, size=10, act='softmax') return predict
首先定义了一组卷积网络,即conv_block。卷积核大小为3x3,池化窗口大小为2x2,窗口滑动大小为2,groups决定每组VGG模块是几次连续的卷积操作,dropouts指定Dropout操作的概率。所利用的img_conv_group是在paddle.fluit.net中预定义的模块,由多少组Conv->BN->ReLu->Dropout 和一组Pooling 组成。
五组卷积操作,即5个conv_block。第一、二组采取两次连续的卷积操作。第三、四、五组采取三次连续的卷积操作。每组末了一个卷积后面Dropout概率为0,即不该用Dropout操作。
末了接两层512维的全连接。
在这里,VGG网络首先提取高层特色,随后在全连接层中将其映射到和种别维度大小同等的向量上,末了通过Softmax方法打算图片划为每个类别的概率。
3、ResNet
ResNet模型的第1、3、4步和VGG模型相同,这里不再先容。紧张先容第2步即CIFAR10数据集上ResNet核心模块。
先先容resnet_cifar10中的一些基本函数,再先容网络连接过程。
• conv_bn_layer: 带BN的卷积层。
• shortcut: 残差模块的\"大众直连\公众路径,\"大众直连\"大众实际分两种形式:残差模块输入和输出特色通道数不等时,采取1x1卷积的升维操作;残差模块输入和输出通道相等时,采取直连操作。
• basicblock: 一个根本残差模块,即图9左边所示,由两组3x3卷积组成的路径和一条\公众直连\"大众路径组成。
• layer_warp: 一组残差模块,由多少个残差模块堆积而成。每组中第一个残差模块滑动窗口大小与其他可以不同,以用来减少特色图在垂直和水平方向的大小。
def conv_bn_layer(input, ch_out, filter_size, stride, padding, act='relu', bias_attr=False): tmp= fluid.layers.conv2d( input=input, filter_size=filter_size, num_filters=ch_out, stride=stride, padding=padding, act=None, bias_attr=bias_attr) return fluid.layers.batch_norm(input=tmp, act=act)def shortcut(input, ch_in, ch_out, stride): if ch_in!= ch_out: return conv_bn_layer(input, ch_out, 1, stride, 0, None) else: return inputdef basicblock(input, ch_in, ch_out, stride): tmp= conv_bn_layer(input, ch_out, 3, stride, 1) tmp= conv_bn_layer(tmp, ch_out, 3, 1, 1, act=None, bias_attr=True) short= shortcut(input, ch_in, ch_out, stride) return fluid.layers.elementwise_add(x=tmp, y=short, act='relu')def layer_warp(block_func, input, ch_in, ch_out, count, stride): tmp= block_func(input, ch_in, ch_out, stride) for iin range(1, count): tmp= block_func(tmp, ch_out, ch_out, 1) return tmp
resnet_cifar10的连接构造紧张有以下几个过程。
底层输入连接一层conv_bn_layer,即带BN的卷积层。
然后连接3组残差模块即下面配置3组layer_warp,每组采取图10 左边残差模块组成。
末了对网络做均值池化并返回该层。
把稳:除第一层卷积层和末了一层全连接层之外,哀求三组layer_warp总的含参层数能够被6整除,即resnet_cifar10的depth 要知足(depth - 2) % 6 = 0
def resnet_cifar10(ipt, depth=32): # depth should be one of 20, 32, 44, 56, 110, 1202 assert (depth- 2) % 6== 0 n= (depth- 2) // 6 nStages= {16, 64, 128} conv1= conv_bn_layer(ipt, ch_out=16, filter_size=3, stride=1, padding=1) res1= layer_warp(basicblock, conv1, 16, 16, n, 1) res2= layer_warp(basicblock, res1, 16, 32, n, 2) res3= layer_warp(basicblock, res2, 32, 64, n, 2) pool= fluid.layers.pool2d( input=res3, pool_size=8, pool_type='avg', pool_stride=1) predict= fluid.layers.fc(input=pool, size=10, act='softmax') return predict
4、Infererence配置
网络输入定义为data_layer(数据层),在图像分类中即为图像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色图,因此输入数据大小为3072(3x32x32)。
def inference_network(): # The image is 32 32 with RGB representation. data_shape = [3, 32, 32] images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') predict = resnet_cifar10(images, 32) # predict = vgg_bn_drop(images) # un-comment to use vgg netreturn predict
5、Train 配置
然后我们须要设置演习程序train_network。它首先从推理程序中进行预测。在演习期间,它将从预测中计算avg_cost。在有监督演习中须要输入图像对应的种别信息,同样通过fluid.layers.data来定义。演习中采取多类交叉熵作为丢失函数,并作为网络的输出,预测阶段定义网络的输出为分类器得到的概率信息。
把稳:演习程序该当返回一个数组,第一个返回参数必须是avg_cost。演习器利用它来打算梯度。
def train_network(predict): label = fluid.layers.data(name='label', shape=[1], dtype='int64') cost = fluid.layers.cross_entropy(input=predict, label=label) avg_cost = fluid.layers.mean(cost) accuracy = fluid.layers.accuracy(input=predict, label=label)return [avg_cost, accuracy]
6、Optimizer 配置
不才面的Adam optimizer,learning_rate是学习率,与网络的演习收敛速率有关系。
def optimizer_program(): return fluid.optimizer.Adam(learning_rate=0.001)
7、演习模型
-1)Data Feeders 配置
cifar.train10()每次产生一条样本,在完成shuffle和batch之后,作为演习的输入。
# Each batch will yield 128 imagesBATCH_SIZE= 128# Reader for training train_reader = paddle.batch( paddle.reader.shuffle( paddle.dataset.cifar.train10(), buf_size=128 100), batch_size=BATCH_SIZE)# Reader for testing. A separated data set for testing. test_reader = paddle.batch( paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)
-2)Trainer 程序的实现
我们须要为演习过程制订一个main_program, 同样的,还须要为测试程序配置一个test_program。定义演习的place,并利用先前定义的优化器。
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() feed_order = ['pixel', 'label'] main_program = fluid.default_main_program() star_program = fluid.default_startup_program() predict = inference_network()avg_cost, acc = train_network(predict)# Test program test_program = main_program.clone(for_test=True) optimizer = optimizer_program() optimizer.minimize(avg_cost) exe = fluid.Executor(place) EPOCH_NUM = 1# For training test cost def train_test(program, reader): count = 0 feed_var_list = [ program.global_block().var(var_name) for var_name in feed_order ] feeder_test = fluid.DataFeeder(feed_list=feed_var_list, place=place) test_exe = fluid.Executor(place) accumulated = len([avg_cost, acc]) [0] for tid, test_data in enumerate(reader()): avg_cost_np = test_exe.run( program=program, feed=feeder_test.feed(test_data), fetch_list=[avg_cost, acc]) accumulated = [ x[0] + x[1][0] for x in zip(accumulated, avg_cost_np) ] count += 1 return [x / count for x in accumulated]
-3)演习主循环以及过程输出
在接下来的主演习循环中,我们将通过输出来来不雅观察演习过程,或进行测试等。
# main train loop. def train_loop(): feed_var_list_loop = [ main_program.global_block().var(var_name) for var_name in feed_order ] feeder = fluid.DataFeeder(feed_list=feed_var_list_loop, place=place) exe.run(star_program) step = 0 for pass_id in range(EPOCH_NUM): for step_id, data_train in enumerate(train_reader()): avg_loss_value = exe.run( main_program, feed=feeder.feed(data_train), fetch_list=[avg_cost, acc]) if step_id % 100 == 0: print(\公众\nPass %d, Batch %d, Cost %f, Acc %f\"大众 % ( step_id, pass_id, avg_loss_value[0], avg_loss_value[1])) else: sys.stdout.write('.') sys.stdout.flush() step += 1 avg_cost_test, accuracy_test = train_test( test_program, reader=test_reader) print('\nTest with Pass {0}, Loss {1:2.2}, Acc {2:2.2}'.format( pass_id, avg_cost_test, accuracy_test)) if params_dirname is not None: fluid.io.save_inference_model(params_dirname, [\"大众pixel\"大众], [predict], exe)train_loop()
-4)演习
通过trainer_loop函数演习, 这里我们只进行了2个Epoch, 一样平常我们在实际运用上会实行上百个以上Epoch
把稳:CPU,每个Epoch 将花费大约15~20分钟。这部分可能须要一段韶光。请随意修正代码,在GPU上运行测试,以提高演习速率。
train_loop()
一轮演习log示例如下所示,经由1个pass,演习集上均匀Accuracy 为0.59 ,测试集上均匀Accuracy 为0.6 。
Pass 0, Batch 0, Cost 3.869598, Acc 0.164062
...................................................................................................
Pass 100, Batch 0, Cost 1.481038, Acc 0.460938
...................................................................................................
Pass 200, Batch 0, Cost 1.340323, Acc 0.523438
...................................................................................................
Pass 300, Batch 0, Cost 1.223424, Acc 0.593750
..........................................................................................
Test with Pass 0, Loss 1.1, Acc 0.6
图13是演习的分类缺点率曲线图,运行到第200个pass后基本收敛,终极得到测试集上分类缺点率为8.54%。
图13. CIFAR10数据集上VGG模型的分类缺点率
运用模型可以利用演习好的模型对图片进行分类,下面程序展示了如何加载已经演习好的网络和参数进行推断。
1、天生预测输入数据
dog.png是一张小狗的图片. 我们将它转换成numpy数组以知足feeder的格式.
from PIL import Image def load_image(infer_file): im = Image.open(infer_file) im = im.resize((32, 32), Image.ANTIALIAS) im = numpy.array(im).astype(numpy.float32) # The storage order of the loaded image is W(width), # H(height), C(channel). PaddlePaddle requires # the CHW order, so transpose them. im = im.transpose((2, 0, 1)) # CHW im = im / 255.0 # Add one dimension to mimic the list format. im = numpy.expand_dims(im, axis=0) return im cur_dir = os.path.dirname(os.path.realpath(__file__)) img = load_image(cur_dir + '/image/dog.png')
2、Inferencer 配置和预测
与演习过程类似,inferencer须要构建相应的过程。我们从params_dirname加载网络和经由演习的参数。我们可以大略地插入前面定义的推理程序。现在我们准备做预测。
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() exe = fluid.Executor(place)inference_scope = fluid.core.Scope() with fluid.scope_guard(inference_scope): # Use fluid.io.load_inference_model to obtain the inference program desc, # the feed_target_names (the names of variables that will be feeded # data using feed operators), and the fetch_targets (variables that # we want to obtain data from using fetch operators). [inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(params_dirname, exe) # The input's dimension of conv should be 4-D or 5-D. # Use inference_transpiler to speedup inference_transpiler_program = inference_program.clone() t = fluid.transpiler.InferenceTranspiler() t.transpile(inference_transpiler_program, place) # Construct feed as a dictionary of {feed_target_name: feed_target_data} # and results will contain a list of data corresponding to fetch_targets. results = exe.run( inference_program, feed={feed_target_names[0]: img}, fetch_list=fetch_targets) transpiler_results = exe.run( inference_transpiler_program, feed={feed_target_names[0]: img}, fetch_list=fetch_targets) assert len(results[0]) == len(transpiler_results[0]) for i in range(len(results[0])): numpy.testing.assert_almost_equal( results[0][i], transpiler_results[0][i], decimal=5) # infer label label_list = [ \公众airplane\公众, \"大众automobile\"大众, \公众bird\公众, \"大众cat\公众, \公众deer\"大众, \公众dog\"大众, \公众frog\公众, \"大众horse\公众, \"大众ship\"大众, \"大众truck\公众 ] print(\"大众infer results: %s\"大众 % label_list[numpy.argmax(results[0])])
总结
传统图像分类方法由多个阶段构成,框架较为繁芜,而端到真个CNN模型构造可一步到位,而且大幅度提升了分类准确率。本文我们首先先容VGG、ResNet两个经典的模型;然后基于CIFAR10数据集,先容如何利用PaddlePaddle配置和演习CNN模型;末了先容如何利用PaddlePaddle的API接口对图片进行预测和特色提取。对付其他数据集比如ImageNet,配置和演习流程是同样的。请参照Github
https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/README_cn.md。
参考文献
[1] G.E. Hinton, N. Srivastava, A. Krizhevsky, I. Sutskever, and R.R. Salakhutdinov. Improving neural networks by preventing co-adaptation of feature detectors. arXiv preprint arXiv:1207.0580, 2012.
[2] K. Chatfield, K. Simonyan, A. Vedaldi, A. Zisserman. Return of the Devil in the Details: Delving Deep into Convolutional Nets. BMVC, 2014。
[3] K. He, X. Zhang, S. Ren, J. Sun. Deep Residual Learning for Image Recognition. CVPR 2016.
[4] He, K., Zhang, X., Ren, S., and Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. ArXiv e-prints, February 2015.
[5] http://deeplearning.net/tutorial/lenet.html
[6] https://www.cs.toronto.edu/~kriz/cifar.html
[7] http://cs231n.github.io/classification/