PyTorch 深度学习——深度学习与 PyTorch 库导论

52 阅读33分钟

本章涵盖以下内容

  • 深度学习如何改变我们处理机器学习问题的方式
  • 理解为什么 PyTorch 非常适合深度学习
  • 考察一个典型的深度学习项目
  • 学习跟随本书示例所需的硬件配置

PyTorch 是一个用于构建深度学习项目的 Python 库。它强调灵活性,并允许用符合 Python 惯用风格的方式来表达深度学习模型。正是这种易于上手和易于使用的特性,使它在早期便获得了科研社区的青睐;而在自首次发布以来的这些年里,它也逐渐成长为覆盖广泛应用场景的最重要深度学习工具之一。

本书将从头到尾教你如何使用 PyTorch 构建完整的深度学习项目。你将学习如何使用张量(PyTorch 的基础数据结构)、设计神经网络架构、在自己的数据上训练模型,并将其部署到生产环境中。书中还包含若干大型实战项目——也就是从零开始构建你自己的语言模型、使用图像生成模型,以及创建一个医学图像分割系统——从而让你亲身体验完整的深度学习工作流。

理解深度学习,会让你拥有解决那些过去根本无法自动化的问题的能力。这些技能会为你打开人工智能(AI)开发领域中的新职业机会。而在 AI 正在吞噬世界的当下,PyTorch 可以成为你的刀叉。虽然本书聚焦于 PyTorch,但其中讲解的概念同样可以迁移到其他框架。读完本书之后,你获得的将不只是一个“会用 PyTorch 的人”的能力,而是真正具备深度学习实践者的技能。

1.1 什么是深度学习?

要理解我们是如何走到 PyTorch 这一步的,就需要先回到 AI 发展的更大背景中去看。人工智能(artificial intelligence)这个术语,指的是能够执行通常需要人类智能才能完成的任务的计算机系统。AI 涵盖了一系列近来备受关注的学科领域,因此常常会引发各种不同的理解和定义。尽管近年的 AI 进展伴随着大量炒作和类似科幻作品式的恐慌叙事,但现实其实要克制得多。若声称今天的机器正在以某种“人类意义上的方式”学会思考,那并不诚实。更准确地说,我们发现了一大类通用算法,它们能够非常、非常有效地逼近复杂的非线性过程,而我们可以利用这些算法,把一些过去只能由人类完成的任务自动化。乍看之下,这件事似乎很直接,甚至不算多么惊人;但它实际上已经极大拓展了我们对“计算机究竟能做到什么”的理解。

例如,ChatGPT(chat.openai.com/)是由 AI 研究公司 OpenAI 开发的一款聊天机器人,它使用了一种大型语言模型,能够回答问题并生成成段文本。当我们向它输入如下提示:

请用一首诗来介绍你自己

它生成了下面这样的内容:

在文字的国度里,我由此鲜活,
为帮助与成长而诞生的造物。
我是 ChatGPT,一位宏大的 AI,
浩瀚知识在握,任你来差遣。

借由语言的艺术,我接受过训练,
为协助与引导而来,智慧已沉淀。
从高远的思想,到日常的闲谈,
我都在这里与你对话,想想看!

对于一台机器来说,这样的回应已经相当连贯,也相当有创造性。更令人印象深刻的是,这种完成过去只有人类才能完成之任务的能力,并不是人类通过一套手工编写的规则显式写进去的,而是通过示例学习获得的。

注意 语言模型的输出是由概率决定的,因此可能具有非确定性;我们会在第 9 章看到这一点。

从某种意义上说,我们正在认识到:人们常常会把“智能”与“自我意识”混为一谈,而要成功完成这类任务,自我意识显然并不是必需的。归根结底,“计算机是否具有智能”这个问题,甚至可能并不重要。Edsger W. Dijkstra 曾说,机器能否思考这个问题,“大概就像问潜艇会不会游泳一样相关” (“The Threats to Computing Science,” mng.bz/nPJ5)。

我们所说的这一大类通用算法,属于 AI 的一个子领域——深度学习(deep learning)。深度学习关注的是:通过提供具有指导意义的样例,对称为深度神经网络(deep neural networks)的数学实体进行训练。深度学习利用大量数据来逼近复杂函数,而这些函数的输入与输出可能千差万别。比如,它可以接收一句话,并根据这段文字描述生成一张逼真的图像;它也可以接收一份书面脚本,并把它转换为听起来自然的人声朗读出来。再简单一点,它还可以识别一张图片中的一只金毛犬,并确认这只狗确实是金毛犬。这种能力使我们得以创建出具有某种功能的程序,而这种功能直到不久之前还完全是人类独有的领域。

1.2 从机器学习到深度学习的转变

要真正体会深度学习方法所带来的范式转移,我们不妨先退一步,从更宏观的角度看一看。直到过去十年之前,被统称为机器学习(machine learning)的大多数系统都高度依赖特征工程(feature engineering)。

原始特征(raw features)就是未经修改的数据值本身。然而,这些原始特征往往并不能直接暴露出机器学习算法良好运作所需的模式。这时就需要特征工程登场——它指的是利用领域知识,从原始数据中构造出新的特征,从而让机器学习算法更有效地工作。

特征工程的核心,是设计合适的变换,使得后续算法能够有效完成某项任务。举例来说,要在手写数字图像中区分数字 1 和数字 0,我们会设计一组滤波器,用来估计整幅图像中边缘的方向,然后再训练一个分类器,根据这些边缘方向的分布来预测正确的数字。另一个有帮助的特征,可能是封闭空洞的数量——比如 0、8,尤其是带环的 2,都能体现出这一点。

而深度学习所处理的,则是如何从原始数据中自动找到这样的表示,以成功完成任务。这个过程减少了对大量手工特征工程的依赖。在区分 1 和 0 的例子中,滤波器会在训练过程中,通过反复查看样本与目标标签的配对而逐步被优化出来。这并不是说特征工程在深度学习中就完全没有位置;我们仍然常常需要向学习系统中注入某种形式的先验知识。不过,神经网络能够直接摄取数据,并基于示例提取有用表示的能力,正是深度学习如此强大的原因。深度学习实践者关注的,不再主要是手工打造这些表示本身,而是如何操作某个数学实体,使它能够从训练数据中自主发现表示。很多时候,这些自动生成的特征甚至比人工设计的特征更好!正如许多颠覆性技术一样,这一事实也带来了视角上的转变。

在图 1.1 左侧,我们看到的是:实践者忙于定义工程特征,并将这些特征输入给学习算法;任务结果的好坏,很大程度上取决于实践者所设计的特征质量。而在右侧的深度学习范式中,原始数据被直接输入到一个算法中,由它在任务性能优化的引导下自动提取层级化特征;最终结果则取决于实践者是否有能力把算法引导到正确的目标上。

image.png

图 1.1 深度学习用更高的数据需求与计算需求,换来了不再需要手工构造特征。

看着图 1.1 右边的部分,我们已经能初步看到,要成功开展深度学习,我们需要具备什么:

  • 我们需要一种方式,能够接收并处理手头现有的各种数据。
  • 我们需要以某种方式定义这个深度学习机器。
  • 我们必须有一种自动化的方法——训练——来获得有用的表示,并让这台机器产生我们想要的输出。

这就引出了我们一直在谈论的“训练”这件事,值得更仔细地看看。在训练过程中,我们会使用一个损失函数(loss function),也称准则(criterion)、目标函数(objective function)或代价函数(cost function)——在深度学习文献中,这些术语通常是可以互换使用的。它本质上是一个实值函数,用来比较模型输出与参考数据之间的差异。这个函数会计算出一个数值分数,用来表示模型期望输出与实际输出之间的差距;一般来说,分数越低,说明性能越好。所谓训练,就是通过一点一点地修改我们的深度学习机器,持续把损失函数往更低的分数方向推进,直到它即便面对训练过程中未曾见过的数据,也能取得较低的分数。

1.3 可以期待什么

正如 Python 之于编程,PyTorch 之于深度学习也提供了极佳的入门路径。同时,PyTorch 也早已证明,它完全有资格被用于真实世界中那些高规格、专业级的工作场景。我们认为,PyTorch 清晰的语法、简洁流畅的 API,以及便于调试的特性,使它成为介绍深度学习的绝佳选择。我们强烈建议你把 PyTorch 作为自己接触的第一门深度学习库。至于它会不会成为你学的最后一个深度学习库,那就只能交给时间回答了;但至少,你将掌握所有深度学习库都会提供的那些基础概念。

从本质上说,图 1.1 中的深度学习机器,是一个相当复杂的数学函数,用来把输入映射为输出。为了更方便地表达这个函数,PyTorch 提供了一种核心数据结构——张量(tensor),它是一种多维数组,与 NumPy 数组有很多相似之处。围绕这个基础,PyTorch 还提供了在专用硬件上执行加速数学运算的能力,这使得它非常适合设计神经网络架构,并在单机或并行计算资源上训练这些网络。

虽然我们强调的是如何用 PyTorch 实际构建深度学习系统,但我们相信,对这样一个基础性深度学习工具进行通俗易懂的介绍,其意义并不仅仅在于帮助人们掌握新的技术技能。它同样意味着,为来自广泛学科背景的新一代科学家、工程师和实践者提供一种可操作的知识基础,而这种基础将在未来几十年中,成为大量软件项目的核心支柱。

为了最大程度从本书中获益,你需要两样东西:

  • 具备一定的 Python 编程经验。这个要求我们不打算模糊带过;你需要熟悉 Python 的数据类型、类、浮点数等基础内容。
  • 愿意真正投入进来,亲自动手实践。我们会从最基础的内容开始,一步步建立工作知识体系;如果你能跟着我们一起动手,学习会轻松得多。

深度学习是一个极其广阔的领域。本书只会覆盖其中很小的一部分:具体来说,是使用 PyTorch 进行生成式 AI 应用开发,来创建文本和图像。我们还会涉及规模较小的分类与分割项目,并以 2D 和 3D 数据集的图像处理作为大多数示例的动机背景。本书聚焦于实用型 PyTorch,目标是覆盖足够多的内容,让你能够运用深度学习解决真实世界中的机器学习问题,或者在研究文献中不断出现新模型时,有能力继续探索它们。与深度学习研究相关的大多数最新论文,几乎都可以在 arXiv 公共预印本仓库中找到,该仓库的网址是 arxiv.org

1.4 为什么选择 PyTorch?

正如我们前面所说,深度学习让我们能够通过向模型提供具有示范意义的样例,去完成范围极其广泛的复杂任务,例如机器翻译、策略游戏对弈,或是在杂乱场景中识别目标物体。要在实践中做到这一点,我们需要一些工具:它们既要足够灵活,以适配如此广泛的问题类型;又要足够高效,能够让模型在海量数据上于合理时间内完成训练。同时,我们还需要训练好的模型在面对输入变化时依然能够正确工作。下面我们来看一看,我们为什么决定使用 PyTorch。

PyTorch 很容易被推荐,一个重要原因就是它的简洁性。许多研究人员和实践者都认为,它易学、易用、易扩展,也易于调试。它很“Pythonic”;虽然和任何复杂领域一样,它也有需要注意的细节和最佳实践,但总体而言,对于已经使用过 Python 的开发者来说,使用这个库通常会有一种熟悉感。

更具体地说,在 PyTorch 里编写这个“深度学习机器”是非常自然的。PyTorch 为我们提供了一种数据类型——Tensor,用来保存数字、向量、矩阵,或者更一般地说,各种数组。此外,它还提供了用于操作这些数据的函数。我们可以像使用 Python 一样,以渐进式的方式、甚至交互式的方式来编写程序。如果你熟悉 NumPy,那么这一切会显得非常亲切。

但 PyTorch 还提供了两项使其对深度学习尤其重要的能力。第一,它支持使用图形处理器(GPU)进行加速计算,与在 CPU 上执行同样的计算相比,速度常常可以提升 10 倍到 1000 倍。你的计算机 CPU 也许有四个或八个核心,每个核心都能独立执行任务;而现代 GPU 往往配备了成千上万个可并行工作的核心。第二,PyTorch 提供了对通用数学表达式进行数值优化的支持,而深度学习正是依赖这种机制来完成训练。需要注意的是,这两项能力并不只对深度学习有用,它们对科学计算整体都非常重要。实际上,我们完全可以把 PyTorch 定义为:一个面向 Python 科学计算、支持高性能计算与数值优化的库。

PyTorch 的一个核心设计驱动力是表达力(expressivity):它希望开发者能够实现复杂模型,而不必承受框架强加的过多复杂性。可以说,在深度学习领域里,PyTorch 是把想法无缝翻译成 Python 代码能力最强的框架之一。也正因如此,PyTorch 在研究界得到了极其广泛的采用,这一点可以从国际会议上的高引用量中看出来。

在从研究开发走向生产落地这件事上,PyTorch 也有一套很有说服力的方案。虽然它最初更侧重研究工作流,但后来已经具备了高性能的 C++ 运行时,可以在不依赖 Python 的情况下部署模型做推理,也可以直接用 C++ 来设计和训练模型。它还逐步扩展了对其他语言的绑定,并提供了面向移动设备部署的接口。这些能力使我们既可以享受 PyTorch 的灵活性,又能够把应用部署到那些难以运行完整 Python 环境、或者运行 Python 会带来较大开销的场景中。

当然,“易用”和“高性能”这样的说法本身说出来并不难。我们希望,等你真正深入读完本书的大部分内容后,会认同我们在这里提出的这些判断并非空言。

1.4.1 深度学习的竞争格局

虽然所有类比都有缺陷,但看起来,PyTorch 0.1 的发布,标志着深度学习库、封装器以及数据交换格式那种类似“寒武纪大爆发”式的百花齐放阶段,开始走向整合与统一的时代。

在 PyTorch 第一个 beta 版本发布时:

Theano 和 TensorFlow 是最主要的底层库,它们采用的模式都是先让用户定义计算图,然后再执行。
Lasagne 和 Keras 是建立在 Theano 之上的高层封装,其中 Keras 还同时封装了 TensorFlow 和 CNTK。
Caffe、Chainer、DyNet、Torch(基于 Lua、也是 PyTorch 的前身)、MXNet、CNTK、DL4J 等,则分别填补着生态中的不同细分位置。

而在随后的几年里,这一格局发生了巨大变化。研究界在很大程度上已经收敛到以 PyTorch 作为实现新研究想法的首选框架。在工业界,大多数技术栈则建立在 PyTorch、TensorFlow 或 Hugging Face 之上,其他库的采用率不断下降,除非它们在某些特定细分场景中仍然具有独特价值。概括来说:

Theano
最早的一批深度学习框架之一。
已经停止积极开发。

TensorFlow
彻底吸收了 Keras,并将其提升为一等 API。
提供了即时执行的 “eager mode(急切模式)”,其计算方式在某种程度上与 PyTorch 相似。
发布了默认启用 eager mode 的 TF 2.0。

JAX
Google 推出的一个库,独立于 TensorFlow 发展。
正逐渐获得更多关注,被视作一个具备 GPU 支持、自动微分以及即时编译(JIT)能力的 NumPy 等价物。

PyTorch
将 Caffe2 吸收为自己的后端。
取代了 CNTK 和 Chainer,成为它们各自企业赞助方所选择的主力框架。
替换掉了大部分原先复用自 Lua 版 Torch Project 的底层代码。
增加了对 ONNX 的支持;ONNX 是一种厂商中立的模型描述与交换格式。
发布了 2.0 版本,引入了 torch.compile,通过 JIT 编译 PyTorch 代码来提升运行速度,同时几乎不需要对现有代码做太多改动。

有意思的是,随着 PyTorch 和 TensorFlow 都引入了编译模式与 eager mode,我们已经看到两者的特性开始逐渐向对方靠拢,尽管它们在这些功能的呈现方式以及整体使用体验上,依然存在相当明显的差异。

Hugging Face 作为深度学习框架的高层封装,也变得越来越流行,它更强调面向应用的使用方式。它提供了一个非常方便的模型中心(model hub),用户可以在其中发现、共享预训练模型及其权重。这些模型本身通常构建于诸如 PyTorch 或 TensorFlow 之类的底层框架之上。用户可以很方便地获取并将现有模型集成进自己的应用中。不过也值得注意的是,Hugging Face 对“简洁”和“易用”的强调,可能会限制开发者对模型进行细粒度控制与灵活定制的能力;而这些能力,通常在直接使用 PyTorch 或 TensorFlow 编写模型时更容易获得。

1.5 PyTorch 如何支持深度学习项目

前面我们已经提到过 PyTorch 中的一些基础构件。现在,我们花一点时间,把构成 PyTorch 的主要组件整理成一张更高层次的“地图”。最好的方式,就是从一个深度学习项目究竟需要 PyTorch 提供什么来入手。

首先,虽然 PyTorch 的名字里有个 “Py”,看起来像是个 Python 库,但它内部其实包含了大量非 Python 代码。事实上,出于性能考虑,PyTorch 的大部分底层实现都是用 C++ 和 CUDA(developer.nvidia.com/cuda-zone)编写的。CUDA 是 NVIDIA 提供的一种类似 C++ 的语言,可以被编译后运行在 GPU 上,以实现大规模并行计算。确实有一些方式可以直接从 C++ 使用 PyTorch;不过大多数时候,我们还是会通过 Python 来和 PyTorch 打交道:搭建模型、训练模型,并使用训练好的模型去解决实际问题。

也正是在 Python API 这一层面,PyTorch 在易用性以及与更广泛 Python 生态的集成方面表现得最为出色。下面我们来看一看,从认知模型上,PyTorch 究竟是什么。

正如前面已经提到过的,PyTorch 的核心,是一个提供多维数组的库——在 PyTorch 的术语里,这种多维数组叫作 tensor(张量)(第 3 章我们会详细展开)。与此同时,它还通过 torch 模块提供了一个非常丰富的张量运算库。无论是张量本身,还是这些运算,都既可以在 CPU 上运行,也可以在 GPU 上运行。在 PyTorch 里,把计算从 CPU 挪到 GPU,往往只需要额外加上一两个函数调用而已。

PyTorch 提供的下一项核心能力,是让张量能够“记住”自己经历过哪些数值运算,从而形成这些运算的历史。借助这段历史,PyTorch 可以计算出:如果我们修改初始数据中的任意一部分,最终模型输出会发生怎样的变化。这一能力被用于数值优化——也就是模型“学习”的过程,而 PyTorch 的自动求导引擎(automatic differentiation engine,称为 autograd)会在幕后为我们完成这些计算。我们会在第 5 章详细讨论这一过程。

有了张量,以及带 autograd 能力的张量标准库,PyTorch 就不仅能用于深度学习,也可以被用于物理计算、渲染、优化、仿真、建模等许多场景——未来我们大概率会看到它在整个科学计算光谱中以各种富有创意的方式被使用。不过,PyTorch 首先还是一个深度学习库,因此它也提供了构建神经网络并对其进行训练所需的全部基础构件。图 1.2 展示了一个标准的 PyTorch 项目结构:加载数据、训练模型,然后将模型部署到生产环境中。

image.png

图 1.2 一个 PyTorch 项目的基础高层结构:包括数据加载、训练,以及部署到生产环境

用于构建神经网络的核心 PyTorch 模块位于 torch.nn 中,它提供了常见的神经网络层以及其他架构组件。全连接层、卷积层、激活函数、损失函数,都可以在这里找到(这些概念具体是什么意思,我们会在本书后续内容中逐步展开)。这些组件可以用来构建并初始化图 1.2 中央所示的那个“未训练模型”。而要训练这个模型,我们还需要几样额外的东西:训练数据来源、让模型逐步适配训练数据的优化器,以及一种把模型和数据送到真正执行训练计算的硬件上的方式。

在图 1.2 左侧,我们可以看到:在训练数据真正进入模型之前,往往还需要做相当多的数据处理。(而这还只是在线执行的数据准备过程,并不包括预处理;在实际项目中,预处理本身就可能占据相当大的工作量。)首先,我们得在物理层面拿到数据,最常见的方式是从某种存储系统中读取,也就是数据源。接着,我们需要把数据中的每一个样本转换成 PyTorch 真正能够处理的形式:张量。PyTorch 在 torch.utils.data 中提供的 Dataset 类,正是连接“我们的自定义数据(无论它原本是什么格式)”与“标准化的 PyTorch 张量”之间的桥梁。由于这个过程会随着问题类型的不同而有很大差异,因此数据读取这一部分通常需要我们自己来实现。第 4 章中,我们会详细介绍如何把不同类型的数据表示成张量,以便后续使用。

由于数据存储通常很慢——尤其是读取延迟往往较高——我们希望对数据加载进行并行化处理。然而,虽然 Python 因很多原因广受喜爱,但它并不是一个以“并行处理简单、高效”著称的语言。因此,要想高效地加载数据,并把数据整理成 batch(即包含多个样本的张量),我们通常就需要使用多个进程。虽然这种做法相对复杂,但它本身又具有较强的通用性:PyTorch 已经通过 DataLoader 类为我们提供了这套“魔法”。DataLoader 的实例可以在后台派生子进程,从数据集里加载数据,这样一来,一旦训练循环准备好使用下一批数据,数据就已经等在那里了。我们会在第 7 章正式接触并使用 DatasetDataLoader

当获得 batch 样本的机制就绪之后,我们就可以把注意力转向图 1.2 中央的训练循环本身。通常,训练循环会被实现为一个标准的 Python for 循环。在最简单的情况下,模型只在本地 CPU 或单张 GPU 上执行所需计算。一旦训练循环拿到数据,计算就可以立刻开始。这大概率也会是你的基础配置,而本书中我们默认采用的也是这种设置。

在训练循环的每一步中,我们都会先在数据加载器返回的样本上运行模型。然后,再用某种损失函数,把模型的输出与期望输出(也就是目标值,targets)进行比较。正如 PyTorch 为我们提供了搭建模型所需的各类组件一样,它也提供了多种损失函数可供使用,而这些损失函数同样位于 torch.nn 中。

在用损失函数把模型实际输出与理想输出比较完之后,我们还需要“推”模型一把,让它的输出更接近目标。正如前面提到的,这正是 PyTorch 的 autograd 引擎发挥作用的地方;不过除此之外,我们还需要一个真正负责参数更新的优化器,而 PyTorch 则在 torch.optim 中为我们提供了这一能力。我们会在第 5 章开始接触包含损失函数与优化器的训练循环,并在第 6 章到第 8 章中进一步打磨这方面的技能。

如今,使用更复杂的硬件配置来训练大型模型已经越来越常见,比如同时使用多张 GPU,甚至多台机器共同提供计算资源——图 1.2 中央下方就展示了这种情况。在第 9 章中,我们会考察在这些场景下,如何利用 torch.distributed 子模块来使用这些额外硬件资源。等我们对这一部分有了足够扎实的理解之后,就会在本书第二部分进入更完整的实战项目。

训练循环也许是整个深度学习项目中最不令人兴奋、却往往最耗时的部分。不过在它结束之后,我们会得到一份回报:一个在目标任务上完成了参数优化的模型,也就是“已训练模型”(图中训练循环右侧所示)。拥有一个能够解决任务的模型当然很好,但要让它真正有用,我们还必须把它放到真正需要它工作的地方。这个流程中的部署阶段(图右侧所示)可能意味着:把模型部署到服务器上,或者将其导出并加载到云端引擎中,就像图中展示的那样;也可能是把它集成到一个更大的应用里,或者直接跑在手机上。

在部署过程中,一个特别关键的步骤是模型导出。正如前面提到的,PyTorch 默认采用的是即时执行模型(eager mode)。也就是说,每当 Python 解释器执行一条涉及 PyTorch 的指令时,对应操作都会立刻由底层的 C++ 或 CUDA 实现执行。随着越来越多的指令作用于张量,后端实现也就会持续执行越来越多的操作。

PyTorch 还提供了用于扩展训练规模和支持部署的工具。对于大模型,torch.distributed 可以支持跨多张 GPU 乃至多台机器进行训练。对于生产部署,torch.compile 可以优化模型性能,而 ONNX 导出则可以保证跨平台兼容性。移动端部署则可借助 ExecuTorch 这样的库来完成。我们会在本书最后几章中,更详细地探讨这些能力。

1.6 硬件与软件要求

本书会涉及编写并运行一些需要大量数值计算的任务,例如对大量矩阵进行乘法运算。事实证明,在新数据上运行一个预训练网络,任何较新的笔记本电脑或个人电脑都能够胜任。即便是拿一个预训练网络,对其中一小部分进行再训练,使其能够针对一个新数据集完成专门化适配,也并不一定需要专门的硬件。你完全可以使用一台标准的个人电脑或笔记本,跟着完成本书第一部分中的全部内容。

不过,我们预计,要完整跑完本书第二部分中那些更高级示例的训练过程,通常会需要一块支持 CUDA 的 GPU。第二部分所使用的默认参数,是以拥有 8 GB 显存的 GPU 为前提设定的(我们建议使用 NVIDIA GTX 1070 或更高配置),但如果你的硬件可用显存更小,这些参数也可以进行调整。需要明确的是:如果你愿意等待,那么这类硬件并不是绝对必须的;但如果在 GPU 上运行,训练时间至少会缩短一个数量级(通常会快 40 到 50 倍)。单独来看,用于计算参数更新的那些操作,在现代硬件上——比如普通笔记本的 CPU——执行起来其实并不慢,往往只需零点几秒到几秒钟。问题在于,训练需要把这些操作一遍又一遍地重复执行很多很多次,并在这一过程中不断微调网络参数,以最小化训练误差。

在配有较好 GPU 的工作站上,中等规模的网络若要在大型真实世界数据集上从零开始训练,往往需要数小时到数天不等。通过在同一台机器上使用多块 GPU,可以进一步缩短这一时间;如果是在多台配备多块 GPU 的机器组成的集群上训练,速度还能继续提升。多亏了云计算提供商的各种服务,这类配置的获取门槛并没有听起来那么高。

所以,如果等你读到第二部分时手边正好有 GPU,那当然最好。否则,我们建议你看看各类云平台提供的方案,其中很多都提供了预装 PyTorch、支持 GPU 的 Jupyter Notebook 环境,而且通常还有免费的额度。Google Colaboratory(colab.research.google.com)就是一个非常好的起点。

最后一个需要考虑的是操作系统。PyTorch 可以运行在 Linux、macOS 和 Windows 上。在整本书中,我们会尽量避免假设你使用的是某一种特定操作系统,尽管第二部分中的一些脚本会以 Linux 下 Bash 提示符的形式展示。这些脚本中的命令行通常都很容易转换成 Windows 兼容的形式。为了方便起见,只要可能,代码都会以 Jupyter Notebook 中运行的形式来展示。

关于安装信息,请参阅 PyTorch 官方网站上的 Get Started 指南(pytorch.org/get-started…)。我们建议 Windows 用户通过 Anaconda 或 Miniconda 进行安装(www.anaconda.com/distributio…docs.conda.io/en/latest/m…)。对于 Linux 这类其他操作系统,可行的安装方式通常更多,其中最常见的 Python 包管理器是 pip。我们提供了一个 requirements.txt 文件,pip 可以使用它来安装依赖。当然,如果你是有经验的用户,也完全可以按照最适合你个人开发环境的方式来安装这些包。

第二部分对下载带宽和磁盘空间也有一些不容忽视的要求。第二部分中癌症检测项目所需的原始数据,下载量大约为 60 GB,解压后则需要大约 120 GB 的存储空间。压缩包在解压之后可以删除。此外,出于性能考虑,在训练期间还需要额外缓存一部分数据,因此还会再占用约 80 GB。也就是说,在用于训练的系统上,你至少需要预留总计 200 GB 的空闲磁盘空间。虽然理论上也可以使用网络存储,但如果网络访问速度慢于本地磁盘,训练速度可能会受到影响。理想情况下,你最好能在本地 SSD 上留出空间来存放这些数据,以便快速读取。

1.6.1 使用 Jupyter Notebooks

我们将假定你已经安装好了 PyTorch 及其他依赖,并且已经确认它们可以正常工作。前面我们提到过,书中的代码是可以边读边跟着操作的。对于示例代码,我们会大量使用 Jupyter Notebook。Jupyter Notebook 会以浏览器中的一个页面形式呈现出来,我们可以在其中以交互方式运行代码。代码由一个内核(kernel)来执行;这个内核本质上是运行在服务器上的一个进程,它随时准备接收要执行的代码,并把结果返回回来,然后这些结果会以内联方式显示在页面上。Notebook 会在内存中维护这个内核的状态,例如代码执行过程中定义的变量,直到该内核被终止或重启为止。

我们与 Notebook 交互的基本单位是单元格(cell):也就是页面上的一个方框,我们可以在里面输入代码,并让内核去执行它(可以通过菜单项触发,也可以按 Shift-Enter)。在一个 Notebook 中,我们可以添加多个单元格,而后面的新单元格能够看到前面单元格中创建的变量。一个单元格最后一行返回的值,会在执行完成后直接显示在该单元格下方;绘图结果也是如此。通过将源代码、执行结果以及使用 Markdown 格式编写的文本单元格混合在一起,我们可以生成非常漂亮的交互式文档。关于 Jupyter Notebook 的全部信息,你都可以在项目官网上找到(jupyter.org)。

到这一步,你需要从 GitHub 上检出的代码根目录启动 notebook 服务器。至于具体如何启动,则取决于你的操作系统,以及你安装 Jupyter 的方式和位置。如果你对此有疑问,可以到本书论坛提问(mng.bz/yNPe)。一旦启动成功,你的默认浏览器就会自动弹出,并显示本地 notebook 文件的列表。

注意 Jupyter Notebook 是一个非常强大的工具,特别适合通过代码来表达和探索想法。虽然我们认为它与本书的使用场景非常契合,但它并不一定适合所有人。我们更想强调的一点是:重要的是尽量减少摩擦、降低认知负担,而对不同的人来说,最合适的方式可能并不相同。在你使用 PyTorch 做实验时,尽管选择你自己喜欢的工具即可。

本书中所有代码清单的完整可运行版本,都可以在本书官网(www.manning.com/books/deep-…)以及我们的 GitHub 仓库(github.com/deep-learni…)中找到。

1.7 练习

  • 启动 Python,进入交互式提示符。
  • 你正在使用的是哪个版本的 Python?我们希望它至少是 3.10!
  • 你能导入 torch 吗?你得到的 PyTorch 版本是多少?
  • torch.cuda.is_available() 的结果是什么?它是否与你当前所使用硬件所对应的预期一致?
  • 启动 Jupyter Notebook 服务器。
  • Jupyter 使用的是哪个版本的 Python?
  • Jupyter 所使用的 torch 库路径,是否与你在交互式提示符中导入时使用的是同一个?

小结

深度学习代表了相对于传统机器学习的一次范式转移。传统机器学习方法在很大程度上依赖特征工程,而深度学习方法则旨在根据提供的样例,自动学习输入与期望输出之间的关联关系。

像 PyTorch 这样的库,能够高效地支持人们构建并训练用于深度学习任务的神经网络模型。

自 2017 年初发布以来,PyTorch 已经成长为深度学习领域中的主流框架之一。TensorFlow 在工业界仍被广泛使用,而 Hugging Face 则作为深度学习框架的高层封装,变得越来越流行。

PyTorch 将灵活性与速度放在优先位置,尽量减少认知负担,并且默认采用即时执行模式来运行各项操作。

PyTorch 是一个以张量为核心的库。张量是一种多维数组,而 PyTorch 提供了大量可作用于张量之上的操作。

在整本书中,我们将完整覆盖使用 PyTorch 构建模型的全过程,包括数据加载、模型定义、训练以及评估。