原文:Learning AI if You Suck at Math — P5 — Deep Learning and Convolutional Neural Nets in Plain English!
作者:Daniel Jeffries
翻译:Kaiser(王司图)
今天,我们要来写一个自己的Python图像识别程序。
为此我们要了解一个强大的深度学习架构——深度卷积神经网络(Deep Convolutional Neural Network, DCNN)。
卷积神经网络可谓计算机视觉界的劳模,从无人汽车到Google图片搜索,背后都有其功劳。在TensorFlow 2017 峰会上,一位研究者展示了用手机里的卷积神经网络诊断皮肤癌。
为什么卷积神经网络如此强劲呢?一个关键原因在于:
那模式识别又是什么?自动了又如何?
模式可能以很多种形式存在,这里只看两个最重要的例子:
定义物理形式的特征
完成特定任务的步骤
在图像处理的模式识别中,又叫作特征提取。
当你看一张照片或者实物的时候,你会选择性地拎出关键特征来进行认识,这是无意识中发生的。
当你看到我家猫Dove的照片,会想到“猫”或者“铲屎官”,但是你并不知道自己是如何想到的,而只是单纯地那样去做了,这都是自动而且无意识发生的
听起来非常简单,每时每刻都在经历,但这是因为真正的复杂性隐藏在深处。大脑是个黑盒,我们谁也没有说明书。哪怕只是一个微小的动作,也包含了巨量的步骤,表面上看似简单,实则无比复杂。
转动眼珠。
接收并分解光线。再传递信号给大脑。
大脑开始工作,将光信号转换为电化学信号。
信号在我们内置的神经网络中传播,激活不同的区块——记忆、联想、感觉等等。
大脑首先感知了低阶模式(耳朵,胡须,尾巴),再组成高阶模式(动物)。
最后,我们进行了分类,转换成词汇,也就是对真实事物的象征表达,这里就是“猫”。
以上种种,全部发生在一瞬之间。
如果你想要教电脑来执行这些,你会如何开始?
如何找到耳朵?
什么是耳朵?
如何描述耳朵?
为什么猫耳不同于人耳、蝙蝠耳(或蝙蝠侠的耳朵)?
耳朵从不同角度看都是什么样?
所有的猫耳朵都一样吗?(当然不,看看苏格兰折耳猫)
类似问题无穷无尽。如果你想不出如何用C++或Python教会电脑的好方法,也不要灰心,因为这已经困扰了计算机科学家们50多年了!
你自然而然完成的,正式深度学习神经网络的关键应用之一——分类器,这里是图像分类器。
起初,AI研究者想做的跟我们刚才一样。他们希望事无巨细,手动定义每一个步骤,比如对于自然语言处理(NLP),他们召集了最顶尖的语言学专家,让他们总结出语言的所有规律,这也是为什么早期的AI又叫“专家系统”。
语言学家坐成一圈开始琢磨了,然后一个接一个,目不暇接的判断语句冒了出来:
鸟会飞吗?
会。否则:
鸟死了
鸟残了
没翅膀
企鹅
这样下去就没完了,而且还不一定靠谱,花很长时间创造这些判断,最后只剩下无尽的争论、表述的偏差、定义的模糊。
深度神经网络代表着真正的突破,因为你不再需要知晓所有细节,而是让机器自动提取出猫的特征。
这里的关键是“自动”,因为每个复杂的行为背后都有数以百万计的隐藏步骤,是不可能去全部明确的,只能选择绕过,然后让电脑自己领悟。
来看第二个例子:计算任务的步骤。
今天我们手动为计算机定义好了每一个步骤,这就是编程。比如你想找到硬盘上所有的图片文件,然后移动到新文件夹。对绝大多任务而言,程序员就是神经网络,就是智能。他学习任务,分解成步骤,再用符号表示(编程语言)告诉计算机。这里是一个Python的小例子,来自Stack Exchange上的Jolly Jumper:
Jolly Jumper为计算机定义好了每一个步骤:
我们需要知道源路径和目标路径
需要分类方法选出目标文件格式,这里是”jpg”
进入路径,搜索jpg并移动到目标路径
对于简单的,甚至一般复杂的问题,这都是可行的。操作系统由上亿行代码组成,可以算是地球上最复杂的软件了,每一行都在显式地知道计算机该做什么(绘图,存储,更新),也帮助人完成任务(复制文件,输入文本,收发邮件,浏览照片等)。
但是随着问题复杂度的增加,我们手动定义问题步骤的能力,也遇到了瓶颈。举个例子,如何开车?这种想想就很复杂的任务,包含数以百万计的小步骤:
沿直线行驶
知道什么是直线,并认出来
从某地行驶到另一地
识别障碍物如墙,人,渣渣
区分有益物(交通号志)还是危险物(作死的人)
实时掌握周边车辆状况
决定下一个动作
在机器学习里,这就是决策制定问题,复杂的该类问题例如:
机器人的运动与感知
语言翻译系统
自动驾驶汽车
股票交易系统
来看深度学习如何通过自动特征提取,来帮助我们解决那些复杂到令人发狂的问题。
如果读过V.Anton Spraul的经典书籍像程序员一样思考(强烈推荐阅读),就会知道编程是有关解决问题的。程序员化大为小,分而治之,临阵画策,写码执行。
而深度学习是代替我们解决问题,但是目前AI还是需要人类(万幸)设计测试AI架构的。让我们对神经网络也分而治之,再创建程序认出我家Dove是只猫。
深度学习是机器学习的子学科,我们把许多不同的层堆叠起来,学习数据中更有意义的表征,因而得名。其中每一个“层”就是神经网络,由人工神经元连接而成。
在强大的GPU帮我们做计算之前,只能建立一些很小的“玩具”神经网络,也做不了多少事情。而现在我们可以堆叠多层,故能成其深。
“神经网络”是在1950年代受人脑研究启发而来,研究者们创造了神经元的数学表达如下(感谢斯坦福大学的优秀公开课件和维基百科):
忘掉所有复杂的数学符号,因为你不需要它们。
基础非常简单,X0代表数据,在神经元的连接当中流动,连接的强度由权重(W0X0, W1X1)代表。如果信号足够强,就会通过“激励函数”激活神经元。
这里是一个三层神经网络的例子:
有些神经元被激活,有些神经元之间的连接被增强,由此系统学习到了那些才是重要的。
接下来我们边写代码,边深入理解深度学习。系统的必要特性有:
训练
输入数据
层
权重
目标
损失函数
优化函数
预测
训练就是我们如何教会神经网络要学什么,分为五个步骤:
建立训练集, 记作x,并导入标签为目标y
前馈数据给网络,得到预测结果y’
定义网络的“损失”,即预测y’和真实目标y之差
计算损失的“梯度”,即我们接近或远离正确目标的速度
沿着梯度的反方向来调整网络权重,并周而复始
在本例中,DCNN的输入数据是一组图片,图片越多越好。与人类不同,计算机需要大量的图片才能学会分类。AI研究者正致力于用尽可能少的数据达到学习目的,但这仍是个前沿问题。
一个著名例子就是ImageNet数据集,由很多手动标注过的图片组成。换句话说就是预先让人类用他们内置的神经网络把图片全部看一遍,然后给数据赋予意义。人们上传照片,并打上标签,比如“狗”,或者某个品种的狗“猎兔犬”。标签代表了网络的准确预测,网络的预测输出(y’)与手动标记数据(y)越接近,就说明其越准确。
数据被分为两部分,训练集和测试集。训练集就是我们给神经网络的输入,根据它们学习多种物体的关键特征,再与测试集中的随机数据相比较以衡量准确性。
在我们的程序中,将用到的是著名的CIFAR-10数据集,由加拿大高等研究所提供。CIFAR10有60000张32×32的彩色图片,共分为10类,每类6000个。其中50000个作为训练集,10000个充当测试集。
当我第一次使用CIFAR的时候,我误以为这会比ImageNet的大尺寸图片更简单。而事实却是,CIFAR10更具挑战性,因为图片尺寸小、数量少,可供神经网络识别的特征也少。
一些最大也是最差的DCNN架构如ResNet可以在ImageNet上达到97%的准确率,但在CIFAR 10却只有87%。根据我的经验,目前处理CIFAR 10的业界标杆是DenseNet,准确率可达95%。但是需要足足250层和1500万个参数!我把这些框架附在了文末,可供参考,但是开始阶段最好还是先关注些简单的问题。
理论已经讲的差不多了,是时候放码过来了。如果你对Python还不是很熟悉,我热烈,强烈,猛烈,剧烈地推荐Fabrizio Romano的Learning Python,此书把每个点都解释得特别好。我从未见过如此优秀的Python书,反而被很多耽误了不少时间。
代码基于Github上的Keras示例代码,我个人的修改可见这里。
我已经调整了架构和参数,并加入了TensorBoard来辅助可视化。首先初始化我们的Python程序,导入数据集和建立DCNN所需的类。所幸,Keras已经集成了很多,所以十分方便。
现在来添加层。大多数神经网络使用全连接层,也就是每个神经元都连接着其他所有神经元。全连接层对各种问题都表现良好,但不幸的是对图像识别的尺度缩放问题解决不到位。所以我们用卷积层来搭建系统,其独到之处就在于,不是所有神经元都相互连接。
斯坦福计算机视觉课程谈卷积神经网络:
“在CIFAR-10中,图片只有32x32x3(32像素宽,32像素高,3个色彩通道),所以常规神经网络的第一个隐藏层上的全连接神经元就有32323=3072个权重值。这个数量看起来还可以,但很明显全连接结构无法应对大图片。比如,生活中常见的图片尺寸,200x200x3,就会使神经元拥有120,000个权重值。不仅如此,我们还希望更多的神经元,所以参数的数量会迅速暴涨!显然,全连接层有些浪费,而大量的参数又很容易导致过拟合。”
过拟合就是把训练集学得炉火纯青,却对没见过的图片束手无措,派不上实际用场。就像你不断地玩着同一局棋,对棋谱都倒背如流,但是对手一旦变招,你就无计可施了,之后我们还会详谈过拟合。
下图表现了数据在DCNN里的流动情况,每次只关注很小一部分数据,探寻模式,基于观察建立高层认知。
注意,前面的几层是简单的模式,比如边缘,颜色,基本形状。随着信息在层间流淌,系统逐渐摸清了更复杂的模式,比如纹理,最终导出物体的类别。
这一思路来自一个生物实验:研究表明猫对于不同的刺激(边缘,颜色),有不同的视觉细胞响应。
人类也是一样,我们的每个视觉细胞只能感受特定的特征。
这一个典型的DCNN架构示意图:
你会注意到这里还有第三种层,池化层,在牛津课程和斯坦福课程中可以获得更多细节。这里我们会跳过很多细节,因为大部分人难以理解,我第一次学的时候就是这样的体会。
对于池化层你需要了解如下内容:它的目的很简单,就是下采样,也就是压缩输入图片,从而降低计算负担和内存消耗。信息少了,工作起来就轻便了。池化同样有助于减少过拟合,就是网络聚焦在了训练集的个别现象上,而忽略了挑选出猫狗鸟。比如有的图片上可能会存在坏点或镜头光晕,网络可能会把光晕和狗当成一体的,而实际上根本风马牛不相及。
最后,多数DCNN会添加几个密集连层接,也即全连接层 来映射前面几层的特征并做出预测。所以现在给我们的卷积网络也加几层。
首先定义些许输入变量。
卷积核与池化面积定义了卷积网络如何处理图片挖掘特征。最小的卷积核可以是1X1,也就是我们认为关键特征只有1像素大小。比较典型的核尺寸有3像素,再将特征池化为2X2网格。
2X2网格从图片中抽取特征,并像炉石卡组一样堆叠起来,这就把它们从原图的特定位置剥离了出来,以便让系统寻找各处的直线或圆圈,而不仅限于最早被发现的位置。
很多教程将这一过程描述为处理“平移不变性”。
什么是平移不变性?好问题。
再看下面这张图:
如层1和层2所示,系统并不是一下子就把特征的本质给抓出来了,而是可能觉得猫鼻子的圆圈只有在图像正中(第一次发现该圆圈处)时,才是重要特征。
以我家Dove为例,如果系统最开始在她的眼睛上发现了一个圈,那么系统可能会错误的认为,这个圈在图中的位置与辨认猫也是相关的。而实际上,系统应该同等重视任何可能出现圆圈的地方:
在给神经网络添加层之前,我们先载入并处理数据:
OK,现在我们终于准备好给神经网络添加一些层了:
神经网络中堆叠起来的层依次有:
卷积
激励
卷积
激励
池化
Dropout
多数类型已经介绍过了,除了两个:dropout和激励。
Dropout更容易理解些,本质上就是模型会按比例丢弃一些信息,就像Netflix的Chaos Monkey,它会按照脚本随机关闭一些服务节点以保证鲁棒性和荣誉度。这里也是一样道理,我们希望神经网络不要过度依赖于某一个特征。
激励层就是决定神经元是否被“激活”的标准,可供选择的激励函数有很多,ReLU是其中最成功的一种,这得益于它较高的计算效率。这里有Keras中可选的不同激励函数列表。
我们还添加了第二个卷积网络与第一个互成镜像,如果我们追求编程效率的话,可以创建一个模型生成器以循环叠加神经网络层。不过这里我们就简单地复制粘贴一下,为图个方便,其实是有违Python之禅的。
最后,我们再添加一些全连接层,已经另一个Dropout层,再把所有特征映射扁平化:
在最后一层,我们使用了不同的激励函数:softmax,因为这定义了每个类的概率分布。
预知以上代码究竟几个意思,且听下回分解。