更多深度文章,请关注:https://yq.aliyun/cloud
几个月前,我写了一篇关于如何使用cnn(卷积神经网络)尤其是vgg16来分类图像的教程,该模型能够以很高的精确度识别我们日常生活中的1000种不同种类的物品。
那时,模型还是和keras包分开的,我们得从free-standinggithubrepo上下载并手动安装;现在模型已经整合进keras包,原先的教程也已经不再适用,所以我决定写一篇新的教程。
在教程中,你将学习到如何编写一个python脚本来分类你自己的图像。
博客结构
1.简要说明一下这几个网络架构;
2.使用python编写代码:载入训练好的模型并对输入图像分类;
3.审查一些样本图片的分类结果。
keras中最新的深度学习图像分类器
keras提供了五种开箱即用型的cnn:
1.vgg16
2.vgg19
3.resnet50
4.inceptionv3
5.xception
什么是imagenet
imagenet曾是一个计算机视觉研究项目:(人工)打标签并分类成22000个不同物品种类。然而,当我们在讨论深度学习和cnn的时候,“imagenet”意味着imagenetlargescalevisualrecognitionchallenge,简写为ilsvrc。
ilsvrc的目的是训练一个能够正确识别图像并分类(1000种)的模型:模型使用约120万张图像用作训练,5万张图像用作验证,10万张图像用作测试。
这1000种分类涵盖了我们的日常生活接触到的东西,具体列表请点击。
在图像分类上,imagenet竞赛已经是计算机视觉分类算法事实上的评价标准——而自2012年以来,排行榜就被cnn和其它深度学习技术所统治。
过去几年中imagenet竞赛里表现优异的模型在keras中均有收录。通过迁移学习,这些模型在imagenet外的数据集中也有着不错的表现。
vgg16和vgg19
图1:vgg网络架构(source)
vgg网络架构于2014年出现在simonyan和zisserman中的论文中,《verydeepconvolutionalnetworksforlargescaleimagerecognition》。
该架构仅仅使用堆放在彼此顶部、深度不断增加的3×3卷积层,并通过maxpooling来减小volume规格;然后是两个4096节点的全连接层,最后是一个softmax分类器。“16”和“19”代表网络中权重层的数量(表2中的d和e列):
在2014年的时候,16还有19层网络还是相当深的,simonyan和zisserman发现训练vgg16和vgg19很有难度,于是选择先训练小一些的版本(列a和列c)。这些小的网络收敛后被用来作为初始条件训练更大更深的网络——这个过程被称为预训练(pre-training)。
预训练很有意义,但是消耗大量时间、枯燥无味,在整个网络都被训练完成前无法进行下一步工作。
如今大部分情况下,我们已经不再使用预训练,转而采用xaiver/glorot初始化或者msra初始化(有时也被称作heetal.初始化,详见《delvingdeepintorectifiers:surpassinghuman-levelperformanceonimagenetclassification》)。如果你感兴趣,可以从这篇文章中理解到weightinitialization的重要性以及深度神经网络的收敛——《allyouneedisagoodinit,mishkinandmatas(2015)》。
vggnet有两个不足:
1.训练很慢;
2.weights很大。
由于深度以及全连接节点数量的原因,vgg16的weights超过533mb,vgg19超过574mb,这使得部署vgg很令人讨厌。虽然在许多深度学习图像分类问题中我们仍使用vgg架构,但是小规模的网络架构更受欢迎(比如squeezenet,googlenet等等)。
resnet
与alexnet、overfeat还有vgg这些传统顺序型网络架构不同,resnet的网络结构依赖于微架构模组(micro-architecturemodules,也被称为network-in-networkarchitectures)。
微架构模组指构成网络架构的“积木”,一系列的微架构积木(连同你的标准conv,pool等)共同构成了大的架构(即最终的网络)。
resnet于2015年出现在heetal的论文《deepresiduallearningforimagerecognition》中,它的出现很有开创性意义,证明极深的网络也可以通过标准sgd(以及一个合理的初始化函数)来训练:
图3:heetal.于2015年提出的残差模组
在2016年的著作《identitymappingsindeepresidualnetworks》中,他们证实了可以通过更新残差模组(residualmodule)来使用标志映射(identifymappings),达到提高精度的目的。
图4:(左)原始残差模组(右)使用预激活(pre-activation)更新的残差模组
尽管resnet比vgg16还有vgg19要深,weights却要小(102mb),因为使用了全局平均池化(globalaveragepooling),而不是全连接层。
inceptionv3
“inception”微架构于2014年出现在szegedy的论文中,《goingdeeperwithconvolutions》。
图5:googlenet中使用的inception模组原型
inception模组的目的是扮演一个“多级特征提取器”,在网络相同的模组内计算1×1、3×3还有5×5的卷积——这些过滤器的输出在输入至网络下一层之前先被堆栈到channeldimension。
该架构的原型被称为googlenet,后继者被简单的命名为inceptionvn,n代表google推出的数字。
keras中的inceptionv3架构来自于szegedyetal.的后续论文,《rethinkingtheinceptionarchitectureforcomputervision(2015)》,该论文打算通过更新inception模组来提高imagenet分类的准确度。
inceptionv3比vgg还有resnet都要小,约96mb。
xception
图6:xception架构
xception是被franoischollet提出的,后者是keras库的作者和主要维护者。
xception是inception架构的扩展,用depthwise独立卷积代替inception标准卷积。
关于xception的出版物《deeplearningwithdepthwiseseparableconvolutions》可以在这里找到。
xception最小仅有91mb。
squeezenet
figure7:“fire”模组,由一个“squeeze”和一个“expand”模组组成。(iandolaetal.,2016)
仅仅4.9mb的squeezenet架构能达到alexnet级别的精确度(~57%rank-1and~80%rank-5),这都归功于“fire”模组的使用。然而squeezenet的训练很麻烦,我会在即将出版的书——《deeplearningforcomputervisionwithpython》——中介绍如何训练squeezenet来处理imagenet数据集。
使用python和keras通过vggnet,resnet,inception和xception对图像分类
新建一个文件,命名为classify_image.py,编辑插入下列代码1#importthenecessarypackages2fromkeras.applicationsimportresnet503fromkeras.applicationsimportinceptionv34fromkeras.applicationsimportxception#tensorflowonly5fromkeras.applicationsimportvgg166fromkeras.applicationsimportvgg197fromkeras.applicationsimportimagenet_utils8fromkeras.applications.inception_v3importpreprocess_input9fromkeras.preprocessing.imageimportimg_to_array10fromkeras.preprocessing.imageimportload_img11importnumpyasnp12importargparse13importcv2
第2-13行导入需要的包,其中大部分都属于keras。
第2-6行分别导入resnet,inceptionv3,xception,vgg16,还有vgg19——注意xception只兼容tensorflow后端。
第7行导入的image_utils包包含了一系列函数,使得对图片进行前处理以及对分类结果解码更加容易。
余下的语句导入其它有用的函数,其中numpy用于数学运算,cv2用于与opencv结合。15#constructtheargumentparseandparsethearguments16ap=argparse.argumentparser()17ap.add_argument(-i,--image,required=true,18help=pathtotheinputimage)19ap.add_argument(-model,--model,type=str,default=vgg16,20help=nameofpre-trainednetworktouse)21args=vars(ap.parse_args())
--image为希望进行分类的图像的路径。
--model为选用的cnn的类别,默认为vgg16。23#defineadictionarythatmapsmodelnamestotheirclasses24#insidekeras25models={26vgg16:vgg16,27vgg19:vgg19,28inception:inceptionv3,29xception:xception,#tensorflowonly30resnet:resnet5031}3233#esnureavalidmodelnamewassuppliedviacommandlineargument34ifargs[model]notinmodels.keys():35raiseassertionerror(the--modelcommandlineargumentshould36beakeyinthe`models`dictionary)
第25-31行定义了一个词典,将类映射到对应的模型名称。
如果没有在该词典中找到“--model”,就会报错。
输入一个图像到一个cnn中会返回一系列键值,包含标签及对应的概率。
imagenet采用的图像尺寸一般为224×224,227×227,256×256,and299×299,但是并不是绝对。
vgg16,vgg19以及resnet接受224×224的输入图像,而inceptionv3和xception要求为299×299,如下代码所示:38#initializetheinputimageshape(224x224pixels)alongwith39#thepre-processingfunction(thismightneedtobechanged40#basedonwhichmodelweusetoclassifyourimage)41inputshape=(224,224)42preprocess=imagenet_utils.preprocess_input4344#ifweareusingtheinceptionv3orxceptionnetworks,thenwe45#needtosettheinputshapeto(299x299)[ratherthan(224x224)]46#anduseadifferentimageprocessingfunction47ifargs[model]in(inception,xception):48inputshape=(299,299)49preprocess=preprocess_input
这里我们初始化inputshape为224×224像素,初始化预处理函数为keras.preprocess_input——执行meansubtraction运算。
如果使用inception或者xception,inputshape需要改为299×299像素,预处理函数改为separatepre-processing函数。
下一步就是从磁盘载入网络架构的weights,并实例化模型:51#loadourthenetworkweightsfromdisk(note:ifthisisthe52#firsttimeyouarerunningthisscriptforagivennetwork,the53#weightswillneedtobedownloadedfirst--dependingonwhich54#networkyouareusing,theweightscanbe90-575mb,sobe55#patient;theweightswillbecachedandsubsequentrunsofthis56#scriptwillbe*much*faster)57print([info]loading{}....format(args[model]))58network=models[args[model]]59model=network(weights=imagenet)
注意:vgg16和vgg19的weights大于500mb,resnet的约等于100mb,inception和xception的介于90-100mb之间。如果这是你第一次运行某个网络,这些weights会自动下载到你的磁盘。下载时间由你的网络速度决定,而且下载完成后,下一次运行代码不再需要重新下载。61#loadtheinputimageusingthekerashelperutilitywhileensuring62#theimageisresizedto`inputshape`,therequiredinputdimensions63#fortheimagenetpre-trainednetwork64print([info]loadingandpre-processingimage...)65image=load_img(args[image],target_size=inputshape)66image=img_to_array(image)6768#ourinputimageisnowrepresentedasanumpyarrayofshape69#(inputshape[0],inputshape[1],3)howeverweneedtoexpandthe70#dimensionbymakingtheshape(1,inputshape[0],inputshape[1],3)71#sowecanpassitthroughthenetwork72image=np.expand_dims(image,axis=0)7374#pre-processtheimageusingtheappropriatefunctionbasedonthe75#modelthathasbeenloaded(i.e.,meansubtraction,scaling,etc.)76image=preprocess(image)
第65行从磁盘载入输入图像,并使用提供的inputshape初始化图像的尺寸。
第66行将图像从pil/pillow实例转换成numpy矩阵,矩阵的shape为(inputshape[0],inputshape[1],3)。
因为我们往往使用cnn来批量训练/分类图像,所以需要使用np.expand_dims在矩阵中添加一个额外的维度,如第72行所示;添加后矩阵shape为(1,inputshape[0],inputshape[1],3)。如果你忘记添加这个维度,当你的模型使用.predict时会报错。
最后,第76行使用合适的预处理函数来执行meansubtraction/scaling。
下面将我们的图像传递给网络并获取分类结果:78#classifytheimage79print([info]classifyingimagewith'{}'....format(args[model]))80preds=model.predict(image)81p=imagenet_utils.decode_predictions(preds)8283#loopoverthepredictionsanddisplaytherank-5predictions+84#probabilitiestoourterminal85for(i,(imagenetid,label,prob))inenumerate(p[0]):86print({}.{}:{:.2f}%.format(i+1,label,prob*100))
第80行调用.predict函数,并从cnn返回预测值。
第81行的.decode_predictions函数将预测值解码为易读的键值对:标签、以及该标签的概率。
第85行和86行返回最可能的5个预测值并输出到终端。
案例的最后一件事,是通过opencv从磁盘将输入图像读取出来,在图像上画出最可能的预测值并显示在我们的屏幕上。88#loadtheimageviaopencv,drawthetoppredictionontheimage,89#anddisplaytheimagetoourscreen90orig=cv2.imread(args[image])91(imagenetid,label,prob)=p[0][0]92cv2.puttext(orig,label:{},{:.2f}%.format(label,prob*100),93(10,30),cv2.font_hershey_simplex,0.8,(0,0,255),2)94cv2.imshow(classification,orig)95cv2.waitkey(0)
vggnet,resnet,inception,和xception的分类结果
所有的例子都是使用2.0以上版本的keras以及tensorflow后台做的。确保你的tensorflow版本大于等于1.0,否则会报错。所有例子也都使用theano后端做过测试,工作良好。
案例需要的图片以及代码请前往原文获取。
使用vgg16分类:1$pythonclassify_image.py--imageimages/soccer_ball.jpg--modelvgg16
图8:使用vgg16来分类足球(source)
输出为:soccer_ball,精确度为93.43%。
如果要使用vgg19,只需要替换下--network参数。1$pythonclassify_image.py--imageimages/bmw.png--modelvgg19
图9:使用vgg19来分类汽车(source)
输出为:convertible(敞篷车),精确度为91.76%。然而,我们看一下其它的4个结果:sportscar(跑车),4.98%(也对);limousine(豪华轿车),1.06%(不正确,但也合理);carwheel(车轮),0.75%(技术上正确,因为图中确实出现了轮子)。
从下面的例子,我们可以看到类似的结果:1$pythonclassify_image.py--imageimages/clint_eastwood.jpg--modelresnet
图10:使用resnet分类(source).
resnet成功将图像分类为revolver(左轮手枪),精确度69.79%。有趣的是rifle(步枪)为7.74%,assaultrifle(突击步枪)为5.63%。考虑到revolver的观察角度还有相对于手枪来说巨大的枪管,cnn得出这么高的概率也是合理的。1$pythonclassify_image.py--imageimages/jemma.png--modelresnet
图11:使用resnet对狗进行分类
狗的种类被正确识别为beagle(小猎兔狗),精确度94.48%。
然后我试着分类《加勒比海盗》中的图片:1$pythonclassify_image.py--imageimages/boat.png--modelinception
图12:使用resnet对沉船进行分类(source)
尽管imagenet中有“boat”(船)这个类别,inception网络仍然正确地将该场景识别为“(ship)wreck”(沉船),精确度96.29%。其它的标签,比如“seashore”(海滩),“canoe”(独木舟),“paddle”(桨),还有“breakwater”(�...