PyTorch深度学习实战

978-7-115-57767-2
作者: [美]伊莱·史蒂文斯(Eli Stevens)[意]卢卡·安蒂加(Luca Antiga)[德]托马斯·菲曼(Thomas Viehmann)
译者: 牟大恩
编辑: 郭媛

图书目录:

详情

虽然很多深度学习工具都使用Python,但PyTorch 库是真正具备Python 风格的。对于任何了解NumPy 和scikit-learn 等工具的人来说,上手PyTorch 轻而易举。PyTorch 在不牺牲高级特性的情况下简化了深度学习,它非常适合构建快速模型,并且可以平稳地从个人应用扩展到企业级应用。由于像苹果、Facebook和摩根大通这样的公司都使用PyTorch,所以当你掌握了PyTorth,就会拥有更多的职业选择。 本书是教你使用 PyTorch 创建神经网络和深度学习系统的实用指南。它帮助读者快速从零开始构建一个真实示例:肿瘤图像分类器。在此过程中,它涵盖了整个深度学习管道的关键实践,包括 PyTorch张量 API、用 Python 加载数据、监控训练以及将结果进行可视化展示。 本书主要内容: (1)训练深层神经网络; (2)实现模块和损失函数; (3)使用 PyTorch Hub 预先训练的模型; (4)探索在 Jupyter Notebooks 中编写示例代码。 本书适用于对深度学习感兴趣的 Python 程序员。了解深度学习的基础知识对阅读本书有一定的帮助,但读者无须具有使用 PyTorch 或其他深度学习框架的经验。

图书摘要

版权信息

书名:PyTorch深度学习实战

ISBN:978-7-115-57767-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 伊莱•史蒂文斯(Eli Stevens)

     [意] 卢卡•安蒂加(Luca Antiga)

     [德] 托马斯•菲曼(Thomas Viehmann)

译    牟大恩

责任编辑 郭 媛

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e57767”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


Original English language edition published by Manning Publications, USA. Copyright © 2020 by Manning Publications. Simplified Chinese-language edition copyright © 2022 by POSTS AND TELECOM PRESS CO., LTD. All rights reserved.

本书简体中文版由Manning出版社授权人民邮电出版社有限公司独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

版权所有,侵权必究。


PyTorch是一个机器学习框架,主要依靠深度神经网络,目前已迅速成为机器学习领域中最可靠的框架之一。本书指导读者使用Python和PyTorch实现深度学习算法。本书首先介绍PyTorch的核心知识,然后带领读者体验一个真实的案例研究项目:构建能够使用CT扫描检测恶性肺肿瘤的算法。你将学习用有限的输入训练网络,并处理数据,以获得一些结果。你将筛选出不可靠的初始结果,并专注于诊断和修复神经网络中的问题。最后,你将研究通过增强数据训练、改进模型体系结构和执行其他微调来改进结果的方法。通过这个真实的案例,你会发现PyTorch是多么有效和有趣,并掌握在生产中部署PyTorch模型的技能。

本书适合具有一定Python知识和基础线性代数知识的开发人员。了解深度学习的基础知识对阅读本书有一定的帮助,但读者无须具有使用PyTorch或其他深度学习框架的经验。


Eli Stevens职业生涯的大部分时间都在美国硅谷的初创公司工作,从软件工程师(网络设备制造业)到首席技术官(开发肿瘤放疗软件)。在本书出版时,他正在汽车自动驾驶行业从事机器学习相关工作。

21世纪初,Luca Antiga担任生物医学工程研究员。2010年到2020年间,他是一家人工智能工程公司的联合创始人和首席技术官。他参与了多个开源项目,包括PyTorch的核心模块。最近,他作为联合创始人创建了一家总部位于美国的初创公司,专注于数据定义软件的基础设施。

Thomas Viehmann是一名德国慕尼黑的机器学习和PyTorch的专业培训师和顾问,也是PyTorch核心开发人员。拥有数学博士学位的他不畏惧理论,擅长将理论应用于实际的计算挑战。

牟大恩,武汉大学硕士研究生毕业,曾先后在网易杭州研究院、优酷土豆集团、海通证券总部负责技术研发及系统架构设计工作,目前任职于东方证券资产管理有限公司。他有多年的Java开发及系统设计经验,专注于互联网金融及大数据应用相关领域,热爱技术,喜欢钻研前沿技术,是机器学习及深度学习的深度爱好者。近年来著有《Kafka 入门与实践》,译有《Kafka Streams实战》,已提交技术发明专利申请两项。


本书封面上的人物叫作“Kardinian”。这幅插图来自Jacques Grasset de Saint-Sauveur(1757—1810)1788年在法国出版的图书,书名为Costumes civils actuels de tous les peuples connus。本系列图书收集了不同国家的服饰图,每幅图都是手工精心绘制和着色的。Grasset de Saint-Sauveur的藏品丰富多样,生动地向我们展示了在200多年前,世界上各地区的文化是多么不同。由于彼此隔绝,人们说着不同的语言。无论是在城市还是在乡村,只要看他们的衣着,就能很容易地认出他们住在哪里、从事什么行业或从事什么职业。

从那以后,我们的着装方式发生了变化,而在当时如此丰富的地区多样性也在逐渐消失。现在已经很难区分来自不同大陆的居民,更不用说不同的国家、地区或城镇了。也许我们已经用文化的多样性换取了更多样的私人生活——当然是更多样和快节奏的科技生活。

在很难将计算机相关的图书区分开的时候,Manning出版社以200多年前丰富多样的地区生活为基础,用图书封面来颂扬计算机行业的创造性和首创精神,使Grasset de Saint-Sauveur的画作重现。


随着“金融科技”的兴起,人工智能在近几年愈发火热,而深度学习作为人工智能领域热门的研究方向,获得了极大的关注和长足的发展。PyTorch是一个针对深度学习的张量库,可以运行在CPU和GPU上,它采用Python语言,具有强大的GPU加速的张量计算能力以及自动求导功能的深度神经网络。PyTorch作为当前主流的深度学习框架之一,无论是在学术界还是在工业界都受到了深度学习爱好者和从业者的青睐。

我很荣幸有机会翻译本书。通过翻译本书,无论是对PyTorch基础知识还是对深度学习技术的理论及应用实践,我都收获颇多。本书深入浅出,详细讲解了从PyTorch基础API到使用PyTorch处理深度学习具体的应用实例。本书通过模拟近乎真实的场景,从场景描述开始,逐步对问题进行剖析,然后利用PyTorch解决问题。阅读本书,读者不仅能够全面掌握PyTorch相关的API的使用方法以及系统掌握深度学习的理论和方法,而且能够轻松学会使用PyTorch实现各种神经网络模型来解决具体的深度学习问题。

在翻译本书的过程中,我印象最深的是,本书不是直接给出解决问题的完整代码,而是在场景描述、问题分析、技术选型等方面给予更多的篇幅,这种方式更能帮助读者真正深入地掌握相关技术的要领,正所谓“授人以鱼,不如授人以渔”。

在此特别感谢人民邮电出版社的郭媛编辑,以及一直鼓励和指导我的杨海玲老师,正是她们一丝不苟、认真专业的工作态度,才使本书翻译工作得以圆满完成。借此机会,我还要感谢我在海通证券的老领导王洪涛和熊友根对我的培养,感谢东方红资管刘峰、陈雄、彭轶君、任炜明对我的指导,以及同事们给予我的帮助。同时,我还要感谢我的妻子吴小华、姐姐屈海林、妹妹石俊豪,感谢她们在我翻译本书时对我和我儿子的悉心照顾,正是她们的帮助,才使我下班回到家时可以全身心投入翻译工作中。在春节长假期间,我甚至90%的非睡眠时间都在翻译本书,没有时间陪伴家人,深感亏欠。同时,将本书送给我的宝贝儿子牟经纬,作为宝宝即将进入幼儿园的礼物,祝他健康、茁壮成长,开心、快乐地学习!

虽然在翻译过程中我力争做到“信、达、雅”,但本书许多概念和术语目前尚无公认的中文翻译,加之个人水平有限,译文中难免有不妥之处,恳请读者批评指正。

牟大恩

2021年4月


2016年年中开始做PyTorch项目时,我们还是一个开源团队。我们这个团队的成员是在网上互相认识的,都是想把深度学习软件编写得更好的计算机“极客”。本书3位作者中的2位——Luca Antiga和Thomas Viehmann对PyTorch今天所取得的成功起到了重要作用。

我们使用PyTorch的目的是构建一个尽可能灵活的框架来表达深度学习算法。我们专注于任务执行,并且希望在相对较短的开发时间里为开发者社区提供一个完善的产品。如果我们不是站在巨人的肩膀上,我想这是不可能完成的。PyTorch的大部分基础代码源于Ronan Collobert等人在2007年发起的Torch7项目,该项目源于Yann LeCun和Leon Bottou首创的编程语言——Lush。正是鉴于这段丰富的历史经验,我们才能关注需要改变的东西,而不是从零开始。

很难将PyTorch的成功归因于单一因素,它具有良好的用户体验、较高的可调试性和灵活性,最终提高了用户的工作效率。同时,PyTorch的大量使用也造就了一个出色的软件生态系统,在此基础上进行的研究使得PyTorch具有更好的用户体验。

一些线上或线下的关于PyTorch的课程和大学里的计划课程,以及大量的线上博客和教程,使得PyTorch学习起来更容易。然而,关于PyTorch的图书很少。2017年有人问我:“什么时候写一本PyTorch的书?”我回答说:“如果现在开始写,我敢保证,等书写完了,书里面的内容就过时了。”

随着本书的出版,我们最终有了一本关于PyTorch的权威著作。它非常详细地介绍了基础知识和抽象概念,对诸如张量和神经网络的数据结构进行了分解介绍,以确保大家能够理解它们的实现原理。此外,本书还涵盖了一些高级主题,如即时(JIT)编译器和生产环境部署,这些内容也是PyTorch的一部分。

此外,本书还有应用程序相关内容,通过使用神经网络来帮助大家解决一个复杂和重要的医学问题。凭借Luca在生物工程和医学成像方面深厚的专业知识,Eli在医疗设备和检测软件方面的开发经验,以及Thomas作为PyTorch核心开发人员的背景,本书的应用程序相关内容值得认真学习。 总之,我希望本书成为你长期的参考文档,成为你的个人图书馆或工作室的一部分。

Soumith Chintala

PyTorch联合创作者


作为20世纪80年代出生的孩子,我们几个接触个人计算机的情况,分别是Eli从Commodore VIC20系统开始,我从Sinclair Spectrum 48K系统开始,Thomas从Commodore C16系统开始。在那个时候我们看到了个人计算机的曙光,学会了在越来越快的机器上编写代码和研究算法,还经常幻想计算机会将我们带到哪里去。当某部谍战片中的主角说“计算机,改进一下”时,我们一起翻白眼,深切意识到现实中计算机的作用与电影中计算机的作用的差距。

后来,在我们的职业生涯中,Eli和我各自在医学图像分析方面挑战自我,在研究能够处理人体自然变化的算法时,面临着同样的困难。在选择最优的算法组合时,会涉及很多试探式方法,这些方法会让事情顺利进行,甚至会挽救局面。Thomas在世纪之交学习了神经网络和模式识别,后来还获得了数据建模的博士学位。

深度学习在21世纪初开始出现在计算机视觉领域,并被应用于医学图像分析任务,如识别医学图像的结构或病变。就在那个时候,也就是21世纪头5年,深度学习引起了我们的关注。我们花了一些时间才意识到,深度学习代表了一种全新的软件编写方式——一种新的多用途算法,可以通过观察数据来学习如何解决复杂的问题。

对于我们“80后”来说,关于计算机能做什么的视野一夜之间就扩展了,计算机能做什么不再受限于程序员的大脑,而受限于数据、神经网络结构和训练过程。在动手实践的过程中,我选择Torch,它是PyTorch的前身。Torch具有灵活、轻量级、运行速度快的特点,具有通过Lua和普通C语言编写的、易于理解的源代码,有一个支持它的社区,并且有着悠久的历史。我热衷于Torch,可能Torch7唯一的缺点是脱离了其他框架可以借鉴的、不断扩展的Python数据科学生态系统。Eli从大学开始就对人工智能感兴趣,但他的职业生涯为他指明了另一个方向,他发现其他早期的深度学习框架使用起来太过费力,以至于令人无法在业余项目中热情地使用它们 [1]

[1] 在那个时候,“深层”神经网络意味着3个隐藏层!

因此当PyTorch的第1个版本在2017年1月18日发布时,我们都非常兴奋。我从那时开始成为PyTorch的核心贡献者。而Eli很早就成为其社区的一员,负责提交一些错误修复文档,实现新特性或对文档进行更新。Thomas为PyTorch贡献了大量的特性,修复了很多错误,并最终成为一名独立的核心贡献者。我们有一种感觉:有一些大型的东西正在起步,这些东西具有适当的复杂性,并且只需要很少的认知开销。PyTorch借鉴了Torch7的一些精益设计,但这次引入了一系列新特性,如自动微分、动态计算图和集成NumPy。

考虑到我们的参与度和热情,在组织了几次PyTorch研讨会之后,我们感觉下一步写一本书是很自然的事了。我们的目标是写一本能够吸引曾经的自己(刚开始学习PyTorch深度学习时的我们)的书。

可以预见的是,我们起初的想法很宏大:教授基础知识,完成一个端到端的项目,并演示PyTorch最新和最好的模型。 我们很快意识到这不是一本书就能完成的事情,因此我们决定专注于我们最初的任务:假设我们之前具备很少或根本没有深度学习的知识,我们将投入时间深入介绍PyTorch背后的关键概念,并最终达到带领读者完成一个完整项目的目的。对于后者,我们回到工作本身,选择演示医学图像分析相关的项目。

Luca Antiga


我们非常感谢PyTorch团队,正是他们的共同努力才使得PyTorch有机会从一个暑期实习项目成长为一个世界级的深度学习工具。我们要表扬Soumith Chintala和Adam Paszke,他们除了技术卓越,还致力于采用“社区优先”的方法来管理项目,PyTorch社区的健康水平和包容性是他们行动的证明。

说到社区,如果没有个人在论坛上帮助早期使用者,没有专家的不懈努力,PyTorch就不会成为现在的样子。在所有可敬的贡献者中,Piotr Bialecki值得我们特别表示感谢。提到本书,我们还要特别感谢Joe Spisak,他相信本书会为社区带来价值。还有Jeff Smith,他做了大量工作来实现这些价值。Bruce Lin摘录了本书第1部分的内容,并将其免费提供给PyTorch社区,他的这些工作也受到了极大的赞赏。

我们要感谢Manning出版社的团队带领我们走过这段旅程,总是提醒我们在各自生活中平衡好家庭、工作和写作之间的关系。感谢Erin Twohey主动问我们是否有兴趣写一本书,也感谢Michael Stephens“哄骗”我们答应写书,尽管我们告诉他我们没有时间。Brian Hanafee所做的事情比审稿人的职责还要多。Arthur Zubarev和Kostas Passadis给出了很好的反馈。Jennifer Houle则需要处理我们奇异风格的图片。我们的文字编辑Tiffany Taylor对细节有敏锐的观察力,如果书中还有错误,那一定是我们自己的问题。我们还要感谢我们的项目编辑Deirdre Hiam、校对Katie Tennant和评论编辑Ivan Martinović。还有很多我们在流程状态更新的邮件抄送列表中看见的一些幕后工作者,所有这些人都在本书的出版过程中起到了不可或缺的作用。同时,也要感谢一些不在我们的感谢列表中的人,包括一些匿名的评论者,他们给出了有用的反馈,帮助本书成为现在的样子。

我们孜孜不倦的编辑Frances Lefkowitz使本书最终得以完成,她理应获得一枚奖章,并在一个热带岛屿上度假一周。感谢她所做的一切,再次感谢!

感谢我们的审稿人,他们在很多方面帮助我们对本书进行了改进:Aleksandr Erofeev、Audrey Carstensen、Bachir Chihani、Carlos Andres Mariscal、Dale Neal、Daniel Berecz、Doniyor Ulmasov、Ezra Stevens、Godfred Asamoah、Helen Mary Labao Barrameda、Hilde Van Gysel、Jason Leonard、Jeff Coggshall、Kostas Passadis、Linnsey Nil、Mathieu Zhang、Michael Constant、Miguel Montalvo、Orlando Alejo Méndez Morales、Philippe Van Bergen、Reece Stevens、Srinivas K. Raman和Yujan Shrestha。

致我们的朋友和家人,那些想知道我们这两年都在做些啥的人:嗨!我们错过了与你们欢聚的时光,我们找个时间聚聚吧!

Eli Stevens

Luca Antiga

Thomas Viehmann


我们写本书的目的是为大家介绍PyTorch深度学习的基础知识,并以一个实际项目来展示。我们力图介绍深度学习底层的核心思想,并向读者展示PyTorch如何将其实现。在本书中,我们试图提供直观印象以帮助大家进一步探索,同时,我们选择性地深入细节,以解剖其背后的奥妙。

本书并不是一本参考书,相反,它是一本概念性的指南,旨在引导你在网上独立探索更高级的材料。因此,我们关注的是PyTorch提供的一部分特性,最值得注意的是循环神经网络,但PyTorch API的其他部分也同样值得重视。

本书适用于那些已成为或打算成为深度学习实践者以及想了解PyTorch的开发人员。我们假设本书的读者是一些计算机科学家、数据科学家、软件工程师、大学生或以后会学习相关课程的学生。由于我们并不要求读者有深度学习的先验知识,因此本书前半部分的某些内容可能对有经验的实践者来说是一些已经了解的概念。对这些读者来说,我们希望本书能够提供一个与已知主题稍有不同的视角。

我们希望读者具备命令式编程和面向对象编程的基本知识。由于本书使用的编程语言是Python,因此大家需要熟悉Python的语法和操作环境,了解如何在所选择的平台上安装Python包和运行脚本。熟悉C++、Java、JavaScript、Ruby或其他类似语言的读者应该可以轻松上手,但是需要在本书之外做一些补充。同样,如果读者熟悉NumPy也很有用,但这并不是强制要求的。我们也希望读者熟悉线性代数的一些基础知识,如知道什么是矩阵、向量和点积。

本书由3个部分组成。第1部分介绍基础知识;第2部分在第1部分的基础上介绍一个端到端的项目,并增加更高级的概念;简短的第3部分以PyTorch部署之旅结束本书。大家可能会注意到各部分的写作风格和图片风格不同。尽管本书是无数小时的协同计划、讨论和编辑的结果,但写作和绘图的工作被分成了几部分。Luca主要负责第1部分,Eli主要负责第2部分[1],Thomas主要负责第3部分(他试图在第3部分将第1部分和第2部分的写作风格结合起来)。我们决定保留这些部分的原始风格,而不是一味寻找各部分风格的共同点。以下是各部分所包括的章及其概述。

[1] Eli和Thomas的写作风格在其他部分也有所体现,如果某章的写作风格改变了,大家不要感到震惊!

第1部分

在第1部分中,我们在PyTorch的使用上迈出第一步,了解PyTorch项目并开始掌握构建自己的项目所需要的基本技能。我们将介绍PyTorch API和一些PyTorch库背后的特性,并训练一个初始的分类模型。在第1部分结束时,我们将准备处理一个真实的项目。

第1章介绍PyTorch用于深度学习的库及其在“深度学习革命”中的地位,并探讨PyTorch与其他深度学习框架的区别。

第2章通过运行一些预训练网络示例来展示PyTorch的实际应用,演示如何在PyTorch Hub中下载和运行模型。

第3章介绍PyTorch的基本构建组件——张量,介绍张量的一些API,并深入底层介绍一些实现细节。

第4章展示不同类型的数据如何被表示为张量,以及深度学习模型期望构造什么样的张量。

第5章介绍梯度下降机制,以及PyTorch如何实现自动微分。

第6章展示利用PyTorch的神经网络(nn)和优化(optim)模块来建立和训练一个用于回归的神经网络的过程。

第7章在第6章的基础上介绍构建一个用于图像分类的全连接层模型,并扩展介绍PyTorch API的知识。

第8章介绍卷积神经网络,并探讨关于构建神经网络模型及其PyTorch实现方面更高级的概念。

第2部分

在第2部分中,每一章都会让我们更接近一个全面的肺癌自动检测解决方案。我们将把这个难题作为动机来演示解决诸如肺癌筛查的大规模问题所需的实际方法。这是一个专注于数据清洗工程、故障排除和问题求解的大型项目。

第9章从CT成像开始,介绍用于肺肿瘤分类的端到端策略。

第10章介绍使用标准PyTorch API加载人工标注数据和CT扫描的图像,并将相关信息转换为张量。

第11章介绍第1个分类模型,该模型基于第10章介绍的训练数据构建。本章还会介绍对模型进行训练,收集基本的性能指标,并使用张量可视化工具TensorBoard来监控训练。

第12章探讨并介绍实现标准性能指标,以及使用这些指标来识别之前完成的训练中的缺陷。然后,介绍通过使用经过数据平衡和数据增强方法改进过的数据集来弥补这些缺陷。

第13章介绍分割,即像素到像素的模型架构,以及使用它来生成覆盖整个CT扫描的可能结节位置的热力图。这张热力图可以用来在CT扫描中找到非人工标注数据的结节。

第14章介绍实现最终的端到端项目:使用新的分割模型和分类方法对癌症患者进行诊断。

第3部分

第3部分为第15章,主要介绍部署相关内容,概述如何将PyTorch模型部署到简单的Web服务中,或将它们嵌入C++程序中,抑或将它们发布到移动电话上。

本书中所有的代码都是基于Python 3.6及以上的版本编写的。本书中的代码可以从异步社区中获取。编写本书时,Python的最新版本是3.6.8,本书正是使用该版本来测试书中的例子的。例如:

$ python
Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

在Bash提示符下输入的命令行以$开头(如本例中的$ python行),固定宽度的内联代码看起来像Python中的self用法一样。

以>>>开头的代码块是Python交互式提示符下的会话脚本。提示符>>>本身不被视为输入,输出文本行也不以>>>和…开头。在某些情况下,在>>>之前插入一个额外的空行,以提高输出的可读性。当你在交互式提示符下输入实际的文本时,这些空白行并不包括在内。

>>> print("Hello, world!")
Hello, world!
              ⇽---  在实际的交互式会话期间不会出现此空行
>>> print("Until next time...")
Until next time...

我们也大量使用Jupyter Notebook,如第1章1.5.1小节所述。我们提供的作为官方GitHub仓库的Jupyter Notebook代码如下所示:

# In[1]:
print("Hello, world!")

# Out[1]:
Hello, world!

# In[2]:
print("Until next time...")

# Out[2]:
Until next time...

绝大多数Jupyter Notebook中的示例代码在第1个单元格(cell,一对In Out会话被视作一个代码单元)中包含以下模板代码(在前几章的代码中可能缺少模板代码中的某几行),这些模板代码在之后的代码中我们不再提及。

# In[1]:
%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.set_printoptions(edgeitems=2)
torch.manual_seed(123)

另外,代码块是.py源文件的一部分或是一份完整的代码。

代码清单 main.py:5, def main

def main():
    print("Hello, world!")

if __name__ == '__main__':
    main()

书中的许多代码示例都使用了2空格缩进。由于版面的限制,代码清单中的每行被限制在80个字符以内,这对于大量缩进的代码段是不切实际的。使用2空格缩进有助于减少换行。本书所有的代码都可以下载(同样请访问异步社区的本书页面),这些代码使用的是一致的4空格缩进。以_t作为后缀的变量是用于CPU存储器的张量,以_g作为后缀的变量用于GPU存储器,以_a作为后缀的变量是NumPy数组。

第1部分不需要任何特定的计算资源,当前任何计算机或在线的计算资源都是足够的,也不需要特定的操作系统。第2部分我们计划完成一个完整的训练运行环境,将需要一个支持CUDA的GPU。第2部分中使用的默认环境配置参数假定为一个有8GB RAM的GPU(我们建议使用NVIDIA GTX 1070或更高的配置),如果你的硬件可用的RAM较少,这些参数可以进行调整。第2部分的癌症检测项目需要下载大约60GB的原始数据,因此至少需要200GB的可用磁盘空间用于训练。幸运的是,某些在线计算服务提供免费使用一定时长的GPU,我们将在适当的部分更详细地讨论计算所需的资源需求。

你需要Python 3.6或更高的版本,相关说明在Python官网上可以找到。有关PyTorch的安装信息,请参阅PyTorch官方网站的入门指南。我们建议Windows用户安装Anaconda或Miniconda。用户若使用其他操作系统,诸如Linux,通常有更多的可行选项,其中pip是Python最常用的包管理器。我们提供了一个名为requirements.txt的文件,pip可以按照该文件来安装依赖项。由于目前的苹果笔记本计算机没有支持CUDA的GPU,因此对于采用macOS的PyTorch预编译软件包只支持CPU。当然,有经验的用户可以自由地安装与你首选的开发环境最兼容的软件包。

虽然本书没有假设读者有深度学习的先验知识,但本书并不是深度学习的基础导论。本书涵盖基础知识,但我们的重点是熟练使用PyTorch库。我们鼓励有兴趣的读者通过阅读本书建立对深度学习的直观理解。为此,Grokking Deep Learning是一本非常值得学习的书,可以帮助大家开发一个强大心智模型,并直观地了解深度神经网络的机制。要获得全面的介绍和参考,我们建议你阅读Goodfellow等人编写的Deep Learning[2]。当然,Manning出版社有一个关于深度学习的目录列表,其中涵盖了该领域各种各样的主题。你可以根据自己的兴趣,从中选择一本作为你下一步要阅读的书。

[2] 中文版《深度学习》由人民邮电出版社出版。——译者注


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供源代码,要获得配套资源,请在异步社区本书页面中单击,跳转到下载界面,按提示进行操作即可。

您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e57767”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可(见下图)。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

扫描下方二维码,您将会在异步社区微信服务号中看到本书信息及相关的服务提示。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/ selfpublish/submission即可)。

如果您所在的学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近40年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号


迎阅读本书的第1部分,这是我们学习PyTorch的起点。在这一部分你将了解PyTorch的基本结构,学会构建PyTorch项目所需的基本技能。

在第1章中,我们将开始接触PyTorch,了解它是什么,它可解决什么问题,以及它与其他深度学习框架的关系。第2章将带我们进行一次轻松的旅行,给我们一个机会去使用那些预先在有趣的任务上训练过的模型。第3章就稍微有点儿严肃了,将介绍PyTorch程序中使用的基本数据结构:张量。第4章将带我们进行另一场旅行,将不同域的数据表示为PyTorch张量。第5章介绍程序如何从样本中学习,以及PyTorch如何支持该过程。第6章介绍神经网络的基本原理,以及如何使用PyTorch构建神经网络。第7章介绍使用神经网络架构解决一个简单的图像分类问题。最后,第8章介绍如何以一种更智能的方法使用卷积神经网络解决同样的问题。

第1部分结束后,我们将在第2部分中了解如何使用PyTorch解决实际问题。


本章主要内容

深度学习如何改变我们机器学习方法。

了解为何PyTorch适合深度学习。

研究一个典型的深度学习项目。

要运行书中例子所需要的硬件资源。

人工智能这个定义模糊的术语涵盖了一系列学科,这些学科经历了大量的研究、推敲、困惑、梦幻般的炒作以及科幻小说般的“恐惧传播”。当然,现实要乐观得多。如果认为如今的机器正在学习人类任何意义上的“思考”,那是不符合实情的。然而,我们发现了一类通用的算法,能够非常有效地近似模拟复杂的非线性过程,可以将以前只能由人类完成的工作自动化。

例如,在talktotransformer网站上,一个叫GPT-2的语言模型可以一次生成一个词,从而生成连贯的文本段落。当我们向该模型输入一些单词时,它将产生以下内容:

Next we’re going to feed in a list of phrases from a corpus of email addresses, and see if the program can parse the lists as sentences. Again, this is much more complicated and far more complex than the search at the beginning of this post, but hopefully helps you understand the basics of constructing sentence structures in various programming languages.

虽然在这些内容背后没有一个明确的论题,但以上内容对于一台机器而言是非常连贯的。

令人惊喜的是,该模型执行这些以前只有人类才能完成的任务的能力是通过样本来获得的,而不是由人类将其编码为一组规则。在某种程度上,我们了解到的智能只是一个概念,我们经常将它和自我意识混为一谈,而自我意识绝对不是成功完成这些任务的必要条件。最后,计算机智能的问题可能都不重要。Edsger W.Dijkstra发现,“机器是否会思考”这个问题与“潜艇是否会游泳”这个问题是差不多的[1]

[1] Edsger W. Dijkstra,“The Threats to Computing Science”。

我们所讨论的一般算法属于深度学习(AI的子类),深度学习是通过提供具有指导意义的例子来训练深度神经网络的数学实体。深度学习使用大量数据来近似输入和输出相距很远的复杂函数,如输入是图像,输出是对输入进行描述的一行文本;或输入是书面文字,输出是朗读该文字的自然语音。或者,更简单地说,把金毛猎犬(golden retriever)的图片和一个标志联系起来,告诉我们“是的,金毛猎犬在这里”。深度学习的这种能力使我们能够创建诸如此类功能的程序,直到现在,这种功能都是人类独有的。

为了理解深度学习带来的范式转变,让我们回顾一下其发展历程。过去10年,被称为机器学习的一类系统都重度依赖于特征工程。特征是对输入数据的转换,它有助于下游算法(如分类器)在新数据上产生正确的结果。特征工程包括提出正确的转换,以便下游算法能够完成任务。例如,为了在手写数字的图像中区分1和0,我们会利用一组过滤器来判断图像上的边缘方向,然后训练一个分类器,在给定边缘方向分布的情况下预测正确的数字。另一个有用的特征可能是封闭圆圈的数量,比如对于数字0和8,特别是对于有2个圈的数字8

另一方面,深度学习实现的是从原始数据中自动找到这样的表征,以便成功执行任务。在区分1和0的例子中,过滤器会在训练中通过迭代地查看成对的例子和目标标签来改进。这并不是说特征工程不适合深度学习,反而我们经常需要在学习系统中注入某种形式的先验知识。无论怎样,神经网络提取数据并根据实例提取有用表征的能力,正是深度学习如此强大的原因。深度学习实践者的重点不是手工提取这些表征,而是操作数学实体,以便能够自动地从训练数据中发现表征。通常,这些自动创建的表征比手工创建的更好!与许多颠覆性技术一样,深度学习不需要花费太多人力构建特征这一事实导致了观点的转变。

在图1.1的左侧,我们看到一个实践者正忙于定义工程特征并将它们输入到一个学习算法中,任务的执行结果与专业的工程师的手工抽取的特征一样好。在图1.1的右侧,实践者通过深度学习,将原始数据输入到一个自动提取分层特征的算法中,以优化算法在任务上的表现为目的,其结果将与实践者驱使算法实现其目标的能力一样好。

通过图1.1的右侧,我们已经大致了解了成功执行深度学习任务需要做些什么。

我们需要一种方法来提取我们手头的所有数据。

我们需要定义深度学习机器。

我们需要通过一种自动化的方法,即训练,来获得有用的表征,并使机器产生预期的输出。

让我们更详细地了解一下我们一直提到的训练问题。在训练期间,我们需要使用一种评估指标,也就是基于模型输出和参考数据的实值函数来为期望输出和实际输出之间的差异提供数字(通常分数越低越好)。训练包括通过逐步修改我们的深度学习机器使评估指标的分值逐渐变得更低,即使在训练期间没有看到的数据上也是如此。

图1.1 随着数据和计算需求的增加,深度学习代替手工提取特征

PyTorch是一个Python程序库,有助于构建深度学习项目。它强调灵活性,并允许用深度学习领域惯用的Python来表示深度学习模型。它的易用性使得它在研究社区中有了早期的使用者,并且在首次发布之后的几年里,它已经成为应用程序中使用最广泛的深度学习工具之一。

就像Python用于编程一样,PyTorch也为深度学习提供了很好的入门指南。同时,PyTorch已经被证明完全可以在实际项目和高规格的专业环境下使用。我们相信PyTorch凭借其清晰的语法、精简的API和易于调试的优点将成为入门深度学习的最佳选择。因此强烈建议你将PyTorch作为你学习的第一个深度学习框架。但是它是否应该成为你学习的最后一个深度学习库,这取决于你自己。

图1.1中的深度学习机器的核心是一个相当复杂的数学函数,它将输入映射到输出。为了便于表达这个函数,PyTorch提供了一个核心数据结构——张量,它是一个多维数组,与NumPy数组有许多相似之处。在此基础上,PyTorch具备在专用硬件上执行加速数学运算的功能,因此不管是在单台机器还是在并行计算资源上,都能很方便地对神经网络体系结构进行计算。

本书旨在辅助软件工程师、数据科学家和精通Python的学生入门PyTorch,使他们能够熟练地使用PyTorch来构建深度学习项目。我们希望本书尽可能地便于理解和实用,希望你能够理解本书中的概念并将它们应用到其他领域。为此,我们采用动手实践的方法,希望你随时准备好计算机,这样你就可以尝试使用示例并进一步进行操作。读完本书后,我们希望你能够获得数据源,并能根据优秀的官方文档来构建一个深度学习项目。

虽然我们重点介绍的是应用PyTorch构建深度学习系统实践方面的内容,但我们相信,为基础深度学习工具提供一个易懂的导读同样是促进获得新技能的一种方式。这更是为向各个学科的新一代科学家、工程师和来自广泛学科的实践者传播知识迈出的一步,这些知识将成为未来几十年许多软件项目的关键。

为了充分利用本书,你需要2样东西。

Python编程经验。我们不打算在这一点上花篇幅介绍。你需要了解Python数据类型、类、浮点数等。

潜心钻研、亲自动手实践的意愿。我们将从基础开始构建我们的工作知识体系。如果你跟着我们一起动手学习,学起来会容易很多。

本书由3个部分组成。第1部分介绍基础知识,详细介绍通过PyTorch提供的工具将图1.1所示的深度学习的过程转化为代码实现。第2部分将带你完成一个完整的涉及医学影像的端到端项目,在CT扫描结果中查找并对肿瘤进行分类,在第1部分介绍的基本概念的基础上,并添加更高级的主题。本书结束部分,即第3部分,简短介绍PyTorch提供的将深度学习模型部署到生产环境的相关内容。

深度学习是一个十分庞大的领域,本书只覆盖该领域的一小部分。具体来说,包括一些使用PyTorch进行较小规模的图像分类和分割的项目,通过一些示例处理二维和三维的图像数据集。

本书的重点在于PyTorch实践,目的是覆盖足够的范围,让你能够通过深度学习来解决现实世界中机器学习的问题,如在视觉领域应用深度学习领域中现有的模型或探索研究文献中提出的新模型。与深度学习相关的最新出版物,大部分可以在arXiv官网的公共预印库中找到[2]

[2] 我们也推荐你在Arxiv Sanity Preserver网站查找感兴趣的研究论文。

就像我们说过的那样,通过模型学习和训练,深度学习允许我们执行很多复杂任务,如机器翻译、玩策略游戏以及在杂乱无章的场景中识别物体等。为了在实践中做到这一点,我们需要灵活且高效的工具,以便能够适用于这些复杂任务,能够在合理的时间内对大量数据进行训练。我们需要训练后的模型在输入发生变化的情况下能够正确执行。接下来看看我们决定使用PyTorch的一些原因。

PyTorch因其简单易用而被推荐。许多研究人员和实践者发现它易于学习、使用、扩展和调试。它是Python化的,虽然和任何复杂领域一样,它有注意事项和最佳实践示例,但对于以前使用过Python的开发人员来说,使用该库和使用其他Python库一样。

更具体地说,在PyTorch中编写深度学习机器是很自然的事情。PyTorch为我们提供了一种数据类型,即张量,通常用来存储数字、向量、矩阵和数组。此外,PyTorch还提供了操作它们的函数,我们可以逐步使用这些函数来编程。如果我们愿意,还可以进行交互式编程,就像平常使用Python一样。如果你知道NumPy,那么你对交互式编程应是非常熟悉的。

PyTorch具备2个特性,使得它与深度学习关联紧密。首先,它使用GPU加速计算,通常比在CPU上执行相同的计算速度快50倍。其次,PyTorch提供了支持通用数学表达式数值优化的工具,该工具用于训练深度学习模型。请注意,这2个特性适用于一般的科学计算,而不只适用于深度学习。事实上,我们完全可以将PyTorch描述为一个在Python中为科学计算提供优化支持的高性能库。

PyTorch的一个设计的驱动因素是表现力,它允许开发人员实现复杂的模型,而不会被PyTorch库强加过高的复杂性(PyTorch不是一个框架)。PyTorch可以说是最无缝地将深度学习领域的思想转化为Python代码的软件之一。因此,PyTorch在研究中得到广泛的采用,国际会议上的高引用次数就证明了这一点[3]

[3] 在2019年国际学习表征会议(International Conference on Learning Representation,ICLR)上,PyTorch在252篇论文中被引用(2018年为87篇),与TensorFlow的引用水平(266篇论文)相当。

PyTorch从研发到成为产品的过程是一件值得关注的事情。虽然PyTorch最初专注于学术研究领域,但它已经配备了高性能的C++运行环境,用于部署模型进行推理而不依赖Python,并且还可用于设计和训练C++模型。它还提供了与其他语言的绑定,以及用于部署到移动设备的接口。这些特性允许我们利用PyTorch的灵活性,还允许我们的程序在难以获得完整的Python运行环境或可能需要极大的开销的情况下运行。

当然,声称高易用性和高性能是很容易的,我们希望当你深入阅读本书的时候,会认同我们的声明。

做一个可能不太恰当的比喻,虽然所有的类比都有瑕疵,但2017年1月PyTorch 0.1的发布标志着深度学习库、封装器和数据交换格式从“寒武纪”爆炸式增长过渡到一个整合和统一的时代。

注意 当前深度学习发展迅速,以至于当你读到本书的印刷版时,它可能已经过时了。如果你对这里提到的一些库不熟悉,那也很正常。

PyTorch第1个测试版本发布时情况如下。

Theano和TensorFlow曾是首选的低级别库,它们使用一个模型,该模型让用户自定义一个计算图,然后执行它。

Lasagne和Keras是Theano的高级封装器,同时Keras还对TensorFlow和CNTK进行了封装。

Caffe、Chainer、DyNet、Torch(以Lua为基础的PyTorch前身)、MXNet、CNTK、DL4J等库在深度学习生态系统中占据了不同的位置。

在接下来大约2年的时间里,情况发生了巨大的变化。除了一些特定领域的库,随着其他深度学习库使用量的减少,PyTorch和TensorFlow社区的地位得到了巩固。变化情况可以总结为以下几点。

Theano是最早的深度学习框架之一,目前它已经停止开发。

TensorFlow:

完全对Keras进行封装,将其提升为一流的API;

提供了一种立即执行的“急切模式(eager mode)”,这种模式有点儿类似于PyTorch处理计算的方式;

TensorFlow 2.0默认采用急切模式。

JAX是谷歌的一个库,它是独立于TensorFlow开发的,作为一个与GPU、Autograd和JIT编译器具有对等功能的NumPy库,它已经开始获得关注。

PyTorch:

Caffe2完全并入PyTorch,作为其后端模块;

替换了从基于Lua的Torch项目重用的大多数低级别代码;

增加对开放式神经网络交换(Open Neural Network Exchange,ONNX)的支持,这是一种与外部框架无关的模型描述和交换格式;

增加一种称为“TorchScript”的延迟执行的“图模型”运行环境;

发布了1.0版本;

取代CNTK和Chainer成为各自企业赞助商选择的框架。

TensorFlow拥有强大的生产线、广泛的行业社区以及巨大的市场份额。由于使用方便,PyTorch在研究和教学领域取得了巨大进展,并且随着研究人员和毕业的学生进入该行业,PyTorch的势头越来越好。它还在生产解决方案方面积累了经验。有趣的是,随着TorchScript和急切模式的出现,PyTorch和TensorFlow的特点集开始趋同,尽管在这些特点的呈现和整体体验上仍然存在很大的差异。

我们已经提及了PyTorch的一些构成要素,接下来我们将正式介绍PyTorch的主要组件的高级导图。我们可以通过查看一个PyTorch深度学习项目所需要的组件来更好地了解这些内容。

首先,PyTorch中的“Py”是指Python,但其中又有很多非Python代码。事实上,由于性能原因,PyTorch大部分是用C++和CUDA编写的,CUDA是一种来自英伟达的类C++的语言,可以被编译并在GPU上以并行方式运行。有一些方法可以直接在C++环境中运行PyTorch,我们将在第15章中讨论这些方法。此功能的动机之一是为生产环境中部署模型提供可靠的策略。但是,大多数情况下我们都是使用Python来与PyTorch交互的,包括构建模型、训练模型以及使用训练过的模型解决实际问题等。

实际上,Python API正是PyTorch在可用性以及与更广泛Python生态系统集成方面的亮点。让我们来看看PyTorch的心智模型。

正如我们前面已经提到的那样,PyTorch的核心是一个提供多维数组(张量)以及由torch模块提供大量操作的库(我们将在第3章详细讨论)。张量及操作可以在CPU或GPU上使用。在PyTorch中,将运算从CPU转移到GPU不需要额外的函数调用。PyTorch提供的第2个核心功能是张量的能力,它可以跟踪在张量上执行的操作,并分析和计算任何输入对应的输出的导数。该功能用于数值优化,是由张量自身提供的,通过PyTorch底层自动求导(autograd)引擎来调度。

通过使用张量以及张量自动求导的标准库,PyTorch可以用于物理学、渲染、优化、仿真、建模等,而且我们很可能会看到PyTorch在科学应用的各个领域以创造性的方式使用。但PyTorch首先是一个深度学习库,因此它提供了构建和训练神经网络所需的所有构建模块。图1.2展示了完成一个深度学习项目的标准步骤,从加载数据到训练模型,最后将该模型部署到生产中。

用于构建神经网络的PyTorch核心模块位于torch.nn中,它提供了通用的神经网络层和其他架构组件。全连接层、卷积层、激活函数和损失函数都可以在这里找到(在本书剩余部分,我们将详细地介绍这些内容)。这些组件可用于构建和初始化图1.2所示的未训练的模型。为了训练模型,我们需要一些额外的东西:模型训练的数据、一个使模型适应训练数据的优化器,以及一种把模型和数据传输到硬件的方法,该硬件用于执行模型训练所需的计算。

图1.2 PyTorch项目的基础、高级结构,包括数据加载、训练和生产部署等

在图1.2的左侧,我们看到训练数据在到达模型之前,需要进行大量的数据处理[4]。首先,我们需要从外部获取数据,通常是从作为数据源的某种存储中获取数据。然后我们需要将数据中的每个样本转换成PyTorch可以处理的张量。我们的自定义数据(无论它的格式是什么)和标准化的PyTorch张量之间的桥梁是PyTorch在torch.utiCls.data中提供的Dataset类。由于不同问题处理过程截然不同,因此我们需要自己定义数据源。在第4章中,我们将详细介绍如何将各种类型的数据表示为张量。

[4] 这只是在运行中完成的数据准备,而不是数据预处理,在实际项目中,数据预处理可能是工作量相当大的一部分工作。

由于数据存储通常很慢,还存在访问延迟,因此我们希望实现数据加载并行化。但是,由于Python提供的许多操作都不具有简单、高效的并行处理能力,因此我们需要多个进程来加载我们的数据,以便将它们组装成批次(batches),即组装成一个包含多个样本的张量。这是相当复杂的,但由于它也是相对通用的,PyTorch很容易在DataLoader类中实现这些功能。它的实例可以生成子进程在后台从数据集中加载数据,提前将数据准备就绪,一旦训练循环开始就可以立即使用。我们将在第7章介绍和使用Dataset和DataLoader。

有了获取批量样本的机制,我们可以转向图1.2中心的训练循环。通常训练循环是作为一个标准的 Python for 循环来实现的。在最简单的情况下,模型在本地CPU或单个GPU上执行所需的计算,一旦训练循环获得数据,计算就可以立即开始。很可能这就是你的基本设置,也是我们在本书中假设的设置。

在训练循环的每个步骤中,我们根据从数据加载器获得的样本来评估模型。然后我们使用一些评估指标或损失函数将模型的输出与期望的输出(目标)进行比较。PyTorch除了提供构建模型的组件,也有各种损失函数供我们使用,torch.nn包中也提供这些函数。在我们用损失函数将实际输出与期望的输出进行比较之后,我们需要稍微修改模型以使其输出更接近目标。正如前面提到的,这正是PyTorch底层的自动求导引擎的用武之地。但是我们还需要一个优化器来进行更新,这是PyTorch在torch.optiom中为我们提供的。在第5章中,我们将开始研究带有损失函数和优化器的训练循环,然后在第2部分开始我们的大项目之前,即在第6章到第8章中先训练一下我们使用PyTorch的技能。

使用更精细的硬件越来越普遍,如多GPU或多台服务器,将这些资源用于训练大型模型,如图1.2的底部中心所示。在这些情况下,可以使用torch.nn.parallel.DistributedDataParallel和torch. distributed子模块来使用附加的硬件。

训练循环可能是深度学习项目中最乏味和耗时的部分。训练循环结束后,我们将得到一个模型,该模型的参数已经在我们的任务上得到了优化,如图1.2训练循环右侧部分所示的训练过的模型。有一个用来解决任务的模型是很好的,但是为了使它有用,我们需要将其放在需要工作的地方。如图1.2所示,在其右侧所示的部署部分可能涉及将模型放在生产服务器上,或将模型导出到云服务中,或者我们可将它与更大的应用程序集成,抑或在手机上运行它。

部署操作中一个特定的步骤是导出模型。如前所述,PyTorch默认为立即执行模式(急切模式)。每当Python解释器执行一个涉及PyTorch的指令,相应的操作就会立即被底层的C++或CUDA的实现来执行。随着对张量进行操作的指令越来越多,后端实现将执行更多的操作。

PyTorch还提供了一种通过TorchScript提前编译模型的方法。使用TorchScript,PyTorch可以将模型序列化为一组独立于Python调用,如在C++程序或在移动设备上调用的指令集。我们可以把模型想象成一个具有有限指令集的虚拟机,用于特定的张量操作。这允许我们导出我们的模型,或者将其作为用于PyTorch运行时的TorchScript导出,或者将其以一种称为ONNX的标准格式导出。这些特性是PyTorch生产部署的基础,我们将在第15章中进行介绍。

本书将要求编码和运行任务,涉及大量数值运算,如涉及大量矩阵的乘法。事实证明,在新数据上运行一个预训练网络几乎是当今任何笔记本计算机或台式计算机都能做到的。即使使用一个预训练网络,并对其中一小部分进行重新训练,使其专门用于一个新的数据集,也不一定需要专门的硬件。你可以使用一台当前主流配置的笔记本计算机或台式计算机来完成我们在本书第1部分中所有的内容。

然而,我们预计在第2部分完成更高级示例的完整训练,这将需要一个支持CUDA的GPU。在第2部分中使用的默认配置参数假设GPU具有8GB的RAM(我们建议使用NVIDIA GTX 1070或更高的配置),但如果硬件可用的RAM较少的话,这些参数可以适当调整。需要说明的是:如果你愿意等待的话,这样的硬件配置并不是硬性要求的。但是在GPU上运行至少可以减少一个数量级的训练时间(通常会快40~50倍)。单独来看,计算参数更新所需的操作在现代硬件上(如运行在笔记本计算机的CPU上)执行得非常快,大概为几分之一秒到几秒。但问题是训练需要反复执行这些操作,不断地更新网络参数,以减少训练误差。

中等规模的网络在配备好GPU的工作站上从头开始训练大型真实世界的数据集可能需要数小时到数天的时间。通过在同一台机器上使用多个GPU,或是在有多个GPU的机器集群中训练,可以缩短花费的时间。由于云计算供应商提供相关服务,这些设备并不像听起来那么让人望而却步。DAWNBench是美国斯坦福大学提出的一项有趣的倡议,旨在提供基于公共数据的、常见的深度学习任务的训练时间和云计算耗时的基准。

如果你在学习第2部分的时候没有GPU,我们建议你查看各种云平台提供的产品,其中许多云平台都提供预装有PyTorch的、支持GPU的Jupyter Notebook,且通常提供一定的免费配额。谷歌的Colaboratory是一个不错的选择。

最后要考虑的是操作系统。PyTorch从发布之初就支持Linux和macOS,并在2018年开始支持Windows。由于目前苹果笔记本计算机没有配备支持CUDA的GPU,因此PyTorch预编译macOS的软件包只支持CPU。在本书中,我们尽量不去假设你在一个特定的操作系统中运行,尽管第2部分的一些脚本表明这些是在Linux下的Bash提示符下运行的,但这些脚本很容易转换为与Windows兼容的格式。为了方便,代码将尽可能地像在Jupyter Notebook上运行一样展示出来。

有关安装信息,请参阅PyTorch官方网站的入门指南。我们建议Windows用户使用Anaconda或Miniconda进行安装。用户若使用其他操作系统,如Linux,通常有很多可行的选择,其中pip是Python最常用的软件包管理器。我们提供了一个名为requirements.txt的文件,pip可以按照该文件来安装依赖项。当然,有经验的用户可以自由地采用与你首选的开发环境最兼容的方式安装软件包。

第2部分还对下载带宽和磁盘空间有一些要求。在第2部分中,癌症检测项目所需要的原始数据大约需要60GB的下载空间,解压后大约需要120GB的空间,压缩包可以在解压之后删除。另外,鉴于性能的原因一些数据需要被缓存,训练时还将需要80GB的空间。因此系统至少需要200GB的可用磁盘空间用于训练。虽然可以使用网络存储来实现此目的,但如果访问网络存储的速度慢于访问本地磁盘的速度,那么可能会对训练速度造成影响。最好使用本地的SSD空间来存储数据,以便快速检索。

我们假设你已经安装了PyTorch和其他依赖项,并且已经验证了一切正常。在前面章节中,我们提到了跟随书中代码进行操作的可能性,在示例代码中,我们大量使用Jupyter Notebook。Jupyter Notebook在浏览器中显示为一个页面,通过它,我们可以交互式地运行代码。代码由内核执行,内核是一个运行在服务器上的进程,它随时准备接收要执行的代码并返回结果,在页面上呈现结果。Notebook在内存中维护内核的状态,就像代码求值期间定义的变量一样,直到内核被终止或重新启动。我们与Notebook交互的基本单元是单元格:页面上的输入框。我们在这里输入代码,然后让内存对其进行执行(通过菜单中的命令或按组合键)。我们可以在Notebook中添加多个单元格,新的单元格能够识别我们在之前的单元格中创建的变量。代码执行后,单元格最后一行返回的值将输出在单元格正下方,绘图也是如此。通过混合源代码、运算结果和Markdown格式的文本单元格,我们可以生成漂亮的交互式文档。你可以在项目网站(Jupyter官网)上阅读关于Jupyter Notebook的所有信息。

此时,你需要从GitHub上下载的Jupyter Notebook代码的根目录下启动Notebook服务器。具体如何启动服务取决于你的操作系统以及你安装Jupyter Notebook的方式和位置。如果你有任何问题,可以在本书的论坛上提问[5]。一旦启动了Jupyter Notebook,你的默认浏览器将会弹出一个本地Notebook文件列表。

[5] Manning出版社官方论坛的deep-learning-with-pytorch栏目。

注意 Jupyter Notebook是一个通过代码来表达和研究想法的强大工具。尽管我们认为它很适合本书的用例,但它并不合适所有用例。我们认为,集中精力消除分歧和最小化认知开销是很重要的,毕竟每个人的想法都可能是不同的,在你用PyTorch实践时使用你喜欢的工具。

书中所有代码清单的完整代码都可以在本书网站以及GitHub代码仓库中找到。

1.启动Python以获得交互式提示符。

a)你使用的Python是什么版本呢?我们希望至少是3.6版本。

b)你能执行import torch命令导入包吗?你使用的PyTorch是哪个版本呢?

c)执行torch.cuda.is_available()的输出结果是什么?它是否符合你所使用的硬件的期望?

2.启动Jupyter Notebook服务器。

a)Jupyter Notebook使用的是哪个版本的Python呢?

b)Jupyter Notebook使用的torch库的位置是否与你从交互式提示符中导入的位置相同呢?

深度学习模型会自动从示例中学习如何将输入与期望的输出相互关联。

PyTorch库允许你高效地构建和训练神经网络模型。

PyTorch在注重灵活性和速度的同时最大限度地减少了认知开销,它还默认为急切模式。

TorchScript允许我们预编译模型,并且不仅可以在Python环境中调用它们,还可以在C++程序和移动设备上调用它们。

自2017年初PyTorch发布以来,深度学习工具生态系统得到了显著巩固。

PyTorch提供了很多实用程序库来推动深度学习项目。

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e57767”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


本章主要内容

运行用于图像识别的预训练模型。

简要介绍生成式对抗网络和循环生成式对抗网络。

可以为图像生成文本描述的字幕模型。

通过Torch Hub分享模型。

由于种种原因,计算机视觉无疑是受深度学习影响最大的领域之一。对自然图像的内容进行分类和解释的需求已经存在,非常大的数据集变得可用,并且发明了诸如卷积层之类的新结构,并可以在GPU上以前所未有的精度快速运行。所有这些因素都与互联网巨头们渴望了解数以百万计的用户用他们的移动设备拍摄并在他们的平台上托管的照片密切相关。原因太多了!

我们将通过下载和运行非常有趣的模型来学习如何使用计算机视觉领域研究人员优秀的研究成果,这些模型已经在开放的大规模数据集上训练过。我们可以把预训练的神经网络看作一个接收输入并生成输出的程序,该程序的行为是由神经网络的结构以及它在训练过程中所看到的样本所决定的,即期望的输入-输出对,或者期望输出应该满足的特性。使用现成的模型是快速启动深度学习项目的一种方法,因为它利用了设计模型的研究人员的专业知识,并节省了训练权重的计算时间。

在本章中,我们将探讨3种常用的预训练模型:一种可以根据内容对图像进行分类的模型,一种可以从真实图像中生成新图像的模型,还有一种可以使用合适的英语来描述图像内容的模型。我们将学习在PyTorch中加载和运行这些预训练模型。我们还将介绍PyTorch Hub,它是一组工具,通过这些工具,我们将要讨论的、预训练模型就可以通过一个统一的接口轻松地获得。在此过程中,我们将讨论数据源,定义标签之类的术语,并参加斑马竞技。

如果你是从其他深度学习框架转过来学习PyTorch的,并且你想直接学习PyTorch的基本原理,那么你可以直接跳到第3章。本章中我们将涉及的内容更多的是趣味性而非基础性的,并且在一定程度上独立于任何给定的深度学习工具。这并不是说它们不重要!但是如果你在其他深度学习框架中使用过预训练模型,那么你应该已经知道它们是多么强大的工具。如果你对生成式对抗网络(Generative Adversarial Network,GAN)游戏很熟悉,就不需要看我们的解释了。

不过,我们希望你能继续阅读,因为本章的乐趣之下隐藏着一些重要的技能。学习使用PyTorch运行预训练模型是一项有用的技能。如果模型是在大型数据集上训练的,那么这一点特别有用。不管我们是否训练过模型,我们要习惯于在真实数据上获取和运行神经网络的机制,然后进行可视化和评估其输出结果。

作为我们对深度学习的首次尝试,我们将运行一个非常先进的深度神经网络,该网络在物体识别任务上进行了预训练。有许多预训练网络可以通过源代码库访问。研究人员通常会在发表论文的同时发布他们的源代码,而且代码通常带有通过在参考数据集上训练模型而获得的权重。例如,使用其中一个模型就可以使我们轻松地为接下来的Web服务配备图像识别功能。

我们即将在这里探讨的预训练网络是已经在ImageNet数据集的子集上训练过的。ImageNet是一个由斯坦福大学维护的包含1400多万幅图像的非常大的数据集。所有图像都用来自WordNet数据集的名词层次结构标记,而WordNet数据集又是一个大型的英语词汇数据库。

ImageNet数据集和其他公共数据集一样,源于学术竞赛。竞赛历来是机构和公司的研究人员经常互相挑战的主要赛场。其中,ImageNet大规模视觉识别挑战赛(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)自2010年成立以来广受欢迎。这个特殊的竞赛基于几个任务,每年可以有所不同,如图像分类(识别图像类别)、目标定位(在图像中识别物体的位置)、目标检测(识别和标记图像中的对象)、场景分类(对图像中的情形进行分类)和场景分析(将图像分割成与语义类别相关的区域,如牛、房子、奶酪和帽子等)等。

具体来说,图像分类任务包括获取一个输入图像,并从1000个类别中生成5个标签的列表,列表按置信度排序,描述图像的内容。

ILSVRC的训练集由120万幅图像组成,每幅图像用1000个名词中的一个来标记,如“dog”,这些名词被称为图像的类(class)。从这个意义上讲,我们将交替使用术语标签(lable)和类。在图2.1中,我们可以看到一些ImageNet图像。

图2.1 ImageNet图像的一个小样本

我们最终能够将我们自己拍摄的图像输入到预训练模型中,如图2.2所示。模型将为该图像生成一个预测的标签列表,我们可以检查该列表以查看模型认为我们的图像是什么。模型对有些图像的预测很准确,有些则不准确。

输入的图像将首先被预处理成一个多维数组类torch.Tensor的实例。它是一个具有高度和宽度的RGB图像,因此这个张量将有3个维度:RGB通道和2个特定大小的空间图像维度。我们将在第3章详细讨论张量是什么,但现在,可以把它想象成一个浮点数字类型的向量或矩阵。我们的模型将把处理过的输入图像传入预训练网络中,以获得每个类的分数。根据权重,最高的分数对应最可能的类。然后将每个类一对一地映射到标签上。该输出被包含在一个含有1000个元素的torch.Tensor张量中,每个元素表示与该类相关的分数。在做这些之前,我们需要先了解网络本身,看看它的底层结构,并了解如何在模型使用数据之前准备数据。

图2.2 推理的过程

如前所述,现在我们将使用在ImageNet上训练过的网络。首先,让我们看看TorchVision项目,该项目包含一些表现优异的、关于计算机视觉的神经网络架构,如AlexNet、ResNet和Inception-v3等。它还可以方便地访问像ImageNet这样的数据集和其他工具,以加快PyTorch的计算机视觉应用程序运行的速度。我们将在本书做进一步探讨。现在,让我们加载并运行这2个网络:首先是AlexNet,它是在图像识别方面早期具有突破性的网络之一;然后是残差网络,简称ResNet,它在2015年的ILSVRC中获胜。如果在第1章你还没有启动和运行PyTorch,现在是时候做这些事情了。

在torchvision.models中可以找到预定义的模型(参见源码code/p1ch2/2pre_trained networks.ipynb):

# In[1]
from torchvision import models

我们可以看看实际的模型:

# In[2]
dir(models)

# Out[2]:
['AlexNet',
 'DenseNet',
 'Inception3',
 'ResNet',
 'SqueezeNet',
 'VGG',
...
 'alexnet',
 'densenet',
 'densenet121',
...
 'resnet',
 'resnet101',
 'resnet152',
...
 ]

首字母大写的名称指的是实现了许多流行模型的Python类,它们的体系结构不同,即输入和输出之间操作的编排不同。首字母小写的名称指的是一些便捷函数,它们返回这些类实例化的模型,有时使用不同的参数集。例如,resnet101表示返回一个有101层网络的ResNet实例,resnet18表示返回一个有18层网络的ResNet实例,以此类推。下面我们将开始介绍AlexNet。

AlexNet架构在2012年的ILSVRC中以较大的优势胜出,前5名的测试错误率(也就是说,正确的标签必须在前5名中)为15.4%。相比之下,没有使用深度网络的第2名则以26.2%的成绩落后。这是计算机视觉史上的一个关键时刻:此刻,社区开始意识到深度学习在视觉任务中的潜力。随之而来的是不断的改进,更现代的架构和训练方法使得前5名的错误率低至3%。

按照现在的标准,与先进的模型相比,AlexNet是一个相当小的网络。但是在我们的例子中,它非常适合作为我们学习的第一个神经网络,通过它我们可以学习如何运行一个训练好的模型处理新图像。

AlexNet架构如图2.3所示,不是说我们现在已经掌握了理解它的所有要素,而是我们可以预先了解该模型的几个方面。首先每个块由一系列乘法和加法运算函数组成,以及我们将在第5章中介绍的一些其他函数。我们可以将每个块看作一个过滤器,一个接收一幅或多幅图像作为输入并生成其他图像作为输出的函数。这种做法是在训练期间,基于训练时所看到的样本和样本所期望的输出决定的。

图2.3 AlexNet架构

在图2.3中,输入图像从左侧进入并依次经过5个过滤器,每个过滤器生成一些输出图像。经过每个过滤器后,图像会被缩小。在过滤器堆栈中,最后一个过滤器产生的图像被排列成一个拥有4096个元素的一维向量,并产生1000个分类类别的输出概率,每一个输出代表一个类别。

为了使用AlexNet模型产生一个输出图片,我们可以创建一个AlexNet类的实例,如下列代码所示。

# In[3]:
alexnet = models.AlexNet()

此时,alexnet是一个可以运行AlexNet架构的对象。现在,我们还不需要了解这个架构的细节。alexNet仅是一个不透明的对象,可以像函数一样调用它。通过向alexnet提供一些精确的输入数据(我们很快会看到这些输入数据应该是什么样的),我们将在网络中运行一个正向传播(forward pass)。也就是说,输入将经过一组神经元,其输出将被传递给下一组神经元,直到得到最后的输出。实际上,如果我们有一个真实类型的input对象,我们可以使用output=alexnet(input)运行正向传播。

但如果我们这样做,我们将通过整个网络提供数据,来生产垃圾数据!这是因为网络没有初始化:它的权重,即输入的相加和相乘所依据的数字没有经过任何训练。网络本身就是一块白板,或者说是随机的白板,我们现在要做的就是要么从头训练它,要么加载之前训练好的网络。

为此,我们再回到models模块。我们已经知道首字母大写的名称对应实现了许多流行模型的Python类,小写的名称是函数,用于实例化具有预定义层数和单元数的模型,可以选择性地下载和加载预训练权重。请注意,使用这些函数并不是必须的:它们只是使实例化模型的层数和单元数与预训练网络的构建方式相匹配变得方便。

现在我们将使用resnet101来实例化一个具有101层的卷积神经网络。客观地说,在2015年ResNet出现之前,在如此深的网络中达到稳定的训练是极其困难的。ResNet提出了一个技巧使之变为可能,并在这一年一举击败了好几个深度学习测试基准。

现在让我们创建一个网络实例。我们将传递一个参数,指示函数下载resnet101在ImageNet数据集上训练好的权重,该数据集包含120万幅图像和1000个类别。

# In[4]:
resnet = models.resnet101(pretrained=True)

下载期间,我们可以花点儿时间来“欣赏”一下resnet101的4450万个参数,竟有如此多的参数需要自动优化。

通过前面的步骤我们获得了什么呢?出于好奇,我们来看看resnet101是什么样子的。我们可以通过输出返回模型的值的方式来实现这一点。它为我们提供了在图2.3中看到的同类信息的文本表示,并提供了关于网络结构的详细信息。目前对我们而言可能信息量有些过大,但随着我们继续阅读本书,我们将对此段代码的理解不断增强。

# In[5]:
resnet

# Out[5]:
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3),
                  bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True,
                     track_running_stats=True)
  (relu): ReLU(inplace)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1,
                       ceil_mode=False)
  (layer1): Sequential(
    (0): Bottleneck(
...
    )
  )
  (avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
  (fc): Linear(in_features=2048, out_features=1000, bias=True)
)

我们在这里看到的是许多模块(modules),每行一个。请注意,它们与Python模块没有任何关系:它们是独立的操作,是神经网络的构建模块。它们在其他深度学习框架中也被称为层(layers)。

如果向下滚动,我们会看到许多Bottleneck模块一个接一个地重复出现,总共有101个,包括卷积和其他模块。这是一个典型的用于计算机视觉的深度神经网络的结构:一个或多个过滤器和非线性函数顺序级联,最后一层(fc)为1000个输出类(out_features)中的每个类生成预测分数。

可以像调用函数一样调用resnet变量,将一幅或多幅图像作为输入,并为1000个ImageNet类生成对等数量的分数。然而,在此之前我们必须对输入的图像进行预处理,使其大小合适,使其值(颜色)大致处于相同的数值范围。为此,TorchVision模块提供了transforms 模块,它允许我们快速定义具有基本预处理功能的管道。

# In[6]:
from torchvision import transforms
preprocess = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )])

在本例中,我们定义了一个预处理函数,将输入图像缩放到256×256个像素,围绕中心将图像裁剪为224×224个像素,并将其转换为一个张量,对其RGB分量(红色、绿色和蓝色)进行归一化处理,使其具有定义的均值和标准差。张量是一种PyTorch多维数组,在本例中,是一个包含颜色、高度和宽度的三维数组。如果我们想让网络产生有意义的答案,那么这些转换就需要与训练期间向网络提供的内容相匹配。在7.1.3小节中,当开始制作自己的图像识别模型时,我们再更深入地讨论transforms模块。

现在我们抓取我们非常喜欢的一幅狗的图像,如从GitHub代码库中下载一张名为bobby.jpg的图片,对其进行预处理,然后查看ResNet对它识别的结果。我们可以使用一个Python的图像操作模块Pillow从本地文件系统加载一幅图像。

# In[7]:
from PIL import Image
img = Image.open("../data/p1ch2/bobby.jpg")

如果我们使用的是Jupyter Notebook,需要做以下操作来查看图像,图像将在下面的<PIL.JpegImagePlugin…>代码之后显示。

# In[8]:
img
# Out[8]:
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1280x720 at
 0x1B1601360B8>

或者,我们可以调用show()方法,它将弹出一个带有查看器的窗口,显示图2.4所示的图像:

>>> img.show()

图2.4 我们非常喜欢的一幅狗的图像

接下来,我们可以通过预处理管道处理图像:

# In[9]:
img_t = preprocess(img)

然后我们可以按照网络期望的方式对输入的张量进行重塑、裁剪和归一化处理。

# In[10]:
import torch
batch_t = torch.unsqueeze(img_t, 0)

现在可以运行我们的模型了。

在深度学习中,在新数据上运行训练过的模型的过程被称为推理(inference)。为了进行推理,我们需要将网络置于eval模式。

# In[11]:
resnet.eval()

# Out[11]:
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3),
                  bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True,
                     track_running_stats=True)
  (relu): ReLU(inplace)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1,
                       ceil_mode=False)
  (layer1): Sequential(
    (0): Bottleneck(
...
    )
  )
  (avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
  (fc): Linear(in_features=2048, out_features=1000, bias=True)
)

如果我们忘记这样做,那么一些预训练模型,如批量归一化(Batch Normalization)和丢弃法(Dropout)将不会产生有意义的答案,这仅仅是因为它们内部工作的方式。现在eval设置好了,我们准备进行推理。

# In[12]:
out = resnet(batch_t)
out

# Out[12]:
tensor([[ -3.4803, -1.6618, -2.4515, -3.2662, -3.2466, -1.3611,
          -2.0465, -2.5112, -1.3043, -2.8900, -1.6862, -1.3055,
...
           2.8674, -3.7442,  1.5085, -3.2500, -2.4894, -0.3354,
           0.1286, -1.1355,  3.3969,  4.4584]])

刚刚完成了一组涉及4450万个参数的惊人操作。最终产生了一个拥有1000个分数的向量,每个ImageNet类对应一个分数,而且这个过程并没有花费多久时间。

我们现在需要找出得分高的类,这将告诉我们模型从图像中得到了什么。如果标签符合人类对图像的描述,就太棒了!这就意味着一切正常。如果不是,那么要么是在训练过程中出现了什么问题,要么是图像与模型期望的完全不同,以至于模型无法正确处理它,或是存在其他类似的问题。

要查看预测标签的列表,我们需要加载一个文本文件,其中按照训练中呈现给网络的顺序列出标签,然后我们选择在网络中产生最高分数的索引处的标签。几乎所有用于图像识别的模型的输出形式都与我们即将使用的输出形式类似。

让我们为ImageNet数据集类加载一个包含1000个标签的文件。

# In[13]:
with open('../data/p1ch2/imagenet_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()]

此时,我们需要确定与我们之前获得的out张量中最高分对应的索引。我们可以使用PyTorch的max()函数来做到这一点,它可以输出一个张量中的最大值以及最大值所在的索引。

# In[14]:
_, index = torch.max(out, 1)

现在我们可以使用索引来访问标签。在这里,索引不是一个普通的Python数字,而是一个拥有单元素的一维张量,如tensor([207])。因此我们需要使用index[0]获得实际的数字作为标签列表的索引。我们还可以使用torch.nn.functional.softmax()将输出归一化到[0,1],然后除以总和。这就给了我们一些大致类似于模型在其预测中的置信度,在本例中,模型有约96%的把握认为它看到的是一只金毛猎犬。

# In[15]:
percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
labels[index[0]], percentage[index[0]].item()

# Out[15]:
('golden retriever', 96.29334259033203)

哦,哪一个好呢?

由于该模型产生了分数,我们还可以找出第2好、第3好等。为此,我们可以使用sort()函数,它将值按升序或降序排列,并提供排序后的值在原始数组中的索引。

# In[16]:
_, indices = torch.sort(out, descending=True)
[(labels[idx], percentage[idx].item()) for idx in indices[0][:5]]

# Out[16]:
[('golden retriever', 96.29334259033203),
 ('Labrador retriever', 2.80812406539917),
 ('cocker spaniel, English cocker spaniel, cocker', 0.28267428278923035),
 ('redbone', 0.2086310237646103),
 ('tennis ball', 0.11621569097042084)]

我们看到前4个答案是狗,之后的结果就变得有趣起来。第5个答案是网球,这是因为在图片中狗的附近有很多的网球,因此模型将狗错误地识别成网球,此时,对于模型而言,它认为在这种场景中只有0.1%的概率将网球识别为其他的东西。这是一个很好的例子,说明了人类和神经网络看待世界在方式上的根本差异,也说明了我们的数据当中很容易混入一些奇怪的、微妙的偏差。

放松一下,我们可以继续用一些随机图像来检测网络,看看它会产生什么结果。该网络成功与否在很大程度上取决于该主题是否在训练集中充分表达。如果我们提交一幅包含训练集之外的图像,网络很可能会以相当高的置信度得出错误的答案。通过实验了解模型对未知数据的反应是很有用的。

我们刚刚运行了一个网络,它在 2015年的图像分类比赛中获胜,它学会了从包含狗及一大堆现实世界的其他物品的图像中识别出狗。现在我们将从图像生成开始,了解不同的体系结构如何实现其他类型的任务。

假设,我们是出售著名艺术家遗失的画作赝品的不法分子。由于我们不是画家,因此当我们画出伦勃朗和毕加索的作品时,很快就会被发现这些都是赝品,而不是真品。即使我们花了很多时间练习,直到得到一幅我们自己都辨别不出真伪的作品,当我们试图在当地的艺术品拍卖行把它转手时,也会被他们立即发现这是赝品。我们必须随机地尝试一些新东西,判断哪些东西需要稍长时间才能识别出是赝品的特征,并在我们未来的尝试中强化这些特征,这可能需要花费很长时间。

因而,我们需要找一位道德水准欠佳的艺术史学工作者来检查我们的作品,让他告诉我们到底从哪里看出这幅作品是赝品。有了这些反馈,我们就可以以清晰、直接的方式提高我们的输出,直到他们难以区分画作真伪。

虽然这种设想有点儿滑稽,但其基础技术是合理的,并可能在未来几年对人们感知到的数据的真实性产生深远影响。“照片为证”的整个概念可能会变得完全靠不住了,因为自动制作令人信服的图像和视频是多么容易的事情。生成这种以假乱真照片的关键因素是数据,现在让我们看看这个过程是如何进行的。

在深度学习的背景下,我们刚才所描述的过程被称为GAN游戏,其中有2个网络,一个作为画家,另一个作为艺术史学工作者,在创作和检测赝品方面互相“对抗”。GAN是生成式对抗网络(generative adversarial network)的缩写,生成式(generative)意味着一些东西正在被创造出来,在这个例子中指的是赝品,而对抗(adversarial)意味着这2个网络在竞争,其中一个要比另一个更聪明,而网络意义就显而易见了。这是深度学习比较新颖的研究成果之一。

请记住,我们的首要目标是生成不能被识别为赝品的一类图像的合成示例。当与真品混杂在一起时,一个有经验的艺术史学工作者很难辨认哪些是真品,哪些是赝品。

生成器网络(generator network)在我们的场景中扮演画家的角色,负责从任意输入开始生成逼真的图像。判别器网络(discriminator network)是一个艺术史学工作者,他需要判断给定的图像是由生成器生成的还是一幅真实的图像。这种双网络设计对于大多数深度学习架构来说并不典型,但是当用于实现GAN游戏时,可能会产生难以置信的结果。

图2.5显示了上述过程的大致情况。生成器的最终目标是欺骗判别器,使其混淆真伪图像。判别器的最终目标是发现它何时被欺骗了,但它也有助于告知生成器在生成图像中可识别的错误。例如,一开始,生成器生成模糊的、3只眼睛的怪物,看起来一点也不像伦勃朗的肖像画。此时判别器很容易把这幅画与真实的画区分开来。随着训练的推进,信息从判别器返回,而生成器使用这些信息进行改进。在训练结束时,生成器可以生成以假乱真的图像了,而判别器却不再能够识别出图像的真伪了。

请注意,无论是判别器获胜还是生成器获胜,都不应该被理解为字面意义上的获胜,因为二者之间没有明确的比赛关系。然而,2个网络都是基于彼此网络的结果进行训练的,并推动彼此对网络参数进行优化。

这项技术已经证明,它能够使用生成器从只有噪声和调节信号的内容中生成逼真的图像,例如对于人脸而言的属性:年轻的、女性的、戴着眼镜的。换句话说,一个训练有素的生成器可以学习一个用于生成图像的模型,能生成即使人类检查时也会觉得逼真的图像。

图2.5 GAN游戏的概念

这个概念的一个有趣演变是CycleGAN。CycleGAN是循环生成式对抗网络的缩写,它可以将一个领域的图像转换为另一个领域的图像,而不需要我们在训练集中显式地提供匹配对。

在图2.6中,我们展示了一个CycleGAN工作流程,可以将一匹马的照片转换为一匹斑马的照片,反之亦然。请注意这里有2个独立的生成器以及2个不同的判别器。

图2.6 一个经过训练可以欺骗2个判别器网络的CycleGAN

如图2.6所示,第1个生成器学习从属于不同分布域的图像(本例是马),生成符合目标域的图像(本例是斑马),使得判别器无法分辨出从马的照片中产生的图像是否真的是斑马的图像。产生的假斑马会通过另一个反向的生成器(在我们的例子中是从斑马到马的),被另一个判别器在另一端进行评估。创建这样一个循环,非常好地稳定了训练过程,这就解决了GAN最初存在的一个问题。

有趣的是,其实我们并不要求源域和目标域的图像内容是匹配的,即本例的斑马和马的图像内容,我们只要求它们的外观是匹配的。从一组不相关的马的图片和斑马的图片开始,生成器就可以学习它们的任务,这就超越了纯粹的有监督设定。这个模型的含义远不止于此:生成器学习如何在无监督的情况下选择性地改变场景中物体的外观。没有信号表明鬃毛是鬃毛、腿是腿,但它们被转换成与其他动物的解剖结构一致的东西。

我们现在可以使用这个模型进行实验。CycleGAN对从ImageNet数据集中提取的(不相关的)马和斑马的数据集进行了训练。学习把一匹或多匹马的图像转换成斑马,图像的其余部分尽可能保持不变。虽然在过去的几千年里人类一直都没有找到一种把马变成斑马的工具,但这项任务展示了这些架构在远程监督下模拟复杂现实世界的能力。尽管它们有自己的局限性,但有迹象表明,在不久的将来,我们可能会无法在实时视频中分辨真假,这将出现一个棘手的问题,我们现在就要解决这个真假难辨的问题。

使用一个预训练CycleGAN将使我们有机会更进一步了解网络是如何实现的,对于本例就是生成器。我们会用熟悉的ResNet,定义一个ResNetGenerator类(相关代码位于3_cyclegan.ipynb文件第1个单元格中),但现在还不涉及具体实现,因为在我们获得更多PyTorch开发经验之前,它显得太复杂了。现在,我们关注的是它能做什么,而不是它怎么做。让我们用默认参数来实例化该类,源代码位于code/p1ch2/3_cyclegan.ipynb文件中。

# In[2]:
netG = ResNetGenerator()

现在netG模型已经创建,但它包含的是随机权重。我们之前提到过将运行一个已经在horse2zebra的数据集上预训练生成器模型,它的训练集包括2个集合,分别为1068张马的照片和1335张斑马的照片。该数据集可以在异步社区本书代码地址中找到。模型的权重保存在一个扩展名为“pth”的文件中,该文件只是一个模型张量参数的pickle文件。我们可以使用模型的load_state_dict()方法将权重加载到ResNetGenerator中。代码如下:

# In[3]:
model_path = '../data/p1ch2/horse2zebra_0.4.0.pth'
model_data = torch.load(model_path)
netG.load_state_dict(model_data)

至此,netG对象已经获得了它在训练中需要获得的所有知识。注意,这与我们在2.1.3小节提到的从TorchVision模块加载resnet101完全相同,但torchvision.resnet101()函数对我们屏蔽了数据加载过程的细节。

让我们将网络置于eval模式,就像我们对resnet101所做的那样。

# In[4]:
netG.eval()

# Out[4]:
ResNetGenerator(
  (model): Sequential(
...
   )
)

就像我们之前所做的那样打印出模型,考虑到该模型所做的事情,我们可以发现它实际上是相当简洁的。它获取一幅图像,通过观察像素识别其中的一匹或多匹马,然后逐个修改这些像素的值,使得到的结果看起来更像一匹真的斑马。在输出中或在源代码中,我们识别不出任何类似斑马的东西,这是因为那里并没有任何类似斑马的东西。该网络就像一个脚手架,核心在于权重。

我们准备随便加载一匹马的图像,看看我们的生成器会生成什么。首先我们需要导入PIL(Python Imaging Library,Python图像库)和TorchVision模块,代码如下:

# In[5]:
from PIL import Image
from torchvision import transforms

然后我们定义一些输入变换,确保数据以正确的形状和大小进入网络,代码如下:

# In[6]:
preprocess = transforms.Compose([transforms.Resize(256),
                                 transforms.ToTensor()])

让我们打开一个马的图像文件,图像如图2.7所示。

# In[7]:
img = Image.open("../data/p1ch2/horse.jpg")
img

图2.7 一个人骑在一匹马上,但马并不乐意

有个骑马的家伙,从图像来看,他学会骑马的时间不会太长。不管怎样,我们通过预处理,将其转化为一个合适形状的变量:

# In[8]:
img_t = preprocess(img)
batch_t = torch.unsqueeze(img_t, 0)

现在我们不需要关注细节,我们需要从远处着手。此时可以将batch_t变量传递给我们的模型:

# In[9]:
batch_out = netG(batch_t)

batch_out变量现在表示生成器的输出,我们可以将它转换为图像。

# In[10]:
out_t = (batch_out.data.squeeze() + 1.0) / 2.0
out_img = transforms.ToPILImage()(out_t)
# out_img.save('../data/p1ch2/zebra.jpg')
out_img

# Out[10]:
<PIL.Image.Image image mode=RGB size=316x256 at 0x23B24634F98>

图2.8所示的图像并不完美,但考虑到这是通过神经网络学习识别出某人骑在马背上的,就这点而言还是不简单的。需要强调的是,学习过程并没有经过直接监督,即由手工描绘了成千上万种马的图片,或是通过图像处理技术绘制了数千条斑马纹的图片。这个生成器已经学会了产生一张图像,这张图像会让判别器误认为那就是一匹斑马,而且这张图片看起来也没有任何可疑之处(很明显,判别器从来没有去参观过竞技表演)。

图2.8 一个人骑在斑马上,但斑马并不乐意

使用对抗性训练或其他方法已开发出了许多其他有趣的生成器。其中一些能够创造出可信的但不存在的人脸,另一些能够将草图转化为想象中的风景的真实图片。生成模型也被用于生成真实的音频、可信的文本和令人愉快的音乐。很可能这些模型将成为支持未来创新过程的工具的基础。

严肃地说,很难夸大这类研究的意义。例如,我们刚刚下载的工具的质量只会变得更高,应用只会变得更普遍。特别是面部交换技术,已经得到了相当多的媒体关注。在浏览器中搜索“深度伪造”将出现大量的示例内容[1],然而我们必须注意到有相当数量的示例被标记为不安全的,就像互联网上的其他内容一样,请小心点击。

[1] 一个相关的例子是在Vox上由Aja Romano撰写的一篇文章,标题为“Jordan Peele’s simulated Obama PSA is a double-edged warning against fake news”。

到目前为止,我们已经有机会使用一个可以看到图像的模型和一个生成新图像的模型。接下来我们将以另外一个模型结束我们的旅程,该模型涉及另一个基本要素:自然语言。

为了获得涉及自然语言的模型的第一手经验,我们将使用预训练图像字幕模型,该模型由Ruotian Luo慷慨提供[2],同时该模型是Andrej Karpathy的NeuralTalk2模型的一个实现。当提供一幅自然图像时,这个模型会生成一段关于场景的英文说明,如图2.9所示。该模型是在一幅巨大的图像和与之相应的英文说明的数据集上训练的,例如一只大花猫倚靠在木桌上,一只爪子放在激光鼠标上,另一只爪子放在黑色笔记本计算机上[3]

[2] 我们维护了该项目(ImageCaptioning)在GitHub上代码的一份副本。

[3] Andrej Karpathy和Li Fei-Fei的论文——“Deep Visual-Semantic Alignments for Generating Image Descriptions”。

图2.9 字幕模型的概念

这个字幕模型有2个相连的部分。该模型的前半部分是一个网络,它学习生成场景的“描述性”数字表征,例如本例中的大花猫、激光鼠标、爪子,然后将其作为后半部分的输入。后半部分是一个循环神经网络,它通过将这些描述性的数字放在一起产生一个连贯的句子。该模型前后2个部分都是在数据集上对图像字幕模型进行训练。

模型的后半部分被称为循环神经网络,是因为它在随后的正向传播中生成输出,即单个单词,其中每个正向传播的输入包括前一个正向传播的输出。这将使下一个单词对前面生成的单词产生依赖性,就像我们在处理句子或处理序列时所期望的那样。

NeuralTalk2模型的源代码可以在GitHub上找到。我们可以在该项目源代码的data目录下放置一些图像,然后运行下面的脚本:

python eval.py --model ./data/FC/fc-model.pth
➥--infos_path ./data/FC/fc-infos.pkl --image_folder ./data

让我们先试一下horse.jpg图像,该模型对该图像的英文说明为“A person riding a horse on a beach.”(一个人在海滩上骑马),非常贴切。

现在,仅为了好玩,让我们看看我们的CycleGAN是否能够欺骗得了NeuralTalk2模型。我们将“zebra.jpg”图像添加到data目录下,模型返回:“A group of zebras are standing in a field.”(一组斑马站在田野上)。好吧,它猜对了是斑马,但是它从图像中看见了不止一匹斑马。当然模型从未见过斑马摆出这样的姿势,也从未见过骑在斑马上的骑手,即一些非斑马的图案。此外,在训练集中通常是以组的形式描述斑马的,因此或许存在一些我们可以探究的偏差。字幕网络也没有描述骑手。同样,这可能出于相同的原因:模型在训练时训练集中没有显示骑在斑马上的骑手。总之,这都是一个令人印象深刻的壮举:我们在不可能的情况下生成了一幅假图像,并且字幕网络足够灵活,它能够正确地捕捉到主题。

我们要强调的是,在深度学习出现之前这是很难实现的。有了深度学习之后,这可以用不超过1000行的代码来实现,使用一个对马和斑马一无所知的通用架构,以及一幅图像及其描述的语料库,本例中采用的是MS COCO数据集[4]。没有硬编码的标点或语法,所有内容,包括句子,都是从数据模式中产生的。

[4] MS COCO是微软构建的数据集,MS COCO为Microsoft Common Objects in Context的缩写。 ——译者注

在某种程度上,最后一个例子中的网络架构比我们前面看到的更复杂,因为它包括2个网络。其中一个是循环网络,但它也是由同样的构建块构建的,所有这些都是PyTorch提供的。

截至撰写本书时,像这样的模型更多作为应用研究或创新项目存在,而不具有明确和具体的用途。这些模型的结果虽然还不错,但还不够好用。然而,随着时间的推移以及训练数据的扩展,我们期望这类模型能够向有视力障碍的人描述世界,从视频转述场景,以及执行其他类似的任务。

在深度学习早期就已经发布了一些预训练模型,但是直到PyTorch 1.0,还没有办法确保用户有一个统一的接口来获取它们。正如我们在本章前面看到的,TorchVision是一个规范接口的好例子,但是其他设计者,如CycleGAN和NeuralTalk2的设计者,他们选择了不同的设计。

PyTorch在1.0版本中引入了Torch Hub,它是一种机制,通过该机制作者可以在GitHub上发布模型,无论是否预训练过权重,都可以通过PyTorch可以理解的接口将其公开发布。这使得从第三方加载预训练的模型就像加载TorchVision模型一样简单。

作者若要通过Torch Hub机制发布模型,只需将一个名为hubconf.py的文件放在GitHub仓库的根目录下。该文件的结构非常简单。

dependencies = ['torch', 'math']    ⇽---  代码所依赖的可选模型块列表

def some_entry_fn(*args, **kwargs):    ⇽---  作为仓库入口点向用户暴露一个或多个函数。这些函数应该根据参数初始化模型并将其返回
    model = build_some_model(*args, **kwargs)
    return model

def another_entry_fn(*args, **kwargs):
    model = build_another_model(*args, **kwargs)
    return model

在我们寻找有趣的预训练模型时,我们现在可以搜索包含hubconf.py文件的GitHub仓库。我们马上就会知道可以使用torch.hub模块来加载他们。让我们看看这在实践中是如何做到的,为此,我们将回到TorchVision,因为它提供了一个关于如何与Torch Hub交互的清晰示例。

在GitHub中访问TorchVision时,我们会注意到它包含一个hubconf.py文件。我们要做的第1件事就是在该文件中查找仓库的入口点,稍后我们需要指定它们。以TorchVision为例,有resnet18和resnet50,我们已经知道它们的作用:它们分别返回18层和50层ResNet模型。我们还可以看到入口点函数包含参数pretrained,该参数值如果为true,返回的模型将使用从ImageNet获得的权重进行初始化,正如我们在本章前面看到的。

现在我们知道了仓库、入口点和一个有趣的关键参数,这就是我们使用torch.hub加载模型所需要的全部内容,我们甚至不需要复制仓库。没错,PyTorch会帮我们处理的。代码如下:

import torch
from torch import hub

resnet18_model = hub.load('pytorch/vision:master',    ⇽---  GitHub仓库的名称和分支
                           'resnet18',    ⇽---  入口点函数的名称
                           pretrained=True)    ⇽---  关键参数

以上代码将设法将pytorch/vision主分支的快照及其权重下载到本地目录,这里默认下载到本地的torch/hub目录下。然后运行resnet18入口点函数,该函数返回实例化的模型。根据环境的不同,Python可能会缺少一些模块,如PIL。Torch Hub不会自动安装缺少的依赖项,但会向我们报告,以便我们采取行动。

此时,我们可以使用适当的参数调用返回的模型来对其运行正向传播,方法与前面相同。令人欣慰的是,现在通过这个机制发布的每一个模型都将被我们使用同样的方式访问,这远远超出了我们的想象。

注意,入口点函数应该返回模型,但是,严格地说,这也不是必须的。例如,我们可以有一个用于转换的入口点,以及一个用于将输出概率转换为文本标签的入口点,或者我们可以有一个模型的入口点,以及包括该模型预处理和后处理步骤的另一个入口点。通过保留这些选项,PyTorch开发人员为社区提供了足够的标准化和极大的灵活性,在这种机遇中将会产生什么样的模式,我们拭目以待。

撰写本书时Torch Hub还是很新的,而且只有少数几个模型是以这种方式发布的。我们可以在谷歌上搜一下GitHub的hubconf.py文件。随着越来越多的作者通过这个机制分享他们的模型,这个清单的内容有望在未来继续丰富。

我们希望这是有趣的一章。我们花了一些时间使用PyTorch来构建模型并对其优化,以执行特定的任务。事实上,我们中比较积极的人可能已将这些模型部署在Web服务器上,然后开始创业,并与原作者分享利润[5]。一旦我们了解了这些模型是如何构建的,我们就能够使用在这里获得的知识来下载一个预训练模型,并在一个稍微不同的任务上快速地对其进行微调。

[5] 联系模型发布者获取许可。

我们还看到了如何使用相同的构建块来构建处理不同类型数据的不同问题的模型。PyTorch做得特别正确的一件事是以基本工具集的形式提供这些构建块。从API的角度来看,尤其是与其他深度学习框架相比,PyTorch并不是很大的库。

本书并不会介绍PyTorch所有的API或回顾深度学习框架,而是介绍这些构建块的实践知识。这样你就能够在坚实的基础上使用优秀的在线文档和仓库。

从第3章开始,我们将开启一段新的旅程,从零开始掌握使用PyTorch相关操作的技能,就像我们在本章中描述的那样。我们还将了解到,当我们拥有的数据不是特别多的时候,我们可以从一个预训练网络开始,在新的数据上对它进行微调,而不是从头开始,这是解决问题的有效方法,这也是预训练网络成为深度学习实践者重要工具的另一个原因。接下来将了解第一个基础构件:张量。

1.将金毛猎犬的图像输入马-斑马模型中。

a)你需要对图像做什么准备工作?

b)输出是什么样的?

2.在GitHub中搜索提供hubconf.py文件的项目。

a)返回多少仓库?

b)找一个外观有趣的带有hubconf.py的项目,你能从文档中了解项目的目的吗?

c)为这个项目加上标签,看完本书后再返回来,你能理解它的实现吗?

预训练网络是已经在数据集上训练过的模型。这类网络通常可以在加载网络参数后立即产生有用的结果。

通过了解如何使用预训练模型,我们可以将神经网络集成到一个项目中,而不需要对其进行设计和训练。

AlexNet和ResNet是2个深度卷积神经网络,它们的发布为图像识别设定了新的基准。

GAN有2个部分,即生成器网络和判别器网络,它们协同工作以产生与真实内容雷同的输出。

CycleGAN使用一种支持在2种不同类型的图像之间来回转换的架构。

PyTorch为NeuralTalk2提供了一种使用混合模型架构来消费图像并生成图像的文本描述。

使用Torch Hub通过适当的hubconf.py文件从其他任何项目加载模型和权重是一种标准化操作方法。

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e57767”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


相关图书

ChatGPT原理与应用开发
ChatGPT原理与应用开发
深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
深度学习:从基础到实践(上、下册)
深度学习:从基础到实践(上、下册)
动手学深度学习(PyTorch版)
动手学深度学习(PyTorch版)
深度学习与医学图像处理
深度学习与医学图像处理
深度强化学习实战:用OpenAI Gym构建智能体
深度强化学习实战:用OpenAI Gym构建智能体

相关文章

相关课程