几个月前,我写了一篇关于如何使用 CNN(卷积神经网络)尤其是 VGG16 来分类图像的教程,该模型能够以很高的精确度识别我们日常生活中的 1000 种不同种类的物品。
那时,模型还是和 keras 包分开的,我们得从 free-standing GitHub repo 上下载并手动安装;现在模型已经整合进 Keras 包,原先的教程也已经不再适用,所以我决定写一篇新的教程。
在教程中,你将学习到如何编写一个 Python 脚本来分类你自己的图像。
博客结构
1. 简要说明一下这几个网络架构;
2. 使用 Python 编写代码:载入训练好的模型并对输入图像分类;
3. 审查一些样本图片的分类结果。
Keras 中最新的深度学习图像分类器
Keras 提供了五种开箱即用型的 CNN:
1. VGG16
2. VGG19
3. ResNet50
4. Inception V3
5. Xception
什么是ImageNet
ImageNet 曾是一个计算机视觉研究项目:(人工)打标签并分类成 22000 个不同物品种类。然而,当我们在讨论深度学习和 CNN 的时候,"ImageNet" 意味着ImageNet Large Scale Visual Recognition Challenge,简写为 ILSVRC。
ILSVRC 的目的是训练一个能够正确识别图像并分类(1000 种)的模型:模型使用约 120 万张图像用作训练,5 万张图像用作验证,10 万张图像用作测试。
这 1000 种分类涵盖了我们的日常生活接触到的东西,具体列表请点击。
在图像分类上,ImageNet 竞赛已经是计算机视觉分类算法事实上的评价标准——而自 2012 年以来,排行榜就被 CNN 和其它深度学习技术所统治。
过去几年中 ImageNet 竞赛里表现优异的模型在 Keras 中均有收录。通过迁移学习,这些模型在 ImageNet 外的数据集中也有着不错的表现。
VGG16和VGG19
图 1: VGG 网络架构(source)
VGG 网络架构于 2014 年出现在 Simonyan 和 Zisserman 中的论文中,《Very Deep Convolutional Networks for Large Scale Image Recognition》。
该架构仅仅使用堆放在彼此顶部、深度不断增加的 3×3 卷积层,并通过 max pooling 来减小 volume 规格;然后是两个 4096 节点的全连接层,最后是一个 softmax 分类器。"16" 和 "19" 代表网络中权重层的数量(表 2 中的 D 和 E 列):
在 2014 年的时候,16 还有 19 层网络还是相当深的,Simonyan 和 Zisserman 发现训练 VGG16 和 VGG19 很有难度,于是选择先训练小一些的版本(列 A 和列 C)。这些小的网络收敛后被用来作为初始条件训练更大更深的网络——这个过程被称为预训练(pre-training)。
预训练很有意义,但是消耗大量时间、枯燥无味,在整个网络都被训练完成前无法进行下一步工作。
如今大部分情况下,我们已经不再使用预训练,转而采用 Xaiver/Glorot 初始化或者 MSRA 初始化(有时也被称作 He et al. 初始化,详见《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》)。如果你感兴趣,可以从这篇文章中理解到 weight initialization 的重要性以及深度神经网络的收敛——《All you need is a good init, Mishkin and Matas (2015)》。
VGGNet 有两个不足:
1. 训练很慢;
2. weights 很大。
由于深度以及全连接节点数量的原因,VGG16 的 weights 超过 533MB,VGG19 超过 574MB,这使得部署 VGG 很令人讨厌。虽然在许多深度学习图像分类问题中我们仍使用 VGG 架构,但是小规模的网络架构更受欢迎(比如 SqueezeNet, GoogleNet 等等)。
ResNet
与 AlexNet、OverFeat 还有 VGG 这些传统顺序型网络架构不同,ResNet 的网络结构依赖于微架构模组(micro-architecture modules,也被称为 network-in-network architectures) 。
微架构模组指构成网络架构的 "积木",一系列的微架构积木(连同你的标准 CONV,POOL 等)共同构成了大的架构(即最终的网络)。
ResNet 于 2015 年出现在 He et al 的论文《Deep Residual Learning for Image Recognition》中,它的出现很有开创性意义,证明极深的网络也可以通过标准 SGD(以及一个合理的初始化函数)来训练:
图 3: He et al. 于 2015 年提出的残差模组
在 2016 年的著作《Identity Mappings in Deep Residual Networks》中,他们证实了可以通过更新残差模组(residual module)来使用标志映射(identify mappings),达到提高精度的目的。
图 4: (左)原始残差模组 (右)使用预激活(pre-activation)更新的残差模组
尽管 ResNet 比 VGG16 还有 VGG19 要深,weights 却要小(102MB),因为使用了全局平均池化(global average pooling),而不是全连接层。
Inception V3
"Inception" 微架构于 2014 年出现在 Szegedy 的论文中,《Going Deeper with Convolutions》。
图 5: GoogleNet 中使用的 Inception 模组原型
Inception 模组的目的是扮演一个 "多级特征提取器",在网络相同的模组内计算 1×1、3×3 还有 5×5 的卷积——这些过滤器的输出在输入至网络下一层之前先被堆栈到 channel dimension。
该架构的原型被称为 GoogleNet,后继者被简单的命名为 Inception vN,N 代表 Google 推出的数字。
Keras 中的 Inception V3 架构来自于 Szegedy et al. 的后续论文,《Rethinking the Inception Architecture for Computer Vision(2015)》,该论文打算通过更新 inception 模组来提高 ImageNet 分类的准确度。
Inception V3 比 VGG 还有 ResNet 都要小,约 96MB。
Xception
图 6: Xception 架构
Xception 是被 François Chollet 提出的, 后者是 Keras 库的作者和主要维护者。
Xception 是 Inception 架构的扩展,用 depthwise 独立卷积代替 Inception 标准卷积。
关于 Xception 的出版物《Deep Learning with Depthwise Separable Convolutions》可以在这里找到。
Xception 最小仅有 91MB。
SqueezeNet
Figure 7: "fire" 模组,由一个 "squeeze" 和一个 "expand" 模组组成。(Iandola et al., 2016)
仅仅 4.9MB 的 SqueezeNet 架构能达到 AlexNet 级别的精确度 (~57% rank-1 and ~80% rank-5),这都归功于 "fire" 模组的使用。然而 SqueezeNet 的训练很麻烦,我会在即将出版的书——《Deep Learning for Computer Vision with Python》——中介绍如何训练 SqueezeNet 来处理 ImageNet 数据集。
使用 Python 和 Keras 通过 VGGNet,ResNet,Inception 和 Xception 对图像分类
新建一个文件,命名为 classify_image.py,编辑插入下列代码
第 2-13 行导入需要的包,其中大部分都属于 Keras。
- 1 # import the necessary packages
- 2 from keras.applications import ResNet50
- 3 from keras.applications import InceptionV3
- 4 from keras.applications import Xception # TensorFlow ONLY
- 5 from keras.applications import VGG16
- 6 from keras.applications import VGG19
- 7 from keras.applications import imagenet_utils
- 8 from keras.applications.inception_v3 import preprocess_input
- 9 from keras.preprocessing.image import img_to_array
- 10 from keras.preprocessing.image import load_img
- 11 import numpy as np
- 12 import argparse
- 13 import cv2
第 2-6 行分别导入 ResNet,Inception V3, Xception, VGG16, 还有 VGG19——注意 Xception 只兼容 TensorFlow 后端。
第 7 行导入的 image_utils 包包含了一系列函数,使得对图片进行前处理以及对分类结果解码更加容易。
余下的语句导入其它有用的函数,其中 NumPy 用于数学运算,cv2 用于与 OpenCV 结合。
--image 为希望进行分类的图像的路径。
- 15 # construct the argument parse and parse the arguments
- 16 ap = argparse.ArgumentParser()
- 17 ap.add_argument("-i", "--image", required=True,
- 18 help="path to the input image")
- 19 ap.add_argument("-model", "--model", type=str, default="vgg16",
- 20 help="name of pre-trained network to use")
- 21 args = vars(ap.parse_args())
--model 为选用的 CNN 的类别,默认为 VGG16。
第 25-31 行定义了一个词典,将类映射到对应的模型名称。
- 23 # define a dictionary that maps model names to their classes
- 24 # inside Keras
- 25 MODELS = {
- 26 "vgg16": VGG16,
- 27 "vgg19": VGG19,
- 28 "inception": InceptionV3,
- 29 "xception": Xception, # TensorFlow ONLY
- 30 "resnet": ResNet50
- 31 }
- 32
- 33 # esnure a valid model name was supplied via command line argument
- 34 if args["model"] not in MODELS.keys():
- 35 raise AssertionError("The --model command line argument should "
- 36 "be a key in the `MODELS` dictionary")
如果没有在该词典中找到 "--model",就会报错。
输入一个图像到一个 CNN 中会返回一系列键值,包含标签及对应的概率。
ImageNet 采用的图像尺寸一般为224×224, 227×227, 256×256, and 299×299,但是并不是绝对。
VGG16,VGG19 以及 ResNet 接受224×224的输入图像, 而 Inception V3 和 Xception 要求为299×299,如下代码所示:
这里我们初始化 inputShape 为224×224 像素,初始化预处理函数为 keras.preprocess_input——执行 mean subtraction 运算。
- 38 # initialize the input image shape (224x224 pixels) along with
- 39 # the pre-processing function (this might need to be changed
- 40 # based on which model we use to classify our image)
- 41 inputShape = (224, 224)
- 42 preprocess = imagenet_utils.preprocess_input
- 43
- 44 # if we are using the InceptionV3 or Xception networks, then we
- 45 # need to set the input shape to (299x299) [rather than (224x224)]
- 46 # and use a different image processing function
- 47 if args["model"] in ("inception", "xception"):
- 48 inputShape = (299, 299)
- 49 preprocess = preprocess_input
如果使用 Inception 或者 Xception,inputShape 需要改为299×299 像素,预处理函数改为separate pre-processing函数。
下一步就是从磁盘载入网络架构的 weights,并实例化模型:
- 51 # load our the network weights from disk (NOTE: if this is the
- 52 # first time you are running this script for a given network, the
- 53 # weights will need to be downloaded first -- depending on which
- 54 # network you are using, the weights can be 90-575MB, so be
- 55 # patient; the weights will be cached and subsequent runs of this
- 56 # script will be *much* faster)
- 57 print("[INFO] loading {}...".format(args["model"]))
- 58 Network = MODELS[args["model"]]
- 59 model = Network(weights="imagenet")
注意:VGG16 和 VGG19 的 weights 大于 500MB,ResNet 的约等于 100MB,Inception 和 Xception 的介于 90-100MB 之间。如果这是你第一次运行某个网络,这些 weights 会自动下载到你的磁盘。下载时间由你的网络速度决定,而且下载完成后,下一次运行代码不再需要重新下载。
- 61 # load the input image using the Keras helper utility while ensuring
- 62 # the image is resized to `inputShape`, the required input dimensions
- 63 # for the ImageNet pre-trained network
- 64 print("[INFO] loading and pre-processing image...")
- 65 image = load_img(args["image"], target_size=inputShape)
- 66 image = img_to_array(image)
- 67
- 68 # our input image is now represented as a NumPy array of shape
- 69 # (inputShape[0], inputShape[1], 3) however we need to expand the
- 70 # dimension by making the shape (1, inputShape[0], inputShape[1], 3)
- 71 # so we can pass it through thenetwork
- 72 image = np.expand_dims(image, axis=0)
- 73
- 74 # pre-process the image using the appropriate function based on the
- 75 # model that has been loaded (i.e., mean subtraction, scaling, etc.)
- 76 image = 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 行使用合适的预处理函数来执行 mean subtraction/scaling。
下面将我们的图像传递给网络并获取分类结果:
第 80 行调用. predict 函数,并从 CNN 返回预测值。
- 78 # classify the image
- 79 print("[INFO] classifying image with '{}'...".format(args["model"]))
- 80 preds = model.predict(image)
- 81 P = imagenet_utils.decode_predictions(preds)
- 82
- 83 # loop over the predictions and display the rank-5 predictions +
- 84 # probabilities to our terminal
- 85 for (i, (imagenetID, label, prob)) in enumerate(P[0]):
- 86 print("{}. {}: {:.2f}%".format(i + 1, label, prob * 100))
第 81 行的. decode_predictions 函数将预测值解码为易读的键值对:标签、以及该标签的概率。
第 85 行和 86 行返回最可能的 5 个预测值并输出到终端。
案例的最后一件事,是通过 OpenCV 从磁盘将输入图像读取出来,在图像上画出最可能的预测值并显示在我们的屏幕上。
- 88 # load the image via OpenCV, draw the top prediction on the image,
- 89 # and display the image to our screen
- 90 orig = cv2.imread(args["image"])
- 91 (imagenetID, label, prob) = P[0][0]
- 92 cv2.putText(orig, "Label: {}, {:.2f}%".format(label, prob * 100),
- 93 (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
- 94 cv2.imshow("Classification", orig)
- 95 cv2.waitKey(0)
VGGNet, ResNet, Inception, 和 Xception 的分类结果
所有的例子都是使用 2.0 以上版本的 Keras 以及 TensorFlow 后台做的。确保你的 TensorFlow 版本大于等于 1.0,否则会报错。所有例子也都使用 Theano 后端做过测试,工作良好。
案例需要的图片以及代码请前往原文获取。
使用 VGG16 分类:
- 1 $ python classify_image.py --image images/soccer_ball.jpg --model vgg16
图 8: 使用 VGG16 来分类足球 (source)
输出为:soccer_ball,精确度为 93.43%。
如果要使用 VGG19,只需要替换下 --network 参数。
- 1 $ python classify_image.py --image images/bmw.png --model vgg19
图 9: 使用 VGG19 来分类汽车 (source)
输出为:convertible(敞篷车),精确度为 91.76%。然而,我们看一下其它的 4 个结果:sports car(跑车), 4.98%(也对);limousine(豪华轿车),1.06%(不正确,但也合理);car wheel(车轮),0.75%(技术上正确,因为图中确实出现了轮子)。
从下面的例子,我们可以看到类似的结果:
- 1 $ python classify_image.py --image images/clint_eastwood.jpg --model resnet
图 10: 使用 ResNet 分类 (source).
ResNet 成功将图像分类为 revolver(左轮手枪),精确度 69.79%。有趣的是 rifle(步枪)为 7.74%,assault rifle(突击步枪)为 5.63%。考虑到 revolver 的观察角度还有相对于手枪来说巨大的枪管,CNN 得出这么高的概率也是合理的。
- 1 $ python classify_image.py --image images/jemma.png --model resnet
图 11: 使用 ResNet 对狗进行分类
狗的种类被正确识别为 beagle(小猎兔狗),精确度 94.48%。
然后我试着分类《加勒比海盗》中的图片:
- 1 $ python classify_image.py --image images/boat.png --model inception
图 12: 使用 ResNet 对沉船进行分类 (source)
尽管 ImageNet 中有 "boat"(船)这个类别,Inception 网络仍然正确地将该场景识别为 "(ship) wreck"(沉船),精确度 96.29%。其它的标签,比如 "seashore"(海滩),"canoe"(独木舟),"paddle"(桨), 还有"breakwater"(防波堤),也都相关,在特定的案例中也绝对正确。
下一个例子是我办公室的长沙发,使用Inception 进行分类。
- 1 $ python classify_image.py --image images/office.png --model inception
图 13: 使用 Inception V3 分类
Inception 准确地识别出图中有 "table lamp"(台灯),精确度 69.68%。其它的标签也完全正确:"studio couch"(两用沙发),"window shade"(窗帘), "lampshade"(灯罩), 还有 "pillow"(枕头)。
下面是 Xception:
- 1 $ python classify_image.py --image images/scotch.png --model xception
图 14: 使用 Xception 分类 (source)
Xception 将图片正确分类为 "barrels"(桶)。
最后一个例子使用 VGG16:
- 1 $ python classify_image.py --image images/tv.png --model vgg16
图 15: 使用 VGG16 分类。
图片来自于《巫师 3:狂猎》。VGG16 的第一个预测值为 "home theatre"(家庭影院),前 5 个预测中还有一个 "television/monitor"(电视 / 显示器),预测很合理。
正如你从上述例子中所看到的,使用 IamgeNet 数据集训练的模型能够准确识别大量日常生活中的物品。希望你能在自己的项目中用到这些代码。
之后呢?
如果你想从头开始训练自己的深度学习网络,该怎么做?
我的新书能够帮助你做到这些,从一个为深度学习的菜鸟成长为专家。
总结
在今天的博客中,我们回顾了 5 种卷积神经网络(CNN):
1. VGG16
2. VGG19
3. ResNet50
4. Inception V3
5. Xception
然后是使用这些架构对你自己的图像进行分类。
如果你对深度学习还有卷积神经网络有更多的兴趣,一定要看一看我的新书《Deep Learning for Computer Vision with Python》,现在就可以进行订购。
附件下载 https://yq.aliyun.com/attachment/download/?spm=a2c4e.11153940.blogcont78726.52.461fb8cbRa43xS&id=1592
来源: https://yq.aliyun.com/articles/78726