用CNN来识别鸟or飞机的图像
你是不是看烦了各种各样对于深度学习的报导,却不知其所云?我们要来改变这个问题。
这一次我们将一起写一个,可以判别鸟类还是飞机的程序!我们将学习到如何写一个,通过深度学习来识别图像中物体的程序。换个角度来说,我们会解释Google Photos搜索图片和识图所用到的“黑科技”。
Google现在可以让你在你自己的图片库里面,根据你的描述搜索图片,即使这些图片根本没有被标注任何标签。这是怎么做到的呢?
和第1 2章一样,这个指南是针对所有对机器学习感兴趣但不知从哪里学起的读者的。本文目标在于平易近人,这意味着文中有大量的概括。但是谁在乎这些呢?只要能让读者对于机器学习更感兴趣,任务也就完成了。
点此查看第一章:如何向你奶奶解释机器学习是什么
第二章:用机器学习制作超级马里奥的关卡
通过深度学习识别物体
你可能曾经看过这个xkcd漫画。
Xkcd漫画编号1425
一个3岁的小孩可以识别出鸟类的照片,然而最顶尖的计算机科学家们已经花了50年时间,来研究如何让电脑识别出不同的问题。漫画里的灵感就是这么来的。
在最近的几年里,我们终于找到了一种通过卷积神经网络(Convolutional Neural Networks)来进行物体识别的好方法。这些个词听起来就像是从威廉·吉布森的科幻小说编造出来的,但是如果你把这个想法逐步分解,你绝对可以理解它。
由浅入深
在我们在识别鸟类之前,让我们先做个更简单的——识别手写的数字“8”
在第二章中我们了解到,神经网络是如何通过连接无数神经元来解决复杂问题的。我们创造了一个小型神经网络,然后根据各种因素(房屋面积,格局,地段等)来估计房屋的价格:
在第一章中我们提到了,机器学习,就是关于重复使用同样的泛型算法,来处理不同的数据,解决不同的问题的一种概念。所以这次,我们稍微修改一下同样的神经网络来识别手写文字。但是为了更加简便,我们只会尝试去识别数字“8”。
机器学习只有在你拥有数据的情况下,最好是大量的数据,才能有效。所以,我们需要有大量的手写“8”来开始我们的尝试。幸运的是,恰好有研究人员创造出了“MNIST手写数字数据库”能助我们一臂之力。MNIST提供了60,000张手写数字的图片,每一张都是一个18×18的图片。下列是数据库中的一些例子:
MNIST数据库中的数字“8”
万物皆“数”
在第二章中我们创造的那个神经网络,它只能接受三个数字输入(卧室数,面积,地段),但是现在,我们需要用神经网络来处理图像。所以到底怎样才能把图片,而不是数字,输入到神经网络里呢?
结论其实极其简单。神经网络会把数字当成输入,而对于电脑来说,图片其实恰好就是一连串代表着每个像素颜色的数字:
我们把一副18×18像素的图片当成一串324个数字的数列,就可以把它输入到我们的神经网络里面了:
为了更好地操控我们的输入数据,我们把神经网络扩大到拥有324个输入节点:
请注意,现在有两个输出了(而不仅仅是一个房子的价格)。第一个输出会预测图片是“8”的概率 而第二个则输出不是“8”的概率。概括地说,我们就可以依靠多种不同的输出,利用神经网络把要识别的物品进行分组。
虽然我们的神经网络要比上次大得多(这次是324,上一次是3!=6),但是现在的计算机一眨眼的功夫就能够对这几百个节点进行运算。当然,你的手机也可以做到。
现在唯一要做的就是训练我们的神经网络了。用各种“8”和非“8”的图片来训练,这样它就能学习怎么去区分了。当我们输入一个“8”的时候,我们会告诉他“是8的概率”是100%而“不是8的概率”是0%,反之亦然。
下面是一些训练数据:
嗯…就是这些训练数据…
我们能在我们笔记本电脑上面用几分钟的时间来训练这种神经网络。完成之后,我们就可以得到一个有着很高的“8”图片识别率的神经网络。欢迎来到(1980年代末的)图像识别的世界!
短浅的目光
仅仅把像素输入到神经网络里,就可以做出图像的识别,这很棒!机器学习就像魔法一样!对不对!!
呵呵,当然,不会,这么,简单,呵呵呵。(Well, of course it’s not that simple.感受作者的神之鄙视吧哈哈哈)
首先,好消息是,当我们的数字就在图片的正中间的时候,我们的识别器干得还不错。
坏消息是:
当数字并不是正好在图片中央的时候,我们的识别器就完全不工作了。一点点的位移我们的识别器就掀桌子不干了(╯‵□′)╯︵┻━┻。
这是因为我们的网络只学习到了正中央的“8”。它并不知道那些偏离中心的“8”长什么样子。它仅仅知道中间是“8”的规律。
在真实世界中,这好像并没什么卵用。真实世界的问题永远不会如此轻松简单。所以,我们需要知道,怎么才能让我们的神经网络在非中心“8”的情况下识别。
暴力算法(Brute Force) 方法1:滑框搜索
我们已经创造出了一个能够很好地识别图片中间“8”的程序。如果我们干脆把整个图片分成一个个小部分,并挨个都识别一遍,直到我们找到“8”,这样能不能行呢?
这个叫做 滑框(Sliding Window)法,是暴力算法之一。在有限的情况下,它能够识别的很好。但实际上它并不怎么有效率,你必须在同一张图片里面一遍一遍的识别不同大小的物体。实际上,我们可以做得更好。
暴力算法 方法2:更多的数据与一个深度神经网
刚刚我们提到,经过训练之后,我们只能找出在中间的“8”。如果我们用更多的数据来训练,数据中包括各种不同位置和大小的“8”,会怎样呢?
我们并不需要收集更多的训练数据。实际上,我们可以写一个小脚本来生成各种各样不同位置“8”的新图片:
通过组合不同版本的训练图片,我们创造出了“合成训练数据(Synthetic Training Data)”。这是一种非常实用的技巧!
使用这种方法,我们能够轻易地创造出无限量的训练数据。
更多的数据让我们的神经网络更难解决这个问题。但是把神经网络扩大,它就能寻找到更复杂的规律了,以此来弥补解决困难问题的不足。
要扩大我们的网络,我们首先要把把节点一层一层的堆积起来:
因为它比传统的神经网络层数更多,所以我们把它称作“深度神经网络(Deep Neural Network)”。
这个想法在1960年代末就出现了,但直至今日,训练这样一个大型神经网络也是一件不切实际的缓慢的事情。然而,一旦我们知道了如何使用3D显卡(最开始是用来进行快速矩阵乘法运算)来替代普通的电脑处理器,使用大型神经网络的想法就立刻变得可行了。实际上,你用来玩守望先锋的NVIDIA GeForce GTX1080这款显卡,就可以极快速的训练我们的神经网络。
但是尽管我们能把我们的神经网络扩张的特别大并使用3D显卡快速训练它,这依然不能让我们一次性得到结论。我们需要更智能的将图片处理后,放入到神经网络里。
仔细想想,如果把图片最上方和最下方的“8”当成两个不同的对象来处理,并写两个不同的网络来识别它们,这件事实在是说不通。
应该有某种方法,使得我们的神经网络,在没有额外的训练数据的基础上,能够非常智能的识别出图片上任何位置的“8”,都是一样是“8”。幸运的是…这就是!
卷积性的解决办法
作为人类,你能够直观的感知到图片中存在某种层级(Hierarchy)或者是概念结构(Conceptual structure).参考下面的这个图片:
作为人类,你立刻就能识别出这个图片的层级:
地面是由草和水泥组成的
有一个小孩在图片中
小孩在骑弹簧木马
弹簧木马在草地上
最重要的是,我们识别出了“小孩儿”,无论这个小孩所处的环境是怎样的。当每一次出现不同的环境时,我们人类不需要重新学习“小孩儿”这个概念。
但是现在,我们的神经网络做不到这些。他认为“8”出现在图片的不同位置,就是不一样的东西。它不能理解“物体出现在图片的不同位置还是同一个物体”这个概念。这意味着在每种可能出现的位置上,它必须重新学习识别各种物体。这弱爆了。
我们需要让我们的神经网络理解“平移不变性(Translation invariance)”这个概念——也就是说,“8”无论出现在图片的哪里,它都是“8”。
我们会通过一个叫做卷积(Convolution)的方法来达成这个目标。卷积的灵感是由计算机科学和生物学共同激发的。(有一些疯狂的生物学家,它们用奇怪的针头去戳猫的大脑,来观察猫是怎样处理图像的。 >_<)
卷积是如何工作的
之前我们提到过,可以把一整张图片当做一串数字输入到神经网络里面。不同的是,这次我们会利用“位移物相同”(译者注:也就是平移不变性)的概念来把这件事做得更智能。
下面就是,它怎样工作的,分步解释——
第一步:把图片分解成部分重合的小图块
和上述的滑框搜索类似的,我们把滑框在整个图片上滑过,并存储下每一个框里面的小图块:
这么做之后,我们把图片分解成了77块同样大小的小图块。
第二步:把每个小图块输入到小型神经网络中
之前,我们把一张图片输入到神经网络中来看这张图片是不是一个“8”。这一次我们还做同样的事情,只不过我们输入的是一个个小图块:
然而,有一个非常重要的不同:对于每个小图块,我们会使用同样的神经网络权重。换一句话来说,我们同样对待每一个小图块。如果哪个小图块有任何异常出现,我们就认为这个图块是“异常(Interesting)”的
第三步:把每一个小图块的结果都保存到一个新的数列当中
我们不想并不想打乱小图块的顺序。所以,我们把每个小图片按照图片上的顺序输入并保存结果,就像这样:
换一句话来说,我们从一整张图片开始,最后得到一个稍小一点的数列,里面存储着我们图片中的哪一部分有异常。
第四步:缩减像素采样
第三步的结论是一个数列,这个数列对应着原始图片中哪一部分最异常。但是这个数列依然很大:
为了减小这个数列的大小,我们利用一种叫做最大池化(Max Pooling)的方法来降低采样(Downsample)。它听起来很棒,但这仍旧不够!
让我们先来看每个2×2的方阵数列,并且留住最大的数:
这里,一旦我们找到组成2×2方阵的4个输入中任何异常的部分,我们就只保留这一个数。这样一来就缩减了我们的数列大小,并且保留住了最重要的部分。
最后一步:作出预测
到现在为止,我们已经把一个很大的图片,缩减到了一个相对较小的数列。
你猜怎么着?数列就是一序列数而已,所以我们我们可以把这个数列输入到另外一个神经网络里面去。最后的这个神经网络会决定这个图片是否匹配。为了区分它和卷积的不同,我们把它称作“完全连接”网络(”Fully Connected” Network)
所以从开始到结束,我们的五步就像管道一样连接起来:
添加更多步骤
我们的图片处理管道是一系列的步骤:卷积,最大池化,还有最后的“完全连接”网络。
你可以把这些步骤组合、堆叠任意多次,来解决真实世界的问题。你可以有2层,3层或者10层卷积层。当你想要缩小你的数据大小的时候,你也随时可以调用最大池化函数。
我们解决问题的基本方法,就是从一整个图片开始,一步一步逐渐的分解它,直到你找到了一个单一的结论。你的卷积层越多,你的网络就越能识别出复杂的特征。
比如说,第一个卷积的步骤可能就是尝试去识别尖锐的东西,而第二个卷积步骤则是通过找到的尖锐物体来找鸟类的喙,最后一步是通过鸟喙来识别整只鸟,以此类推。
下面是一个更实际的深层卷积网络的样子(就是你们能在研究报告里面找到的那种例子一样):
这里,他们从一个224×224像素的图片开始,使用了卷积和最大池化两次,再使用3次卷积,最大池化一次,最后在使用两个完全连接层。最终的结果是这个图片能被分类到1000种不同类别当中的某一种!
建造正确的网络
所以,你是怎么知道我们需要结合哪些步骤来让我们的图片分类器工作呢?
说句良心话,你必须做许多的实验和检测才能回答这个问题。在为你要解决的问题找到完美的结构和参数之前,你可能需要训练100个网络。机器学习包含了许多的尝试和错误!
建立我们的鸟类分类器
现在我们已经做够了准备,我们已经可以写一个小程序来判定一个图片是不是一只鸟了。
诚然,我们需要数据来开始。CIFAR10数据库免费提供了6000张鸟类的图片和52000张非鸟类的图片。但是为了获取更多的数据,我们仍需添加Caltech-UCSD Birds-200-2011数据库,这里面包括了另外的12000张鸟类的图片。
这是我们整合后的数据库里面的一些鸟类的图片:
这是数据库里一些非鸟类图片:
这些数据对于我们这篇文章解释说明的目的来说已经够了,但是对于真实世界的问题来说,72000张低分辨率的图片还是太少了。如果你想要达到Google这种等级的表现的话,你需要上百万张高清无码大图。在机器学习这个领域中,有更多的数据总比一个更好的算法更重要!现在你知道为什么谷歌总是乐于给你提供无限量免费图片存储了吧? 他们,需要,你的,数据!!
为了简历我们的分类器,我们将会使用TFLearn。TFLearn是Google TensorFlow的一个封装,Google TensorFlow包含了一个拥有简单API的深度学习库。使用它来定义我们网络的层级,建立卷积网络的过程和写几行代码一样简单。
下面是定义并训练我们网络的代码:
# -*- coding: utf-8 -*-"""Based on the tflearn example located here:https://github.com/tflearn/tflearn/blob/master/examples/images/convnet_cifar10.py"""from __future__ import division, print_function, absolute_import# Import tflearn and some helpersimport tflearnfrom tflearn.data_utils import shufflefrom tflearn.layers.core import input_data, dropout, fully_connectedfrom tflearn.layers.conv import conv_2d, max_pool_2dfrom tflearn.layers.estimator import regressionfrom tflearn.data_preprocessing import ImagePreprocessingfrom tflearn.data_augmentation import ImageAugmentationimport pickle# Load the data setX, Y, X_test, Y_test = pickle.load(open("full_dataset.pkl", "rb"))# Shuffle the dataX, Y = shuffle(X, Y)# Make sure the data is normalizedimg_prep = ImagePreprocessing()img_prep.add_featurewise_zero_center()img_prep.add_featurewise_stdnorm()# Create extra synthetic training data by flipping, rotating and blurring the# images on our data set.img_aug = ImageAugmentation()img_aug.add_random_flip_leftright()img_aug.add_random_rotation(max_angle=25.)img_aug.add_random_blur(sigma_max=3.)# Define our network architecture:# Input is a 32x32 image with 3 color channels (red, green and blue)network = input_data(shape=[None, 32, 32, 3],
data_preprocessing=img_prep,
data_augmentation=img_aug)# Step 1: Convolutionnetwork = conv_2d(network, 32, 3, activation='relu')# Step 2: Max poolingnetwork = max_pool_2d(network, 2)# Step 3: Convolution againnetwork = conv_2d(network, 64, 3, activation='relu')# Step 4: Convolution yet againnetwork = conv_2d(network, 64, 3, activation='relu')# Step 5: Max pooling againnetwork = max_pool_2d(network, 2)# Step 6: Fully-connected 512 node neural networknetwork = fully_connected(network, 512, activation='relu')# Step 7: Dropout - throw away some data randomly during training to prevent over-fittingnetwork = dropout(network, 0.5)# Step 8: Fully-connected neural network with two outputs (0=isn't a bird, 1=is a bird) to make the final predictionnetwork = fully_connected(network, 2, activation='softmax')# Tell tflearn how we want to train the networknetwork = regression(network, optimizer='adam',
loss='categorical_crossentropy',
learning_rate=0.001)# Wrap the network in a model objectmodel = tflearn.DNN(network, tensorboard_verbose=0, checkpoint_path='bird-classifier.tfl.ckpt')# Train it! We'll do 100 training passes and monitor it as it goes.model.fit(X, Y, n_epoch=100, shuffle=True, validation_set=(X_test, Y_test),
show_metric=True, batch_size=96,
snapshot_epoch=True,
run_id='bird-classifier')# Save model when training is complete to a filemodel.save("bird-classifier.tfl")print("Network trained and saved as bird-classifier.tfl!")
如果你使用一款非常好的有着足够RAM的游戏显卡(比如说Nvidia GeForce GTX980 Ti)来训练的话,训练一个小时以内就能结束,但是如果你用一般的cpu来训练的话,他可能需要更长的时间。
随着训练的进行,准确度也会增加。在第一遍训练之后,它的准确度是75.4%。10次训练之后,就上升到了91.7%。当训练了至少50次的时候,它的准确率达到了95.5%。继续训练并没有增加他的准确度,所以我停止了。
恭喜!!我们的程序现在能识别鸟类的图片了!
测试我们的网络
现在我们拥有了一个训练过的神经网络,我们可以开始使用它了!这儿有一个,通过接收一个图片来预测他是否是鸟类。
但是为了真正检测我们的神经网络有多有效果,我们需要进行大量的图片测试。我创造的那个数据库里面有15000张用来验证的图片。当我把这15000张图片放到程序里运行的时候,它的预测准确率达到了95%。
看起来还不错,对吧?额…这事儿吧还得辩证的看…
95%准确是有多准确?
我们的网络声称有95%准确。但是细节决定成败(devil is in the detail),这意味着各种各样问题可能产生。
比如说,如果我们的训练数据有5%是鸟类而剩下95%不是呢?一个程序即使每次都猜“不是鸟”也能达到95%的准确率。这也就意味着这个程序并没有什么作用。
相比于准确度,我们必须更多的关注在数字本身。为了判别一个分类系统有多好,我们需要知道它是怎样出错误的,而不是仅仅关注它错了多少次。
与其只考虑我们预测的对与错,不如把我们的程序分解成四个不同的类别——
首先,对于那些被我们的网络正确辨认为鸟类而且确实是鸟类的,我们叫他们“真阳性(True Positives)”
第二,被辨认为非鸟类,而且确实是非鸟类的,我们叫“真阴性(True Negatives)”
第三,被辨认为鸟类,但却是非鸟类的,我们叫“假阳性(False Positives)”
第四,被辨认为非鸟类,但却是鸟类的,我们叫“假阴性(False Negatives)”
下面的数据是使用那15000张验证图片,在每种类别中我们猜测的数量:
为什么我们要把结果做上述分类呢?因为并不是每一个错误产生的几率都是一样的。
设想如果我们写一个通过MRI图像来探测癌症的程序。如果我们探测到了癌症,我们更希望它是“假阳性”而不是“假阴性”。因为假阴性是最可怕的情况——那就是你的程序告诉你,你绝对没有病,但实际上你已经病入膏肓了。
我们需要计算准确和召回指标(Precision and Recall metrics)而并不仅仅关注总体的准确度。准确和召回指标给了我们程序表现的一个清晰的反馈:
这告诉我们,当我们猜“鸟类”的时候,97%的时候是正确的。但是这同时也告诉我们说,我们只找到了真实鸟类里面的90%。换句话说,我们可能不会找到每一只鸟,但是当我们找到一只鸟的时候,我们很确定它就是一只鸟!
路还在远方…
现在你知道了一些关于深度卷积网络的基本概念了,你可以用TFLearn尝试一下。它甚至包括了自带的数据,你根本不用自己去收集图片。
你同时也知道了如何开始创造分支或是学习机器学习的其他领域。接下来为什么不尝试着去学习如何用算法来呢?
原文:Adam Geitgey
原文链接:
翻译:巡洋舰科技——赵95
更多阅读