视觉系统的深度学习(一)
两年前,我决定写一本书,从直观的角度教授计算机视觉的深度学习。我的目标是开发一个全面资源,将学习者从仅了解机器学习基础知识带到构建他们可以应用于解决复杂计算机视觉问题的先进深度学习算法。问题: 简而言之,到目前为止,还没有任何书籍以我想要学习计算机视觉深度学习的方式教授。作为一名初学者机器学习工程师,我希望读一本书,从 A 点到 Z 点都能涵盖。
原文:Deep Learning for Vision Systems
译者:飞龙
前置内容
前言
两年前,我决定写一本书,从直观的角度教授计算机视觉的深度学习。我的目标是开发一个全面资源,将学习者从仅了解机器学习基础知识带到构建他们可以应用于解决复杂计算机视觉问题的先进深度学习算法。
问题 : 简而言之,到目前为止,还没有任何书籍以我想要学习计算机视觉深度学习的方式教授。作为一名初学者机器学习工程师,我希望读一本书,从 A 点到 Z 点都能涵盖。我计划专门从事构建现代计算机视觉应用,并希望有一个单一的资源可以教会我完成以下两件事:1)使用神经网络构建端到端的计算机视觉应用,2)能够舒适地阅读和实施研究论文,以跟上最新的行业进展。
我发现自己需要在在线课程、博客、论文和 YouTube 视频之间跳转,以为自己创建一个全面的课程。试图在更深的层次上理解底层发生的事情是具有挑战性的:不仅是一个基本理解,还包括概念和理论在数学上的意义。很难找到一本全面资源,它(在水平方向上)涵盖了我在处理复杂计算机视觉应用时需要学习的最重要主题,同时也足够深入(在垂直方向上)以帮助我理解使魔法生效的数学原理。
作为一名初学者,我搜索了但找不到任何能满足这些需求的东西。所以现在我已经写了它。我的目标不仅是写一本在我开始时想要学习的内容的书,而且还要提高你自学的能力。
我的解决方案是一本全面深入的书:
-
水平方向 – 这本书解释了工程师需要学习的大部分主题,以构建生产就绪的计算机视觉应用,从神经网络及其工作原理到不同类型的神经网络架构以及如何训练、评估和调整网络。
-
垂直方向 – 这本书比代码深入一层或两层,直观(且温和)地解释了底层的数学原理,以使你能够舒适地阅读和实施研究论文,甚至发明你自己的技术。
在写作的时候,我认为这是唯一一种以这种方式教授视觉系统深度学习的资源。无论你是想找一份计算机视觉工程师的工作,想要更深入地理解计算机视觉中的高级神经网络算法,还是想要构建你的产品或初创公司,我写这本书是考虑到你的。我希望你喜欢它。
致谢
这本书工作量很大。不,这样说更准确:工作量真的很大!但我希望你会觉得它很有价值。有很多人在我写作过程中给予了我帮助。
我要感谢 Manning 出版社的同事们,是你们让这本书成为可能:出版人 Marjan Bace 以及编辑和制作团队的所有人,包括 Jennifer Stout、Tiffany Taylor、Lori Weidert、Katie Tennant 以及许多幕后工作的同事们。
我要向由 Alain Couniot 领导的同行评审团队表示衷心的感谢——Al Krinker、Albert Choy、Alessandro Campeis、Bojan Djurkovic、Burhan ul haq、David Fombella Pombal、Ishan Khurana、Ita Cirovic Donev、Jason Coleman、Juan Gabriel Bono、Juan José Durillo Barrionuevo、Michele Adduci、Millad Dagdoni、Peter Hraber、Richard Vaughan、Rohit Agarwal、Tony Holdroyd、Tymoteusz Wolodzko 和 Will Fuger——以及那些在论坛上积极提供反馈的读者们。他们的贡献包括纠正错别字、代码错误和技术错误,以及提出有价值的主题建议。每次通过评审流程和每次通过论坛主题实施的反馈都塑造和塑造了这本书的最终版本。
最后,我要感谢整个 Synapse Technology 团队。你们创造了一些非常酷的东西。感谢 Simanta Guatam、Aleksandr Patsekin、Jay Patel 以及其他人回答我的问题,并为这本书出谋划策。
关于这本书
适合阅读这本书的人
如果你熟悉基本的机器学习框架,能够在 Python 中编程,并且想要学习如何构建和训练高级、生产就绪的神经网络来解决复杂的计算机视觉问题,这本书是为你写的。这本书是为那些具有中级 Python 经验和基本机器学习理解,希望探索深度神经网络训练并学习如何将深度学习应用于解决计算机视觉问题的读者所写。
当我开始写这本书时,我的主要目标是这样的:“我想写一本书来提升读者的技能,而不是仅仅传授他们内容。”为了实现这个目标,我必须关注两个主要原则:
-
教你如何学习。我不想读一本只是罗列科学事实的书。我可以在互联网上免费获取这些信息。如果我读一本书,我希望读完之后我的技能有所增长,这样我就可以进一步研究这个主题。我想学习如何思考所提出的解决方案,并提出自己的见解。
-
深入探究。如果我成功地满足了第一个原则,那么这个原则就变得容易了。如果你学会了如何学习新概念,那么我就能够深入挖掘,而不必担心你可能落后。这本书不会回避学习的数学部分,因为理解数学方程式将赋予你在人工智能世界中最优秀的技能:阅读研究论文、比较创新以及在你自己的问题中实施新概念的正确决策能力。但我承诺只介绍你需要的数学概念,并承诺以一种不会打断你理解概念(如果你更喜欢没有数学部分)的方式呈现它们。
本书是如何组织的:路线图
本书分为三个部分。第一部分详细解释深度学习,作为剩余主题的基础。我强烈建议你不要跳过这一部分,因为它深入探讨了神经网络组件和定义,并解释了理解神经网络内部工作所需的所有概念。阅读第一部分后,你可以直接跳到剩余章节中感兴趣的主题。第二部分解释深度学习技术来解决对象分类和检测问题,第三部分解释深度学习技术来生成图像和视觉嵌入。在几个章节中,实际项目实现了所讨论的主题。
关于代码
本书的所有代码示例都使用免费下载的开源框架。我们将使用 Python、Tensorflow、Keras 和 OpenCV。附录 A 将指导你完成完整的设置。我还建议如果你想在你的机器上运行本书的项目,最好能访问到一个 GPU,因为第六章到第十章包含更复杂的项目,用于训练深度网络,这些项目在普通 CPU 上需要很长时间。另一个选择是使用像 Google Colab 这样的免费或付费的云环境。
源代码示例既出现在编号列表中,也出现在正常文本中。在两种情况下,源代码都以fixed-width font like this这样的固定宽度字体格式化,以将其与普通文本区分开来。有时代码也会被**in** **bold**加粗,以突出显示与章节中先前步骤不同的代码,例如当新功能添加到现有代码行时。
在许多情况下,原始源代码已经被重新格式化;我们添加了换行符并重新调整了缩进,以适应书籍中可用的页面空间。在极少数情况下,即使这样也不够,列表中还包括了行续接标记(➥)。此外,当代码在文本中描述时,源代码中的注释通常也会从列表中移除。许多列表旁边都有代码注释,突出显示重要概念。
本书示例的代码可以从 Manning 网站 www.manning.com/books/deep-learning-for-vision-systems 和 GitHub github.com/moelgendy/deep_learning_for_vision_systems 下载。
liveBook 讨论论坛
购买《视觉系统深度学习》包括免费访问由 Manning Publications 运营的私人网络论坛,您可以在论坛上对本书发表评论、提出技术问题,并从作者和其他用户那里获得帮助。要访问论坛,请访问 livebook.manning.com/#!/book/deep-learning-for-vision-systems/discussion。您还可以在 livebook.manning.com/#!/discussion 了解更多关于 Manning 论坛和行为准则的信息。
Manning 对读者的承诺是提供一个平台,让读者之间以及读者与作者之间可以进行有意义的对话。这并不是对作者参与特定数量活动的承诺,作者在论坛上的贡献仍然是自愿的(且未付费)。我们建议您尝试向作者提出一些挑战性的问题,以免他的兴趣转移!只要本书有售,论坛和先前讨论的存档将可通过出版社网站访问。
关于作者
穆罕默德·埃尔根迪是 Rakuten 工程副总裁,在那里他领导着其 AI 平台和产品的开发。此前,他曾在 Synapse Technology 担任工程主管,构建专有计算机视觉应用程序,以在全球安全检查站检测威胁。在亚马逊,穆罕默德建立了中央 AI 团队,为 AWS 和 Amazon Go 等亚马逊工程团队提供深度学习智库服务。他还开发了亚马逊机器大学计算机视觉深度学习课程。穆罕默德经常在亚马逊 DevCon、O’Reilly 的 AI 会议和谷歌的 I/O 等人工智能会议上发表演讲。
关于封面插图
《视觉系统深度学习》封面上的插图描绘了伊本·海瑟姆,一位阿拉伯数学家、天文学家和物理学家,他因对光学和视觉感知原理的重大贡献而常被称为“现代光学之父”。该插图是根据约翰内斯·海维利乌斯作品《月相学》十五世纪版扉页修改的。
在他的著作《光学之书》(Kitab al-Manazir)中,伊本·海瑟姆是第一个解释视觉发生时,光线从物体反射然后进入眼睛的现象的人。他也是第一个证明视觉发生在大脑而不是眼睛中的人——许多这些概念都是现代视觉系统的核心。当你阅读这本书的第一章时,你会看到这种关联。
在这个领域工作和创新的过程中,伊本·海瑟姆对我来说是一个巨大的灵感来源。通过在这本书的封面上纪念他的记忆,我希望激励同行从业者,我们的工作可以存活并激励他人数千年。
第一部分. 深度学习基础
计算机视觉是一个技术领域,得益于过去几年在人工智能和深度学习方面取得的巨大进步而迅速发展。神经网络现在帮助自动驾驶汽车在周围导航,避开其他车辆、行人和其他障碍物;推荐代理在建议类似其他产品的产品方面变得越来越聪明。面部识别技术也在变得更加复杂,使得智能手机在解锁手机或门之前能够识别面部。像这些以及其他计算机视觉应用已经成为我们日常生活中的必备品。然而,通过超越简单对象的识别,深度学习赋予了计算机想象和创造新事物的能力,如之前不存在过的艺术、新的人类面孔和其他物体。本书的第一部分探讨了深度学习的基础、不同形式的神经网络以及通过超参数调整等概念进一步发展的结构化项目。
1 欢迎来到计算机视觉
本章涵盖
-
视觉系统组件
-
计算机视觉应用
-
理解计算机视觉流程
-
预处理图像和提取特征
-
使用分类学习算法
嘿!我非常激动你能在这里。你做出了一个伟大的决定–掌握深度学习(DL)和计算机视觉(CV)。时机再合适不过了。得益于近年来人工智能和深度学习的巨大进步,CV 领域正在快速发展。神经网络现在使得自动驾驶汽车能够确定其他车辆和行人的位置,并绕过它们。我们越来越多地在家中的智能设备中使用 CV 应用程序–从安全摄像头到门锁。CV 还使面部识别工作得比以往任何时候都要好:智能手机可以识别面部以解锁,智能锁可以解锁门。如果在未来某个时候,你的沙发或电视能够识别你家中特定的人并根据他们的个人喜好做出反应,我并不会感到惊讶。这不仅仅是识别物体–DL 已经赋予了计算机想象和创造新事物(如艺术品;新物体;甚至独特、逼真的面部)的能力。
我对深度学习在计算机视觉中的兴奋,以及是什么吸引我进入这个领域的主要原因,是 AI 研究的快速进步正在使每天都能在不同行业中构建新的应用,这在几年前是不可能的。CV 研究的无限可能性激发了我写这本书的灵感。通过学习这些工具,也许你将能够发明新的产品和应用。即使你最终没有从事 CV 工作,你也会发现这本书中的许多概念对你的某些 DL 算法和架构非常有用。那是因为虽然主要关注 CV 应用,但本书涵盖了最重要的 DL 架构,如人工神经网络(ANNs)、卷积网络(CNNs)、生成对抗网络(GANs)、迁移学习以及更多,这些可以转移到其他领域,如自然语言处理(NLP)和语音用户界面(VUIs)。
本章的高级布局如下:
-
计算机视觉直觉 – 我们将从视觉感知直觉开始,学习人类与机器视觉系统之间的相似性。我们将探讨视觉系统有两个主要组件:一个感知设备和解释设备。每个设备都针对完成特定任务而定制。
-
计算机视觉应用 – 在这里,我们将从宏观角度审视不同计算机视觉应用中使用的深度学习算法。然后,我们将讨论不同生物的视觉问题。
-
计算机视觉流程–最后,我们将聚焦于视觉系统的第二个组成部分:解释设备。我们将逐步讲解视觉系统在处理和解析图像数据时所采取的步骤序列。这些步骤被称为计算机视觉流程。CV 流程由四个主要步骤组成:图像输入、图像预处理、特征提取以及用于解释图像的机器学习模型。我们将讨论图像形成以及计算机是如何“看”图像的。然后,我们将快速回顾图像处理技术和特征提取。
准备好了吗?让我们开始吧!
1.1 计算机视觉
任何人工智能系统的核心概念是它能够感知其环境并根据其感知采取行动。计算机视觉关注的是视觉感知部分:它是通过构建世界的物理模型,使人工智能系统能够采取适当的行动,通过图像和视频感知和理解世界的一门科学。对于人类来说,视觉只是感知的一个方面。我们通过视觉感知世界,但也通过声音、气味以及我们的其他感官。对于人工智能系统来说也是如此–视觉只是理解世界的一种方式。根据你正在构建的应用,你选择最能捕捉世界的传感器。
1.1.1 视觉感知是什么?
视觉感知在最基本的意义上是通过视觉或视觉输入观察模式和对象的行为。例如,对于自动驾驶汽车来说,视觉感知意味着理解周围的对象及其具体细节–例如行人,或者车辆是否需要保持在特定车道中央–以及检测交通标志并理解其含义。这就是为什么“感知”这个词是定义的一部分。我们不仅仅是想要捕捉周围的环境。我们试图构建能够通过视觉输入真正理解该环境的系统。
1.1.2 视觉系统
在过去的几十年里,传统的图像处理技术被认为是计算机视觉系统,但这并不完全准确。一个处理图像的机器与理解图像中发生的事情的机器完全不同,这并不是一个简单任务。现在,图像处理只是更大、更复杂系统的一部分,该系统旨在解释图像内容。
人类视觉系统
在最高层面上,视觉系统对于人类、动物、昆虫以及大多数生物体来说几乎是相同的。它们由一个传感器或眼睛来捕捉图像,以及一个大脑来处理和解释图像。系统随后根据从图像中提取的数据输出对图像组件的预测(图 1.1)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-1.png
图 1.1 人类视觉系统利用眼睛和大脑来感知和解释图像。
让我们看看人类视觉系统是如何工作的。假设我们想要解释图 1.1 中的狗的图像。我们看它,并直接理解图像由一群狗(具体来说是三只)组成。对我们来说,在这个图像中分类和检测物体是非常自然的,因为我们多年来一直在接受识别狗的训练。
假设有人第一次给你看一张狗的图片–你肯定不知道这是什么。然后他们告诉你这是一只狗。经过几次这样的实验后,你将接受训练来识别狗。现在,在接下来的练习中,他们给你看一张马的图片。当你看这张图片时,你的大脑开始分析物体特征:嗯嗯,它有四条腿,长脸,长耳朵。它可能是一只狗吗?“错误:这是一匹马,”你被告知。然后你的大脑在其算法中调整了一些参数来学习狗和马之间的差异。恭喜!你刚刚训练了你的大脑来分类狗和马。你能把更多的动物加到等式中,比如猫、老虎、猎豹等等吗?当然可以。你可以训练你的大脑来识别几乎任何东西。对计算机来说也是如此。你可以训练机器来学习和识别物体,但人类比机器更直观。你只需要几幅图像就能学会识别大多数物体,而机器则需要成千上万,甚至在更复杂的情况下,数百万个图像样本来学习识别物体。
机器学习的视角
让我们从机器学习的角度来回顾之前的例子:
-
你通过观察几个标注为狗的图像的例子来学习识别狗。这种方法被称为监督学习。
-
标注数据是指你已经知道目标答案的数据。你被展示了一幅狗的样本图像,并被告知这是一只狗。你的大脑学会了将你看到的特征与这个标签“狗”相关联。
-
然后,你被展示了一个不同的物体,一匹马,并被要求识别它。起初,你的大脑认为它是一只狗,因为你之前没有见过马,你的大脑将马的特征与狗的特征混淆了。当你被告知你的预测是错误的时,你的大脑调整了它的参数来学习马的特征。“是的,两者都有四条腿,但马的腿更长。更长的腿意味着这是一匹马。”我们可以多次运行这个实验,直到大脑不再犯错误。这被称为通过试错进行训练。
人工智能视觉系统
科学家们受到人类视觉系统的启发,近年来在用机器复制视觉能力方面做了惊人的工作。为了模仿人类视觉系统,我们需要相同的两个主要组件:一个感知设备来模仿眼睛的功能,以及一个强大的算法来模仿大脑在解释和分类图像内容时的功能(图 1.2)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-2.png
图 1.2 计算机视觉系统的组成部分包括一个感知设备和解释设备。
1.1.3 感知设备
视觉系统旨在完成特定任务。设计的一个重要方面是选择最佳的感应设备来捕捉特定环境的周围环境,无论是摄像头、雷达、X 射线、CT 扫描、激光雷达,还是提供完整环境场景以完成任务的设备组合。
让我们再次看看自动驾驶汽车(AV)的例子。AV 视觉系统的主要目标是让汽车能够理解其周围的环境,并安全、及时地从 A 点移动到 B 点。为了实现这一目标,车辆配备了包括摄像头和传感器在内的组合设备,这些设备可以检测到 360 度的运动——行人、骑自行车的人、车辆、道路施工和其他物体——从最远三个足球场外的地方。
这里有一些通常用于自动驾驶汽车中感知周围区域的感应设备:
-
激光雷达,一种类似雷达的技术,使用不可见的光脉冲来创建周围区域的高分辨率 3D 地图。
-
照相机可以看到街牌和路面标记,但不能测量距离。
-
雷达可以测量距离和速度,但不能看到细节。
医学诊断应用使用 X 射线或 CT 扫描作为感应设备。或者,你可能需要使用其他类型的雷达来捕捉农业视觉系统的景观。有各种各样的视觉系统,每个系统都设计来执行特定的任务。设计视觉系统的第一步是确定它们是为完成什么任务而构建的。在设计端到端视觉系统时,这一点需要牢记在心。
识别图像
动物、人类和昆虫都使用眼睛作为感应设备。但并非所有眼睛的结构、输出图像质量和分辨率都相同。它们是根据生物体的特定需求定制的。例如,蜜蜂和许多其他昆虫都有复眼,由多个透镜组成(单个复眼中多达 30,000 个透镜)。复眼分辨率低,这使得它们在远距离识别物体方面不太擅长。但它们对运动非常敏感,这对于高速飞行时的生存至关重要。蜜蜂不需要高分辨率的图像。它们的视觉系统被构建成能够在快速飞行时捕捉到最小的运动。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-1.png
复眼分辨率低,但对运动敏感。
1.1.4 解释设备
计算机视觉算法通常用作解释设备。解释器是视觉系统的“大脑”。其作用是从感应设备获取输出图像,学习特征和模式以识别物体。因此,我们需要构建一个大脑。简单!科学家们受到我们大脑工作方式的启发,试图逆向工程中枢神经系统,以获得有关如何构建人工大脑的见解。因此,人工神经网络(ANNs)应运而生(图 1.3)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-3.png
图 1.3 生物神经元与人工系统之间的相似性
在图 1.3 中,我们可以看到生物神经元与人工系统之间的类比。两者都包含一个主要处理元素,即神经元,它有输入信号(x[1],x[2],…,x[n])和一个输出。
生物神经元的 学习行为启发了科学家们创建一个相互连接的神经元网络。模仿人类大脑中信息处理的方式,当足够多的输入信号被激活时,每个人工神经元会向它所连接的所有神经元发送信号。因此,神经元在个体层面上具有非常简单的机制(你将在下一章中看到);但是当你有成千上万的这些神经元堆叠在层中并相互连接时,每个神经元都连接到成千上万的其它神经元,从而产生学习行为。构建多层神经网络被称为深度学习(图 1.4)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-4.png
图 1.4 深度学习涉及网络中的神经元层。
深度学习方法通过数据在神经元层中的连续变换来学习表示。在这本书中,我们将探讨不同的深度学习架构,例如人工神经网络和卷积神经网络,以及它们在计算机视觉应用中的使用。
机器学习能否比人类大脑实现更好的性能?
嗯,如果你在 10 年前问我这个问题,我可能会说不可能,机器无法超越人类的准确率。但让我们看看以下两个场景:
-
假设你被给了一本包含 10,000 张狗的照片的书,这些照片按品种分类,并要求你学习每种品种的特性。你需要在 10,000 张照片中学习 130 种品种需要多长时间?如果你被要求对 100 张狗的照片进行测试,并基于你所学的内容进行标记,那么在这 100 张照片中,你能正确标记多少张?嗯,经过几小时训练的神经网络可以达到超过 95%的准确率。
-
在创作方面,神经网络可以研究特定艺术品笔触、色彩和阴影中的模式。基于这种分析,它可以将原始艺术作品的风格转移到新图像中,并在几秒钟内创建出新的原创艺术品。
最近人工智能和深度学习的发展使得机器在许多图像分类和目标检测应用中超越了人类的视觉能力,并且这种能力正在迅速扩展到许多其他应用。但不要仅凭我的话,在下一节中,我们将讨论一些使用深度学习技术的最流行的计算机视觉应用。
1.2 计算机视觉的应用
几十年前,计算机开始能够在图像中识别人脸,但现在人工智能系统正在与计算机在照片和视频中分类对象的能力相媲美。得益于计算能力和可用数据量的显著进步,人工智能和深度学习已经在许多复杂的视觉感知任务上实现了超越人类的表现,如图像搜索和字幕、图像和视频分类以及物体检测。此外,深度神经网络不仅限于计算机视觉任务:它们在自然语言处理和语音用户界面任务中也取得了成功。在这本书中,我们将重点关注应用于计算机视觉任务的视觉应用。
深度学习被用于许多计算机视觉应用中,以识别对象及其行为。在本节中,我不会尝试列出所有现有的计算机视觉应用。那需要一本书的篇幅。相反,我将为您提供一个鸟瞰图,展示一些最受欢迎的深度学习算法及其在不同行业中的可能应用。这些行业包括自动驾驶汽车、无人机、机器人、店内摄像头和能够检测早期肺癌的医疗诊断扫描仪。
1.2.1 图像分类
图像分类是将图像分配到预定义类别集合中的标签的任务。卷积神经网络是一种在处理和分类图像方面真正发光的神经网络类型,它在许多不同的应用中表现出色:
-
肺癌诊断 – 肺癌是一个日益严重的问题。肺癌非常危险的主要原因是在诊断时通常处于中期或晚期。在诊断肺癌时,医生通常用眼睛检查 CT 扫描图像,寻找肺中的小结节。在早期阶段,结节通常非常小,难以发现。几家计算机视觉公司决定利用深度学习技术来应对这一挑战。
几乎所有的肺癌都是从一个小结节开始的,这些结节以医生需要多年时间才能学会识别的各种形状出现。医生在识别中等大小和大结节,如 6-10 毫米的结节方面非常擅长。但当结节为 4 毫米或更小时,有时医生难以识别它们。深度学习网络,特别是卷积神经网络,现在能够自动从 X 射线和 CT 扫描图像中学习这些特征,并在它们变得致命之前早期检测到小结节(图 1.5)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-5.png
图 1.5 现在的视觉系统能够从 X 射线图像中学习模式,以识别早期发展阶段的肿瘤。
-
交通标志识别 – 传统上,标准计算机视觉方法被用于检测和分类交通标志,但这种方法需要耗时的人工工作来手工制作图像中的重要特征。相反,通过将深度学习应用于这个问题,我们可以创建一个可靠地分类交通标志的模型,该模型通过自身学习识别最合适的特征(图 1.6)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-6.png
图 1.6 视觉系统可以以非常高的性能检测交通标志。
注意:越来越多的图像分类任务正在使用卷积神经网络得到解决。由于它们的高识别率和快速执行,CNNs 极大地提升了大多数计算机视觉任务,无论是现有的还是新的。就像癌症诊断和交通标志的例子一样,你可以将成千上万的图像输入到 CNN 中,将它们标注成你想要的任意多类。其他图像分类的例子包括识别人物和物体、区分不同的动物(如猫、狗和马)、不同品种的动物、适合农业的土地类型等等。简而言之,如果你有一组标注好的图像,卷积网络可以将它们分类到一组预定义的类别中。
1.2.2 物体检测与定位
图像分类问题是 CNNs 最基本的应用。在这些问题中,每张图像只包含一个物体,我们的任务是识别它。但如果我们希望达到人类水平的理解,我们必须增加这些网络的复杂性,以便它们能够识别图像中的多个物体及其位置。为此,我们可以构建 YOLO(你只需看一次)、SSD(单次检测器)和 Faster R-CNN 等目标检测系统,这些系统不仅能够对图像进行分类,还能够定位和检测包含多个物体的图像中的每个物体。这些深度学习系统可以观察一张图像,将其分解成更小的区域,并为每个区域标注一个类别,以便在给定的图像中定位和标注可变数量的物体(图 1.7)。你可以想象,这样的任务对于自动驾驶系统等应用来说是基本的前提条件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-7.png
图 1.7 深度学习系统可以在图像中分割物体。
1.2.3 生成艺术(风格迁移)
神经风格迁移是计算机视觉中最有趣的 应用之一,它用于将一种图像的风格转移到另一种图像上。风格迁移的基本思想是这样的:你取一张图像——比如说,一座城市的图像——然后为这张图像应用一种艺术风格——比如说,文森特·梵高的《星夜》——输出与原始图像相同的城市,但看起来像是梵高所绘(图 1.8)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-8.png
图 1.8 将梵高的《星夜》风格转移到原始图像上,产生了一件感觉像是原始艺术家创作的艺术品
这实际上是一个很酷的应用。如果你了解任何画家,令人惊讶的是,完成一幅画可能需要几天甚至几周的时间,然而这里有一个应用可以在几秒钟内根据现有的风格创作出新的图像。
1.2.4 创建图像
虽然早期的例子确实是 AI 在计算机视觉应用方面的真正令人印象深刻的例子,但我看到真正的魔法在这里发生:创造的魔法。2014 年,Ian Goodfellow 发明了一种新的深度学习模型,可以想象新事物,称为生成对抗网络(GANs)。这个名字听起来有点吓人,但我向你保证它们并不如此。GAN 是一种演化的 CNN 架构,被认为是深度学习中的一个重大进步。所以当你理解 CNNs 时,GANs 将对你来说更有意义。
GANs(生成对抗网络)是复杂的深度学习模型,可以生成令人惊叹的、逼真的合成图像,包括物体、人物和地点等。如果你给他们一组图像,他们可以制作出全新的、看起来非常逼真的图像。例如,StackGAN 是 GAN 架构变体之一,可以使用对象的文本描述来生成与该描述匹配的高分辨率图像。这不仅仅是数据库中的图像搜索。这些“照片”以前从未见过,完全是虚构的(图 1.9)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-9.png
图 1.9 生成对抗网络(GANs)可以从一组现有图像中创建新的、“虚构”的图像。
GAN 是近年来机器学习中最有希望的发展之一。对 GAN 的研究是新的,结果非常令人鼓舞。到目前为止,GANs 的大部分应用都是图像相关的。但它让你想:如果机器被赋予创造图片的想象力,它们还能创造什么?在未来,你最喜欢的电影、音乐,甚至书籍会不会由计算机创造?将一种数据类型(文本)合成另一种(图像)的能力最终将使我们能够仅使用详细的文本描述来创造各种娱乐。
GANs 创作艺术品
2018 年 10 月,一幅名为《Edmond Belamy 肖像》的 AI 创作画作以 43.25 万美元的价格售出。这件艺术品展示了一个名为 Edmond de Belamy 的虚构人物,可能是法国人——从他的深色大衣和平凡的白色领口来看,他可能是一位牧师。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-2.png
一幅以虚构人物 Edmond de Belamy 命名的 AI 生成艺术品以 43.25 万美元的价格售出。
这件艺术品是由三位 25 岁的法国学生使用 GANs 创作的。该网络在 14 至 20 世纪之间绘制的 15,000 幅肖像画数据集上进行了训练,然后创作了自己的作品。该团队打印了这幅画,装裱并签署了 GAN 算法的一部分。
1.2.5 人脸识别
人脸识别(FR)使我们能够精确地识别或标记一个人的图像。日常应用包括在网络上搜索名人以及在图像中自动标记朋友和家人。人脸识别是一种细粒度分类。
著名的《人脸识别手册》(Li 等人,Springer,2011)将 FR 系统的两种模式进行了分类:
-
面部识别——面部识别涉及一对一的匹配,将查询面部图像与数据库中的所有模板图像进行比较,以确定查询面的身份。另一个面部识别场景涉及城市当局的观察名单检查,其中查询面与嫌疑人名单(一对一的匹配)进行匹配。
-
面部验证——面部验证涉及一对一的匹配,将查询面部图像与声称身份的模板面部图像进行比较(见图 1.10)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-10.png
图 1.10 面部验证(左)和面部识别(右)的示例
1.2.6 图像推荐系统
在这个任务中,用户试图根据给定的查询图像找到相似的图像。购物网站根据特定产品的选择提供产品建议(通过图像),例如,显示与用户选择的鞋子相似的各种鞋子。图 1.11 展示了服装搜索的一个例子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-11.png
图 1.11 服装搜索。每行最左边的图像是查询/点击的图像,后续的列显示了相似的服装。(来源:刘等,2016 年。)
1.3 计算机视觉流程:全景
好的,现在我已经吸引了你的注意,让我们深入一层了解计算机视觉系统。记住,在本章前面,我们讨论了视觉系统由两个主要组件组成:感知设备和解释设备(图 1.12 提供了一个提醒)。在本节中,我们将探讨解释设备组件用于处理和理解图像的流程。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-12.png
图 1.12 关注计算机视觉系统中的解释设备
计算机视觉的应用多种多样,但典型的视觉系统使用一系列不同的步骤来处理和分析图像数据。这些步骤被称为计算机视觉流程。许多视觉应用遵循获取图像和数据、处理这些数据、执行一些分析和识别步骤,然后基于提取的信息进行预测的流程(见图 1.13)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-13.png
图 1.13 计算机视觉流程,它接收输入数据,处理它,提取信息,然后将它发送到机器学习模型进行学习
让我们将图 1.13 中的流程应用于图像分类器示例。假设我们有一张摩托车的图片,我们希望模型从以下类别中预测该对象的可能性:摩托车、汽车和狗(见图 1.14)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-14.png
图 1.14 使用机器学习模型从摩托车、汽车和狗类别预测摩托车对象的可能性
定义:图像分类器是一种算法,它接收图像作为输入,并输出一个标签或“类别”,以识别该图像。在机器学习中,类别(也称为类别)是数据的输出类别。
这是图像通过分类管道的流程:
-
计算机从像相机这样的成像设备接收视觉输入。这种输入通常以图像或形成视频的图像序列的形式捕获。
-
然后每个图像都会通过一些预处理步骤,其目的是标准化图像。常见的预处理步骤包括调整图像大小、模糊、旋转、改变其形状,或者将图像从一种颜色转换到另一种颜色,例如从彩色转换为灰度。只有通过标准化图像——例如,使它们具有相同的大小——你才能然后比较它们并进一步分析它们。
-
我们提取特征。特征是我们定义对象的东西,它们通常是关于对象形状或颜色的信息。例如,区分摩托车的一些特征是车轮的形状、车头灯、泥板等。这个过程输出的结果是特征向量,它是一系列独特的形状列表,用于识别对象。
-
特征被输入到分类模型中。这一步查看前一步的特征向量并预测图像的类别。假设你是一名分类器模型几分钟,让我们通过分类过程。你逐个查看特征向量中的特征列表,并试图确定图像中有什么:
-
首先你看到一个轮子特征;这可能是一辆汽车、一辆摩托车还是一只狗?显然它不是狗,因为狗没有轮子(至少,正常的狗,不是机器人)。然后这可能是一张汽车或摩托车的照片。
-
你继续到下一个特征,即车头灯。它更有可能是摩托车而不是汽车。
-
下一个特征是后泥板——同样,它更有可能是摩托车。
-
这个物体只有两个轮子;这更接近于摩托车。
-
你继续分析所有特征,如车身形状、踏板等,直到你到达对图像中物体的最佳猜测。
-
这个过程的输出是每个类别的概率。正如你在我们的例子中所看到的,狗的概率最低,只有 1%,而这是一个摩托车的概率是 85%。你可以看到,尽管模型能够以最高的概率预测正确的类别,但它仍然对区分汽车和摩托车有些困惑——它预测有 14%的可能性这是一张汽车的照片。既然我们知道它是一辆摩托车,我们可以说我们的机器学习分类算法的准确率是 85%。还不错!为了提高这个准确率,我们可能需要做更多步骤 1(获取更多的训练图像),或者步骤 2(更多的处理以去除噪声),或者步骤 3(提取更好的特征),或者步骤 4(更改分类器算法并调整一些超参数),或者甚至允许更多的训练时间。我们可以采取的许多不同方法来提高我们模型的表现力都包含在一个或多个的管道步骤中。
那是图像如何在 CV 管道中流动的大致情况。接下来,我们将深入探讨管道的每个步骤。
1.4 图像输入
在 CV 应用中,我们处理图像或视频数据。现在让我们谈谈灰度图像和彩色图像,在后面的章节中,我们将讨论视频,因为视频只是图像的堆叠顺序帧。
1.4.1 图像作为函数
一张图像可以被表示为两个变量 x 和 y 的函数,它们定义了一个二维区域。数字图像由像素网格组成。像素是图像的基本构建块。每个图像都由一组像素组成,这些像素的值代表图像中特定位置的光强度。让我们再次看看摩托车示例,在应用像素网格后(图 1.15)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-15.png
图 1.15 图像由称为像素的原始构建块组成。像素值代表图像中特定位置出现的光强度。
图 1.14 中的图像大小为 32 × 16。这意味着图像的尺寸是 32 像素宽和 16 像素高。x 轴从 0 到 31,y 轴从 0 到 16。总的来说,该图像有 512(32 × 16)个像素。在这张灰度图像中,每个像素包含一个值,代表该特定像素上的光强度。像素值范围从 0 到 255。由于像素值代表光强度,值 0 代表非常暗的像素(黑色),255 代表非常亮(白色),而中间的值代表灰度上的强度。
你可以看到图像坐标系与笛卡尔坐标系相似:图像是二维的,位于 x-y 平面上。原点(0, 0)位于图像的左上角。为了表示特定的像素,我们使用以下符号:F 作为函数,x,y作为像素在 x 和 y 坐标中的位置。例如,位于 x = 12 和 y = 13 的像素是白色的;这由以下函数表示:F(12, 13) = 255。同样,位于摩托车前部的像素(20, 7)是黑色的,表示为 F(20, 7) = 0。
Grayscale => F(*x, y*) gives the intensity at position (*x, y*)
那是关于灰度图像的情况。彩色图像又是如何呢?
在彩色图像中,不是只用一个数字来表示像素的值,而是用三个数字来表示像素中每种颜色的强度。例如,在 RGB 系统中,像素的值由三个数字表示:红色强度、绿色强度和蓝色强度。图像还有其他颜色系统,如 HSV 和 Lab。所有这些在表示像素值时都遵循相同的概念(关于彩色图像的更多内容将在后面讨论)。以下是 RGB 系统中表示彩色图像的函数:
Color image in RGB => F(*x, y*) = [ red (*x, y*), green (*x, y*), blue (*x, y*) ]
将图像视为一个函数在图像处理中非常有用。我们可以将图像视为 F(x, y)的函数,并在数学上对其操作以将其转换为新的图像函数 G(x, y)。让我们看看表 1.1 中的图像变换示例。
表 1.1 图像变换示例函数
| 应用 | 变换 |
|---|---|
| 使图像变暗。 | G(*x, y*) = 0.5 * F(*x, y*) |
| 使图像变亮。 | G(*x, y*) = 2 * F(*x, y*) |
| 将对象向下移动 150 个像素。 | G(*x, y*) = F(x, *y* + 150) |
| 将图像中的灰色去除以将图像转换为黑白。 | G(*x, y*) = { 0 if F(*x, y*) < 130, 255 otherwise } |
1.4.2 计算机如何识别图像
当我们看图像时,我们看到物体、风景、颜色等等。但计算机并不是这样。考虑图 1.16。你的人类大脑可以处理它,并立即知道这是一张摩托车图片。对于计算机来说,图像看起来像像素值的 2D 矩阵,这些值代表颜色光谱中的强度。这里没有上下文,只是一大堆数据。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-16.png
图 1.16 计算机将图像视为值的矩阵。这些值代表像素在颜色光谱中的强度。例如,灰度图像的像素值范围在 0(黑色)到 255(白色)之间。
图 1.16 中的图像大小为 24 × 24。这个大小表示图像的宽度和高度:水平方向有 24 个像素,垂直方向也有 24 个像素。这意味着总共有 576(24 × 24)个像素。如果图像大小为 700 × 500,那么矩阵的维度将是(700,500),其中矩阵中的每个像素代表该像素的亮度强度。0 代表黑色,255 代表白色。
1.4.3 彩色图像
在灰度图像中,每个像素只代表一种颜色的强度,而在标准的 RGB 系统中,彩色图像有三个通道(红色、绿色和蓝色)。换句话说,彩色图像由三个矩阵表示:一个表示像素中红色的强度,一个表示绿色,一个表示蓝色(图 1.17)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-17.png
图 1.17 彩色图像由红色、绿色和蓝色通道表示,矩阵可以用来表示这些颜色的强度。
如图 1.17 所示,彩色图像由三个通道组成:红色、绿色和蓝色。现在的问题是,计算机是如何看到这张图像的?同样,它们看到的是一个矩阵,与只有单一通道的灰度图像不同。在这种情况下,我们将有三个矩阵堆叠在一起;这就是为什么它是一个 3D 矩阵。700×700 彩色图像的维度是(700, 700, 3)。假设第一个矩阵代表红色通道;那么该矩阵的每个元素代表该像素中红色颜色的强度,绿色和蓝色同理。彩色图像中的每个像素都与三个数字(0 到 255)相关联。这些数字代表该特定像素中红色、绿色和蓝色的强度。
以像素(0,0)为例,我们可以看到它代表的是绿色草地的左上角像素。当我们查看这个像素在彩色图像中的样子时,它看起来就像图 1.18 所示。图 1.19 中的例子展示了绿色的一些色调及其 RGB 值。
计算机是如何看到颜色的?
计算机将图像视为矩阵。灰度图像有一个通道(灰色);因此,我们可以用二维矩阵来表示灰度图像,其中每个元素代表该特定像素的亮度强度。记住,0 代表黑色,255 代表白色。灰度图像有一个通道,而彩色图像有三个通道:红色、绿色和蓝色。我们可以用三维矩阵来表示彩色图像,其中深度为三。
我们也看到了图像如何被当作空间函数来处理。这个概念使我们能够在数学上操作图像,并从中改变或提取信息。将图像视为函数是许多图像处理技术的基础,例如将彩色转换为灰度或缩放图像。这些步骤只是通过数学方程对图像像素进行逐像素的转换。
-
灰度图:f(x, y)给出位置(x, y)的强度
-
彩色图像:f(x, y) = [红色(x, y),绿色(x, y),蓝色(x, y)]
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-3a.png
绿色草地的图像实际上是由不同强度的三种颜色组成的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-19.png
图 1.19 展示了不同深度的绿色代表三种图像颜色(红色、绿色、蓝色)的不同强度。
1.5 图像预处理
在机器学习(ML)项目中,你通常会经历一个数据预处理或清洗步骤。作为一名机器学习工程师,在构建学习模型之前,你将花费大量时间清理和准备数据。这一步骤的目标是使数据准备好,以便机器学习模型更容易分析和处理。对于图像来说,也是如此。根据你要解决的问题和手头的数据集,在将图像输入机器学习模型之前,可能需要进行一些数据整理。
图像处理可能涉及简单的任务,如图像缩放。稍后,您将了解到为了将图像数据集输入到卷积网络中,所有图像都必须具有相同的大小。其他处理任务也可以进行,如几何和颜色变换、将颜色转换为灰度等。本书的章节和项目中我们将涵盖各种图像处理技术。
获得的数据通常很杂乱,来自不同的来源。为了将其输入到机器学习模型(或神经网络)中,它需要被标准化和清理。预处理用于执行将减少算法复杂度并提高准确性的步骤。我们不能为图像拍摄的每种条件编写一个独特的算法;因此,当我们获取图像时,我们将其转换为一种形式,以便通用的算法可以解决它。以下小节描述了一些数据预处理技术。
1.5.1 将彩色图像转换为灰度图像以降低计算复杂性
有时候,您可能会发现从图像中删除不必要的信 息以减少空间或计算复杂度很有用。例如,假设您想将彩色图像转换为灰度图像,因为对于许多物体来说,颜色并不是识别和解释图像所必需的。灰度图像可能足以识别某些物体。由于彩色图像包含比黑白图像更多的信息,它们可能会增加不必要的复杂性,并在内存中占用更多空间。请记住,彩色图像以三个通道表示,这意味着将它们转换为灰度图像将减少需要处理的像素数量(图 1.20)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-20.png
图 1.20 将彩色图像转换为灰度图像会减少需要处理的像素数量。这对于不依赖于颜色信息损失的应用程序来说可能是一个好的方法。
在这个例子中,您可以看到如何使用亮度和暗度(强度)的模式来定义许多物体的形状和特征。然而,在其他应用中,颜色对于定义某些物体很重要,例如皮肤癌检测,它严重依赖于皮肤颜色(红色皮疹)。
-
标准化图像 ——正如您将在第三章中看到的那样,某些机器学习算法(如 CNN)存在的一个重要约束是,需要将数据集中的图像调整到统一的尺寸。这意味着在将图像输入到学习算法之前,您的图像必须进行预处理并缩放,以具有相同的宽度和高度。
何时颜色很重要?
将图像转换为灰度可能不是某些问题的好选择。有一些应用中颜色非常重要:例如,在医学图像中建立一个诊断系统来识别红色皮肤疹。这个应用严重依赖于皮肤中红色颜色的强度。从图像中移除颜色将使解决这个问题更加困难。一般来说,彩色图像在许多医学应用中提供了非常有帮助的信息。
图像中颜色重要性的另一个例子是自动驾驶汽车中的车道检测应用,其中汽车必须区分黄色和白色线条,因为它们被处理方式不同。灰度图像无法提供足够的信息来区分黄色和白色线条。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-3b.png
基于灰度的图像处理器无法区分彩色图像。
识别问题中颜色重要性的经验法则是用肉眼观察图像。如果你能够在灰度图像中识别你正在寻找的对象,那么你可能已经提供了足够的信息给你的模型。如果不能,那么你的模型肯定需要更多的信息(颜色)。同样的规则可以应用于我们将要讨论的大多数其他预处理技术。
-
数据增强 --另一种常见的预处理技术是将现有数据集与现有图像的修改版本相结合。缩放、旋转和其他仿射变换通常用于扩大你的数据集,使神经网络接触到你图像的广泛变化。这使得你的模型更有可能在任何形式和形状中出现时识别对象。图 1.21 展示了应用于蝴蝶图像的图像增强的一个例子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-21.png
图 1.21 图像增强技术创建了输入图像的修改版本,为机器学习模型提供更多学习样本。
-
其他技术 --有许多预处理技术可供选择,以便为训练机器学习模型准备你的图像。在某些项目中,你可能需要从你的图像中移除背景颜色以减少噪声。在其他项目中,可能需要你调整图像的亮度或暗度。简而言之,你需要应用到数据集上的任何调整都是预处理的一部分。你将根据手头的数据集和你要解决的问题选择合适的处理技术。在这本书中,你将看到许多图像处理技术,这有助于你建立自己在项目工作中需要哪些技术的直觉。
免费午餐定理
这是一个由 David Wolpert 和 William Macready 在“优化中的无免费午餐定理”(IEEE Transactions on Evolutionary Computation 1, 67)中提出的短语。当团队在机器学习项目中工作时,你经常会听到这句话。这意味着没有一种规定的食谱适合所有模型。在机器学习项目中工作时,你需要做出许多选择,比如构建你的神经网络架构、调整超参数以及应用适当的数据预处理技术。虽然有一些经验法则可以解决某些问题,但事实上并没有一种保证在所有情况下都能有效工作的单一食谱。
你必须对你正在尝试解决的问题的数据集和问题做出某些假设。对于某些数据集,最好将彩色图像转换为灰度图像,而对于其他数据集,你可能需要保留或调整彩色图像。
好消息是,与传统的机器学习不同,深度学习算法需要最少的数据预处理,因为正如你很快就会看到的,神经网络在处理图像和提取特征方面做了大部分繁重的工作。
1.6 特征提取
特征提取是 CV 流程的核心组件。实际上,整个深度学习模型都是围绕提取有用特征的想法展开的,这些特征可以清楚地定义图像中的对象。因此,我们将在这里花更多的时间,因为了解什么是特征、什么是特征向量以及为什么我们要提取特征是非常重要的。
定义 在机器学习中,特征是观察现象的个别可测量属性或特征。特征是你输入到你的机器学习模型中以输出预测或分类的数据。假设你想预测房屋的价格:你的输入特征(属性)可能包括square_foot(面积)、number_of_rooms(房间数)、bathrooms(浴室数量)等,模型将根据你的特征值输出预测价格。选择能够清楚地区分你的对象的良好特征可以增加机器学习算法的预测能力。
1.6.1 计算机视觉中的特征是什么?
在计算机视觉(CV)中,一个特征是图像中一个可测量的数据片段,它对该特定物体是独特的。它可能是一种独特的颜色或特定的形状,如线条、边缘或图像片段。一个好的特征被用来区分不同的物体。例如,如果我给你一个像轮子这样的特征,并让你猜测一个物体是摩托车还是狗,你会怎么猜?摩托车。正确!在这种情况下,轮子是一个强大的特征,它清楚地区分了摩托车和狗。然而,如果我给你同样的特征(一个轮子)并让你猜测一个物体是自行车还是摩托车,这个特征就不足以区分这些物体。你需要寻找更多像镜子、车牌或可能还有踏板这样的特征,这些特征共同描述了一个物体。在机器学习(ML)项目中,我们希望将原始数据(图像)转换成特征向量,以展示给我们的学习算法,这样算法就可以学习物体的特征(图 1.22)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-22.png
图 1.22 示例输入图像被输入到特征提取算法中,以在图像中找到模式并创建特征向量
在图中,我们将摩托车原始输入图像输入到特征提取算法中。现在,让我们将特征提取算法视为一个黑盒,我们稍后再来讨论它。现在,我们需要知道提取算法产生一个包含特征列表的向量。这个特征向量是一个一维数组,它对物体提供了一个稳健的表示。
特征泛化能力
需要强调的是,图 1.22 反映的是仅从一辆摩托车中提取的特征。一个特征的重要特性是可重复性。特征应该能够检测到一般的摩托车,而不仅仅是这一特定的摩托车。因此,在现实世界的问题中,一个特征并不是输入图像中某一部分的精确副本。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-4K.png
特征需要检测一般模式。
以轮子特征为例,这个特征并不完全像某一特定摩托车的轮子。相反,它看起来像是一个带有一些识别训练数据集中所有图像中轮子的模式的圆形形状。当特征提取器看到成千上万张摩托车的图像时,它识别出定义一般轮子的模式,而不管它们在图像中的位置和它们属于哪种类型的摩托车。
1.6.2 什么因素使一个特征(有用的)变得好?
机器学习模型的好坏取决于你提供的特征。这意味着提出好的特征是构建机器学习模型的重要工作。但什么因素使一个特征变得好?你如何判断?
让我们用一个例子来讨论这个问题。假设我们想要构建一个分类器来区分两种类型的狗:灰狗和拉布拉多。让我们选取两个特征——狗的高度和它们的眼睛颜色——并对它们进行评估(图 1.23)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-23.png
图 1.23 灰狗和拉布拉多狗的例子
让我们从高度开始。你认为这个特征有多有用?嗯,平均来说,灰狗通常比拉布拉多高几英寸,但并不总是这样。狗的世界中有很多变化。所以让我们评估这两种品种群体中不同值上的这个特征。让我们在图 1.24 中的直方图上可视化玩具例子中的高度分布。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-24.png
图 1.24 玩具狗数据集上高度分布的可视化
从直方图上,我们可以看到,如果狗的高度是 20 英寸或更少,有超过 80%的概率这只狗是拉布拉多。在直方图的另一边,如果我们看那些身高超过 30 英寸的狗,我们可以相当有信心地说这只狗是灰狗。那么,关于直方图中间的数据(20 到 30 英寸的高度)呢?我们可以看到,每种类型狗的概率相当接近。在这种情况下,思考过程如下:
如果高度 ≤ 20:
返回更高的拉布拉多概率
如果高度 ≥ 30:
返回更高的灰狗概率
如果 20 < 高度 < 30:
寻找其他特征来分类对象
因此,在这种情况下,狗的高度是一个有用的特征,因为它有助于(增加信息)区分两种狗类型。我们可以保留它。但是,它并不总是能区分灰狗和拉布拉多,这是可以接受的。在机器学习项目中,通常没有一种特征可以单独对所有的对象进行分类。这就是为什么在机器学习中,我们几乎总是需要多个特征,每个特征捕捉不同类型的信息。如果只有一个特征就能完成这项工作,我们就可以直接写if-else语句,而不用费心训练分类器。
小贴士:类似于我们之前在颜色转换(颜色与灰度)中做的,为了确定你应该为特定问题使用哪些特征,进行一个思想实验。假装你是分类器。如果你想区分灰狗和拉布拉多,你需要知道哪些信息?你可能会问关于毛发长度、身体大小、颜色等等。
为了快速举例说明一个无用的特征,让我们看看狗的眼睛颜色。在这个玩具例子中,假设我们只有两种眼睛颜色,蓝色和棕色。图 1.25 显示了这种例子可能看起来像直方图。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-25.png
图 1.25 玩具狗数据集中眼睛颜色分布的可视化
很明显,对于大多数值,两种类型的分布大约是 50/50。所以实际上,这个特征告诉我们 nothing,因为它与狗的类型不相关。因此,它不能区分灰狗和拉布拉多。
什么是物体识别的好特征?
一个好的特征将帮助我们以所有可能的方式识别一个对象。一个好的特征的特点如下:
-
可识别
-
容易追踪和比较
-
在不同尺度、光照条件和视角下保持一致性
-
即使在噪声图像中或只有部分对象可见时仍然可见
1.6.3 提取特征(手工提取与自动提取)
这是在机器学习中一个很大的主题,可能需要一整本书来阐述。它通常在被称为特征工程的主题背景下进行描述。在这本书中,我们只关注从图像中提取特征。因此,我将在本章中简要介绍这个想法,并在后面的章节中进一步展开。
传统机器学习使用手工特征
在传统的机器学习问题中,我们花费大量时间在手工特征选择和工程上。在这个过程中,我们依赖我们的领域知识(或与领域专家合作)来创建使机器学习算法表现更好的特征。然后,我们将生成的特征输入到支持向量机(SVM)或 AdaBoost 等分类器中,以预测输出(图 1.26)。一些手工特征集包括以下内容:
-
方向梯度直方图(HOG)
-
Haar 级联
-
尺度不变特征变换(SIFT)
-
加速鲁棒特征(SURF)
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-26.png
图 1.26 传统机器学习算法需要手工特征提取。
使用自动提取特征的深度学习
然而,在深度学习中,我们不需要从图像中手动提取特征。网络自动提取特征,并通过对其连接应用权重来学习它们在输出中的重要性。你只需将原始图像输入到网络中,当它通过网络层时,网络会识别图像中的模式以创建特征(图 1.27)。可以将神经网络视为特征提取器加分类器,它们是端到端可训练的,与传统机器学习模型使用的手工特征形成对比。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-27.png
图 1.27 深度神经网络通过其层自动提取特征并对对象进行分类。不需要手工特征。
神经网络是如何区分有用特征和非有用特征的?
你可能会觉得神经网络只理解最有用的特征,但这并不完全正确。神经网络会收集所有可用的特征并给它们随机分配权重。在训练过程中,神经网络调整这些权重以反映它们的重要性以及它们应该如何影响输出预测。出现频率最高的模式将具有更高的权重,被认为是更有用的特征。权重最低的特征对输出的影响非常小。这个过程将在下一章中更详细地讨论。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-unnumb-5key.png
对不同特征进行加权以反映其在识别对象中的重要性
为什么使用特征?
输入图像包含太多不必要的额外信息,这些信息对于分类来说是不必要的。因此,预处理图像后的第一步是通过提取重要信息并丢弃非必要信息来简化它。通过提取重要的颜色或图像片段,我们可以将复杂和大量的图像数据转换为更小的特征集。这使得基于特征对图像进行分类的任务更加简单和快速。
考虑以下示例。假设我们有一个包含 10,000 张摩托车图像的数据集,每张图像的宽度为 1,000 像素,高度为 1,000 像素。一些图像有均匀的背景,而其他图像则有繁忙的背景和不必要的数据。当这些数千张图像被输入到特征提取算法中时,我们丢失了所有对识别摩托车不重要的非必要数据,我们只保留了一个可以直接输入到分类器中的有用特征列表(图 1.28)。这个过程比让分类器查看 10,000 张图像的原始数据集来学习摩托车的属性要简单得多。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-28.png
图 1.28 从数千张图像中提取并巩固特征,形成一个特征向量以供分类器使用
1.7 分类学习算法
到目前为止,关于分类器管道我们已经讨论了以下几点:
-
输入图像 – 我们已经看到图像是如何表示为函数的,以及计算机将图像视为灰度图像的二维矩阵和彩色图像的三维矩阵(三个通道)。
-
图像预处理 – 我们讨论了一些图像预处理技术,以清理我们的数据集并使其准备好作为机器学习算法的输入。
-
特征提取 – 我们将我们的大量图像数据集转换为一个描述图像中对象的独特有用特征的向量。
现在是时候将提取的特征向量输入到分类器中,以输出图像的类别标签(例如,摩托车或其他)。
正如我们在上一节中讨论的,分类任务可以通过以下方式之一完成:传统的机器学习算法,如支持向量机(SVMs),或深度神经网络算法,如卷积神经网络(CNNs)。虽然传统的机器学习算法可能在某些问题上获得相当好的结果,但 CNNs 在处理和分类最复杂的问题中的表现尤为出色。
在这本书中,我们将详细讨论神经网络以及它们是如何工作的。现在,我想让你知道神经网络会自动从你的数据集中提取有用特征,并充当分类器,为你的图像输出类别标签。输入图像通过神经网络的层来学习它们的特征,一层一层地学习(图 1.29)。你的网络越深(层越多),它就会学习到数据集的更多特征:因此得名深度学习。更多的层伴随着一些权衡,我们将在下一章中讨论。神经网络的最外层通常充当分类器,输出类别标签。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/1-29.png
图 1.29 输入图像通过神经网络的层,以便它可以逐层学习特征。
摘要
-
人类和机器视觉系统都包含两个基本组件:一个感知设备和解释设备。
-
解释过程包括四个步骤:输入数据,预处理,进行特征提取,并生成机器学习模型。
-
一张图像可以被表示为 x 和 y 的函数。计算机将图像视为像素值的矩阵:灰度图像有一个通道,彩色图像有三个通道。
-
图像处理技术因问题和数据集而异。这些技术中的一些是将图像转换为灰度以降低复杂性,将图像调整到统一大小以适应您的神经网络,以及数据增强。
-
特征是图像中用于分类其对象的独特属性。传统的机器学习算法使用多种特征提取方法。
2 深度学习和神经网络
本章涵盖
-
理解感知器和多层感知器
-
使用不同类型的激活函数
-
使用前向传播、误差函数和误差优化来训练网络
-
执行反向传播
在上一章中,我们讨论了计算机视觉(CV)管道组件:输入图像、预处理、提取特征和学习算法(分类器)。我们还讨论了在传统的机器学习(ML)算法中,我们手动提取特征,生成一个特征向量,由学习算法进行分类,而在深度学习(DL)中,神经网络既是特征提取器又是分类器。神经网络自动识别模式并从图像中提取特征,并将它们分类为标签(图 2.1)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-1.png
图 2.1 传统的机器学习算法需要手动提取特征。深度神经网络通过其层传递输入图像来自动提取特征。
在本章中,我们将从 CV 背景中短暂休息,打开图 2.1 中的 DL 算法框。我们将深入探讨神经网络如何学习特征和做出预测。然后,在下一章中,我们将回到 CV 应用,介绍最流行的 DL 架构之一:卷积神经网络。
本章的高级布局如下:
-
我们将从神经网络最基本的部分开始:感知器,这是一个只包含一个神经元的神经网络。
-
然后,我们将转向一个包含数百个神经元的更复杂的神经网络架构,以解决更复杂的问题。这个网络被称为多层感知器(MLP),其中神经元堆叠在隐藏层中。在这里,你将学习神经网络架构的主要组成部分:输入层、隐藏层、权重连接和输出层。
-
你将了解到网络训练过程包括三个主要步骤:
-
前向操作
-
计算误差
-
误差优化:使用反向传播和梯度下降来选择最优化参数,以最小化误差函数
-
我们将深入探讨这些步骤的每一个。你会发现构建神经网络需要做出必要的设计决策:选择优化器、损失函数和激活函数,以及设计网络架构,包括应该有多少层相互连接以及每层应该有多少个神经元。准备好了吗?让我们开始吧!
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-2.png
图 2.2 人工神经网络由节点层组成,节点或神经元通过边连接。
2.1 理解感知器
让我们看看第一章(图 2.2)中的人工神经网络(ANN)图。你可以看到,ANN 由许多按层结构排列的神经元组成,以执行某种计算并预测输出。这种架构也可以称为多层感知器,因为它更直观,因为它暗示网络由多层感知器组成。MLP 和 ANN 这两个术语可以互换使用,以描述这种神经网络架构。
在图 2.2 中的 MLP 图中,每个节点被称为神经元。我们很快将讨论 MLP 网络是如何工作的,但首先让我们聚焦于神经网络最基本的部分:感知器。一旦你理解了单个感知器的工作原理,理解多个感知器如何协同工作以学习数据特征就会变得更加直观。
2.1.1 什么是感知器?
最简单的神经网络是感知器,它由单个神经元组成。从概念上讲,感知器的工作方式类似于生物神经元(图 2.3)。生物神经元从其树突接收电信号,以各种程度调节电信号,然后仅在输入信号的总体强度超过某个阈值时通过其突触发出输出信号。然后输出被馈送到另一个神经元,依此类推。
为了模拟生物神经元的特性,人工神经元执行两个连续的功能:它计算输入的加权总和来表示输入信号的总体强度,然后对结果应用一个阶跃函数,以确定是否在信号超过某个阈值时输出 1,如果没有超过阈值则输出 0。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-3.png
图 2.3 人工神经元是受生物神经元启发的。不同的神经元通过携带信息的突触相互连接。
正如我们在第一章中讨论的,并非所有输入特征都同等有用或重要。为了表示这一点,每个输入节点都被分配一个权重值,称为其连接权重,以反映其重要性。
连接权重
并非所有输入特征都是同等重要(或有用)的特征。每个输入特征(x[1])都被分配其自己的权重(w[1]),以反映其在决策过程中的重要性。分配了更高权重的输入对输出的影响更大。如果权重高,它会放大输入信号;如果权重低,它会减弱输入信号。在神经网络的一般表示中,权重由从输入节点到感知器的线条或边表示。
例如,如果你是根据一组特征(如大小、邻里和房间数量)预测房价,那么有三个输入特征(x[1]、x[2]和x[3])。这些输入中的每一个都将有不同的权重值,代表其对最终决策的影响。例如,如果房屋的大小对价格的影响是邻里的两倍,而邻里对房间数量的影响是两倍,那么你将看到权重值分别为 8、4 和 2。
连接值是如何分配的,以及学习是如何发生的,这是神经网络训练过程的核心。这是我们将在本章剩余部分讨论的内容。
在图 2.4 的感知器图中,你可以看到以下内容:
-
输入向量 --输入到神经元的特征向量。通常用大写x表示输入向量的输入(x[1]、x[2],……,x[n]*)。
-
权重向量 --每个x[1]被分配一个权重值w[1],它表示其在区分不同输入数据点中的重要性。
-
神经元函数 --在神经元内进行的计算,以调节输入信号:加权总和和阶跃激活函数。
-
输出 --由你为网络选择的激活函数类型控制。有不同类型的激活函数,我们将在本章详细讨论。对于阶跃函数,输出为 0 或 1。其他激活函数产生概率输出或浮点数。输出节点代表感知器的预测。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-4.png
图 2.4 输入向量被输入到神经元中,并分配权重以表示其重要性。神经元内部进行的计算是加权总和和激活函数。
让我们更深入地了解神经元内部发生的加权总和和阶跃函数计算。
加权总和函数
也称为线性组合,加权总和函数是所有输入乘以其权重之和,然后加上一个偏置项。此函数产生以下方程表示的直线:
z = Σx[i] · w[i] + b(偏置)
z = x[1] · w[1] + x[2] · w[2] + x[3] · w[3] + … + x[n] · w[n] + b
这里是如何在 Python 中实现加权总和的:
z = np.dot(w.T,X) + b ❶
❶ x 是输入向量(大写 X),w 是权重向量,b 是 y 轴截距。
感知器中的偏置是什么,为什么我们要添加它?
让我们复习一下线性代数的一些概念,以帮助理解底层发生了什么。以下是直线的函数:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-1.png
直线的方程
直线的函数由方程 (y = mx + b) 表示,其中 b 是 y 轴截距。要能够定义一条线,你需要两样东西:线的斜率和线上的一个点。偏差就是 y 轴上的那个点。偏差允许你将线在 y 轴上上下移动,以更好地拟合预测与数据。没有偏差(b),线总是必须通过原点(0,0),这将导致拟合较差。为了可视化偏差的重要性,请看上面的图表,并尝试使用通过原点(0,0)的线将圆圈与星号分开。这是不可能的。
输入层可以通过引入一个始终具有值为 1 的额外输入节点来给予偏差,正如你可以在下一个图中看到的那样。在神经网络中,偏差(b)的值被视为一个额外的权重,并由神经元学习并调整以最小化成本函数,正如我们将在本章的后续部分中学习的那样。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-2.png
输入层可以通过引入一个始终具有值为 1 的额外输入来给予偏差。
步激活函数
在人工和生物神经网络中,一个神经元不仅仅输出它接收到的原始输入。相反,还有一个额外的步骤,称为激活函数;这是大脑的决策单元。在 ANNs 中,激活函数接受之前相同的加权求和输入(z = Σx[i] · w[i] + b)并在加权求和高于某个阈值时激活(触发)神经元。这种激活基于激活函数的计算。在本章的后面部分,我们将回顾不同类型的激活函数及其在神经网络更广泛背景中的通用目的。感知器算法使用的最简单的激活函数是步函数,它产生二进制输出(0 或 1)。它基本上说,如果求和输入 ≥ 0,它“触发”(输出 = 1);否则(求和输入 < 0),它不触发(输出 = 0)(图 2.5)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-5.png
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-5.png
这就是 Python 中步函数看起来像什么:
def step_function(*z*): ❶
if z <= 0:
return 0
else:
return 1
❶ z 是加权求和 = σ x[i] · w[i] + b
2.1.2 感知器是如何学习的?
感知器通过试错法从错误中学习。它通过调整权重的值上下移动,直到网络被训练(图 2.6)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-6.png
图 2.6 在学习过程中调整权重上下移动以优化损失函数的值。
感知器的学习逻辑是这样的:
-
神经元计算加权求和并应用激活函数来做出预测 ŷ。这被称为前馈过程:
- ŷ = activation(Σx[i] · w[i] + b)
-
它将输出预测与正确标签进行比较来计算错误:
- 错误 = y - ŷ
-
然后,它会更新权重。如果预测过高,它会调整权重以在下一次做出更低的预测,反之亦然。
-
重复!
这个过程会重复多次,神经元会持续更新权重以改善其预测,直到步骤 2 产生一个非常小的错误(接近零),这意味着神经元的预测非常接近正确值。此时,我们可以停止训练,并将产生最佳结果的权重值保存下来,以应用于未来结果未知的情况。
2.1.3 一个神经元足够解决复杂问题吗?
简短的回答是不,但让我们看看原因。感知器是一个线性函数。这意味着训练后的神经元将产生一条直线来分离我们的数据。
假设我们想要训练一个感知器来预测一个玩家是否会被大学队接受。我们收集了前几年的所有数据,并训练感知器根据仅有的两个特征(身高和体重)来预测玩家是否会被接受。训练后的感知器将找到最佳权重和偏差值,以产生最佳拟合的直线,将接受者与非接受者分开(最佳拟合)。这条线的方程如下:
z = 身高 · w[1] + 年龄 · w[2] + b
在训练数据上完成训练后,我们可以开始使用感知器来预测新玩家的数据。当我们得到一个身高 150 厘米、12 岁的玩家时,我们用值(150,12)计算前面的方程。当在图表(图 2.7)中绘制时,你可以看到它低于这条线:神经元预测这个玩家不会被接受。如果它高于这条线,那么这个玩家将被接受。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-7.png
图 2.7 线性可分的数据可以通过一条直线分离。
在图 2.7 中,单个感知器工作得很好,因为我们的数据是线性可分的。这意味着训练数据可以通过一条直线分离。但生活并不总是那么简单。当我们有一个更复杂的、不能通过直线分离的数据集(非线性数据集)时会发生什么?
正如你在图 2.8 中看到的,一条单独的直线无法分离我们的训练数据。我们说它不适合我们的数据。我们需要一个更复杂的网络来处理这种更复杂的数据。如果我们构建一个包含两个感知器的网络会怎样?这将产生两条线。这会帮助我们更好地分离数据吗?
好吧,这确实比直线要好。但我仍然看到一些颜色预测错误。我们能否添加更多的神经元来使函数更好地拟合?现在你明白了。从概念上讲,我们添加的神经元越多,网络就越能拟合我们的训练数据。事实上,如果我们添加太多的神经元,这会使网络过度拟合训练数据(不好)。但我们会稍后讨论这个问题。这里的普遍规则是,我们的网络越复杂,它就越能学习我们数据的特点。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-8.png
图 2.8 在非线性数据集中,一条直线无法分离训练数据。在这个例子中,一个具有两个感知器的网络可以产生两条直线,并帮助进一步分离数据。
2.2 多层感知器
我们看到,单个感知器在可以由直线分割的简单数据集上表现良好。但是,正如你可以想象的那样,现实世界比这复杂得多。这就是神经网络可以展示其全部潜力的地方。
线性与非线性问题
-
线性数据集–数据可以用一条直线分割。
-
非线性数据集–数据不能仅用一条直线分割。我们需要多条直线来形成一个分割数据的形状。
看看这个二维数据。在线性问题中,星号和点可以通过画一条直线轻松分类。在非线性数据中,一条直线无法将两种形状分开。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-3key.png
线性数据和非线性数据的示例
要分割非线性数据集,我们需要多条直线。这意味着我们需要提出一个架构,在神经网络中使用成百上千的神经元。让我们看看图 2.9 中的例子。记住,感知器是一个产生直线的线性函数。因此,为了拟合这些数据,我们试图创建一个类似三角形的形状,以分割暗点。看起来三条线就能完成这项工作。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-9.png
图 2.9 感知器是一个产生直线的线性函数。因此,为了拟合这些数据,我们需要三个感知器来创建一个类似三角形的形状,以分割暗点。
图 2.9 是一个用于模拟非线性数据的小型神经网络示例。在这个网络中,我们使用了一个隐藏层,该层由三个堆叠在一起的神经元组成,之所以称为隐藏层,是因为在训练过程中我们看不到这些层的输出。
2.2.1 多层感知器架构
我们已经看到如何设计一个包含多个神经元的神经网络。让我们通过一个更复杂的数据集来扩展这个想法。图 2.10 中的图来自 Tensorflow playground 网站(playground.tensorflow.org)。我们试图模拟一个螺旋数据集以区分两个类别。为了拟合这个数据集,我们需要构建一个包含数十个神经元的神经网络。一个非常常见的神经网络架构是将神经元堆叠在彼此之上,称为隐藏层。每一层有 n 个神经元。层通过权重连接相互连接。这导致了图中的多层感知器(MLP)架构。
神经网络架构的主要组件如下:
-
输入层 --包含特征向量。
-
隐藏层 --神经元堆叠在隐藏层之上。它们被称为“隐藏”层,因为我们看不到或控制进入这些层的输入或输出。我们唯一做的就是将特征向量输入到输入层,并查看输出层输出的结果。
-
权重连接(边) --权重被分配给节点之间的每个连接,以反映它们对最终输出预测的影响的重要性。在图网络术语中,这些被称为连接节点的边。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-10.png
图 2.10 Tensorflow playground 示例表示深度神经网络中的特征学习
-
输出层 --我们从输出层得到答案或预测。根据神经网络的结构设置,最终输出可能是一个实值输出(回归问题)或一组概率(分类问题)。这取决于我们在输出层神经元中使用的激活函数的类型。我们将在下一节讨论不同类型的激活函数。
我们讨论了输入层、权重和输出层。这个架构的下一个区域是隐藏层。
2.2.2 什么是隐藏层?
这是特征学习过程的核心所在。当你查看图 2.10 中的隐藏层节点时,你会看到早期层检测简单的模式以学习低级特征(直线)。后面的层检测模式中的模式以学习更复杂特征和形状,然后是模式中的模式,以此类推。当我们讨论卷积网络时,这个概念将很有用。现在,要知道,在神经网络中,我们堆叠隐藏层,从彼此学习复杂特征,直到我们的数据拟合。所以当你设计你的神经网络时,如果你的网络没有拟合数据,解决方案可能是添加更多的隐藏层。
2.2.3 每层有多少层,每层有多少节点?
作为机器学习工程师,你将主要设计你的网络并调整其超参数。虽然没有一种单一的推荐配方适用于所有模型,但在这本书的整个过程中,我们将尝试建立你的超参数调整直觉,并推荐一些起点。当你与神经网络一起工作时,层数和每层的神经元数量是你要设计的重要超参数之一。
一个网络可以有一个或多个隐藏层(技术上,可以是你想要的任意多个)。每个层有一个或多个神经元(同样,可以是你想要的任意多个)。作为机器学习工程师,你的主要任务是设计这些层。通常,当我们有两个或更多隐藏层时,我们称之为深度神经网络。一般规则是这样的:你的网络越深,它就越能拟合训练数据。但是,过多的深度并不是好事,因为网络可以拟合训练数据到一定程度,以至于当展示新数据时无法泛化(过拟合);同时,它也变得更加计算密集。所以你的任务是构建一个既不太简单(一个神经元)也不太复杂的数据网络。建议你阅读其他人成功实施的不同神经网络架构,以建立对问题过于简单的直觉。从这一点开始,也许三到五层(如果你在 CPU 上训练),观察网络性能。如果表现不佳(欠拟合),则添加更多层。如果你看到过拟合的迹象(稍后讨论),则减少层数。为了了解添加更多层时神经网络的表现,可以在 Tensorflow playground(playground.tensorflow.org)上尝试操作。
全连接层
需要强调的是,在经典的 MLP 网络架构中,层与下一隐藏层是完全连接的。在下面的图中,注意一个层中的每个节点都与前一层的所有节点相连。这被称为全连接网络。这些边是表示该节点对输出值重要性的权重。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-4K.png
一个全连接网络
在后面的章节中,我们将讨论神经网络架构的其他变体(如卷积网络和循环网络)。现在,知道这是最基本的神经网络架构,并且可以用以下任何一种名称来引用:ANN、MLP、全连接网络或前向网络。
让我们做一个快速练习,找出我们示例中有多少条边。假设我们设计了一个具有两个隐藏层的 MLP 网络,每个隐藏层有五个神经元:
-
Weights_0_1: (输入层有 4 个节点) × (层 1 有 5 个节点) + 5 个偏置[每个神经元一个偏置] = 25 条边 -
Weights_1_2: 5 × 5 个节点 + 5 个偏置 = 30 条边 -
Weights_2_output: 5 × 3 个节点 + 3 个偏置 = 18 条边 -
本网络中的总边(权重)数 = 73
在这个非常简单的网络中,我们总共有 73 个权重。这些权重的值是随机初始化的,然后网络通过前向传播和反向传播来学习最适合训练数据的权重最佳值。
要查看这个网络中的权重数量,尝试以下方式在 Keras 中构建这个简单的网络:
model = Sequential([
Dense(5, input_dim=4),
Dense(5),
])
打印模型摘要:
model.summary()
输出将如下所示:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 5) 25
_________________________________________________________________
dense_1 (Dense) (None, 5) 30
_________________________________________________________________
dense_2 (Dense) (None, 3) 18
=================================================================
Total params: 73
Trainable params: 73
Non-trainable params: 0
2.2.4 本节的一些要点
让我们回顾一下我们迄今为止讨论的内容:
-
我们讨论了生物神经元和人工神经元之间的类比:两者都有输入和一个执行某些计算以调节输入信号并创建输出的神经元。
-
我们聚焦于人工神经元的计算,以探索其两个主要功能:加权总和和激活函数。
-
我们看到网络为所有边分配随机权重。这些权重参数反映了这些特征在输出预测中的有用性(或重要性)。
-
最后,我们看到了感知器包含单个神经元。它们是线性函数,产生一条直线来分割线性数据。为了分割更复杂的数据(非线性),我们需要在我们的网络中应用多个神经元来形成一个多层感知器。
-
MLP 架构包含输入特征、连接权重、隐藏层和输出层。
-
我们讨论了感知器学习的高级过程。学习过程是三个主要步骤的重复:前向计算以产生预测(加权总和和激活)、计算误差以及反向传播误差并更新权重以最小化误差。
我们还应该记住一些关于神经网络超参数的重要观点:
-
隐藏层数量 – 你可以拥有你想要的任何数量的层,每层可以有任意数量的神经元。一般想法是,你拥有的神经元越多,你的网络将越好地学习训练数据。但是,如果你有太多的神经元,这可能会导致一种称为过拟合的现象:网络学习训练集如此之多,以至于它记住了它而不是学习其特征。因此,它将无法泛化。为了获得适当的层数,从一个小的网络开始,并观察网络性能。然后开始添加层,直到你得到满意的结果。
-
激活函数 – 有许多类型的激活函数,最流行的是 ReLU 和 softmax。建议你在隐藏层中使用 ReLU 激活函数,在输出层中使用 Softmax(你将在本书中的大多数项目中看到这是如何实现的)。
-
误差函数 – 衡量网络的预测与真实标签之间的距离。均方误差是回归问题的常见度量,交叉熵是分类问题的常见度量。
-
优化器 – 优化算法用于找到最小化误差的最优权重值。有几种优化器类型可供选择。在本章中,我们讨论了批量梯度下降、随机梯度下降和小批量梯度下降。Adam 和 RMSprop 是两种其他流行的优化器,我们在此不讨论。
-
批处理大小 – 小批量大小是指网络接收到的子样本数量,之后参数更新发生。较大的批处理大小学习速度更快,但需要更多的内存空间。批处理大小的良好默认值可能是 32。也可以尝试 64、128、256 等。
-
训练轮数 --在训练过程中,整个训练数据集被展示给网络的次数。增加训练轮数,直到验证准确率开始下降,即使训练准确率在增加(过拟合)。
-
学习率 --优化器的一个输入参数,我们需要调整。从理论上讲,过小的学习率保证能够达到最小误差(如果你无限期地训练)。过大的学习率会加快学习速度,但并不保证找到最小误差。大多数深度学习库中优化器的默认
lr值是一个合理的起点,以获得不错的结果。从那里开始,可以按一个数量级上下调整。我们将在第四章中详细讨论学习率。
更多关于超参数的内容
我们尚未讨论的其他超参数包括 dropout 和正则化。在第三章介绍卷积神经网络之后,我们将在第四章中详细讨论超参数调整。
通常,调整超参数的最佳方式是通过试错。通过自己动手做项目以及从其他现有的神经网络架构中学习,你将开始培养出对超参数良好起点的直觉。
学习分析你网络的性能,并了解你需要调整哪个超参数来解决每个症状。这正是本书将要讨论的内容。通过理解这些超参数背后的推理,并观察章节末尾的项目中的网络性能,你将培养出对特定效果调整哪个超参数的直觉。例如,如果你看到你的错误值没有下降并且持续振荡,那么你可能通过降低学习率来解决这个问题。或者,如果你看到网络在训练数据的学习中表现不佳,这可能意味着网络欠拟合,你需要通过添加更多神经元和隐藏层来构建一个更复杂的模型。
2.3 激活函数
当你在构建你的神经网络时,你需要做出的设计决策之一是为你神经元的计算选择哪种激活函数。激活函数也被称为转移函数或非线性,因为它们将加权求和的线性组合转换成非线性模型。激活函数被放置在每个感知器的末尾,以决定是否激活这个神经元。
为什么一定要使用激活函数呢?为什么不能只是计算我们网络的加权求和,并通过隐藏层传播以产生输出呢?
激活函数的目的是将非线性引入网络。没有它,多层感知器将表现得与单个感知器相似,无论我们添加多少层。激活函数需要将输出值限制在某个有限值内。让我们回顾一下预测玩家是否被接受(图 2.11)的例子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-11.png
图 2.11 本例重新审视了从 2.1 节预测玩家是否被接受的情况。
首先,模型计算加权总和并产生线性函数(z):
z = 身高 · w[1] + 年龄 · w[2] + b
该函数的输出没有界限。z可以是任何数字。我们使用激活函数将预测值包裹在一个有限值内。在这个例子中,我们使用一个阶跃函数,如果z > 0,则在线上(接受)以上,如果z < 0,则在线下(拒绝)。因此,如果没有激活函数,我们只有一个产生数字的线性函数,但在这种感知器中并没有做出任何决定。激活函数决定了是否触发这个感知器。
激活函数有无限种。实际上,在过去的几年里,在创建最先进的激活函数方面取得了大量进展。然而,仍然相对较少的激活函数能够满足大部分激活需求。让我们更深入地探讨一些最常见的激活函数类型。
2.3.1 线性传递函数
线性传递函数,也称为恒等函数,表示函数将信号通过而不改变。在实践中,输出将等于输入,这意味着我们实际上没有激活函数。所以无论我们的神经网络有多少层,它所做的只是计算一个线性激活函数,或者最多对输入的加权平均值进行缩放。但它不会将输入转换为非线性函数。
激活(z) = z = wx + b
两个线性函数的合成是一个线性函数,所以除非你在神经网络中添加一个非线性激活函数,否则无论你使网络有多深,你都不会计算任何有趣的函数。这里没有学习!
为了理解为什么,让我们计算激活z(x) = w · x + b的导数,其中w = 4 和b = 0。当我们绘制这个函数时,它看起来像图 2.12。然后z(x) = 4x的导数是z’(x) = 4(图 2.13)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-12.png
图 2.12 激活函数f(x) = 4x的图像
线性函数的导数是常数:它不依赖于输入值 x。这意味着每次我们进行反向传播时,梯度都将相同。这是一个大问题:因为我们实际上并没有真正改进误差,因为梯度几乎相同。这一点在我们稍后讨论反向传播时会更加清晰。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-13.png
图 2.13 z(x) = 4x 的导数图像为 z’(x) = 4.
2.3.2 海维塞德步进函数(二元分类器)
步进函数产生二元输出。它基本上说,如果输入 x > 0,则触发(输出 y = 1);否则(输入 < 0),则不触发(输出 y = 0)。它主要用于二元分类问题,如真或假、垃圾邮件或非垃圾邮件、通过或失败(图 2.14)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-14.png
图 2.14 步进函数在二元分类问题中常用,因为它将输入转换为零或一。
2.3.3 Sigmoid/logistic 函数
这是最常见的激活函数之一。它通常用于二元分类器中,当你有两个类别时,预测一个类别的概率。sigmoid 函数将所有值压缩到 0 到 1 之间的概率,这减少了数据中的极端值或异常值,而没有移除它们。sigmoid 或 logistic 函数将无限连续变量(范围在 -∞ 到 +∞ 之间)转换为 0 到 1 之间的简单概率。它也被称为 S 形曲线,因为当在图表中绘制时,它会产生 S 形曲线。当步进函数用于产生离散答案(通过或失败)时,sigmoid 函数用于产生通过和失败的概率(图 2.15):
σ(z) = 1/(1 + e^(-1))
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-15.png
图 2.15 当步进函数用于产生离散答案(通过或失败)时,sigmoid 函数用于产生通过或失败的概率。
下面是如何在 Python 中实现 sigmoid 函数的示例:
import numpy as np ❶
def sigmoid(*x*): ❷
return 1 / (1 + np.exp(-x))
❶ 导入 numpy
❷ Sigmoid 激活函数
实时线性代数(可选)
让我们深入探讨 sigmoid 函数的数学方面,以了解它帮助解决的问题以及 sigmoid 函数方程是如何驱动的。假设我们正在尝试根据只有一个特征:年龄,来预测患者是否患有糖尿病。当我们绘制我们关于患者的数据时,我们得到图中的线性模型:
z = β0 + β1 年龄
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-5K.png
当我们绘制关于我们患者的数据时,我们得到的线性模型
在这个图中,你可以观察到应该从 0 到 1 变化的概率平衡。注意,当患者年龄低于 25 岁时,预测的概率是负数;而当患者年龄超过 43 岁时,它们高于 1(100%)。这是一个明显的例子,说明了为什么线性函数在大多数情况下不起作用。现在,我们如何修复这个问题,以给出 0 < 概率 < 1 范围内的概率?
首先,我们需要做一些事情来消除所有负概率值。指数函数是解决这个问题的绝佳方案,因为任何东西的指数(我的意思是任何东西)总是正的。所以让我们将这个应用到我们的线性方程中,以计算概率 (p):
p = exp(z) = exp(β[0] + β[1] 年龄)
这个方程确保我们总是得到大于 0 的概率。那么,对于高于 1 的值怎么办?我们需要对它们做些什么。通过比例,任何给定的数除以一个大于它的数,都会得到一个小于 1 的数。让我们对前面的方程做同样的事情。我们将方程除以其值加上一个小值:要么是 1,要么是 a(在某些情况下非常小)的值——让我们称它为 epsilon(ε):
p = exp(z) / (exp(z) + ε)
如果你将方程除以 exp(z),你得到
p = 1/(1 + exp(-z))
当我们绘制这个方程的概率时,我们得到 sigmoid 函数的 S 形,其中概率不再低于 0 或高于 1。事实上,随着患者年龄的增长,概率渐近地接近 1;而当权重向下移动时,函数渐近地接近 0,但永远不会超出 0 < p < 1 的范围。这是 sigmoid 函数和逻辑回归的图像。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-6K.png
随着患者年龄的增长,概率渐近地接近 1。这是 sigmoid 函数和逻辑回归的图像。
Softmax 函数
softmax 函数是 sigmoid 函数的推广。当我们有超过两个类别时,它用于获得分类概率。它迫使神经网络的输出之和为 1(例如,0 < 输出 < 1)。在深度学习问题中,一个非常常见的用例是从许多选项(超过两个)中预测一个单一类别。
softmax 方程如下:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_15a.png
图 2.16 展示了 softmax 函数的一个示例。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-16.png
图 2.16 softmax 函数将输入值转换为 0 到 1 之间的概率值。
TIP 当你在处理需要预测两个以上类别的问题时,softmax 函数是你在分类器的输出层经常使用的首选函数。如果你正在对两个类别进行分类,softmax 函数也能很好地工作。它基本上会像 sigmoid 函数一样工作。在本节结束时,我会告诉你关于何时使用每个激活函数的建议。
2.3.5 双曲正切函数(tanh)
双曲正切函数是 sigmoid 函数的平移版本。tanh 不是将信号值挤压在 0 到 1 之间,而是将所有值挤压到-1 到 1 的范围内。tanh 几乎总是比 sigmoid 函数在隐藏层中工作得更好,因为它有使你的数据中心化的效果,使得数据的平均值接近零而不是 0.5,这使得下一层的学习变得容易一些:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_15d_F2.png
sigmoid 和 tanh 函数的一个缺点是,如果(z)非常大或非常小,那么这个函数的梯度(或导数或斜率)会变得非常小(接近零),这将减慢梯度下降(图 2.17)。这就是 ReLU 激活函数(下文将解释)提供解决方案的时候。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-17.png
图 2.17 如果(z)非常大或非常小,那么这个函数的梯度(或导数或斜率)将非常小(接近零)。
2.3.6 矩形线性单元
矩形线性单元(ReLU)激活函数仅在输入大于零时激活节点。如果输入小于零,输出始终为零。但是当输入高于零时,它与输出变量之间存在线性关系。ReLU 函数表示如下:
f(x) = max (0, x)
在撰写本文时,ReLU 被认为是最先进的激活函数,因为它在许多不同的情况下都表现良好,并且它在隐藏层中训练通常比 sigmoid 和 tanh 更好(图 2.18)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-18.png
图 2.18 ReLU 函数通过将所有负值输入转换为零来消除输入的所有负值。
这是 ReLU 在 Python 中的实现方式:
def relu(*x*): ❶
if *x* < 0:
return 0
else:
return x
❶ ReLU 激活函数
2.3.7 Leaky ReLU
ReLU 激活的一个缺点是当(x)为负时,导数等于零。Leaky ReLU 是 ReLU 的一种变体,试图减轻这个问题。Leaky ReLU 在(x)为负时引入了一个小的负斜率(大约 0.01),而不是当x < 0 时函数为零。它通常比 ReLU 函数表现更好,尽管在实践中并不常用。看看图 2.19 中的 Leaky ReLU 图;你能看到泄漏吗?
f (x) = max(0.01x, x)
为什么是 0.01?有些人喜欢将其用作另一个可调的超参数,但这将是过度杀戮,因为你已经有其他更大的问题需要担心。请随意尝试在你的模型中使用不同的值(0.1,0.01,0.002)并看看它们的效果如何。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-19.png
图 2.19 Leaky ReLU 在x < 0 时引入了一个小的负斜率(大约 0.01),而不是当x < 0 时函数为零。
这是 Leaky ReLU 在 Python 中的实现方式:
def leaky_relu(*x*): ❶
if *x* < 0:
return *x* * 0.01
else:
return x
❶ 带有 0.01 泄漏的 Leaky ReLU 激活函数
表 2.1 总结了本节中讨论的各种激活函数。
表 2.1 最常见激活函数速查表
超参数警告
由于激活函数的种类繁多,选择适合你网络的激活函数可能看起来是一项艰巨的任务。虽然选择一个好的激活函数很重要,但我保证在你设计网络时,这不会是一项具有挑战性的任务。你可以从一些经验法则开始,然后根据需要调整模型。如果你不确定该使用什么,以下是我关于选择激活函数的两分钱建议:
-
对于隐藏层——在大多数情况下,你可以在隐藏层中使用 ReLU 激活函数(或 Leaky ReLU),正如你将在本书中构建的项目中看到的那样。它正变得越来越成为默认选择,因为它比其他激活函数计算得更快。更重要的是,它减少了梯度消失的可能性,因为它不会对大输入值饱和——与 sigmoid 和 tanh 激活函数相反,它们在~ 1 处饱和。记住,梯度是斜率。当函数达到平台期时,这将导致没有斜率;因此,梯度开始消失。这使得下降到最小误差变得更加困难(我们将在后面的章节中更多地讨论这种现象,称为梯度消失/爆炸)。
-
对于输出层——当类别互斥时,softmax 激活函数通常是大多数分类问题的良好选择。当进行二分类时,sigmoid 函数也起到相同的作用。对于回归问题,你可以简单地不使用任何激活函数,因为加权求和节点会产生你需要的连续输出:例如,如果你想根据同一地区的其他房屋价格预测房价。
2.4 前馈过程
现在你已经了解了如何将感知器堆叠成层,通过权重/边连接它们,执行加权求和函数,并应用激活函数,让我们实现完整的前向传递计算以生成预测输出。计算线性组合并应用激活函数的过程称为前馈。在前面的几个部分中,我们简要讨论了前馈几次;让我们更深入地看看在这个过程中发生了什么。
术语前馈用来表示信息从输入层通过隐藏层流向输出层的正向方向。这个过程通过实现两个连续的函数:加权求和和激活函数来完成。简而言之,前向传递是通过层进行计算以做出预测的过程。
让我们看看图 2.20 中的简单三层神经网络,并探索其每个组成部分:
-
层 – 这个网络由一个具有三个输入特征输入层和三个具有每层 3、4、1 个神经元的隐藏层组成。
-
权重和偏差 (w, b) – 节点之间的边被分配随机权重,表示为 Wab,其中 (n) 表示层号,(ab) 表示连接第 (n) 层中第 a 个神经元和前一层 (n - 1) 中第 b 个神经元的加权边。例如,W[23]^((2)) 是连接第 2 层第二个节点和第 1 层第三个节点的权重(a[2]² 到 a[1]³)。(请注意,你可以在其他深度学习文献中看到 W[ab]^((n) 的不同表示,只要你在整个网络中遵循一个约定即可。)
偏差被处理得与权重相似,因为它们是随机初始化的,其值在训练过程中学习。因此,为了方便起见,从现在开始我们将使用与权重相同的符号来表示基(w)。在深度学习文献中,你通常会看到所有权重和偏差都表示为(w),以简化表示。
-
激活函数 σ(x) – 在这个例子中,我们使用 sigmoid 函数 σ(x) 作为激活函数。
-
节点值 (a) – 我们将计算加权求和,应用激活函数,并将此值分配给节点 amn,其中 n 是层号,m 是层中的节点索引。例如,a 23 表示第 3 层中的第 2 个节点。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-20.png
图 2.20 一个简单的三层神经网络
2.4.1 前馈计算
我们已经拥有了开始前馈计算所需的一切:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_19_T1.png
然后我们对第 2 层进行相同的计算
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_19_T2.png
直至第 3 层的输出预测:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_19_T3.png
就这样!你刚刚计算了一个两层神经网络的正向传播。让我们花点时间来反思一下我们刚刚做了什么。看看我们为这样一个小的网络需要解多少个方程。当我们有一个更复杂的问题,输入层有数百个节点,隐藏层有数百个节点时会发生什么?使用矩阵一次传递多个输入会更有效率。这样做可以大幅提高计算速度,尤其是在使用 NumPy 这样的工具时,我们可以用一行代码来实现这一点。
让我们看看矩阵计算看起来是什么样子(图 2.21)。我们在这里所做的只是简单地将输入和权重堆叠成矩阵并相乘。直观地阅读这个方程的方法是从右到左。从最右边开始,跟我一起来:
-
我们将所有输入堆叠成一个向量(行,列),在这种情况下(3,1)。
-
我们将输入向量与第 1 层的权重矩阵(W^((1)))相乘,然后应用 sigmoid 函数。
-
我们将第 2 层的输出结果(σ · W^((2)))和第 3 层的输出结果(σ · W^((3)))相乘。
-
如果我们有第 4 层,我们将步骤 3 的结果乘以σ · W^((4)),依此类推,直到我们得到最终的预测输出 ŷ!
这里是这个矩阵公式的简化表示:
ŷ = σ · W^((3)) · σ · W^((2)) · σ · W^((1)) · (x)
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-21.png
图 2.21 从左到右阅读,我们将输入堆叠成一个向量,将输入向量与第 1 层的权重矩阵相乘,应用 sigmoid 函数,并乘以结果。
2.4.2 特征学习
隐藏层中的节点(ai)是在每一层学习后的新特征。例如,如果你看图 2.20,你会看到我们有三个特征输入(x1,x2 和 x3)。在第一层进行正向传播计算后,网络学习到模式,这些特征被转换成具有不同值的三个新特征!。然后,在下一层,网络学习模式中的模式并产生新的特征!,依此类推)。每一层产生的特征并不完全理解,我们看不到它们,也没有太多控制它们。这是神经网络魔法的一部分。这就是为什么它们被称为隐藏层。我们所做的是:我们查看最终的输出预测,并调整一些参数,直到我们对网络的性能满意为止。
再次强调,让我们通过一个小例子来看一下。在图 2.22 中,你可以看到一个小的神经网络,它根据三个特征来估算房价:卧室数量、房屋大小以及所在的社区。你可以看到,在第一层的正向传播过程中,原始输入特征值 3、2000 和 1 被转换成了新的特征值!。然后它们再次被转换成预测输出值(ŷ)。在训练神经网络时,我们查看预测输出并与真实价格进行比较,以计算误差并重复此过程,直到我们得到最小误差。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-22.png
图 2.22 一个小的神经网络,基于三个特征估算房价:卧室数量、房屋大小以及所在的社区
为了帮助可视化特征学习过程,让我们再次看看 Tensorflow playground 中的图 2.9(在此处重复为图 2.23)。你可以看到,第一层学习基本特征,如线条和边缘。第二层开始学习更复杂的特征,如角落。这个过程一直持续到网络的最后几层,学习到更复杂的特征形状,如适合数据集的圆形和螺旋形。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-23.png
图 2.23 多个隐藏层中的特征学习
那就是神经网络学习新特征的方式:通过网络的隐藏层。首先,它们识别数据中的模式。然后,它们识别模式中的模式;然后模式中的模式中的模式,以此类推。网络越深,它对训练数据了解得就越多。
向量和矩阵复习
如果你理解了我们刚才在正向传播讨论中做的矩阵计算,你可以自由跳过这个边栏。如果你仍然不确信,请耐心等待:这个边栏是为你准备的。
正向传播的计算是一组矩阵乘法。虽然你不会手动进行这些计算,因为有很多优秀的深度学习库可以只用一行代码为你完成这些计算,但了解底层发生的数学原理是有价值的,这样你可以调试你的网络。特别是因为这个过程非常简单且有趣,让我们快速回顾一下矩阵计算。
让我们从矩阵的一些基本定义开始:
-
标量是一个单一的数字。
-
向量是一组数字。
-
矩阵是一个二维数组。
-
张量是一个 n 维数组,其中 n > 2。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-14K.png
矩阵维度:标量是一个单一的数字,向量是一组数字的数组,矩阵是一个二维数组,张量是一个 n 维数组。
我们将遵循大多数数学文献中使用的惯例:
-
标量用小写和斜体表示:例如,n。
-
向量用小写、斜体和粗体表示:例如,x。
-
矩阵用大写、斜体和粗体表示:例如,X。
-
矩阵维度表示如下:(行 × 列)。
乘法:
-
矩阵的标量乘法——简单地将标量数乘以矩阵中的所有数。注意,标量乘法不会改变矩阵的维度:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-14bK.png
-
矩阵乘法——当乘以两个矩阵时,例如在 (行[1] × 列[1]) × (行[2] × 列[2]) 的情况下,列[1] 和行[2] 必须相等,其乘积将具有 (行[1] × 列[2]) 的维度。例如,
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-15K.png
其中 x = 3 · 13 + 4 · 8 + 2 · 6 = 83,y = 63 和 z = 37 同理。
现在你已经知道了矩阵乘法规则,拿出一张纸,处理一下之前神经网络示例中的矩阵维度。以下图再次显示了矩阵方程,以供你方便参考。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-16K.png
主文中的矩阵方程。使用它来处理矩阵维度。
我希望你们对矩阵了解的最后一件事是转置。通过转置,你可以将行向量转换为列向量,反之亦然,其中形状 (m × n) 被反转并变为 (n × m)。转置矩阵使用上标 (A^T) 表示:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-16Kb.png
2.5 误差函数
到目前为止,你已经学会了如何在神经网络中实现前向传递以生成由加权求和加激活操作组成的预测。现在,我们如何评估网络刚刚生成的预测?更重要的是,我们如何知道这个预测离正确答案(标签)有多远?答案是:测量误差。误差函数的选择是神经网络设计的重要方面之一。误差函数也可以称为代价函数或损失函数,这些术语在深度学习文献中可以互换使用。
2.5.1 什么是误差函数?
误差函数是衡量神经网络预测相对于预期输出(标签)的“错误程度”的度量。它量化了我们离正确解有多远。例如,如果我们有一个高的损失,那么我们的模型做得不好。损失越小,模型做得越好。损失越大,我们的模型就需要更多的训练来提高其准确性。
2.5.2 为什么我们需要误差函数?
计算误差是一个优化问题,这是所有机器学习工程师都喜欢的(数学家也是如此)。优化问题侧重于定义一个误差函数并尝试优化其参数以获得最小误差(关于优化的更多内容将在下一节中介绍)。但就现在而言,要知道,在一般情况下,当我们处理优化问题时,如果我们能够为问题定义误差函数,我们就很有机会通过运行优化算法来最小化误差函数来解决它。
在优化问题中,我们的最终目标是找到最优变量(权重),以尽可能多地最小化误差函数。如果我们不知道我们离目标有多远,我们怎么知道在下一轮迭代中要改变什么?最小化这个错误的过程称为误差函数优化。在下一节中,我们将回顾几种优化方法。但就目前而言,我们需要从误差函数中了解的是我们离正确预测有多远,或者我们偏离了期望的性能程度有多远。
2.5.3 错误始终为正
考虑以下场景:假设我们有两个数据点,我们试图让我们的网络正确预测。如果第一个数据点产生 10 的错误,而第二个数据点产生-10 的错误,那么我们的平均错误为零!这具有误导性,因为“错误=0”意味着我们的网络正在产生完美的预测,而实际上它两次都偏离了 10。我们不想这样。我们希望每个预测的错误都是正数,这样当我们计算平均错误时,错误就不会相互抵消。想象一个弓箭手瞄准目标并偏离了 1 英寸。我们并不真正关心他们偏离的方向;我们只需要知道每一箭与目标的距离。
图 2.24 显示了两个独立模型随时间变化的损失函数的可视化。你可以看到模型#1 在最小化错误方面做得更好,而模型#2 在 6 个 epoch 之前表现更好,然后趋于平稳。
不同的损失函数会对相同的预测给出不同的错误,从而对模型的性能产生相当大的影响。对损失函数的详细讨论超出了本书的范围。相反,我们将专注于两种最常用的损失函数:均方误差(及其变体),通常用于回归问题,以及交叉熵,用于分类问题。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-24.png
图 2.24 两个独立模型损失函数随时间变化的可视化
2.5.4 均方误差
均方误差(MSE)在需要输出为实值(如房价)的回归问题中常用。与仅比较预测输出与标签(ŷ[i] - y[i])不同,误差被平方并平均到数据点的数量,正如你在这个方程中看到的那样:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_28_F1b.png
MSE 有几个优点。平方确保了错误始终为正数,并且较大的错误比较小的错误受到更多的惩罚。此外,它使数学变得简单,这始终是一个加分项。公式中的符号列在表 2.2 中。
均方误差(MSE)对异常值非常敏感,因为它平方了误差值。这可能不是你正在解决的问题的具体问题。事实上,对异常值的敏感性在某些情况下可能是有益的。例如,如果你正在预测股票价格,你会希望考虑异常值,对异常值的敏感性会是一件好事。在其他情况下,你可能不希望构建一个受异常值偏斜的模型,例如预测一个城市的房价。在这种情况下,你更感兴趣的是中位数,而不是平均值。为了这个目的,开发了一种均方误差(MSE)的变体,称为平均绝对误差(MAE),它在整个数据集上平均绝对误差,而不对误差值进行平方:
表 2.2 回归问题中使用的符号含义
| 符号 | 含义 |
|---|---|
| E(W, b) | 损失函数。在其他文献中也标注为 J(W, b)。 |
| W | 权重矩阵。在某些文献中,权重用希腊字母θ表示。 |
| b | 偏置向量。 |
| N | 训练样本数量。 |
| ŷi | 预测输出。在某些深度学习文献中也表示为 hw, b(x)。 |
| y[i] | 正确的输出(标签)。 |
| (ŷi - yi) | 通常称为残差。 |
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_28_F2b.png
2.5.5 交叉熵
交叉熵在分类问题中常用,因为它量化了两个概率分布之间的差异。例如,假设对于特定的训练实例,我们正在尝试从三个可能的类别(狗、猫、鱼)中分类一张狗的图片。这个训练实例的真实分布如下:
Probability(cat) P(dog) P(fish)
0.0 1.0 0.0
我们可以将这个“真实”分布理解为训练实例有 0%的概率属于类别 A,100%的概率属于类别 B,0%的概率属于类别 C。现在,假设我们的机器学习算法预测以下概率分布:
Probability(cat) P(dog) P(fish)
0.2 0.3 0.5
预测分布与真实分布有多接近?这就是交叉熵损失函数所确定的。我们可以使用以下公式:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_28_F3b.png
其中 (y) 是目标概率,(p) 是预测概率,(m) 是类别数量。求和是针对三个类别:猫、狗和鱼。在这种情况下,损失是 1.2:
E = - (0.0 * log(0.2) + 1.0 * log(0.3) + 0.0 * log(0.5)) = 1.2
因此,这就是我们的预测与真实分布之间的“错误”或“远离”程度。
让我们再来一次,只是为了展示当网络做出更好的预测时损失是如何变化的。在之前的例子中,我们向网络展示了一只狗的图片,它预测这张图片有 30%的可能性是狗,这离目标预测非常远。在后续的迭代中,网络学习了一些模式,并将预测结果稍微改进,达到了 50%:
Probability(cat) P(dog) P(fish)
0.3 0.5 0.2
然后我们再次计算损失:
E = - (0.0*log(0.3) + 1.0*log(0.5) + 0.0*log(0.2)) = 0.69
你可以看到,当网络做出更好的预测(狗的概率从 30%上升到 50%)时,损失从 1.2 下降到 0.69。在理想情况下,当网络预测图像有 100%的可能性是狗时,交叉熵损失将是 0(不妨尝试一下数学计算)。
为了计算所有训练示例(n)的交叉熵误差,我们使用这个通用公式:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_28_F4b.png
注意:重要的是要注意,你不会手动进行这些计算。理解底层的工作原理,当你设计神经网络时,会给你更好的直觉。在深度学习项目中,我们通常使用 Tensorflow、PyTorch 和 Keras 等库,其中误差函数通常是一个参数选择。
2.5.6 关于误差和权重的一个最后说明
如前所述,为了使神经网络学习,它需要尽可能最小化误差函数(0 是理想的)。误差越低,模型在预测值方面的准确性就越高。我们如何最小化误差?
让我们通过以下具有单个输入的感知器示例来了解权重和误差之间的关系:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-17.png
假设输入x = 0.3,其标签(目标预测)y = 0.8。这个感知器的预测输出(ŷ)计算如下:
ŷ[i] = w · x = w · 0.3
误差,在其最简单形式中,是通过比较预测ŷ和标签y来计算的:
error = |ŷ - y |
= |(w · x) - y |
= |w · 0.3 - 0.8|
如果你观察这个误差函数,你会注意到输入(x)和目标预测(y)是固定值。对于这些特定的数据点,它们永远不会改变。在这个方程中,我们唯一可以改变的两个变量是误差和权重。现在,如果我们想达到最小误差,我们可以调整哪个变量?正确:权重!权重充当网络需要上下调整以获得最小误差的旋钮。这就是网络学习的方式:通过调整权重。当我们绘制误差函数相对于权重的图像时,我们得到图 2.25 中所示的图表。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-25.png
图 2.25 网络通过调整权重来学习。当我们绘制误差函数相对于权重的图像时,我们得到这种类型的图表。
如前所述,我们用随机权重初始化网络。权重位于这条曲线上,我们的任务是让它沿着曲线下降到最小误差的最优值。寻找神经网络目标权重的过程是通过使用优化算法在迭代过程中调整权重值来实现的。
2.6 优化算法
训练神经网络涉及向网络展示许多示例(训练数据集);网络通过前向计算进行预测,并将预测结果与正确标签进行比较以计算误差。最后,神经网络需要调整权重(所有边上的权重)直到它得到最小误差值,这意味着最大准确度。现在,我们只需要构建能够为我们找到最优权重的算法。
2.6.1 优化是什么?
啊哈,优化!这是一个我非常喜爱,也是每个机器学习工程师(数学家也是如此)都喜爱的主题。优化是一种将问题框架化的方式,以最大化或最小化某个值。计算误差函数的最好之处在于,我们将神经网络转化为一个优化问题,我们的目标是最小化误差。
假设你想优化从家到工作的通勤。首先,你需要定义你正在优化的指标(误差函数)。也许你想优化通勤的成本、时间或距离。然后,基于这个特定的损失函数,你通过改变一些参数来工作以最小化其值。改变参数以最小化(或最大化)一个值被称为优化。如果你选择损失函数为成本,你可能选择一个需要两小时的更长通勤,或者(假设性地)你可能步行五小时以最小化成本。另一方面,如果你想优化通勤时间,你可能愿意花 50 美元乘坐出租车,将通勤时间缩短到 20 分钟。基于你定义的损失函数,你可以开始改变你的参数以获得你想要的结果。
TIP 在神经网络中,优化误差函数意味着更新权重和偏差,直到我们找到最优权重,或者产生最小误差的最佳权重值。
让我们看看我们正在尝试优化的空间:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-18.png
在最简单的神经网络形式中,一个只有一个输入的感知器,我们只有一个权重。我们可以很容易地绘制出相对于这个权重的误差(我们试图最小化的误差),如图 2.26 中的 2D 曲线(之前已展示)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-26.png
图 2.26 对于单个感知器,误差函数相对于其权重的 2D 曲线。
但如果我们有两个权重呢?如果我们绘制这两个权重的所有可能值,我们得到一个包含误差的 3D 平面(图 2.27)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-27.png
图 2.27 绘制两个权重的所有可能值得到一个 3D 误差平面。
那么对于超过两个权重的情况呢?您的网络可能拥有数百或数千个权重(因为您的网络中的每条边都有自己的权重值)。由于我们人类只能理解最多 3 个维度,当我们有 10 个权重时,我们无法可视化错误图,更不用说数百或数千个权重参数了。因此,从现在开始,我们将使用错误函数的 2D 或 3D 平面来研究错误。为了优化模型,我们的目标是搜索这个空间,找到能够实现最低可能错误的最佳权重。
我们为什么需要一个优化算法?难道我们不能只是通过尝试大量的权重值(比如 1,000 个值)直到我们得到最小误差吗?
假设我们使用了一种暴力方法,只是尝试了大量的不同可能的权重(比如说 1,000 个值),并找到了产生最小误差的权重。这能行得通吗?好吧,从理论上讲,是的。这种方法在我们只有很少的输入并且网络中只有一个或两个神经元时可能有效。让我尝试说服您这种方法不会扩展。让我们看看一个非常简单的神经网络场景。假设我们只想根据四个特征(输入)和一个包含五个神经元的隐藏层来预测房价(见图 2.28)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-28.png
图 2.28 如果我们只想根据四个特征(输入)和一个包含五个神经元的隐藏层来预测房价,我们将有从输入层到隐藏层的 20 个边缘(权重),再加上从隐藏层到输出预测的 5 个权重。
如您所见,我们从输入层到隐藏层的边缘(权重)有 20 个,再加上从隐藏层到输出预测的 5 个权重,总共需要调整 25 个权重变量以获得最佳值。如果我们为每个权重尝试 1,000 个不同的值,那么我们将有总共 1,075 种组合:
1,000 × 1,000 × … × 1,000 = 1,000²⁵ = 1,075 种组合
假设我们能够得到世界上最快的超级计算机:神威·太湖之光,其运行速度为 93 petaflops ⇒ 93 × 10¹⁵ 每秒浮点运算(FLOPs)。在最佳情况下,这台超级计算机将需要
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_34_F1.png
这是一个巨大的数字:它比宇宙存在的时间还要长。谁有那么多时间等待网络训练?记住,这是一个非常简单的神经网络,通常使用智能优化算法只需要几分钟就能训练。在现实世界中,您将构建更复杂的网络,这些网络有数千个输入和数十个隐藏层,并且您需要在几小时(或几天,有时是几周)内训练它们。因此,我们必须想出一种不同的方法来找到最佳权重。
希望我已经说服你,通过暴力优化过程不是答案。现在,让我们研究神经网络中最受欢迎的优化算法:梯度下降。梯度下降有几个变体:批量梯度下降(BGD)、随机梯度下降(SGD)和迷你批量 GD(MB-GD)。
2.6.2 批量梯度下降
梯度的通用定义(也称为导数)是,它是告诉你曲线在任意给定点的切线斜率或变化率的函数。这只是曲线斜率或陡度的花哨说法(图 2.29)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-29.png
图 2.29 梯度是描述曲线在任意给定点的切线斜率变化率的函数。
梯度下降简单来说就是迭代更新权重,以下降误差曲线的斜率,直到我们到达最小误差的点。让我们看看我们之前引入的关于权重的误差函数。在初始权重点,我们计算误差函数的导数以得到下一步的斜率(方向)。我们重复这个过程,沿着曲线向下走,直到我们达到最小误差(图 2.30)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-30.png
图 2.30 梯度下降通过增量步骤下降误差函数。
梯度下降是如何工作的?
为了可视化梯度下降的工作原理,让我们在 3D 图中绘制误差函数(图 2.31),并逐步分析这个过程。随机初始权重(起始权重)位于点 A,我们的目标是下降到误差山的目标权重值 w[1] 和 w[2],这些权重值产生最小的误差值。我们这样做的方式是通过一系列沿着曲线的步骤,直到我们得到最小误差。为了下降误差山,我们需要确定每一步的两个东西:
-
步长方向(梯度)
-
步长大小(学习率)
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-31.png
图 2.31 随机初始权重(起始权重)位于点 A。我们下降到产生最小误差值的权重值 w[1] 和 w[2],以下降误差山。
方向(梯度)
假设你站在误差山的顶部点 A。为了到达底部,你需要确定导致最深下降(具有最陡斜率)的步长方向。那么斜率是什么呢?它是曲线的导数。所以如果你站在那座山的顶部,你需要看看你周围的各个方向,找出哪个方向会导致最深下降(例如 1、2、3 或 4)。假设是方向 3;我们选择那条路。这把我们带到了点 B,然后我们重新开始这个过程(计算前向传播和误差)并找到最深下降的方向,以此类推,直到我们到达山的底部。
这个过程被称为梯度下降。通过对权重相对于误差的导数(dE / Dw)进行计算,我们得到我们应该采取的方向。现在还有一件事。梯度只决定了方向。步长的大小应该是多少?它可能是一英尺的步长,也可能是一百英尺的跳跃。这是我们接下来需要确定的事情。
步长(学习率α)
学习率是网络在下降误差山时每一步的大小,通常用希腊字母 alpha(α)表示。它是你在训练神经网络时调整的最重要超参数之一(关于这一点稍后还会讨论)。较大的学习率意味着网络将学习得更快(因为它以更大的步长下降山),而较小的步长意味着学习较慢。听起来很简单。让我们使用大的学习率,在几分钟内完成神经网络训练,而不是等待几个小时。对吧?并不完全是这样。让我们看看如果我们设置一个非常大的学习率值可能会发生什么。
在图 2.32 中,你从点 A 开始。当你沿着箭头方向迈出大步时,你不会下降误差山,而是最终到达点 B,在另一边。然后另一个大步带你到 C,以此类推。误差将持续振荡,永远不会下降。我们稍后会更多地讨论调整学习率以及如何确定误差是否在振荡。但就目前而言,你需要知道这一点:如果你使用一个非常小的学习率,网络最终会下降到山脚下,并达到最小误差。但这种训练会花费更长的时间(可能是几周或几个月)。另一方面,如果你使用一个非常大的学习率,网络可能会持续振荡,永远不会训练。所以我们通常将学习率初始化为 0.1 或 0.01,并观察网络的性能,然后进一步调整。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-32.png
图 2.32 设置一个非常大的学习率会导致误差振荡而永远不会下降。
将方向和步长结合起来
通过将方向(导数)乘以步长(学习率),我们得到每一步的权重变化:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_38_F1.png
我们添加负号是因为导数总是计算向上的斜率。由于我们需要下降山,我们就沿着斜率的反方向前进:
w[next-step] = w[current] + Δw
微积分复习:计算偏导数
导数是变化的研究。它测量了图表上特定点的曲线的陡峭程度。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-19K.png
我们想要找到曲线在确切权重点的陡峭程度。
看起来数学已经给了我们我们正在寻找的东西。在误差图表上,我们想要找到曲线在确切权重点的陡峭程度。谢谢,数学!
导数的其他术语是斜率和变化率。如果误差函数表示为 E(x),那么误差函数关于权重的导数表示为
d/dw•E(x)或简称为dE(x)/dw。
这个公式显示了当我们改变权重时,总误差将如何变化。
幸运的是,数学家为我们制定了一些规则来计算导数。由于这不是一本数学书,我们不会讨论这些规则的证明。相反,我们将从这一点开始应用这些规则来计算我们的梯度。以下是一些基本的导数规则:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-19b.png
让我们看看一个简单的函数来应用导数规则:
f(x) = 10x⁵ + 4x⁷ + 12x
我们可以应用幂、常数和求和规则来得到df/df,也称为f’ (x):
然后,f’ (x) = 50x⁴ + 28x⁶ + 12
为了理解这意味着什么,让我们绘制f(x)的图像:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-20K.png
使用简单函数应用导数规则。要得到任何点的斜率,我们可以在该点计算 f’ (x)。
如果我们想在任何一点得到斜率,我们可以在该点计算 f ’ (x)。所以 f ’ (2)给出了左侧线的斜率,而 f ’ (6)给出了第二条线的斜率。明白了吗?
对于导数的最后一个例子,让我们应用幂规则来计算 sigmoid 函数的导数:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-20b.png
注意,你不需要记住导数规则,也不需要自己计算函数的导数。多亏了出色的深度学习社区,我们有了伟大的库,只需一行代码就能为你计算这些函数。但了解底层发生的事情是有价值的。
批量梯度下降的陷阱
梯度下降是一个非常强大的算法,用于达到最小误差。但它有两个主要陷阱。
首先,并非所有成本函数都像我们之前看到的简单碗一样。可能会有洞、脊和各种不规则地形,这使得达到最小误差非常困难。考虑图 2.33,其中误差函数稍微复杂一些,有起伏。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-33.png
图 2.33 复杂的误差函数由更复杂的曲线表示,具有许多局部最小值。我们的目标是达到全局最小值。
记住,在权重初始化期间,起点是随机选择的。如果梯度下降算法的起点如图所示,误差将开始下降右侧的小山,并确实达到一个最小值。但这个最小值,称为局部最小值,并不是这个误差函数可能的最小误差值。它是算法随机开始的地方的局部山的最小值。相反,我们想要达到最低可能的误差值,即全局最小值。
第二,批量梯度下降在每一步都使用整个训练集来计算梯度。还记得这个损失函数吗?
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_42_F1.png
这意味着如果您的训练集(n)有 1 亿(1 亿)条记录,算法需要计算 1 亿条记录的总和才能迈出一步。这在计算上非常昂贵且缓慢。这就是为什么这个算法也被称为批量梯度下降——因为它在一次批次中使用了整个训练数据。
解决这两个问题的一个可能方法是随机梯度下降。我们将在下一节中探讨 SGD。
2.6.3 随机梯度下降
在随机梯度下降中,算法随机选择数据点,并逐个数据点进行梯度下降(图 2.34)。这提供了许多不同的权重起始点,并下降到所有山峰以计算它们的局部最小值。然后,所有这些局部最小值中的最小值就是全局最小值。这听起来非常直观;这就是 SGD 算法背后的概念。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-34.png
图 2.34 随机梯度下降算法随机选择曲线上的数据点,并将它们全部下降以找到局部最小值。
随机只是一个花哨的词,指的是随机。随机梯度下降可能是机器学习中最常用的优化算法,尤其是在深度学习中。虽然梯度下降测量整个训练集的损失和梯度,以向最小值迈出一步,但 SGD 在每一步随机选择训练集中的单个实例,并仅基于该单个实例计算梯度。让我们看一下 GD 和 SGD 的伪代码,以更好地理解这些算法之间的差异:
| GD | 随机 GD |
|---|
|
-
取用所有数据。
-
计算梯度。
-
更新权重并向下移动一步。
-
重复执行 n 个 epoch(迭代)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-21.pngGD 沿着误差曲线的平滑路径下降 |
-
随机打乱训练集中的样本。
-
选择一个数据实例。
-
计算梯度。
-
更新权重并向下移动一步。
-
选择另一个数据实例。
-
重复执行 n 个 epoch(训练迭代)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-22.pngSGD 沿着误差曲线的振荡路径下降 |
由于我们在批量梯度下降中在整个训练数据上计算梯度后才会进行一步,因此你可以看到误差下降的路径是平滑的,几乎是一条直线。相比之下,由于随机梯度下降(SGD)的随机(随机)性质,你看到指向全局成本最小值的路径不是直接的,如果在二维空间中可视化成本表面,它可能会出现曲折。这是因为 SGD 中,每次迭代都试图更好地拟合单个训练示例,这使得它变得非常快,但并不保证每一步都会使曲线下降。它将接近全局最小值,一旦到达那里,它将继续弹跳,永远不会稳定下来。在实践中,这并不是一个问题,因为接近全局最小值对于大多数实际应用来说已经足够好了。SGD 几乎总是比批量梯度下降表现得更好、更快。
2.6.4 小批量梯度下降
小批量梯度下降(MB-GD)是批量梯度下降(BGD)和随机梯度下降(SGD)之间的折中方案。我们不是从单个样本(SGD)或所有样本(BGD)计算梯度,而是将训练样本分成小批量,从这些小批量中计算梯度(常见的批量大小是 k = 256)。由于我们更频繁地更新权重,MB-GD 比 BGD 收敛得更快;然而,MB-GD 允许我们使用向量运算,这通常会导致比 SGD 更好的计算性能提升。
2.6.5 梯度下降要点
这里有很多内容,让我们总结一下,好吗?以下是我脑海中总结的梯度下降方法:
-
三种类型:批量、随机和小批量。
-
所有这些方法都遵循相同的概念:
-
找到最陡斜率的方向:误差相对于权重的一阶导数 dE/Dw[i]
-
设置学习率(或步长)。算法将计算斜率,但你会将学习率作为一个超参数来设置,并通过试错法进行调整。
-
将学习率从 0.01 开始,然后降至 0.001、0.0001、0.00001。你设置的学习率越低,你越有保证能够下降到最小误差(如果你无限期地训练)。由于我们没有无限的时间,0.01 是一个合理的起点,然后我们从这个值开始逐渐降低。
-
-
批量梯度下降(Batch GD)在计算所有训练数据的梯度后更新权重。当数据量很大时,这可能在计算上非常昂贵。它扩展性不好。
-
随机梯度下降(Stochastic GD)在计算训练数据单个实例的梯度后更新权重。SGD 比批量梯度下降(BGD)更快,并且通常非常接近全局最小值。
-
小批量梯度下降(Mini-batch GD)是批量梯度下降和随机梯度下降之间的折中方案,既不使用所有数据,也不使用单个实例。相反,它选取一组训练实例(称为小批量),在这些实例上计算梯度并更新权重,然后重复此过程,直到处理完所有训练数据。在大多数情况下,MB-GD 是一个很好的起点。
-
batch_size是一个需要调整的超参数。这一点将在第四章的超参数调整部分再次提到。但通常,你可以从 batch_size = 32, 64, 128, 256 开始实验。 -
不要将 batch_size 与 epochs 混淆。一个 epoch 是在所有训练数据上的完整循环。批次数是在我们计算梯度的组中训练样本的数量。例如,如果我们有 1,000 个样本在训练数据中,并且将 batch_size 设置为 256,那么 epoch 1 = 256 个样本的 batch 1 加上 batch 2(256 个样本)加上 batch 3(256 个样本)加上 batch 4(232 个样本)。
-
最后,你需要知道,多年来已经使用了大量的梯度下降变体,这是一个非常活跃的研究领域。其中一些最受欢迎的改进包括
-
Nesterov 加速梯度
-
RMSprop
-
Adam
-
Adagrad
不要担心这些优化器。在第四章中,我们将更详细地讨论调整技术来改进你的优化器。
我知道这听起来很多,但请继续听。这些都是我想让你从本节记住的主要事情:
-
梯度下降是如何工作的(斜率加步长)
-
批量、随机和迷你批量的梯度下降之间的区别
-
你将调整的 GD 超参数:学习率和 batch_size
如果你已经掌握了这些,你就可以进入下一节了。而且不要过于担心超参数调整。我将在接下来的章节中更详细地介绍网络调整,并在本书中的几乎所有项目中。
2.7 反向传播
反向传播是神经网络学习的基础。到目前为止,你已经了解到训练神经网络通常通过重复以下三个步骤来完成:
-
前馈:获取线性组合(加权求和),并应用激活函数以获得输出预测(ŷ):
ŷ = σ · W^((3)) · σ · W^((2)) · σ · W^((1)) · (x)
-
将预测与标签进行比较,以计算误差或损失函数:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_42_F1.png
-
使用梯度下降优化算法来计算Δw,以优化误差函数:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_38_F1.png
通过网络反向传播Δw 以更新权重:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-23.png
在本节中,我们将深入探讨最后一步:反向传播。
2.7.1 什么是反向传播?
反向传播,或反向传递,意味着传播误差相对于每个特定权重的导数
dE/dw*[i]*
从最后一层(输出)回到第一层(输入)以调整权重。通过从预测节点(ŷ)向后传播Δw,穿过隐藏层并回到输入层,权重得到更新:
(w*[next-step]* = w*[current]* + Δw)
这将使误差沿着误差山下降一步。然后循环再次开始(步骤 1 到 3)以更新权重并将误差再下降一步,直到我们达到最小误差。
当我们只有一个权重时,反向传播可能听起来更清晰。我们只需通过添加 Δw 到旧权重 w*[new]* = w- α•dE/dw*[i]* 来调整权重。
但当我们有一个具有许多权重变量的多层感知器(MLP)网络时,事情就会变得复杂。为了使这一点更清晰,请考虑图 2.35 中的场景。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-35.png
图 2.35 当我们有一个具有许多权重变量的多层感知器(MLP)网络时,反向传播变得复杂。
我们如何计算总误差相对于 w[13] 的变化率 dE/dw*[13]*?记住,dE/dw*[13]*基本上是说,“当我们改变参数 w[13] 时,总误差会有多少变化?”
我们通过在误差函数上应用导数规则学习了如何计算 dE/dw*[21]*。这很简单,因为 w[21] 是直接连接到误差函数的。但为了计算总误差相对于权重直到输入的导数,我们需要一个微积分规则,称为链式法则。
微积分复习:导数的链式法则
再次回到微积分。还记得我们之前列出的导数规则吗?其中最重要的规则之一就是链式法则。让我们深入探讨它,看看它在反向传播中的实现方式:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_45_F1.png
链式法则是计算由其他函数内部函数组成的函数的导数的公式。它也被称为外-内规则。看看这个:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_45_F2.png
链式法则指出,“在函数组合中,导数只是相乘。”这在实现反向传播时对我们非常有用,因为前向传播只是组合了一组函数,而反向传播是在这个函数的每一部分上求导。
为了在反向传播中实现链式法则,我们只需要将多个偏导数相乘,以得到误差效应一直回到输入层。下面是如何工作的——但首先,记住我们的目标是反向传播误差直到输入层。所以在这个例子中,我们想要计算 dE/dx,这是总误差对输入 (x) 的影响:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-unnumb-24K.png
我们在这里所做的只是将上游梯度乘以局部梯度,直到我们得到目标值。
图 2.36 显示了反向传播如何使用链式法则将梯度反向流动通过网络。让我们应用链式法则来计算误差相对于第一个输入的第三个权重 w1,3(1) 的导数,其中 (1) 表示第 1 层,w1,3 表示节点编号 1 和权重编号 3:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/02_46_F1.png
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-36.png
图 2.36 反向传播使用链式法则将梯度反向流动通过网络。
方程式一开始可能看起来很复杂,但我们实际上只是在从输出节点开始,将边的偏导数乘到输入节点。所有这些符号都使得这个方程看起来很复杂,但一旦你理解了如何读取 w*[1,3]**^((1))* ,反向传播方程看起来就像这样:
反向传播到边 w*[1,3]**^((1))* 的错误 = 边 4 上的错误效应 × 边 3 上的错误效应 × 边 2 上的错误效应 × 目标边的错误效应
这就是神经网络用来更新权重以最佳拟合我们问题的反向传播技术。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/2-37.png
图 2.37 前向传播计算输出预测(左)。反向传播将误差的导数反向传播以更新其权重(右)。
2.7.2 反向传播要点
-
反向传播是神经元的学习过程。
-
反向传播反复调整网络中连接(权重)的权重,以最小化成本函数(实际输出向量与期望输出向量之间的差异)。
-
由于权重调整的结果,隐藏层开始代表除了输入层中代表的特征之外的重要特征。
-
对于每一层,目标是找到一组权重,确保对于每个输入向量,产生的输出向量与(或接近)期望的输出向量相同。产生的输出和期望输出之间的差异称为误差函数。
-
反向传播(图 2.37)从网络的末端开始,递归地应用链式法则来计算梯度,将错误反向传播或反馈,一直计算到网络的输入端,然后更新权重。
-
再次强调,典型神经网络问题的目标是发现一个最佳拟合我们数据的模型。最终,我们希望通过选择最佳的一组权重参数来最小化成本或损失函数。
摘要
-
感知器对于可以用一条直线(线性操作)分离的数据集效果良好。
-
无法用直线建模的非线性数据集需要一个包含许多神经元的更复杂的神经网络。通过层叠神经元创建多层感知器。
-
网络通过重复三个主要步骤来学习:前向传播、计算误差和优化权重。
-
参数是网络在训练过程中更新的变量,如权重和偏差。这些在训练过程中由模型自动调整。
-
超参数是你调整的变量,例如层数、激活函数、损失函数、优化器、早停和学习率。我们在训练模型之前调整这些参数。
3 卷积神经网络
本章涵盖
-
使用 MLP 对图像进行分类
-
使用 CNN 架构对图像进行分类
-
理解在彩色图像上的卷积
在之前,我们讨论了人工神经网络(ANNs),也称为多层感知器(MLPs),它们基本上是由具有可学习权重和偏差的神经元层堆叠而成的。每个神经元接收一些输入,这些输入通过它们的权重相乘,并通过激活函数应用非线性。在本章中,我们将讨论卷积神经网络(CNNs),它们被认为是 MLP 架构的一种演变,在图像处理方面表现更好。
本章的高级布局如下:
-
使用 MLP 进行图像分类 —— 我们将从一个使用 MLP 拓扑进行图像分类的小型项目开始,检查常规神经网络架构如何处理图像。你将了解 MLP 架构在处理图像时的缺点以及为什么我们需要一个新的、创新的神经网络架构来完成这项任务。
-
理解 CNN —— 我们将探索卷积网络,了解它们如何从图像中提取特征并对对象进行分类。你将了解 CNN 的三个主要组成部分:卷积层、池化层和全连接层。然后我们将应用这些知识在另一个小型项目中使用 CNN 对图像进行分类。
-
彩色图像 —— 我们将比较计算机如何看到彩色图像与灰度图像,以及卷积如何在彩色图像上实现。
-
图像分类项目 —— 我们将应用本章所学的一切,在一个端到端的图像分类项目中使用 CNN 对彩色图像进行分类。
网络如何学习和优化参数的基本概念在 MLP 和 CNN 中是相同的:
-
架构 —— MLP 和 CNN 由堆叠在一起的神经元层组成。CNN 具有不同的结构(卷积层与全连接层),正如我们将在接下来的章节中看到的。
-
权重和偏差 —— 在卷积层和全连接层中,推理工作方式相同。两者都有初始随机生成的权重和偏差,其值由网络学习。它们之间的主要区别在于,MLP 中的权重以向量形式存在,而在卷积层中,权重以卷积滤波器或核的形式存在。
-
超参数 —— 与 MLP 一样,当我们设计 CNN 时,我们总会指定误差函数、激活函数和优化器。前几章中解释的所有超参数保持不变;我们将添加一些特定于 CNN 的新超参数。
-
训练 – 两个网络以相同的方式进行学习。首先,它们执行前向传播以获得预测;其次,它们将预测与真实标签进行比较以获得损失函数(y − ŷ);最后,它们使用梯度下降优化参数,将错误反向传播到所有权重,并更新它们的值以最小化损失函数。
准备好了吗?让我们开始吧!
3.1 使用 MLP 进行图像分类
让我们回顾一下第二章中的 MLP 架构。神经元堆叠在彼此之上,通过权重连接。MLP 架构由一个输入层、一个或多个隐藏层和一个输出层组成(图 3.1)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-1.png
图 3.1 MLP 架构由通过权重连接的神经元层组成。
本节将利用您从第二章了解的关于 MLP 的知识,使用 MNIST 数据集解决图像分类问题。这个分类器的目标将是将 0 到 9 的数字图像(10 个类别)进行分类。首先,让我们看看我们 MLP 架构的三个主要组件(输入层、隐藏层和输出层)。
3.1.1 输入层
当我们处理 2D 图像时,在将它们输入网络之前,我们需要对它们进行预处理,使其成为网络可以理解的形式。首先,让我们看看计算机是如何感知图像的。在图 3.2 中,我们有一个宽度为 28 像素、高度为 28 像素的图像。计算机将这个图像视为一个 28×28 的矩阵,像素值范围从 0 到 255(0 为黑色,255 为白色,介于两者之间的为灰度)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-2.png
图 3.2 计算机将这个图像视为一个像素值范围在 0 到 255 之间的 28×28 矩阵。
由于 MLP 只接受维度为(1,p)的 1D 向量作为输入,因此它们不能接受维度为(x, y)的原始 2D 图像矩阵。为了将图像放入输入层,我们首先需要将我们的图像转换成一个包含图像中所有像素值的大向量,其维度为(1,p)。这个过程称为图像展平。在这个例子中,这个图像的总像素数(n)为 28×28=784。然后,为了将这个图像输入到我们的网络中,我们需要将(28×28)矩阵展平成一个长向量,其维度为(1,784)。输入向量看起来如下:
x = [行1, 行2, 行3, …, 行 28]
也就是说,在这个例子中,输入层将包含总共 784 个节点:x[1],x[2],…,x[784]。
可视化输入向量
为了帮助可视化扁平化的输入向量,让我们看看一个更小的矩阵(4,4):
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-1.png
输入(x)是一个维度为(1,16)的扁平向量:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-2KEY.png
因此,如果我们用 0 表示黑色,用 255 表示白色,输入向量将如下所示:
输入 = [0, 255, 255, 255, 0, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0]
这是我们在 Keras 中展平输入图像的方法:
from keras.models import Sequential ❶
from keras.layers import Flatten ❷
model = Sequential() ❸
model.add( Flatten(input_shape = (28,28) )) ❹
❶ 如前所述,导入 Keras 库
❷ 导入一个名为 Flatten 的层,将图像矩阵转换为向量
❸ 定义模型
❹ 添加 Flatten 层,也称为输入层
Keras 中的Flatten层为我们处理这个过程。它将 2D 图像矩阵输入转换为 1D 向量。请注意,Flatten层必须提供一个参数值,即输入图像的形状。现在图像已经准备好被输入到神经网络中。
接下来是什么?隐藏层。
3.1.2 隐藏层
如前一章所述,神经网络可以有一个或多个隐藏层(技术上,可以有任意多个)。每一层有一个或多个神经元(同样,可以有任意多个)。作为神经网络工程师,你的主要任务是设计这些层。为了这个例子,让我们假设你决定任意设计网络,使其有两个隐藏层,每个隐藏层有 512 个节点–并且别忘了为每个隐藏层添加 ReLU 激活函数。
选择激活函数
在第二章中,我们详细讨论了不同类型的激活函数。作为一名深度学习工程师,当你构建你的网络时,你将经常面临许多不同的选择。选择最适合你正在解决的问题的激活函数是这些选择之一。虽然没有一种单一的答案适合所有问题,但在大多数情况下,ReLU 函数在隐藏层中表现最佳;对于大多数类别互斥的分类问题,softmax 函数通常在输出层是一个好的选择。softmax 函数为我们提供了输入图像描述(n)个类别之一的概率。
如前一章所述,让我们添加两个全连接层(也称为密集层),使用 Keras:
from keras.layers import Dense ❶
model.add(Dense(512, activation = 'relu')) ❷
model.add(Dense(512, activation = 'relu')) ❷
❶ 导入 Dense 层
❷ 添加两个每个有 512 个节点的 Dense 层
3.1.3 输出层
输出层相当直接。在分类问题中,输出层的节点数应该等于你试图检测的类别数。在这个问题中,我们正在对 10 个数字(0,1,2,3,4,5,6,7,8,9)进行分类。然后我们需要添加一个包含 10 个节点的最后一个Dense层:
model.add(Dense(10, activation = ‘softmax’))
3.1.4 整合所有内容
当我们将所有这些层组合在一起时,我们得到一个如图 3.3 所示的神经网络。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-3.png
图 3.3 通过组合输入、隐藏和输出层我们创建的神经网络
下面是它在 Keras 中的样子:
from keras.models import Sequential ❶
from keras.layers import Flatten, Dense ❷
model = Sequential() ❸
model.add( Flatten(input_shape = (28,28) )) ❹
model.add(Dense(512, activation = 'relu')) ❺
model.add(Dense(512, activation = 'relu')) ❺
model.add(Dense(10, activation = 'softmax')) ❻
model.summary() ❼
❶ 导入 Keras 库
❷ 导入 Flatten 层以将图像矩阵转换为向量
❸ 定义神经网络架构
❹ 添加 Flatten 层
❺ 添加两个每个有 512 个节点的隐藏层。建议在隐藏层中使用 ReLU 激活函数。
❻ 添加一个包含 10 个节点的输出 Dense 层。对于多类分类问题,建议在输出层使用 softmax 激活函数。
❼ 打印模型架构摘要
当你运行这段代码时,你会看到如图 3.4 所示的模型摘要。
你可以看到,正如之前讨论的那样,flatten 层的输出是一个包含 784 个节点的向量,因为每个 28 × 28 的图像中都有 784 个像素。按照设计,隐藏层每个都产生 512 个节点;最后,输出层(dense_3)产生一个包含 10 个节点的层。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-4.png
图 3.4 模型摘要
参数#字段表示每个层产生的参数(权重)的数量。这些是在训练过程中将被调整和学习的权重。它们的计算如下:
-
flatten 层之后的参数数 = 0,因为这个层只是将图像展平成向量以供输入层使用。权重尚未添加。
-
层 1 之后的参数数 = (输入层中的 784 个节点)×(隐藏层 1 中的 512 个节点)+(到偏置的 512 个连接)= 401,920。
-
层 2 之后的参数数 = (隐藏层 1 中的 512 个节点)×(隐藏层 2 中的 512 个节点)+(到偏置的 512 个连接)= 262,656。
-
层 3 之后的参数数=(隐藏层 2 中的 512 个节点)×(输出层中的 10 个节点)+(到偏置的 10 个连接)= 5,130。
-
网络中的总参数数 = 401,920 + 262,656 + 5,130 = 669,706。
这意味着在这个小小的网络中,我们总共有 669,706 个参数(权重和偏置)需要网络学习,并调整其值以优化误差函数。对于这样一个小的网络来说,这是一个巨大的数字。你可以看到,如果我们添加更多的节点和层或使用更大的图像,这个数字会如何失控。这是我们将在下面讨论的 MLPs 的两个主要缺点之一。
MLPs 与 CNNs
如果你将示例 MLP 在 MNIST 数据集上训练,你会得到相当好的结果(与 CNNs 的 99%相比,接近 96%的准确率)。但 MLPs 和 CNNs 通常不会产生可比较的结果。MNIST 数据集是特殊的,因为它非常干净且预处理得很好。例如,所有图像都有相同的大小,并且位于一个 28 × 28 像素网格的中心。此外,MNIST 数据集只包含灰度图像。如果图像有颜色或数字倾斜或未居中,这将是一个更困难的任务。
如果你尝试使用稍微复杂一些的数据集,比如 CIFAR-10,正如我们在本章末尾的项目中将要做的,网络的表现将非常差(大约 30-40%的准确率)。在更复杂的数据集上表现会更差。在混乱的真实世界图像数据中,CNNs 确实优于 MLPs。
3.1.5 MLPs 处理图像的缺点
我们几乎准备好讨论本章的主题:CNNs。但首先,让我们讨论 MLPs 中的两个主要问题,卷积网络旨在解决这些问题。
空间特征损失
将二维图像展平为 1D 向量输入会导致丢失图像的空间特征。正如我们在前面的迷你项目中看到的那样,在将图像输入到 MLP 的隐藏层之前,我们必须将图像矩阵展平为 1D 向量。这意味着丢弃图像中包含的所有 2D 信息。将输入视为没有特殊结构的简单数字向量可能对 1D 信号有效;但在二维图像中,这会导致信息丢失,因为当网络试图寻找模式时,它不会将像素值相互关联。MLP 没有意识到这些像素数字最初是按网格空间排列的,并且它们彼此相连。另一方面,CNN 不需要展平的图像。我们可以将原始像素图像矩阵输入到 CNN 网络中,CNN 将理解彼此靠近的像素比彼此远离的像素关系更紧密。
让我们简化一下,以便更多地了解图像中空间特征的重要性。假设我们正在尝试教一个神经网络识别正方形的形状,假设像素值 1 是白色,0 是黑色。当我们在一个黑色背景上画一个白色正方形时,矩阵将看起来像图 3.5。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-5.png
图 3.5 如果像素值 1 是白色,0 是黑色,这是我们识别正方形时矩阵的样子。
由于 MLP 以 1D 向量作为输入,我们必须将 2D 图像展平为 1D 向量。图 3.5 的输入向量看起来是这样的:
输入向量 = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
当训练完成时,网络将学会仅在输入节点 x1、x2、x5 和 x6 被激活时识别正方形。但是,当我们有如图 3.6 所示的新图像,其中正方形形状位于图像的不同区域时,会发生什么?
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-6.png
图 3.6 图像不同区域的正方形形状
MLP 将无法知道这些是正方形的形状,因为网络没有将正方形形状作为特征学习。相反,它学习了当被激活时可能导致正方形形状的输入节点。如果我们想让我们的网络学习正方形,我们需要在图像的各个位置放置大量的正方形形状。你可以看到这种解决方案对于复杂问题来说不会扩展。
特征学习的另一个例子是:如果我们想教一个神经网络识别猫,那么理想情况下,我们希望网络学习猫的所有形状特征,无论它们出现在图像的哪个位置(耳朵、鼻子、眼睛等)。这只有在网络将图像视为一组像素时才会发生,当这些像素彼此靠近时,它们关系密切。
CNN 的学习机制将在本章中详细解释。但图 3.7 显示了网络如何在其层中学习特征。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-7.png
图 3.7 CNN 通过其层学习图像特征。
完全连接(密集)层
多层感知器(MLPs)由密集层组成,这些层之间是完全连接的。完全连接意味着一个层中的每个节点都与前一层的所有节点以及下一层的所有节点相连。在这种情况下,每个神经元都有参数(权重)需要从前一层的每个神经元进行训练。虽然这对 MNIST 数据集来说不是大问题,因为图像尺寸真的很小(28 × 28),但当我们尝试处理更大的图像时会发生什么?例如,如果我们有一个 1,000 × 1,000 像素的图像,它将为第一隐藏层中的每个节点产生一百万个参数。所以如果第一隐藏层有 1,000 个神经元,那么即使在如此小的网络中,这也会产生十亿个参数。你可以想象在只有第一层之后优化十亿个参数的计算复杂性。当我们有数十或数百层时,这个数字将急剧增加。这可能会很快失控,并且不会按比例增长。
另一方面,如图 3.8 所示,CNN 是局部连接层:节点仅与前一层的节点的小子集相连。局部连接层使用的参数比密集连接层少得多,正如你将看到的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-8.png
图 3.8(左)所有神经元都与图像的所有像素相连的完全连接神经网络。(右)局部连接网络,其中只有一小部分像素与每个神经元相连。这些子集被称为滑动窗口。
所有这些意味着什么?
将二维图像矩阵展平为 1D 向量所造成的信息损失以及完全连接层在处理更大图像时的计算复杂性表明,我们需要一种全新的图像输入处理方式,其中二维信息不会完全丢失。这就是卷积网络发挥作用的地方。CNN 接受完整的图像矩阵作为输入,这显著帮助网络理解像素值中包含的图案。
3.2 CNN 架构
正规神经网络包含多个层,允许每一层依次找到更复杂的特征,这正是卷积神经网络(CNNs)的工作方式。卷积的第一层学习一些基本特征(边缘和线条),下一层学习稍微复杂一些的特征(如圆形、正方形等),接下来的层找到更复杂的特征(如面部的一部分、汽车轮子、狗的胡须等),依此类推。你很快就会看到这个演示。现在,要知道 CNN 架构遵循与神经网络相同的模式:我们在隐藏层中堆叠神经元;权重在网络训练期间随机初始化并学习;我们应用激活函数,计算误差(y − ŷ),并将误差反向传播以更新权重。这个过程是相同的。不同之处在于,我们在特征学习部分使用卷积层而不是常规的完全连接层。
3.2.1 整体概念
在我们详细探讨 CNN 架构之前,让我们先退一步,看看整体情况(图 3.9)。还记得我们在第一章中讨论过的图像分类流程吗?
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-9.png
图 3.9 图像分类流程包括四个组件:数据输入、数据预处理、特征提取和机器学习算法。
在深度学习(DL)之前,我们通常手动从图像中提取特征,然后将得到的特征向量输入到分类器(如 SVM 等常规机器学习算法)中。有了神经网络提供的魔力,我们可以用神经网络(MLP 或 CNN)来替换图 3.9 中步骤 3 的手动工作,该神经网络既能进行特征学习又能进行分类(步骤 3 和 4)。
我们在数字分类项目中看到,如何使用 MLP 来学习特征并对图像进行分类(步骤 3 和 4 合并)。结果证明,我们与全连接层的问题并不在于分类部分——全连接层在这方面做得很好。我们的问题是全连接层处理图像以学习特征的方式。让我们有点创意:我们将保留有效部分,并对无效部分进行修改。全连接层在特征提取(步骤 3)方面做得不好,所以让我们用局部连接层(卷积层)来替换它。另一方面,全连接层在分类提取的特征(步骤 4)方面做得很好,所以让我们保留它们用于分类部分。
CNN 的高级架构看起来像图 3.10:
-
输入层
-
用于特征提取的卷积层
-
用于分类的全连接层
-
输出预测
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-10.png
图 3.10 CNN 架构包括以下部分:输入层、卷积层、全连接层和输出预测。
记住,我们还在讨论整体情况。我们很快就会深入到每个组件。在图 3.10 中,假设我们正在构建一个 CNN 来将图像分类为两个类别:数字 3 和 7。看看图中的内容,并按照以下步骤进行:
-
将原始图像输入到卷积层中。
-
图像通过 CNN 层来检测模式和提取称为特征图的特征。这一步骤的输出随后被展平成一个包含图像学习特征的向量。请注意,图像的维度在每一层之后都会缩小,特征图的数量(层深度)增加,直到我们在特征提取部分的最后一层得到一个长数组的小特征。从概念上讲,你可以将这一步骤视为神经网络学习表示原始图像的更抽象特征。
-
将展平的特征向量输入到全连接层中,以对图像提取的特征进行分类。
-
神经网络激活代表图像正确预测的节点。注意,在这个例子中,我们正在对两个类别(3 和 7)进行分类。因此,输出层将有两个节点:一个代表数字 3,另一个代表数字 7。
定义 神经网络的基本思想是神经元从输入中学习特征。在 CNN 中,特征图是应用于前一层的某个滤波器的输出。它被称为特征图,因为它表示了图像中某种特征的位置。CNN 寻找的特征包括直线、边缘,甚至是物体。每当它们发现这些特征时,它们就会将它们报告给特征图。每个特征图都在寻找特定的事物:一个可能是在寻找直线,另一个可能是在寻找曲线。
3.2.2 深入了解特征提取
你可以将特征提取步骤想象成将大图像分割成包含特征的小块,并将它们堆叠成一个向量。例如,数字 3 的图像是一个图像(深度 = 1),它被分割成包含数字 3 的特定特征的小图像(图 3.11)。如果它被分割成四个特征,那么深度等于 4。随着图像通过 CNN 层,它在维度上缩小,层变得更深,因为它包含了更多的小特征图像。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-11.png
图 3.11 图像被分割成包含独特特征的小图像。
注意,这只是一个比喻,用来帮助可视化特征提取过程。CNN 并非字面意义上将图像分割成碎片。相反,它们提取有意义的特征,这些特征将这个对象与其他训练集中的图像区分开来,并将它们堆叠在一个特征数组中。
3.2.3 深入了解分类
在特征提取完成后,我们添加全连接层(一个常规的 MLP)来查看特征向量,并说,“第一个特征(顶部)看起来像一条边缘:这可能是一个 3,或者 7,或者可能是一个难看的 2。我不确定;让我们看看第二个特征。嗯,这肯定不是一个 7,因为它有一个曲线,”等等,直到 MLP 确信图像是数字 3。
CNN 学习模式的方式
重要的是要注意,CNN 并不是直接在一层中将图像输入转换为特征向量。这通常发生在数十或数百层中,正如你将在本章后面看到的那样。特征学习过程在每个隐藏层之后逐步发生。因此,第一层通常学习非常基本的特征,如线条和边缘,第二层将这些线条组装成可识别的形状、角落和圆形。然后,在更深的层中,网络学习更复杂的形状,如人手、眼睛、耳朵等。例如,这里是一个 CNN 学习人脸的简化版本。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-3.png
CNN 学习人脸的简化版本
你可以看到,早期层检测图像中的模式以学习低级特征,如边缘,而后期层检测模式中的模式以学习更复杂的特征,如面部的一部分,然后是模式中的模式中的模式,等等:
输入图像
-
层 1 ⇒ 模式
-
层 2 ⇒ 模式中的模式
-
层 3 ⇒ 模式中的模式中的模式
…等等
当我们在后面的章节中讨论更高级的 CNN 架构时,这个概念将非常有用。现在,你知道在神经网络中,我们堆叠隐藏层来相互学习模式,直到我们有一个包含识别图像的具有意义的特征数组。
3.3 CNN 的基本组件
不再拖延,让我们讨论 CNN 架构的主要组件。你几乎在每一个卷积网络(图 3.12)中都会看到三种主要类型的层:
-
卷积层(CONV)
-
池化层(POOL)
-
全连接层(FC)
CNN 文本表示
图 3.12 中架构的文本表示如下:
CNN 架构:输入 ⇒ CONV ⇒ RELU ⇒ POOL ⇒ CONV ⇒ RELU ⇒ POOL ⇒ FC ⇒ SOFTMAX
注意,ReLU 和 softmax 激活函数并不是真正的独立层——它们是前一层中使用的激活函数。它们在文本表示中这样显示的原因是为了指出 CNN 设计者正在卷积层中使用 ReLU 激活函数,在全连接层中使用 softmax 激活函数。因此,这代表了一个包含两个卷积层和一个全连接层的 CNN 架构。你可以添加你认为合适的任意数量的卷积层和全连接层。卷积层用于特征学习或提取,而全连接层用于分类。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-12.png
图 3.12 卷积网络的基本组件包括卷积层和池化层用于特征提取,以及全连接层用于分类。
现在我们已经看到了卷积网络的完整架构,让我们深入探讨每种层类型的工作原理。然后在本节的最后,我们将它们全部组合起来。
3.3.1 卷积层
卷积层是卷积神经网络的核心构建块。卷积层就像一个特征查找窗口,逐像素地在图像上滑动以提取识别图像中对象的具有意义的特征。
什么是卷积?
在数学中,卷积是两个函数的操作,以产生第三个修改后的函数。在 CNN 的上下文中,第一个函数是输入图像,第二个函数是卷积滤波器。我们将执行一些数学运算以产生具有新像素值的修改后的图像。
让我们放大查看第一个卷积层,看看它是如何处理图像的(图 3.13)。通过在输入图像上滑动卷积滤波器,网络将图像分解成小块,并单独处理这些块以组装修改后的图像,即特征图。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-13.png
图 3.13 一个 3 × 3 的卷积滤波器在输入图像上滑动。
记住这个图,以下是一些关于卷积滤波器的事实:
-
中间的 3 × 3 小矩阵是卷积滤波器,也称为核。
-
核在原始图像上逐像素滑动,并进行一些数学计算,以获取下一层“卷积”的新图像的值。滤波器卷积的图像区域称为感受野(见图 3.14)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-14.png
图 3.14 核在原始图像上逐像素滑动,并在下一层计算卷积图像。卷积区域称为感受野。
什么是核值?在卷积神经网络(CNNs)中,卷积矩阵是权重。这意味着它们也是随机初始化的,值是由网络学习的(所以你不必担心为其分配值)。
卷积操作
从我们对多层感知器(MLPs)的讨论中,数学应该看起来很熟悉。记得我们是如何将输入乘以权重并将它们全部相加以得到加权总和的吗?
加权总和 = x[1] · w[1] + x[2] · w[2] + x[3] · w[3] + … + x[n] · w[n] + b
我们在这里做同样的事情,只是在 CNN 中,神经元和权重以矩阵形状结构化。因此,我们将感受野中的每个像素与卷积滤波器中的对应像素相乘,并将它们全部相加,以得到新图像中中心像素的值(图 3.15)。这与我们在第二章中看到的相同的矩阵点积:
(93 × -1) + (139 × 0) + (101 × 1) + (26 × -2) + (252 × 0) + (196 × 2) + (135 × -1) + (240 × 0) + (48 × 1) = 243
滤波器(或核)在整个图像上滑动。每次,我们逐个元素地乘以每个对应的像素,然后将它们全部相加,以创建一个具有新像素值的新图像。这个卷积后的图像称为特征图或激活图。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-15.png
图 3.15 将感受野中的每个像素与卷积滤波器中的对应像素相乘,并将它们相加,得到新图像中中心像素的值。
应用滤波器以学习特征
让我们不要偏离最初的目标。我们做所有这些是为了让网络从图像中提取特征。应用滤波器是如何达到这个目标的?在图像处理中,滤波器用于过滤掉不需要的信息或放大图像中的特征。这些滤波器是数字矩阵,它们与输入图像卷积以修改它。看看这个边缘检测滤波器:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-4.png
当这个核(K)与输入图像 F(x,y)进行卷积时,它创建了一个新的卷积图像(特征图),放大了边缘。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-5.png
在图像上应用边缘检测核
为了理解卷积是如何发生的,让我们放大图像的一小部分。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-6KEY.png
在输入图像上应用边缘核的计算
这张图像显示了图像某一区域的卷积计算,以计算一个像素的值。我们通过将核在输入图像上逐像素滑动并应用相同的卷积过程来计算所有像素的值。
这些核通常被称为权重,因为它们决定了像素在形成新输出图像中的重要性。类似于我们在关于 MLP 和权重的讨论中提到的,这些权重代表了特征在输出中的重要性。在图像中,输入特征是像素值。
可以应用其他过滤器来检测不同类型的特征。例如,一些过滤器检测水平边缘,其他检测垂直边缘,还有一些检测更复杂的形状,如角,等等。关键是这些过滤器在卷积层中的应用会产生我们之前讨论过的特征学习行为:首先学习简单的特征,如边缘和直线,然后更深的层学习更复杂的特征。
我们基本上完成了滤波器的概念。这就是全部内容!
现在,让我们整体看看卷积层:每个卷积层包含一个或多个卷积滤波器。每个卷积层的滤波器数量决定了下一层的深度,因为每个滤波器都会产生自己的特征图(卷积图像)。让我们看看 Keras 中的卷积层,看看它们是如何工作的:
from keras.layers import Conv2D
model.add(Conv2D(filters=16, kernel_size=2, strides='1', padding='same',
activation='relu'))
就这样。一行代码就创建了一个卷积层。我们将在本章后面看到这条线在完整代码中的位置。让我们专注于卷积层。从代码中可以看出,卷积层有五个主要参数。正如第二章所述,我们建议在神经网络的隐藏层中使用 ReLU 激活函数。这样,一个参数就解决了。现在,让我们解释剩下的四个超参数,它们控制输出体积的大小和深度:
-
过滤器:每层的卷积滤波器数量。这代表了其输出的深度。
-
核大小:卷积滤波器矩阵的大小。大小各异:2 × 2,3 × 3,5 × 5。
-
步长。
-
填充。
我们将在下一节讨论步长和填充。但现在,让我们看看这四个超参数中的每一个。
注意:正如你在第二章关于深度学习的学习中了解到的,超参数是在配置神经网络以改进性能时调整(增加和减少)的旋钮。
卷积层中的滤波器数量
每个卷积层有一个或多个滤波器。为了理解这一点,让我们回顾第二章中的 MLP。记得我们是如何在隐藏层中堆叠神经元的,每个隐藏层有 n 个神经元(也称为隐藏单元)?图 3.16 展示了第二章中的 MLP 图。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-16.png
图 3.16 神经元在隐藏层中堆叠,每个隐藏层有 n 个神经元(隐藏单元)。
类似地,在 CNN 中,卷积层是隐藏层。为了增加隐藏层中的神经元数量,我们增加卷积层中的核数量。每个核单元被视为一个神经元。例如,如果我们卷积层中有 3 × 3 的核,这意味着在这一层我们有 9 个隐藏单元。当我们添加另一个 3 × 3 核时,我们就有 18 个隐藏单元。再添加一个,我们就有 27 个,以此类推。因此,通过增加卷积层中的核数量,我们增加了隐藏单元的数量,这使得我们的网络更加复杂,能够检测更复杂的模式。当我们向 MLP 的隐藏层添加更多的神经元(隐藏单元)时,情况也是如此。图 3.17 展示了 CNN 层,显示了核数量的概念。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-17.png
图 3.17 展示 CNN 层核数量概念的表示
核大小
记住,卷积滤波器也称为核。它是一个权重矩阵,在图像上滑动以提取特征。核大小指的是卷积滤波器的维度(宽度乘以高度;图 3.18)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-18.png
图 3.18 核大小指的是卷积滤波器的维度。
kernel_size 是你在构建卷积层时将要设置的超参数之一。像大多数神经网络超参数一样,没有单一的最好答案适用于所有问题。直观地说,较小的滤波器会捕捉到图像的非常精细的细节,而较大的滤波器会错过图像中的微小细节。
记住,滤波器包含网络将要学习的权重。因此,从理论上讲,kernel_size 越大,网络越深,这意味着它学得越好。然而,这伴随着更高的计算复杂度,可能会导致过拟合。
内核滤波器几乎总是正方形,大小从最小的 2 × 2 到最大的 5 × 5 不等。从理论上讲,你可以使用更大的滤波器,但这并不推荐,因为它会导致丢失重要的图像细节。
调整
我不希望你被所有的超参数调整所压倒。深度学习实际上是一门艺术,也是一门科学。我无法强调这一点:作为深度学习工程师,你大部分的工作将不是构建实际的算法,而是构建你的网络架构和设置,进行实验,调整你的超参数。今天,大量的研究都集中在尝试为给定类型的问题找到 CNN 的最佳拓扑结构和参数。幸运的是,调整超参数的问题不必像看起来那么困难。在整个书中,我将指出使用超参数的良好起点,并帮助你培养评估你的模型和分析其结果的本能,以了解你需要调整哪个旋钮(超参数)(增加或减少)。
步长和填充
你通常会一起考虑这两个超参数,因为它们都控制卷积层输出的形状。让我们看看如何:
-
步长 – 滤波器在图像上滑动的量。例如,每次滑动一个像素,步长值是 1。如果我们想每次跳过两个像素,步长值就是 2。步长为 3 或更大的情况在实践中很少见。跳过像素会产生较小的空间输出体积。
步长为 1 将使输出图像大致与输入图像的宽度和高度相同,而步长为 2 将使输出图像大致是输入图像大小的一半。我说“大致”是因为这取决于你如何设置填充参数来处理图像的边缘。
-
填充 – 通常称为零填充,因为我们会在图像的边缘添加零(如图 3.19 所示)。填充最常用于允许我们保留输入体积的空间大小,以便输入和输出的宽度和高度相同。这样,我们可以在不必要缩小体积的高度和宽度的情况下使用卷积层。这对于构建更深的网络很重要,因为否则,高度/宽度会随着我们进入更深的层而缩小。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-19.png
图 3.19 零填充在图像的边缘添加零。填充 = 2 在边缘添加两层零。
注意:使用步长和填充超参数的目标是以下两个之一:保留图像的所有重要细节并将它们传递到下一层(当步长值为 1 且填充值为 same 时);或者忽略图像的一些空间信息,以使处理在计算上更经济。请注意,我们将添加池化层(将在下一节讨论)以减小图像的大小,以便关注提取的特征。目前,请了解步长和填充超参数的目的是控制卷积层的行为及其输出的大小:是传递所有图像细节还是忽略其中的一些。
3.3.2 池化层或子采样
添加更多的卷积层会增加输出层的深度,这会导致网络需要优化的参数(学习)数量增加。你可以看到,添加几个卷积层(通常是几十甚至几百)会产生大量的参数(权重)。这种网络维度的增加会增加学习过程中发生的数学运算的时间和空间复杂度。这就是池化层派上用场的时候。子采样或池化通过减少传递给下一层的参数数量来帮助减小网络的大小。池化操作通过应用总结统计函数(如最大值或平均值)来调整其输入的大小,从而减少传递给下一层的参数总数。
池化层的目标是将卷积层产生的特征图下采样到更少的参数数量,从而降低计算复杂度。在 CNN 架构中,在每层卷积层之后或之后每两层卷积层之后添加池化层是一种常见的做法(图 3.20)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-20.png
图 3.20 池化层通常在每个卷积层之后或之后每两个卷积层之后添加。
最大池化与平均池化比较
池化层主要有两种类型:最大池化和平均池化。我们首先讨论最大池化。
与卷积核类似,最大池化核是具有一定大小和步长值的窗口,在图像上滑动。与最大池化的不同之处在于,窗口没有权重或任何值。它们所做的只是滑动到由前一个卷积层创建的特征图上,并选择最大像素值传递到下一层,忽略其他值。在图 3.21 中,你可以看到一个大小为 2×2、步长为 2 的池化滤波器(滤波器在滑动图像时跳过 2 个像素)。这个池化层将特征图的大小从 4×4 减少到 2×2。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-21.png
图 3.21 一个 2×2 的池化滤波器和步长为 2,将特征图从 4×4 减少到 2×2
当我们将此操作应用于卷积层中的所有特征图时,我们得到维度更小的图(宽度乘以高度),但层的深度保持不变,因为我们将对每个来自前一个滤波器的特征图应用池化滤波器。因此,如果卷积层有三个特征图,池化层的输出也将有三个特征图,但尺寸更小(图 3.22)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-22.png
图 3.22 如果卷积层有三个特征图,池化层的输出将包含三个更小的特征图。
全局平均池化是一种更极端的降维类型。与设置窗口大小和步长不同,全局平均池化计算特征图中所有像素的平均值(图 3.23)。在图 3.24 中,你可以看到全局平均池化层将一个 3D 数组转换成一个向量。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-23.png
图 3.23 全局平均池化计算特征图中所有像素的平均值。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-24.png
图 3.24 全局平均池化层将 3D 数组转换为向量。
为什么使用池化层?
如我们从所讨论的示例中可以看到,池化层降低了卷积层的维度。降低维度之所以重要,是因为在复杂的项目中,CNN 包含许多卷积层,每个都有数十或数百个卷积滤波器(核)。由于核包含网络学习的参数(权重),这可能会迅速失控,我们的卷积层维度可能会变得非常大。因此,添加池化层有助于保持重要特征并将它们传递到下一层,同时缩小图像维度。将池化层想象成图像压缩程序。它们在保持图像重要特征的同时降低图像分辨率(图 3.25)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-25.png
图 3.25 池化层降低图像分辨率并保留图像的重要特征。
池化与步长和填充
池化和步长的主要目的是减少神经网络中的参数数量。我们拥有的参数越多,训练过程就越昂贵。许多人不喜欢池化操作,认为我们可以通过调整步长和填充卷积层来避免它。例如,“追求简单:全卷积网络”a 提出丢弃池化层,转而采用仅由重复卷积层组成的架构。为了减少表示的大小,作者建议在卷积层中偶尔使用较大的步长。丢弃池化层也被发现有助于训练良好的生成模型,例如生成对抗网络(GANs),我们将在第十章中讨论。似乎未来的架构将具有非常少的池化层。但到目前为止,池化层仍然被广泛用于将图像从一层下采样到下一层。
a Jost Tobias Springenberg, Alexey Dosovitskiy, Thomas Brox, 和 Martin Riedmiller, “追求简单:全卷积网络”,arxiv.org/abs/1412.6806。
卷积和池化层回顾
让我们回顾一下到目前为止我们所做的工作。到目前为止,我们使用一系列卷积和池化层来处理图像并提取训练数据集中图像的特定有意义特征。为了总结我们是如何到达这里的:
-
原始图像被输入到卷积层,这是一个在图像上滑动以提取特征的核滤波器集合。
-
卷积层具有以下我们需要配置的属性:
from keras.layers import Conv2D model.add(Conv2D(filters=16, kernel_size=2, strides='1', padding='same', activation='relu'))-
filters是每层中核滤波器的数量(隐藏层的深度)。 -
kernel_size是过滤器的尺寸(也称为核)。通常为 2、3 或 5。 -
strides表示过滤器在图像上滑动的量。通常建议从 1 或 2 开始作为良好的起点。 -
padding在图像的边缘添加零值的列和行,以保留下一层的图像大小。 -
在隐藏层中强烈推荐使用
relu的activation。
-
-
池化层具有以下属性,我们需要进行配置:
from keras.layers import MaxPooling2D model.add(MaxPooling2D(pool_size=(2, 2), strides = 2))
我们继续添加成对的卷积和池化层,以达到我们“深度”神经网络所需的深度。
可视化每层之后发生的情况
在卷积层之后,图像保持其宽度和高度维度(通常是),但每经过一层都会变得更深。为什么?还记得我们之前提到的将图像切割成特征块的类比吗?这就是卷积层之后发生的事情。
例如,假设输入图像是 28 × 28(如 MNIST 数据集所示)。当我们添加一个 CONV_1 层(具有 4 个filters、1 个strides和same的padding)时,输出将具有相同的宽度和高度维度,但depth为 4(28 × 28 × 4)。现在我们添加一个具有相同超参数但更多过滤器的 CONV_2 层,我们得到更深的输出:28 × 28 × 12。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-7key.png
在池化层之后,图像保持其深度,但宽度和高度会缩小:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-8KEY.png
将卷积和池化结合在一起,我们得到如下内容:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-9key.png
这会一直发生,直到最后我们得到一个包含原始图像所有特征的细长图像管。
卷积和池化层的输出产生一个特征管(5 × 5 × 40),几乎可以用于分类。在这里我们使用 40 作为特征管深度的示例,即 40 个特征图。最后一步是在将其输入到全连接层进行分类之前将这个管子展平。如前所述,展平层将具有(1,m)的维度,其中 m = 5 × 5 × 40 = 1,000 个神经元。
3.3.3 全连接层
在通过卷积和池化层对图像进行特征学习过程之后,我们已经提取了所有特征并将它们放入一个长管中。现在是我们使用这些提取的特征来对图像进行分类的时候了。我们将使用第二章中讨论的常规神经网络架构,MLP。
为什么使用全连接层?
MLP 在分类问题中表现良好。我们在这章中使用卷积层的原因是,当从图像中提取特征时,MLP 会丢失大量有价值的信息——我们必须在将图像输入网络之前将其展平——而卷积层可以处理原始图像。现在我们已经提取了特征,并在将它们展平后,我们可以使用常规的 MLP 对它们进行分类。
我们在第二章中详细讨论了 MLP 架构:这里没有新的内容。为了重申,以下是全连接层(图 3.26):
-
输入展平向量——如图 3.26 所示,为了将特征管输入到 MLP 进行分类,我们需要将其展平成一个维度为(1,p)的向量。例如,如果特征管的维度为 5 × 5 × 40,则展平后的向量将是(1,1000)。
-
隐藏层——我们添加一个或多个全连接层,每个层有一个或多个神经元(类似于我们在构建常规 MLPs 时所做的)。
-
输出层——第二章建议对于涉及两个以上类别的分类问题使用 softmax 激活函数。在这个例子中,我们正在对 0 到 9 的数字进行分类:10 个类别。输出层中的神经元数量等于类别的数量;因此,输出层将有 10 个节点。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-26.png
图 3.26 MLP 的全连接层
MLPs 和全连接层
记住第二章的内容,多层感知器(MLPs)也被称为全连接层,因为一层的所有节点都与前一层和后一层的所有节点相连。它们也被称为密集层。MLP、全连接、密集以及有时正向传播这些术语可以互换使用,以指代常规神经网络架构。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-10key.png
3.4 使用 CNN 进行图像分类
好的,你现在已经完全准备好构建自己的 CNN 模型来对图像进行分类。对于这个小型项目,这是一个简单的问题,但将有助于为后续章节中更复杂的问题打下基础,我们将使用 MNIST 数据集。(MNIST 数据集就像是深度学习的“Hello World”。)
注意:无论你决定使用哪个深度学习库,概念基本上是相同的。你首先在心中或在一张纸上设计 CNN 架构,然后开始堆叠层并设置它们的参数。Keras 和 MXNet(以及 TensorFlow、PyTorch 和其他深度学习库)都有其优缺点,我们将在后面讨论,但概念是相似的。因此,在本书的剩余部分,我们将主要使用 Keras,并在适当的地方简要介绍其他库。
3.4.1 构建模型架构
这是您项目中定义和构建 CNN 模型架构的部分。要查看包含图像预处理、训练和评估模型的完整项目代码,请访问本书的 GitHub 仓库 github.com/moelgendy/deep_learning_for_vision_systems,打开 mnist_cnn 笔记本,或访问本书的网站:www.manning.com/books/deep-learning-for-vision-systems 或 www.computerVisionBook.com。在此阶段,我们关注的是构建模型架构的代码。在本章末尾,我们将构建一个端到端图像分类器,并深入探讨其他部分:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
model = Sequential() ❶
model.add(Conv2D(32, kernel_size=(3, 3), strides=1, padding='same',
activation='relu', input_shape=(28,28,1))) ❷
model.add(MaxPooling2D(pool_size=(2, 2))) ❸
model.add(Conv2D(64, (3, 3), strides=1, padding='same', activation='relu')) ❹
model.add(MaxPooling2D(pool_size=(2, 2))) ❺
model.add(Flatten()) ❻
model.add(Dense(64, activation='relu')) ❼
model.add(Dense(10, activation='softmax')) ❽
model.summary() ❾
❶ 构建 model 对象
❷ CONV_1: 添加一个具有 ReLU 激活和深度 = 32 个核的卷积层
❸ POOL_1: 对图像进行下采样以选择最佳特征
❹ CONV_2: 增加深度到 64
❺ POOL_2: 更多的下采样
❻ Flatten,因为维度太多;我们只想得到分类输出
❼ FC_1: 完全连接以获取所有相关数据
❽ FC_2: 输出 softmax 以将矩阵压缩为 10 个类别的输出概率
❾ 打印模型架构摘要
当您运行此代码时,您将看到如图 3.27 所示的模型摘要被打印出来。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-27.png
图 3.27 打印的模型摘要
在我们查看模型摘要之前,有一些一般性的观察:
-
我们只需要将
input_shape参数传递给第一个卷积层。然后我们不需要向模型声明输入形状,因为前一个层的输出是当前层的输入——它已经为模型所知。 -
您可以看到每个卷积层和池化层的输出都是一个形状为 (
None,height,width,channels) 的 3D 张量。height和width的值非常直观:它们是图像在这一层的维度。channels值表示层的深度。这代表每个层中的特征图数量。这个元组中的第一个值设置为None,表示在这一层中处理图像的数量。Keras 将其设置为None,这意味着这个维度是可变的,可以接受任何数量的batch_size。 -
如您在输出形状列中看到的,随着您在网络中深入,图像维度缩小,深度增加,正如我们在本章前面讨论的那样。
-
注意网络需要优化的总参数(权重)数量:220,234,与我们本章早期创建的 MLP 网络的参数数量(669,706)相比。我们将其减少了近三分之一。
让我们逐行查看模型摘要:
-
CONV_1–我们知道输入形状:(28 × 28 × 1)。看看conv2d的输出形状:(28 × 28 × 32)。由于我们将strides参数设置为1,padding设置为same,输入图像的尺寸没有改变。但深度增加到 32。为什么?因为我们在本层添加了 32 个过滤器。每个过滤器产生一个特征图。 -
POOL_1–本层的输入是其前一层的输出:(28 × 28 × 32)。经过池化层后,图像尺寸缩小,但深度保持不变。由于我们使用了 2 × 2 的池化,输出形状为(14 × 14 × 32)。 -
CONV_2–与之前相同,卷积层增加深度并保持尺寸。前一层输入为(14 × 14 × 32)。由于本层的过滤器设置为 64,输出为(14 × 14 × 64)。 -
POOL_2–与之前相同,2 × 2 池化保持深度并缩小尺寸。输出为(7 × 7 × 64)。 -
Flatten–将具有(7 × 7 × 64)维度的特征管扁平化,将其转换为(1, 3136)维度的平坦向量。 -
Dense_1–我们将这个全连接层设置为 64 个神经元,因此输出为 64。 -
Dense_2–这是输出层,我们将其设置为 10 个神经元,因为我们有 10 个类别。
3.4.2 参数数量(权重)
好的,现在我们知道如何构建模型,并逐行阅读摘要以查看图像形状如何随着通过网络层而变化。还有一个重要的事情需要注意:模型摘要中右侧的Param #列。
参数是什么?
参数只是权重的一个名称。这些是网络学习的东西。正如我们在第二章中讨论的,网络的目的是在梯度下降和反向传播过程中更新权重值,直到找到最小化误差函数的最优参数值。
这些参数是如何计算的?
在 MLP 中,我们知道层之间是完全连接的,因此权重连接或边简单地通过乘以每层的神经元数量来计算。在 CNN 中,权重计算并不那么直接。幸运的是,有一个方程可以用来计算:
参数数量 = 过滤器数量 × 核大小 × 前一层深度 + 过滤器数量(对于偏差)
让我们用一个例子来应用这个方程。假设我们想要计算之前的小项目第二层的参数。这是CONV_2的代码再次:
model.add(Conv2D(64, (3, 3), strides=1, padding='same', activation='relu'))
由于我们知道前一层深度为 32,那么
⇒ 参数 = 64 × 3 × 3 × 32 + 64 = 18,496
注意,池化层不添加任何参数。因此,在模型摘要中,您将看到池化层后的Param #值为 0。对于扁平化层也是如此:没有添加额外的权重(见图 3.28)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-28.png
图 3.28 池化和扁平化层不添加参数,因此在模型摘要中池化和扁平化层后的Param #值为 0。
当我们将参数编号列中的所有参数相加时,我们得到这个网络需要优化的参数总数:220,234。
可训练和不可训练参数
在模型摘要中,您将看到参数总数,以及其下方可训练和不可训练参数的数量。可训练参数是神经网络在训练过程中需要优化的权重。在本例中,我们所有的参数都是可训练的(图 3.29)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-29.png
图 3.29 我们所有的参数都是可训练的,需要在训练过程中进行优化。
在后面的章节中,我们将讨论使用预训练网络并将其与您自己的网络结合以获得更快、更准确的结果:在这种情况下,您可能决定冻结一些层,因为它们是预训练的。因此,并非所有网络参数都将进行训练。这在开始训练过程之前了解您模型的内存和空间复杂度很有用;但更多内容将在后面讨论。据我们所知,我们所有的参数都是可训练的。
3.5 添加 dropout 层以避免过拟合
到目前为止,您已经介绍了 CNN 的三个主要层:卷积、池化和全连接。您几乎可以在每个 CNN 架构中找到这三种层类型。但这还不是全部——还有额外的层可以添加以避免过拟合。
3.5.1 什么是过拟合?
机器学习性能不佳的主要原因要么是过拟合,要么是欠拟合数据。欠拟合正如其名:模型无法拟合训练数据。这种情况发生在模型过于简单,无法拟合数据:例如,使用一个感知器对一个非线性数据集进行分类。
相反,过拟合意味着过度拟合数据:记住训练数据而没有真正学习特征。这种情况发生在我们构建一个超级网络,可以完美地拟合训练数据集(训练时的错误率非常低),但无法推广到它之前未见过的新数据样本。您将看到,在过拟合的情况下,网络在训练数据集上表现良好,但在测试数据集上表现不佳(图 3.30)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-30.png
图 3.30 欠拟合(左):模型无法很好地表示数据。恰到好处(中):模型很好地拟合了数据。过拟合(右):模型过度拟合数据,因此它无法推广到未见过的新例子。
在机器学习中,我们不希望构建过于简单而欠拟合数据或过于复杂而过拟合数据的模型。我们希望使用其他技术构建一个适合我们问题的神经网络。为了解决这个问题,我们将在下一节讨论 dropout 层。
3.5.2 什么是 dropout 层?
Dropout 层是防止过拟合最常用的层之一。Dropout 关闭了构成您网络一层的一定比例的神经元(节点)(如图 3.31 所示)。这个比例被识别为一个超参数,当您构建网络时对其进行调整。通过“关闭”,我的意思是这些神经元不包括在特定的正向或反向传播中。在网络中丢弃连接可能看起来有些反直觉,但随着网络的训练,一些节点可能会支配其他节点或最终犯下大错误。Dropout 为您提供了一种平衡网络的方法,使每个节点都能平等地朝着同一个目标努力,如果其中一个节点犯了错误,它不会支配您模型的行为。您可以将 dropout 视为一种使网络具有弹性的技术;通过确保没有节点太弱或太强,它使所有节点作为一个团队良好地工作。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-31.png
图 3.31 Dropout 关闭了构成网络层的一定比例的神经元。
3.5.3 为什么我们需要 dropout 层?
在训练过程中,神经元之间会形成相互依赖,这控制了每个神经元的个体能力,导致训练数据的过拟合。为了真正理解为什么 dropout 是有效的,让我们更仔细地看看图 3.31 中的 MLP,并思考每一层的节点真正代表什么。第一层(最左边)是输入层,包含输入特征。第二层包含从上一层的模式中学习到的特征,当乘以权重时。然后是下一层,它学习的是模式中的模式,依此类推。每个神经元代表一个特定的特征,当乘以权重时,会转换成另一个特征。当我们随机关闭一些这些节点时,我们迫使其他节点在没有仅依赖一个或两个特征的情况下学习模式,因为任何特征在任何时候都可能被随机丢弃。这导致权重在所有特征之间分散,导致更多训练神经元。
Dropout 有助于减少神经元之间的相互依赖学习。从这个意义上讲,它有助于将 dropout 视为一种集成学习方法。在集成学习中,我们分别训练多个较弱的分类器,然后在测试时通过平均所有集成成员的响应来使用它们。由于每个分类器都是分别训练的,因此它们已经学习了数据的不同方面,它们的错误(误差)也不同。将它们结合起来有助于产生一个更强的分类器,这种分类器不太可能过拟合。
直觉
一个有助于我理解 dropout 的类比是使用杠铃训练二头肌。当我们用双臂举起杠铃时,我们倾向于依赖我们的更强臂举起比我们的弱臂更多的重量。我们的更强臂最终会得到比其他部位更多的训练并发展出更大的肌肉:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-11K.png
Dropout 意味着稍微打乱我们的训练(训练)过程。我们绑住我们的右手,只训练左手。然后我们绑住左手,只训练右手。然后我们打乱它,带着两只手臂回到杠铃,以此类推。过了一段时间,你会发现你的两个二头肌都得到了锻炼:
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-12K.png
这正是我们在训练神经网络时发生的情况。有时网络的一部分具有非常大的权重并主导所有训练,而网络的另一部分则没有得到很多训练。dropout 的作用是关闭一些神经元,让其余的神经元进行训练。然后,在下一个 epoch 中,它关闭其他神经元,这个过程持续进行。
3.5.4 dropout 层在 CNN 架构中的位置在哪里?
正如您在本章中学到的,一个标准的 CNN 由交替的卷积层和池化层组成,以全连接层结束。为了防止过拟合,在将图像展平后,在架构末尾的全连接层之间注入几个 dropout 层已成为标准做法。为什么?因为 dropout 在卷积神经网络的完全连接层中已知效果良好。然而,它在卷积和池化层中的效果尚未得到充分研究:
CNN 架构:… 卷积 ⇒ 池化 ⇒ 展平 ⇒ DO ⇒ FC ⇒ DO ⇒ FC
让我们看看我们如何使用 Keras 将 dropout 层添加到我们之前的模型中:
# CNN and POOL layers
# ...
# ...
model.add(Flatten()) ❶
model.add(Dropout(rate=0.3)) ❷
model.add(Dense(64, activation='relu')) ❸
model.add(Dropout(rate=0.5)) ❹
model.add(Dense(10, activation='softmax')) ❺
model.summary() ❻
❶ 展平层
❷ 30%概率的 dropout 层
❸ FC_1:完全连接以获取所有相关数据
❹ 50%概率的 dropout 层
❺ FC_2:输出 softmax 将矩阵压缩成 10 个类别的输出概率
❻ 打印模型架构摘要
如您所见,dropout 层以rate作为参数。该比率表示要丢弃的输入单元的比例。例如,如果我们把rate设置为 0.3,这意味着该层中 30%的神经元将在每个 epoch 中被随机丢弃。所以如果我们有一个层中有 10 个节点,那么其中的 3 个神经元将被关闭,而 7 个将被训练。这三个神经元是随机选择的,在下一个 epoch 中,其他随机选择的神经元将被关闭,以此类推。由于我们是随机进行的,一些神经元可能被关闭的次数比其他的多,有些可能永远不会被关闭。这是可以的,因为我们这样做很多次,所以平均来看,每个神经元将得到几乎相同的治疗。请注意,这个比率是我们构建 CNN 时调整的另一个超参数。
3.6 对彩色图像的卷积(3D 图像)
记得在第一章中,计算机将灰度图像视为像素的二维矩阵(图 3.32)。对于计算机来说,图像看起来像是一个像素值的二维矩阵,这些值代表颜色光谱中的强度。这里没有上下文,只有大量数据。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-32.png
图 3.32 对于计算机来说,图像看起来像是一个像素值的二维矩阵。
另一方面,计算机将彩色图像解释为具有高度、宽度和深度的 3D 矩阵。在 RGB 图像(红色、绿色和蓝色)的情况下,深度为 3:每个颜色一个通道。例如,一个 28 × 28 的彩色图像将被计算机视为一个 28 × 28 × 3 的矩阵。想象一下,这是一个由三个 2D 矩阵堆叠而成的——每个矩阵分别对应图像的红色、绿色和蓝色通道。每个矩阵代表其颜色的强度值。当它们堆叠在一起时,就构成了一个完整的彩色图像(图 3.33)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-33.png
图 3.33 彩色图像由三个矩阵表示。每个矩阵代表其颜色的强度值。将它们堆叠起来就构成了一个完整的彩色图像。
注意:为了泛化,我们用三维数组表示图像:高度 × 宽度 × 深度。对于灰度图像,深度为 1;对于彩色图像,深度为 3。
3.6.1 我们如何在彩色图像上执行卷积?
与我们对灰度图像所做的方法类似,我们将卷积核在图像上滑动并计算特征图。现在核本身是三维的:每个维度对应一个颜色通道(图 3.34)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-34.png
图 3.34 我们将卷积核在图像上滑动并计算特征图,从而得到一个 3D 核。
为了执行卷积,我们将做与之前相同的事情,只是现在我们的求和项是之前的 3 倍。让我们看看如何(图 3.35):
-
每个颜色通道都有自己的对应过滤器。
-
每个过滤器都会在其图像上滑动,逐元素相乘对应的像素元素,然后将它们全部相加以计算每个过滤器的卷积像素值。这与我们之前所做的方法类似。
-
然后我们将这三个值相加得到卷积图像或特征图中单个节点的值。别忘了加上 1 的偏置值。然后我们将过滤器滑动一个或多个像素(基于步长值)并执行相同操作。我们继续这个过程,直到计算完特征图中所有节点的像素值。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-35.png
图 3.35 执行卷积
3.6.2 计算复杂度会发生什么变化?
注意,如果我们用一个 3 × 3 的过滤器在灰度图像上滑动,我们将为每个过滤器有总共 9 个参数(权重)(如前所述)。在彩色图像中,每个过滤器本身就是一个 3D 过滤器。这意味着每个过滤器都有一个参数数量:(高度 × 宽度 × 深度)=(3 × 3 × 3)= 27。你可以看到,当处理彩色图像时,网络复杂性如何增加,因为它必须优化更多的参数;彩色图像也占用更多的内存空间。
彩色图像包含比灰度图像更多的信息。这可能会增加不必要的计算复杂性和占用内存空间。然而,彩色图像对于某些分类任务也非常有用。这就是为什么在某些用例中,作为计算机视觉工程师的你将根据自己的判断来决定是否将彩色图像转换为灰度图像,因为在很多情况下,颜色并不是识别和解释图像所必需的:灰度图像可能就足够识别物体了。
在图 3.36 中,你可以看到一个物体(强度)中明暗模式的如何被用来定义其形状和特征。然而,在其他应用中,颜色对于定义某些物体很重要:例如,皮肤癌检测很大程度上依赖于皮肤颜色(红色皮疹)。一般来说,当涉及到 CV 应用,如识别汽车、人或皮肤癌时,你可以通过思考自己的视觉来决定颜色信息是否重要。如果我们人类在颜色上更容易识别问题,那么算法看到彩色图像可能也更容易。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-36.png
图 3.36 在灰度图像中,一个物体(强度)的明暗模式可以用来定义其形状和特征。
注意,在图 3.36 中,我们只添加了一个过滤器(包含 3 个通道),它产生了一个特征图。与灰度图像类似,我们添加的每个过滤器都会产生它自己的特征图。在图 3.37 中的 CNN 中,我们有一个尺寸为(7 × 7 × 3)的输入图像。我们添加了两个尺寸为(3 × 3)的卷积过滤器。输出特征图的深度为 2,因为我们添加了两个过滤器,这与我们在灰度图像中所做的一样。
关于 CNN 架构的重要注意事项
我强烈建议查看现有的架构,因为很多人已经做了将事物组合在一起并看看什么有效的工作。从实际的角度来说,除非你正在研究问题,否则你应该从一个已经由其他人构建的用于解决类似你问题的 CNN 架构开始。然后进一步调整以适应你的数据。
在第四章中,我们将解释如何诊断你网络的性能,并讨论调整策略以改进它。在第五章中,我们将讨论最流行的 CNN 架构,并检查其他研究人员是如何构建它们的。我希望你从这个部分得到的是,首先,对 CNN 构建的概念理解;其次,更多的层导致更多的神经元,这导致更多的学习行为。但这伴随着计算成本。因此,你应该始终考虑你的训练数据的大小和复杂性(对于简单任务,可能不需要很多层)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-37.png
图 3.37 我们的输入图像尺寸为(7 × 7 × 3),我们添加了两个尺寸为(3 × 3)的卷积过滤器。输出特征图的深度为 2。
3.7 项目:彩色图像的分类
让我们看看一个端到端图像分类项目。在这个项目中,我们将训练一个 CNN 来对 CIFAR-10 数据集(www.cs.toronto.edu/ ~kriz/cifar.html)中的图像进行分类。CIFAR-10 是一个用于物体识别的成熟 CV 数据集,它是 8000 万小图像数据集的一个子集 1,包含 60000 张(32 × 32)彩色图像,每类有 6000 张图像。现在,启动你的笔记本,让我们开始吧。
第 1 步:加载数据集
第一步是将数据集加载到我们的训练和测试对象中。幸运的是,Keras 为我们提供了load_data()方法来加载 CIFAR 数据集。我们只需要导入keras.datasets然后加载数据:
import keras
from keras.datasets import cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data() ❶
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize=(20,5))
for i in range(36):
ax = fig.add_subplot(3, 12, i + 1, xticks=[], yticks=[])
ax.imshow(np.squeeze(x_train[i]))
❶ 加载预洗牌的训练和测试数据
第 2 步:图像预处理
根据你的数据集和你要解决的问题,你需要进行一些数据清理和预处理,以便为你的学习模型做好准备。成本函数的形状像一个碗,但如果特征具有非常不同的尺度,它可能是一个拉长的碗。图 3.38 显示了在特征 1 和 2 具有相同尺度(左侧)的训练集上的梯度下降,以及在特征 1 的值比特征 2 小得多的训练集(右侧)。
提示:在使用梯度下降时,你应该确保所有特征具有相似的尺度;否则,收敛将需要更长的时间。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-38.png
图 3.38 显示了标准化特征具有相同尺度,用一个均匀的碗表示(左侧)。未标准化特征尺度不同,用一个拉长的碗表示(右侧)。在具有相同尺度的特征上的训练集(左侧)和在特征 1 的值比特征 2 小得多的训练集(右侧)上的梯度下降。
调整图像尺度
按以下方式调整输入图像的尺度:
x_train = x_train.astype('float32')/255 ❶
x_test = x_test.astype('float32')/255
❶ 通过除以 255 来调整图像的像素值:[0,255] ⇒ [0,1]
准备标签(独热编码)
在本章以及整本书中,我们将讨论计算机如何通过将其转换为像素强度的矩阵形式来处理输入数据(图像)。那么标签呢?计算机是如何理解标签的?我们数据集中的每一张图像都有一个特定的标签,用文本解释了这张图像是如何被分类的。在这个特定的数据集中,例如,标签被分为以下 10 个类别:['飞机', '汽车', '鸟', '猫', '鹿', '狗', '青蛙', '马', '船', '卡车']。我们需要将这些文本标签转换为计算机可以处理的形式。计算机擅长处理数字,所以我们将进行一种称为独热编码的过程。独热编码是一种将分类变量转换为数值形式的过程。
假设数据集看起来如下:
| 图像 | 标签 |
|---|---|
| image_1 | 狗 |
| image_2 | 汽车 |
| image_3 | 飞机 |
| image_4 | truck |
| image_5 | bird |
独热编码后,我们得到以下内容:
| airplane | bird | cat | deer | dog | frog | horse | ship | truck | automobile | |
|---|---|---|---|---|---|---|---|---|---|---|
| image_1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| image_2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| image_3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| image_4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| image_5 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
幸运的是,Keras 有一个方法可以为我们做到这一点:
from keras.utils import np_utils
num_classes = len(np.unique(y_train)) ❶
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
❶ 对标签进行独热编码
将数据集分为训练集和验证集
除了将我们的数据分为训练集和测试集之外,将训练数据进一步分为训练集和验证集是一种标准做法(图 3.39)。为什么?因为每个拆分都有不同的用途:
-
训练数据集 – 用于训练模型的样本数据。
-
验证数据集 – 使用的数据样本,用于在调整模型超参数时对训练数据集上的模型拟合进行无偏评估。当将验证数据集上的技能纳入模型配置时,评估变得更具偏差。
-
测试数据集 – 使用的数据样本,用于在训练数据集上对最终模型拟合进行无偏评估。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-39.png
图 3.39 将数据拆分为训练、验证和测试子集
这里是 Keras 代码:
(x_train, x_valid) = x_train[5000:], x_train[:5000] ❶
(y_train, y_valid) = y_train[5000:], y_train[:5000] ❶
print('x_train shape:', x_train.shape) ❷
print(x_train.shape[0], 'train samples') ❸
print(x_test.shape[0], 'test samples') ❸
print(x_valid.shape[0], 'validation samples') ❸
❶ 将训练集拆分为训练集和验证集
❷ 打印训练集的形状
❸ 打印训练集、验证集和测试集的图像数量
标签矩阵
独热编码将 (1 × n) 标签向量转换为维度为 (10 × n) 的标签矩阵,其中 n 是样本图像的数量。所以,如果我们数据集中有 1,000 张图像,标签向量将具有 (1 × 1000) 的维度。独热编码后,标签矩阵的维度将是 (1000 × 10)。这就是为什么,在下一步定义我们的网络架构时,我们将输出 softmax 层包含 10 个节点,每个节点代表我们拥有的每个类别的概率。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-13K.png
第 3 步:定义模型架构
你已经了解到 CNN(以及神经网络)的核心构建块是层。大多数深度学习项目都由堆叠简单层组成,这些层实现了数据蒸馏的一种形式。正如你在本章所学,主要的 CNN 层包括卷积、池化、全连接和激活函数。
你是如何决定网络架构的?
应该创建多少个卷积层?多少个池化层?在我看来,了解一些最流行的架构(AlexNet、ResNet、Inception)以及提取导致设计决策的关键思想是非常有帮助的。观察这些最先进的架构是如何构建的,并在你自己的项目中尝试,将帮助你建立对最适合你解决问题的 CNN 架构的直觉。我们将在第五章讨论最流行的 CNN 架构。在此之前,你需要了解以下内容:
-
你添加的层越多,理论上你的网络学习效果越好;但这也将带来计算和内存空间复杂度增加的代价,因为它增加了需要优化的参数数量。你还将面临网络过拟合训练集的风险。
-
当输入图像通过网络层时,其深度会增加,而维度(宽度,高度)会逐层缩小。
-
通常情况下,对于较小的数据集,可以从两到三层 3 × 3 卷积层开始,然后跟一个 2 × 2 池化层,这可以是一个不错的起点。添加更多的卷积和池化层,直到你的图像达到合理的尺寸(比如 4 × 4 或 5 × 5),然后添加几个全连接层进行分类。
-
你需要设置几个超参数(如
filter、kernel_size和padding)。记住,你不需要重新发明轮子:相反,查阅文献看看通常对其他人有效的超参数。以对其他人有效的架构作为起点,然后调整这些超参数以适应你的情况。下一章将专门探讨对其他人有效的方法。
学习如何与层和超参数一起工作
我不希望你在一开始构建 CNN 时过于纠结于超参数的设置。获得如何组合层和超参数直觉的最好方法之一是实际看到其他人如何具体操作。作为深度学习工程师,你大部分的工作将涉及构建架构和调整参数。本章的主要收获如下:
-
理解主要 CNN 层(卷积、池化、全连接、dropout)的工作原理以及它们存在的原因。
-
理解每个超参数的作用(卷积层中的滤波器数量、内核大小、步长和填充)。
-
最后,理解如何在 Keras 中实现任何给定的架构。如果你能够在你自己的数据集上复制这个项目,那么你就准备好了。
在第五章中,我们将回顾几个最先进的架构,并看看它们是如何工作的。
图 3.40 所示的架构被称为 AlexNet:它是一个流行的 CNN 架构,在 2011 年赢得了 ImageNet 挑战赛(关于 AlexNet 的更多细节请见第五章)。AlexNet CNN 架构由五个卷积和池化层以及三个全连接层组成。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-40.png
图 3.40 AlexNet 架构
让我们尝试一个更小的 AlexNet 版本,看看它在我们的数据集上的表现如何(图 3.41)。根据结果,我们可能会添加更多层。我们的架构将堆叠三个卷积层和两个全连接(密集)层,如下所示:
CNN:输入 ⇒ CONV_1 ⇒ POOL_1 ⇒ CONV_2 ⇒ POOL_2 ⇒ CONV_3 ⇒ POOL_3 ⇒ DO ⇒ FC ⇒ DO ⇒ FC (softmax)
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-41.png
图 3.41 我们将构建一个包含三个卷积层和两个密集层的简单 CNN。
注意,我们将对所有隐藏层使用 ReLU 激活函数。在最后一个密集层,我们将使用具有 10 个节点的 softmax 激活函数,以返回一个包含 10 个概率分数的数组(总和为 1)。每个分数将是当前图像属于我们 10 个图像类别的概率:
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
model = Sequential()
model.add(Conv2D(filters=16, kernel_size=2, padding='same', ❶
activation='relu', input_shape=(32, 32, 3))) ❶
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=32, kernel_size=2, padding='same', ❷
activation='relu')) ❷
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='same', ❸
activation='relu')) ❸
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3)) ❹
model.add(Flatten()) ❺
model.add(Dense(500, activation='relu')) ❻
model.add(Dropout(0.4)) ❼
model.add(Dense(10, activation='softmax')) ❽
model.summary() ❾
❶ 第一个卷积和池化层。注意,我们只需要在第一个卷积层中定义 input_shape。
❷ 第二个卷积和池化层,带有 ReLU 激活函数
❸ 第三个卷积和池化层
❹ 30%率的 dropout 层以避免过拟合
❺ 将最后一个特征图展平成一个特征向量
❻ 添加第一个全连接层
❼ 另一个 40%率的 dropout 层
❽ 输出层是一个包含 10 个节点的全连接层,并使用 softmax 激活函数为 10 个类别提供概率。
❾ 打印模型架构摘要
当你运行这个单元格时,你会看到模型架构以及特征图维度如何随着每一层的连续变化,如图 3.42 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-42.png
图 3.42 模型摘要
我们之前讨论了如何理解这个摘要。正如你所见,我们的模型有 528,054 个参数(权重和偏差)需要训练。我们之前也讨论了如何计算这个数字。
第 4 步:编译模型
在训练我们的模型之前,最后一步是定义三个额外的超参数–损失函数、优化器以及在训练和测试期间监控的指标:
-
损失函数 --网络将如何衡量其在训练数据上的性能。
-
优化器 --网络将使用的机制来优化其参数(权重和偏差)以产生最小损失值。它通常是第二章中解释的随机梯度下降的变体之一。
-
指标 --模型在训练和测试期间要评估的指标列表。通常我们使用
metrics=['accuracy']。
随意回顾第二章以获取关于确切目的和不同类型的损失函数及优化器的更多细节。
这是编译模型的代码:
model.compile(loss='categorical_crossentropy', optimizer='rmsprop',
metrics=['accuracy'])
第 5 步:训练模型
我们现在准备好训练网络了。在 Keras 中,这是通过调用网络的.fit()方法(如将模型拟合到训练数据)来完成的:
from keras.callbacks import ModelCheckpoint
checkpointer = ModelCheckpoint(filepath='model.weights.best.hdf5', verbose=1,
save_best_only=True)
hist = model.fit(x_train, y_train, batch_size=32, epochs=100,
validation_data=(x_valid, y_valid), callbacks=[checkpointer],
verbose=2, shuffle=True)
当你运行这个单元格时,训练将开始,图 3.43 中显示的详细输出将每次显示一个时代。由于 100 个时代的显示不适合一页纸,截图显示了前 13 个时代。但当你在这个笔记本上运行时,显示将一直持续到 100 个时代。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-43.png
图 3.43 训练的前 13 个时代
查看图 3.43 中的详细输出将帮助你分析你的网络性能,并提出哪些旋钮(超参数)需要调整。我们将在第四章中详细讨论这一点。现在,让我们看看最重要的要点:
-
loss和acc是训练数据的错误和准确率值。val_loss和val_acc是验证数据的错误和准确率值。 -
查看每个时代后的
val_loss和val_acc值。理想情况下,我们希望val_loss下降,val_acc上升,这表明网络在每个时代之后实际上是在学习的。 -
从第 1 个时代到第 6 个时代,你可以看到模型在每个时代之后都会保存权重,因为验证损失值在提高。所以每个时代结束时,我们保存被认为是迄今为止最佳权重的权重。
-
在第 7 个时代,
val_loss从 0.9445 上升到 1.1300,这意味着它没有改进。所以网络在这个时代没有保存权重。如果你现在停止训练并从第 6 个时代加载权重,你将得到训练期间达到的最佳结果。 -
对于第 8 个时代,
val_loss下降,因此网络将权重保存为最佳值。在第 9 个时代,没有改进,以此类推。 -
如果你训练 12 个时代后停止并加载最佳权重,网络将加载在第 10 个时代保存的权重(
val_loss= 0.9157)和(val_acc= 0.6936)。这意味着你可以期望在测试数据上获得接近 69%的准确率。
关注这些常见现象
-
val_loss在波动。如果val_loss上下波动,你可能想要降低学习率超参数。例如,如果你看到val_loss从 0.8 到 0.9,到 0.7,到 1.0,以此类推,这可能意味着你的学习率太高,无法下降错误山。尝试降低学习率,并让网络训练更长的时间。https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dl-vis-sys/img/3-unnumb-14K.png
如果
val_loss波动,学习率可能太高。 -
val_loss没有改进(欠拟合)。如果val_loss没有下降,这可能意味着你的模型太简单,无法拟合数据(欠拟合)。那么你可能需要通过添加更多隐藏层来构建一个更复杂的模型,以帮助网络拟合数据。 -
loss正在下降,而val_loss停止了提升。这意味着你的网络开始对训练数据进行过拟合,并且未能减少验证数据的错误。在这种情况下,考虑使用防止过拟合的技术,如丢弃层。我们将在下一章讨论其他避免过拟合的技术。
第 6 步:加载具有最佳 val_acc 的模型
现在训练完成,我们使用 Keras 方法load_weights()将产生最佳验证准确率分数的权重加载到我们的模型中:
model.load_weights('model.weights.best.hdf5')
第 7 步:评估模型
最后一步是评估我们的模型,并计算准确率值作为百分比,表示我们的模型正确预测图像分类的频率:
score = model.evaluate(x_test, y_test, verbose=0)
print('\n', 'Test accuracy:', score[1])
当你运行这个单元格时,你将得到大约 70%的准确率。这还不错。但我们可以做得更好。尝试通过添加更多的卷积和池化层来调整 CNN 架构,看看你是否能提高你的模型。
在下一章中,我们将讨论设置深度学习(DL)项目策略和超参数调整以改进模型性能的方法。在第四章结束时,我们将重新审视这个项目,应用这些策略并将准确率提高到 90%以上。
摘要
-
MLPs、ANNs、密集和前馈都指的是我们在第二章中讨论的常规全连接神经网络架构。
-
MLPs 通常适用于一维输入,但它们在处理图像时表现不佳,主要有两个原因。首先,它们只接受以向量形式具有维度(1 × n)的特征输入。这需要展平图像,这将导致丢失其空间信息。其次,MLPs 由全连接层组成,在处理较大图像时会产生数百万甚至数十亿个参数。这将增加计算复杂度,并且对于许多图像问题无法扩展。
-
CNNs 在图像处理方面表现尤为出色,因为它们可以直接将原始图像矩阵作为输入,而不需要将图像展平。它们由称为卷积滤波器的局部连接层组成,这与 MLPs 的密集层形成对比。
-
卷积神经网络(CNNs)由三个主要层组成:用于特征提取的卷积层、用于降低网络维度的池化层,以及用于分类的全连接层。
-
机器学习预测性能不佳的主要原因要么是过拟合,要么是欠拟合数据。欠拟合意味着模型过于简单,无法拟合(学习)训练数据。过拟合意味着模型过于复杂,它记住了训练数据,并且无法对之前未见过的测试数据进行泛化。
-
添加一个丢弃层以防止过拟合。丢弃层会关闭网络层中一定比例的神经元(节点)。
- 安东尼奥·托拉尔巴、罗布·弗格森和威廉·T·弗里曼,“8000 万个小图像:用于非参数对象和场景识别的大数据集”,IEEE 信号处理与机器智能杂志(2008 年 11 月),
doi.org/10.1109/TPAMI.2008.128.
更多推荐
所有评论(0)