实践出真知:通过TensorFlow和Keras实践例子来理解深度CNN
Posted 北辰文阁
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实践出真知:通过TensorFlow和Keras实践例子来理解深度CNN相关的知识,希望对你有一定的参考价值。
翻译:TalkingData JasonYe (Datascientist in the making)
发表时间:2017年11月13日
深度学习是当前人工智能领域中最引人入胜的主题。它是轻依赖于生物学理论发展而来的一类算法,目前在许多领域取得了振奋人心的成果:计算机视觉、自然语言处理、语音识别等等。
过去的五年里,深度学习在工业界产生了大范围的影响。
最近许多技术的突破口都归功于使用了这项技术。这里列出一些:特斯拉的自动驾驶汽车、脸书的图片打标签系统、虚拟助手(Siri/Cortana)、聊天机器人、摄像机物体识别。在许多涉及语言理解和图片分析的识别任务领域中,深度学习达到了人类的表现水准。
下面例子展现了深度学习算法已经能够做到的事情:自动识别和标记情景中的物体。
深度学习也成为了科技媒体中的热词。
在本文中,我将会呈现深度学习的应用实例,让你掠过媒体的炒作浮华。
我将会展示如何构建一个深度神经网络,能够以90%的准确度正确将图片分类。这个看似简单的任务,对于计算机科学家其实非常困难,在上面已经耗费了多年的研究直至深度网络模型,尤其是CNN(卷积神经网络模型)崛起。
本文将会分成四部分讲述:
展示数据和使用案例,解释图片分类任务的复杂程度。
详细阐述CNN的细节要点。我将解释它们的内在工作机制和为什么CNN比传统神经网络模型更适合图片分类的原因。
基于AWS上一台带GPU的强大EC2机器上布置机器学习。
训练两个深度学习模型:一个从头开发使用Keras和Tensorflow的端到端管道;另外一个是通过在大数据集上的预训练网络模型获得
这些部分是互相独立的。如果你对理论不感冒,你可以略过部分一和部分二。
理解深度学习是极具挑战的。作为深度学习实践者,我经常花很多时间来学习这个科目。在最后一部分,我会分享送过去学习的材料,你可以学习并开启自己的机器学习之路。
本文是我在神经网络多年学习知识的真实总结。如果在阅读中有任何觉得不正确的地方,请不吝赐教。如果有任何疑惑或者想法想表达,也期望能够大胆提出,一起讨论。
在我的Githup账号上有这篇文章涉及的相关代码,欢迎拉取并试验。
下面正式进入正文!
一、有趣的案例:如何分类猫和狗?
目前有许许多多标注给深度学习模型的图片数据集。
本文使用的是来自于Kaggle竞赛的猫和狗对比的数据集。是的,如你所料,就是一堆标注了猫和狗的图片。
和其他Kaggle竞赛一样,我们有两份文件数据:
一份是训练集合:包括2万5千张猫和狗的图片,每张图片的文件名已经包括了标注信息(是猫或者是狗)。我们将会用这份数据来训练我们的模型。
一份是测试集合:包括1万2千5百张图片,命名只是单纯的数字。该测试集数据都需要我们用模型去预测出图片里面是猫或者是狗的概率(标注1为狗,0为猫)。然后会标注为你的模型在kaggle上的分数。
如上所示,我们有许多有价值的图片。这些猫和狗图片中它们千姿百态。有不同的种类、所处的位置、颜色。有的坐着,有的不是。有的欢乐,有的悲伤。有的猫睡着了,有的狗在吼叫。同时照片也是在不同的角度和不同焦点下拍摄的。
无限的配置情况是可能的。对于一个人类来所,不费吹灰之力就可以在一系列不同的照片集里区分出宠物来。而对于一台机器来说,并非轻而易举之事。事实上,自动分类器需要站在能够稳定标记猫为猫、狗为狗的假定基础上工作,而这种假设则需要真正知道如何描述一只动物的本质特征。
深度学习网络模型真正起作用的地方,在于能够在给定的一类分类任务下,自动学习获取同一类物体的抽象特征的能力。具备即使在极端的变化下也有应对变形或者简单几何变化下数据集中学习出模式的稳定性。
二、全联接网络模型 Vs CNN
也有很多人采用全联接网络模型来解决图片分类问题。但是,他们需要意识到,这些网络模型并非完全适合这类任务。
让我们理解为什么这么说。
2-1 : 一个全联接神经网络模型
全联接神经网络模型是在每一个神经元与它相邻下一层的每个神经元都连接的模型。这是标准和典型的神经网络模型。想学习更多神经网络背后的理论知识可以点击这份链接或者这个:这两个都是Andrej Karpathy的斯坦福课程,讲述得十分简洁精彩。
为了证明,以下是一个三层的全联接网络模型。
采用全联接网络模型,图片在作为输入前都先转化为一维的向量。
比如,一张有颜色的图片尺寸是256 X 256,可以用三元组(255,255,3)代表,其中的3来表示三基色,因此图片的大小为 256 X 256 X 3 = 196608。这里我们首先应该进行的工作是将这份图片转化为一个长向量。该向量的每一个元素代表一个像素值。
在256 X 256的有颜色图片上采用神经网络全联接模型,我们将会获得:
大小为196608的一个输入层,是由每个神经元将图片的每一个像素点压缩获取。
大小为2的一个输出层,每一个神经元都会生成一个输出类的预测值。
输入输出层之间的隐藏层和隐藏神经元。
全联接神经网络模型能够成为有效的分类器。在监督学习算法领域中,假定我们设计出了一个不会过拟合的稳定结构,这类算法能够学习复杂的非线性模式,生成良好的模型。
注意!当处理图片数据时,全联接神经网络模型并非最适合的工具!
我所了解的主要是下面两点原因:
1.假设我们有一个隐藏层,有1000个隐藏神经元。在给定的输入层大小下,1000是个合适的值。在这样的配置下,权重系数连接着我们的输入层和第一层隐藏层等价于 196608 X 1000 = 196608000! 这不仅仅是一个庞大的数字,这样的情况下,神经网络也表现不佳,往往不能处理出稳定的模型。但公平的是,我们可以说隐藏层设置1000个隐藏单元是很合适的。我们计算一下内存消耗,一个浮点型的权重能够压缩成8个字节。1966980000 个权重将会花费 ... 1572864000 bytes,这估计是 1562G,因此,我们需要 1562G来存储权重,而这仅仅只是一个隐藏层络而已。除非你的电脑有许多RAM,否者这并非良策。
2.使用全联接神经网络,我们会失去图片里面的空间特征结构。事实上,当将照片转化为长向量时,每一个像素点其他像素点在神经网络中的处理方式都是一致的。因此像素点之间的空间联系就被破坏了。每个像素点都是独立的,毫无疑问损失了庞大的信息量,这也是我们模型需要压缩的。
为了解决这两个局限,一堆研究工作开始展开,产生了许多新的神经网络模型,能够适应处理复杂的图像数据。
我们学习其中的卷积神经网络模型(CNNs)。
2-2 : 卷机神经网络
卷积神经网络模型(或者简称CNNs)是一种特殊的神经网络结构,是为处理图像数据专门设计的。从1990年代初期,LeCun引入使用后,CNNs在例如手写字分类和脸部识别等问题上被证明了其高效性。在过去的几年里,数篇论文的发表表明了它在更具挑战性视觉分类问题上取得了非凡的成果。其中最著名的,是2012年Krizhevsky展示的在ImageNet数据集上取得的成果,采用了AlexNet获得了16.4%的误差,远超第二名的26.1%。
CNNs并不是独力奏效,同时也有几点有趣的因素共同影响。
几百万标注数据的大规模数据的有效性。最出名的是ImageNet。
强力的GPU处理器,能够真正训练非常大的模型。
增强了模型的正则化策略,如Dropout。
CNN能够有效解决图像分类任务,专门设计去解决之前全联接网络模型的两个缺点。
CNNs有它自己的结构和属性。看起来与(接下来将会展示)标准的全联接神经网络模型不同但也有相同的机制。两者中,我们都会有隐藏层、权重、偏差、隐藏神经元、Loss函数、后回馈和随机梯度下降。申明下,如果你不熟悉这些概念,我鼓建议你看看Andrej Karpathy关于神经网络的课程。
CNNs由五个部分组成:理解这五个部分就能够对全局的机制有清晰的理解。
输出层
卷积层
ReLU层
池化层
全联接层
结构示意图如下所示:
如图所示,整个图像经过这么多层的处理后,输出神经元有着各个分类的预测值。
那么让我们深入了解每个层的功能。
输入层
在全联接神经网络模型中,输出层是许多垂直的列(实际上是向量)。无论我们处理不处理图片,我们都需要先将图片数据转换成这样的数据。
而在Convnet模型中,当我们处理图像数据时,输入层将图片当作平方的,每个神经元代表一个像素点。因此CNNs保持图片原有的形状而不是将他们转为向量。
如下图说明两者的区别:
卷积层
这一层主要是由convnet组成。在解释它做什么之前,我们先要理解convnets和全联接神经网络模型的联系。这是非常关键的思想,阐释如下:
我们一开始知道:全联接神经网络的关键是“全联接”。如果你想说是密集也是可以的。这点意味着给定层上每一个神经元与接邻层的每一个神经元都连接着。当一个多维度的数据点从一层流向下一层时,给定层的每一个神经元的活跃性取决于上一层所有神经元的权重,而这些神经元各有说法。
但是,在CNNs里面不同的是,他们并不是完全全联接。
这意味着,隐藏层的每一个神经元并不是与上一层的所有神经元联接。而是与上一层的一块小的正方区域相联。
如下图说明:
上图中,第一个隐藏层的第一个神经元(我们也称该层作特征映射表),与输入层的3 X 3的方格相连接。这个隐藏神经元仅仅依赖于这块小区域,最终通过学习,捕捉到特征。
那这个神经元的值代表着什么呢?这是称之为kernel的权重矩阵(小灰色方块)与图片中相同大小区域Receptive Field(称之为接受域)卷积后的结果值。
这个操作后面其实非常简单:两个矩阵的元素逐一相乘操作,kernel和3 X 3的图片区域。相乘后的值求和作为输出值。本例中,我们会有9个乘积求和进入第一层隐藏层。
这个神经元仅仅学习了Receptive Field的可视化模式。你可以当作它的值是呈现区域的特征强度而不是图片的特征。
那么其他的隐藏神经元呢?它们应该怎么计算?
计算第二个隐藏神经元,仅仅将kernel在输入图片上从左往右偏移一个单位长度(或者称顺移),应用相同的卷积,再执行过滤。如图所示:
现在我们就知道整个图片的每一个kernel片段应该怎么进行卷积计算存储输出放进特征映射表中。实际上,计算如下动态图所示:
上图展示了整个卷积层所做的工作:给定一个过滤区域,扫描输入图片并生成一张特征映射表:
那么这个卷积操作代表的是什么含义呢?怎么解释输出结果特征映射表?
我会通过在图片中可视化卷积层捕捉的可视化模式进行阐述,让我来为你证明解惑。
我先从数据集中下载了一张猫的图片。然后会采用不同的卷积方式,每次都改变kernel并输出各种结果的图片可视化结果。
%matplotlib inline
from scipy.signal import convolve2d
import numpy as np
import cv2
from matplotlib import pyplot as plt
image = cv2.imread('./data/train/cats/cat.46.jpg') # converting the image to grayscale
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
我先定义了一个函数,将kernel作为输入,在图片上生成卷积值,然后绘画原始图片和紧挨着展示卷积后的图片。
kernel是小的方阵(之前图中的灰色区域)
def show_differences(kernel):
convolved = convolve2d(image, kernel)
fig = plt.figure(figsize=(15, 15))
plt.subplot(121)
plt.title('Original image')
plt.axis('off')
plt.imshow(image, cmap='gray')
plt.subplot(122)
plt.title('Convolved image')
plt.axis('off')
plt.imshow(convolved, cmap='gray')
return convolved
那么,先从过滤器开始:
这称作Box blur。在输入图片上使用过滤器于一个像素点,会与相邻的8个邻点一起计算,这也是为什么矩阵都是1,然后最后会有1/9的系数。
数学上,这只是简单的取平均值。可视化上,这个结果平滑了图片上突出的差异点。
所以Box blur经常用在噪声移除中。
让我们看看应用后图片有什么变化。
kernel = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])/9
output = show_differences(kernel)
你可以看出相比于原图,卷积后的图片更加柔和,更少的突出的像素点。
让我们扩大这种效果来看看有啥变化:
kernel = np.ones((8,8), np.float32)/64
dx = show_differences(kernel)
一些过滤器能够用于抓取图片的内在细节,例如边。
这是一个是实际例子,计算出图片A的垂直变化近似值。
kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
dx = show_differences(kernel)
白色的区域是过滤器反馈后的,代表的是前图的垂直边,注意看猫的左耳,关心它的边是如何被捕捉到的。
很酷,对吧?
这里还有另外一个过滤器,能够捕捉水平的变化。
kernel = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)
dy = show_differences(kernel)
注意胡须已经被检测出来了。
前面两种过滤器属于梯度算子。在某种程度上,它们能够根据一个方向揭露图片的的内在结构。当然Gx可以和Gy结合到以下公式:
这样才能更好的检测边。
mag = np.hypot(dx, dy) # magnitude
mag *= 255.0 / np.max(mag) # normalize (Q&D)
fig = plt.figure(figsize=(15, 15))
plt.subplot(121)
plt.title('Original image')
plt.axis('off')
plt.imshow(image, cmap='gray')
plt.subplot(122)
plt.title('Convoluted image with highlighted edges')
plt.axis('off')
plt.imshow(mag, cmap='gray')
这个称为索博尔(Sobol)过滤器。这是两个简单卷机的非线性组合。我们在之后可以看到卷积层能够在非线性风格中聚合多种特征映射,因此能够检测出边的存在。
当然还有很多各种各种的过滤器。如果你感兴趣的话可以打开这个 维基百科链接)
我们现在知道了卷积神经网络中卷积层所做的事情:根据输入数据存在的可视元素生成一个卷积输出值。输出可能是降维后的大小,可以当作是输入指向一个特殊特征的集成版本。
kernel弄清楚卷积寻找的特征。它充当特征检测器的角色。我们可以认为滤器的功能是为了检测边、半圆、角等图片结构。
卷积层可以使用超过一个过滤器么?
在经典的CNN结构中,我们卷积层不会只使用一个简单的过滤器。有时候我们用10、16或者32个,有时候更多。本例中,我们每一层采用了和卷积值一样多的过滤器数量。这种思路生成了许多不同的特征映射,每个都指向了图片中的特定简单特征。采用越多的过滤器,将能够抽取出图片更多的内在特征。
请记住这些简单的抽取出来的特征在后面将会结合到一起组成探测出更复杂的模式。
过滤器权重是什么?怎么选择?
我们不会依据对数据掌握的领域知识去手动设置过滤器权重。
实践中,训练CNN的时候我们不会人工设置过滤器权重。这些值是由神经网络模型自动计算得出。你知道全联接神经网络模型是怎么通过反向传播学习这些权重的么?是的,卷积神经网络也是这样做的。
取代了每一层大规模的权重矩阵,CNN学习了过滤器权重。换句话说,这意味着神经网络先是随机生成权重,随着降低分类误差来调整这些权重,最后生成对我们感兴趣的特征合适的过滤器权重。这是一个颠覆工程师直觉的强大思想。
我发现卷积神经网络既让人印象深刻,又保持着神秘感。这里是三个神经网络学习到的第一层特征映射。(一个称为AlexNet的非常出名的例子)
可以看到,通过简单的边边角角形成了奇奇怪怪得形状特征。
权重分享
一份特征映射当且仅由一个过滤器生成,例如,所有隐藏神经元“分享”同样的权重,因为相同的过滤器生成的映射表是一样的。这也是我们所说的权重分享。这个特点让CNN能够降低学习参数数量,大幅度加快训练。
提及加速训练,权重分享的概念是由可视化模型能够在一张图片里以不同形态多次出现,因此用一个简单的过滤器从整个图片检测出来是可以的。
ReLU层
一旦一张特征映射表从卷积层中解析出来,下一步操作就是ReLU层.它通常和卷积层紧密联合,一同工作。
ReLU层应用点乘ReLu函数进行特征映射。ReLU函数将所有x轴低于0的设置为0,输出的结果称为校正的特征映射。
ReLU优势主要有两个:
引进了非线性模型技术。事实上,所有算子都迥然不同:卷积、元素矩阵乘、累积和等等都是线性的,如果我们没有非线性的,那么根线性模型没有区别, 也无法奏效。
通过避免了梯度消失问题加速了训练过程。
下图展示了一个图片例子中ReLU所做的事情:
池化层
校正的特征映射会进入池化层。池化是一个降样本量操作,降低了特征映射的维度。
最常见的池化操作是最大池化。它涉及一个小的窗口(通常2X2),每次两个单位移动,每一步中提取该窗口中最大的数值。
例如10X10的校正后的特征映射表将会转化为5X5的输出。
最大池化有以下的优点:
降低了校正特征映射表的大小和可训练参数量,因此控制过拟合现象。
通过保留最重要特征来凝缩特征映射表
使得神经网络不变地进行小变换、变体和翻译(对输入的小的变换不会改变输出的池化-因此取了里面的最大值)
最大池化背后的思想并不直观。根据我的理解,总结如下:
如果特征(一条边)被识别出来了,在图片一个小的部分,例如2X2的红色区域内,我们并不关心具体是哪一个像素点让特征出现。反而,我们选择这个部分里面最大的值,并假设该像素代表了可视特征。这个方法似乎值得质疑,它丢失了空间信息。但事实上,这真的非常快捷并且有效。你其实并不需要介意4X4的图片例子。当最大池化应用于高度相关的图片,主要的空间信息忍让保留。只有无关紧要的细节会被忽略。这也是为什么最大池化能够防止过拟合。它让神经网络更加关注于图片里的相关联信息。
全联接层
CNN也有全联接层,与经典的全联接网络模型一致。通常它处于整个网络模型的最后一层,将向量完整地全联接到输出层上,对应着预测向量结果(大小是分类的种数)。
如果你还记得之前的各个层次:输入层-> 卷积层-> ReLU层->最大池化,现在增加全联接层到输出层,你就得到了完整的神经网络概略图。
如下所示:
注意拓平层只是池化层之前的一个向量版本。
为什么我们还需要全联接层呢?
全联接层的功能是进行分类任务,而前面的层的任务是特征提取的工作。
全联接层采用了卷积层、纠正和池化所凝聚和规整的结果,并集合起来,组合并完成分类工作。
脱离分类而言,增加全联接层也是一种学习这些特征非线性组合的一种方式。大部分从卷积层和池化层获取的特征都有益于分类任务,但是将这些特征组合起来会效果更佳。
可以把全联接层作为抽象水平层加进神经网络。
总结
让我们概括下至今我们学到的各个层的功能:
层 | 功能 |
---|---|
输入层 | 将图片输入并保留空间结构 |
卷积层 | 从输入层中抽取出特征映射表,每个反馈对应到特殊的模式 |
ReLU层 | 通过将负值设置为0阐释非线性 |
最大池化层 | 降低校正特征映射表的样本量,因此降低空间维度,保留重要特征,防止过拟合 |
全联接层 | 学习特征的非线性组合并完成分类任务 |
在经典的CNN结构中,每一类中不会只有一层。
事实上,如果你思考一个有两个成功的卷积、池化层的神经网络,核心的思想是,第二层卷积层凝缩了图片,抽取了图片中的特性的存在是否。所以你可以把第二层当作图片的另外一版本的输入层,是抽象和概括后的一版,并且仍然有许多空间结构。
我们同层也有2~3层全联接层。这允许我们学习出特征的很多非线性组合,最后作用于分类任务。
可以引用Andrej Karpathy的观点:
卷积神经网络架构最常见的事一些卷积-ReLU层,在池化层中采用这些,重复模型直至图片已经合并足够小的有空间结构的大小。某种观点上说,这是到全联接层自然而然的过度。最后的全联接层直接联系输出层,例如分类得分。换句话说,最常见的卷积神经网络依据下列模式:
INPUT->[[CONV->RELU]N->POOL]M->[FC->RELU]*K->FC
现在我们了解了卷积神经网络的所有框架,让我们看一些经典的神经网络。
LeNet5
下面是最流行的卷积神经网络,是由LeCun在1998年设计出来的,称作LeNet5
我们来详细了解这个神经网络结构,我鼓励你先自己尝试总结一下:
输出层:一个32X32 灰度的图片(一个颜色位)
卷积层1:在图片上应用了6个不同的5X5过滤器,产生了6个28X28的特征映射表(激活函数直接应用于这一层6个特征映射表,而不是之后采用ReLU层)
池化层将前面的6个28X28特征映射表生成6个14X14的池化后特征映射表(缩减了1/4)
卷积层2:应用16个不同的5X5过滤器在6个14X14的池化后特征映射上。生成了16个10X10的特征映射表。这里面每一个代表了6个第一遍过滤器和6个输入之间卷积层结果的总和。
基于16个10X10的特征映射表的池化层生成16个5X5的池化后映射。
第一层全联接层包括120个神经元。每一个神经元连接着16个5X5的特征映射表,这一层需要学习16X5X5X120=48000个学习权重
第二层全联接层包括84个神经元。这一层是前面的全联接层。有120X84=10080个学习权重
全联接层连向输出层。84X10=840个学习权重。
更多先进的CNN结构
如果你对其他复杂的卷积神经网络结构感兴趣,你可以通过这个博客了解更多。
下面是2012年有名的ImageNet竞赛中的神经网络(AlexNet)
三、配置深度学习环境
深度学习的计算量非常沉重。你将会在你的笔记本上布置第一个模型后了解到这一点。
不管怎样,如果你使用GPU,你能够极快地加速这一训练过程。因为GPU能够有效地执行并行化任务,例如矩阵乘法。因为神经网络计算全部与矩阵乘法相关,所以效果非常明显。
我的笔记本上没有强力的GPU。所以我在AWS上使用了虚拟机器。称为p2.xlarge,是亚马逊EC2服务。有12G的英伟达GPU,61GB的RAM,4vCPU和2496 CUDA核。非常强大,每个小时花费90美分。
当然也有其他强大的服务,但给定的任务我们使用p2.xlarge已经足够使用了。
我从深度学习AMI CUDA 8 Ubuntu版本开始这个服务。这里可以了解更多内容。
基于Ubuntu16.04的服务器,能够支持所有深度学习框架(Tensorflow,Theano,Caffe,Keras)的需求和GPU驱动器(安装是一个噩梦)。
这是非常强大的:AWS提供有效的服务,已经准备好可以使用的深度学习可迁移环境,让你能够足够快的搭建自己的项目。
如果你对AWS不熟悉,你可以看一下两个链接:
https://blog.keras.io/running-jupyter-notebooks-on-gpu-on-aws-a-starter-guide.html
https://hackernoon.com/keras-with-gpu-on-amazon-ec2-a-step-by-step-instruction-4f90364e49ac
能够帮助你开启: 1. 搭建EC2虚拟机并连接 2. 配置网络安全允许你的jupyter notebook远程连接。
四、采用TensorFlow和Keras搭建一个猫/狗分类器
环境现在已经安装好了。我们准备开始我们的实践例子。我们将会学习构建一个卷积神经网络模型来区分猫和狗。
我们将会使用TensorFlow深度学习框架和Keras。
Keras是一个高度抽象水平的神经网络模型API,由Python写,能够在TensorFlow、CNTK或者Theano中运行,它专注于赋能更快的体验而研发。能够从想法最小化延迟可能获得结果是做好实验的关键。
4-1 从零开始构建一个卷积神经网络模型
在第一节里,我们会安装一个端到端的pipeline来训练CNN。我们会经过数据预处理和扩大、结构设计、训练和验证。我们在训练集和验证集上会画出loss和准确矩阵,这个可以让我们了解模型在训练过程中的提升。
数据预处理
我们想要做的是从kaggle上下载和解压训练集。
%matplotlib inline
from matplotlib import pyplot as plt
from PIL import Image
import numpy as np
import os
import cv2
from tqdm import tqdm_notebook
from random import shuffle
import shutil
import pandas as pd
我们现在需要组织数据,这个用keras很容易处理。
我们创建了在数据文件夹下面创建两个子文件夹:
训练集
校验集
每一个里面会有两个文件夹:
猫
狗
因此我们会有下面的目录结构
data/
train/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
...
validation/
dogs/
dog001.jpg
dog002.jpg
...
cats/
cat001.jpg
cat002.jpg
这样的结构能够让我们了解清楚我们获得的数据的标签。
这里有个函数能够允许你构建这个文件树。这里有两个参数:图片总量n和校验集比例r.
def organize_datasets(path_to_data, n=4000, ratio=0.2):
files = os.listdir(path_to_data)
files = [os.path.join(path_to_data, f) for f in files]
shuffle(files)
files = files[:n]
n = int(len(files) * ratio)
val, train = files[:n], files[n:]
shutil.rmtree('./data/')
print('/data/ removed')
for c in ['dogs', 'cats']:
os.makedirs('./data/train/{0}/'.format(c))
os.makedirs('./data/validation/{0}/'.format(c))
print('folders created !')
for t in tqdm_notebook(train):
if 'cat' in t:
shutil.copy2(t, os.path.join('.', 'data', 'train', 'cats'))
else:
shutil.copy2(t, os.path.join('.', 'data', 'train', 'dogs'))
for v in tqdm_notebook(val):
if 'cat' in v:
shutil.copy2(v, os.path.join('.', 'data', 'validation', 'cats'))
else:
shutil.copy2(v, os.path.join('.', 'data', 'validation', 'dogs'))
print('Data copied!')
我使用的是
n:25000(全量)
r:0.2
ratio = 0.2
n = 25000
organize_datasets(path_to_data='./train/', n=n, ratio=ratio)
让我们加载keras和它的相关依赖:
import keras
from keras.preprocessing.image import ImageDataGenerator
from keras_tqdm import TQDMNotebookCallback
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten
from keras.constraints import maxnorm
from keras.optimizers import SGD
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.utils import np_utils
from keras.callbacks import Callback
图像生成器和数据扩大
当我们训练模型时,我们不会加载所有的图片数据放进内存,这样并不有效,特别是当你使用本地的机器时。
我们将会使用ImageDataGenerator类。它能够让你从训练集和验证集文件夹的图片数据变成批次流。每一批次流进神经网络中,生成一份forward-prop、一份back-prop,之后更新参数,然后下一批次的数据执行同样的操作,以此循环。
在ImageDataGenerator对象里,我们打算用随机模块化处理每个批次数据。这个过程称之为数据扩大。能够生成更多的数据,并且我们的模型不会看到一摸一样的图片。这能够帮助我们防止过拟合,使得模型更优。
我们会创建两个ImageDataGeneratro对象。
train_datagen是为了训练集数据,val_datagen 是为了验证集数据。这两个对象会应用于图片的重扩展,只有 train_datagen 会引入更多模块化。
batch_size = 32
train_datagen = ImageDataGenerator(rescale=1/255.,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True
)
val_datagen = ImageDataGenerator(rescale=1/255.)
从前面两个对象中我们生成了两个文件生成器:
train_generator
validatation_generator
每一个生成器,直接对应目录,按批次实时进行图片数据的数据扩大。数据将会(批次里)无限循环扩展,直至满足数量。
train_generator = train_datagen.flow_from_directory(
'./data/train/',
target_size=(150, 150),
batch_size=batch_size,
class_mode='categorical')
validation_generator = val_datagen.flow_from_directory(
'./data/validation/',
target_size=(150, 150),
batch_size=batch_size,
class_mode='categorical')
发现20000张图片属于2分类 发现5000张图片属于2分类
模型结构
我将会使用三层卷积/池化结构和两层全联接层。
三层卷积层将会采用 32,32和 64 3X3的过滤器。
我采用了两层全联接层来放置过拟合:
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(150, 150, 3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, (3, 3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax'))
我适用随机梯度下降进行优化,设置学习率为0.01,动量为0.9。
因为我们是一个二分类问题,所以我使用了二分类交叉熵Loss函数
epochs = 50
lrate = 0.01
decay = lrate/epochs
sgd = SGD(lr=lrate, momentum=0.9, decay=decay, nesterov=False)
model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
kereas提供便捷的方法展示模型的概要。对于每一层来说,会展示输出形式和训练的参数数量。
这是我们在调优模型前需要做的检查。
model.summary()
让我们看看网络模型的结构:
可视化模型结构:
训练模型
在开始模型训练前,我定义了两个callback函数,将会在训练的过程中调用。
一个是为了在训练过程中如果loss函数在验证集上停止提升效果时提前终止。
一个是为了保存验证集loss和准确率,能够画出训练错误图。
class LossHistory(Callback):## Callback for loss logging per epoch
def on_train_begin(self, logs={}):
self.losses = []
self.val_losses = []
def on_epoch_end(self, batch, logs={}):
self.losses.append(logs.get('loss'))
self.val_losses.append(logs.get('val_loss'))
history = LossHistory()
early_stopping = keras.callbacks.EarlyStopping(monitor='val_loss',
min_delta=0,
patience=2,
verbose=0, mode='auto')## Callback for early stopping the training
我也使用了keras-tqdm,是keras里面一个非常有效的处理进度条。
能够让你轻易地监控你的模型训练过程。
当你需要时仅仅从keras_tqdm加载TQDMNotebookCallback类,把它当作第三个callback函数。
这是keras-tqdm的效果展示图:
一些关于训练的:
我们会使用fit_generator方法,是一个不变的标准训练方法。
我们会训练超过50轮迭代模型:每一次迭代中20000张不同的扩展图片会按32/批次进入网络模型,生成向前传播和向后传播,用SGD调节权重,这种常见的多迭代是用来防止过拟合。
fitted_model = model.fit_generator(
train_generator,
steps_per_epoch= int(n * (1-ratio)) // batch_size,
epochs=50,
validation_data=validation_generator,
validation_steps= int(n * ratio) // batch_size,
callbacks=[TQDMNotebookCallback(leave_inner=True, leave_outer=True), early_stopping, history],
verbose=0)
这是代价非常高的计算量
如果你是在自己的笔记本上计算,那么每一轮需要花费15分钟
如果你采用和我一样的p2.xlarge,那么每一轮只需要大约2分钟
tqdm能够让你监控验证集的每一轮loss和准确率。能够有效验证你的模型质量。
分类结果
我们达到了 89.4%准确率(在校验集中)34轮迭代.(训练/验证集误差和准确率会在下面展示)
这是非常好的结果,在没有花费太多时间设计结构的情况下。
我们可以保存我们的模型。
model.save('./models/model4.h5')
让我们画出训练集和校验集loss图:
losses, val_losses = history.losses, history.val_losses
fig = plt.figure(figsize=(15, 5))
plt.plot(fitted_model.history['loss'], 'g', label="train losses")
plt.plot(fitted_model.history['val_loss'], 'r', label="val losses")
plt.grid(True)
plt.title('Training loss vs. Validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
当验证集loss在两轮迭代中没有提升的话,我们会停止训练。
让我们画出两个集合中的准确率:
losses, val_losses = history.losses, history.val_losses
fig = plt.figure(figsize=(15, 5))
plt.plot(fitted_model.history['acc'], 'g', label="accuracy on train set")
plt.plot(fitted_model.history['val_acc'], 'r', label="accuracy on validation set")
plt.grid(True)
plt.title('Training Accuracy vs. Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
两个图片保持增加趋势,直至模型稳定,34轮后甚至开始过拟合。
4-2 加载预训练模型
太好了:我们设计了一个经典的卷积神经网络模型,效果达到了约89%准确率。
还有更好的方式得到更高的分数:加载在大的数据集上预训练卷积神经网络模型的参数,包括猫和狗等1000类分类的图片。这样的一个神经网络能够学习我们分类任务的相关性特征。
我加载了VGG16神经网络的权重:特别地,我将会加载斤最后一层卷积层里。神经网络部分起着特征检测器的作用,我们打算增加全联接层。
对比与LeNet5,VGG16是非常大的神经网络模型。有16层的训练权重和大约14000万参数。可以通过这份[PDF](https://arxiv.org/pdf/1409.1556.pdf)来了解VGG16。
我们先加载了VGG16权重(在ImageNet训练),我们并不关心最后三层全联接层。
from keras import applications
# include_top: whether to include the 3 fully-connected layers at the top of the network.
model = applications.VGG16(include_top=False, weights='imagenet')
datagen = ImageDataGenerator(rescale=1. / 255)
现在我们通过神经网络中得到了特征代表,将会输入到神经网络分类器中。
我们在训练集和测试集中这样做:
generator = datagen.flow_from_directory('./data/train/', target_size=(150, 150),
batch_size=batch_size,
class_mode=None,
shuffle=False)
bottleneck_features_train = model.predict_generator(generator, int(n * (1 - ratio)) // batch_size)
np.save(open('./features/bottleneck_features_train.npy', 'wb'), bottleneck_features_train)
发现20000图片属于分类2
generator = datagen.flow_from_directory('./data/validation/',
target_size=(150, 150),
batch_size=batch_size,
class_mode=None,
shuffle=False)
bottleneck_features_validation = model.predict_generator(generator, int(n * ratio) // batch_size,)
np.save('./features/bottleneck_features_validation.npy', bottleneck_features_validation)
发现5000张图片属于分类2
当图片输入神经网络时按照正确的顺序,这样能够联系标签。
train_data = np.load('./features/bottleneck_features_train.npy')
train_labels = np.array([0] * (int((1-ratio) * n) // 2) + [1] * (int((1 - ratio) * n) // 2))
validation_data = np.load('./features/bottleneck_features_validation.npy')
validation_labels = np.array([0] * (int(ratio * n) // 2) + [1] * (int(ratio * n) // 2))
现在我们设计了一个小的全联接网络模型,应用于VGG16的特征抽取,能够作为CNN的分类器部分。
model = Sequential()
model.add(Flatten(input_shape=train_data.shape[1:]))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy', metrics=['accuracy'])https://static.licdn.com/scds/common/u/images/promo/ads/ad_17x700_transparent.png
fitted_model = model.fit(train_data, train_labels,
epochs=15,
batch_size=batch_size,
validation_data=(validation_data, validation_labels[:validation_data.shape[0]]),
verbose=0,
callbacks=[TQDMNotebookCallback(leave_inner=True, leave_outer=False), history])
我们仅仅通过15轮迭代就达到了90.7%的准确率,不错。
每一轮在我个人笔记本电脑上花费约1分钟。
fig = plt.figure(figsize=(15, 5))
plt.plot(fitted_model.history['loss'], 'g', label="train losses")
plt.plot(fitted_model.history['val_loss'], 'r', label="val losses")
plt.grid(True)
plt.title('Training loss vs. Validation loss - VGG16')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
fig = plt.figure(figsize=(15, 5))
plt.plot(fitted_model.history['acc'], 'g', label="accuracy on train set")
plt.plot(fitted_model.history['val_acc'], 'r', label="accuracy on validation sete")
plt.grid(True)
plt.title('Training Accuracy vs. Validation Accuracy - VGG16')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
许多深度学习先锋者鼓励我们采用预训练神经网络来进行分类任务。事实上,这通常平衡于大数据上的大规模网络模型训练。
keras允许你很容易地采用预训练模型,如VGG16、GoogleNet和ResNet。更多信息可见[这里](https://keras.io/applications/)
格言说到:
不要成为英雄,不要重造轮子,是用预训练网络模型!
我们这里收获了什么?
如果你感兴趣优化经典CNN:
在数据集角度上,可以了解更多数据扩展
玩一下参数调节:卷积层数量、过滤器数量、过滤器大小。用验证集测试每一个线性组合。
调节优化器,或则改变它。
尝试不同代价函数
从用更多全联接层
了解更多的创意设计
如果你感兴趣预训练神经网络模型得到更好的结果:
采用不同的神经网络结构
在更多的隐藏单元里采用全联接层
如果你想了解CNN模型学习到的:
可视化特征映射表,我还没有完成,这里有一份有趣的论文最对于这个主题。
如果你想使用训练模型:
装载进网页APP里面并使用在新的猫/狗图片中。这是很好的实践。
总结
本文是一个纵览卷积神经网络模型的一次学习入口,解释了主要成分的一些深入细节。
也是一份搭建AWS深度学习环境的指导手册,能够从头开始开发上端到端的模型,也可以基于预训练开始。
使用python进行深度学习非常有趣。keras使得处理数据和构建层更容易。记住如果有一天你想有构建经典的神经网络组件,你需要换其他的框架。
我希望能够从这里得到一些关于卷积神经网络机制的启发,有更大的兴趣去学习更多的知识。
卷机神经网络非常棒!效果出奇,任何计算机视觉工作者相信其稳定性和有效性。
这些应用有更多的应用空间。NLP也开始切换到使用神经网络模型解决问题。这里是他们的应用:
CNN文本分类器
图片自动标题,image+Text
特征级别的文本分类
引用
这里是我学习神经网络模型和卷积神经网络的资源清单:
neuralnetworksanddeeplearning.com : By far the best notes on neural networks and deep learning. I highly recommend this website to anyone who want to start learning about neural nets.
CS231n Convolutional Neural Networks for Visual Recognition: Andrej Karpathy's lectures at Stanford. Great mathematical focus.
A Beginner Guide to Understanding Neural Networks: A three-part post explaining CNNs, from the basic high level inuition to the architecture details. Very interesting read. Learnt a lot from it.
Running jupyter notebooks on GPU on AWS
Building powerful image classification models using very little data
CatdogNet - Keras Convnet Starter
A quick introduction to Neural Networks
An Intuitive Explanation of Convolutional Neural Networks
Visualizing parts of Convolutional Neural Networks using Keras and Cats
打赏:
以上是关于实践出真知:通过TensorFlow和Keras实践例子来理解深度CNN的主要内容,如果未能解决你的问题,请参考以下文章
Keras还是TensorFlow?深度学习框架选型实操分享