PyTorch 口袋参考(一)
译者:飞龙
前言
我们生活在激动人心的时代!我们中的一些人有幸经历了技术的巨大进步——个人计算机的发明,互联网的兴起,手机的普及以及社交媒体的出现。现在,人工智能领域正在发生重大突破!
看到并参与这种变革是令人兴奋的。我认为我们才刚刚开始,想到未来十年世界可能会发生怎样的变化,这是令人惊奇的。我们能够生活在这个时代并参与人工智能的扩展,这是多么伟大的事情?
毫无疑问,PyTorch 已经实现了一些深度学习和人工智能领域的最好进展。它是免费下载和使用的,任何拥有计算机或互联网连接的人都可以运行人工智能实验。除了像这样更全面的参考资料外,还有许多免费和廉价的培训课程、博客文章和教程可以帮助您。任何人都可以开始使用 PyTorch 进行机器学习和人工智能。
谁应该阅读这本书
这本书是为对机器学习和人工智能感兴趣的初学者和高级用户编写的。最好具有一些编写 Python 代码的经验以及对数据科学和机器学习的基本理解。
如果您刚开始学习机器学习,这本书将帮助您学习 PyTorch 的基础知识并提供一些简单的示例。如果您已经使用其他框架,如 TensorFlow、Caffe2 或 MXNet,这本书将帮助您熟悉 PyTorch 的 API 和编程思维方式,以便扩展您的技能。
如果您已经使用 PyTorch 一段时间,这本书将帮助您扩展对加速和优化等高级主题的知识,并在您日常开发中使用 PyTorch 时提供快速参考资源。
我为什么写这本书
学习和掌握 PyTorch 可能会非常令人兴奋。有很多东西可以探索!当我开始学习 PyTorch 时,我希望有一个资源可以教会我一切。我想要一些东西,可以让我对 PyTorch 提供的内容有一个很好的高层次了解,但也可以在我需要深入了解时提供示例和足够的细节。
有一些关于 PyTorch 的很好的书籍和课程,但它们通常侧重于张量和深度学习模型的训练。PyTorch 的在线文档也非常好,并提供了许多细节和示例;然而,我发现使用它通常很麻烦。我不断地不得不点击以学习或谷歌我需要知道的内容。我需要一本书放在桌子上,我可以标记并在编码时作为参考。
我的目标是这将是您的终极 PyTorch 参考资料。除了通读以获取对 PyTorch 资源的高层次理解之外,我希望您为您的开发工作标记关键部分并将其放在桌子上。这样,如果您忘记了某些内容,您可以立即得到答案。如果您更喜欢电子书或在线书籍,您可以在线收藏本书。无论您如何使用,我希望这本书能帮助您用 PyTorch 创建一些令人惊人的新技术!
浏览本书
如果您刚开始学习 PyTorch,您应该从第一章开始,并按顺序阅读每一章。这些章节从初学者到高级主题逐渐展开。如果您已经有一些 PyTorch 经验,您可能想跳到您最感兴趣的主题。不要忘记查看关于 PyTorch 生态系统的第八章。您一定会发现一些新东西!
这本书大致组织如下:
-
第一章简要介绍了 PyTorch,帮助您设置开发环境,并提供一个有趣的示例供您尝试。
-
第二章介绍了张量,PyTorch 的基本构建块。这是 PyTorch 中一切的基础。
-
第三章全面介绍了您如何使用 PyTorch 进行深度学习,而第四章提供了示例参考设计,让您可以看到 PyTorch 的实际应用。
-
第五章和第六章涵盖了更高级的主题。第五章向您展示了如何自定义 PyTorch 组件以适应您自己的工作,而第六章则向您展示了如何加速训练并优化您的模型。
-
第七章向您展示如何通过本地机器、云服务器和移动或边缘设备将 PyTorch 部署到生产环境。
-
第八章引导您下一步,介绍 PyTorch 生态系统,描述流行的软件包,并列出额外的培训资源。
本书中使用的约定
本书使用以下印刷约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
等宽字体
用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
等宽粗体
显示用户应该按照字面意义输入的命令或其他文本。此外,在表格中,粗体用于强调函数。
等宽斜体
显示应该用用户提供的值或上下文确定的值替换的文本。此外,在表格中列出的转换目前不受 TorchScript 支持。
使用代码示例
补充材料(代码示例、练习等)可在https://github.com/joe-papa/pytorch-book下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需征得我们的许可。例如,编写一个使用本书中几个代码块的程序不需要许可。销售或分发 O'Reilly 图书中的示例需要许可。通过引用本书回答问题并引用示例代码不需要许可。将本书中大量示例代码合并到产品文档中需要许可。
我们感谢,但通常不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“PyTorch 口袋参考 作者 Joe Papa(O'Reilly)。版权所有 2021 年 Mobile Insights Technology Group,LLC,978-1-492-09000-7。”
如果您认为您对代码示例的使用超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。
致谢
作为读者,我经常在阅读其他作者的致谢时感到惊讶。写一本书并不是一件小事,写一本好书需要许多人的支持。阅读致谢是一个不断提醒我们不能独自完成的过程。
我感谢我的朋友 Matt Kirk 的支持和鼓励,多年前在 O'Reilly 会议上认识他。他对个人发展的共同热情激励着我创作图书和课程,帮助他人充分发挥个人和职业潜力。在疫情期间,我们每周的 Zoom 聊天和自助项目确实帮助我保持理智。没有 Matt,这本书就不可能完成。
我要感谢 Rebecca Novack 建议这个项目并给我机会,以及 O'Reilly 的工作人员让这个项目得以实现。
写一本书需要努力,但写一本好书需要关心读者的专注审阅者。我要感谢 Mike Drob、Axel Sirota 和 Jeff Bleiel 花时间审查这本书并提供无数建议。Mike 的建议增加了许多实用资源,否则我可能会忽视。他确保我们使用的是最先进的工具和您在在线文档中找不到的最佳实践。
Axel 对细节的关注令人难以置信。我感谢他对本书代码和技术细节的审查和努力。Jeff 是一位出色的编辑。我感谢他对本书的顺序和流程提出的建议。他显著帮助我成为一个更好的作者。
PyTorch 真正是一个社区项目。我感谢 Facebook 和超过 1700 名贡献者开发了这个机器学习框架。我特别要感谢那些创建文档和教程来帮助像我这样的人快速学习 PyTorch 的人。
对我帮助最大的一些人包括 Suraj Subramanian、Seth Juarez、Cassie Breviu、Dmitry Soshnikov、Ari Bornstein、Soumith Chintala、Justin Johnson、Jeremy Howard、Rachel Thomas、Francisco Ingham、Sasank Chilamkurthy、Nathan Inkawhich、Sean Robertson、Ben Trevett、Avinash Sajjanshetty、James Reed、Michael Suo、Michela Paganini、Shen Li、Séb Arnold、Rohan Varma、Pritam Damania、Jeff Tang,以及关于 PyTorch 主题的无数博主和 YouTuber。
我感谢 Manbir Gulati 介绍我认识 PyTorch,感谢 Rob Miller 给我机会领导 PyTorch 的 AI 项目。我也感谢与我的朋友 Isaac Privitera 分享这本书的深度学习思想。
当然,没有我妈妈 Grace 的辛勤工作和奉献精神,我在生活中无法取得任何成就,她带领我们从不起眼的开始,给了我和我哥哥一个生活的机会。我每天都在想念她。
特别感谢我的哥哥文尼,在完成家庭项目时给予了很大帮助,让我有更多时间写作。我感激我的继父卢,在我写书时给予的鼓励。我还要感谢我的孩子们,萨凡娜、卡罗琳和乔治,在爸爸工作时耐心理解。
最后,我想感谢我的妻子艾米莉。她一直无限支持我的想法和梦想。当我着手写这本书的任务时,当然又一次依靠了她。在疫情期间照顾我们的三个孩子并承担新的责任是一项艰巨的任务。
然而,她一直是我完成写作所需的支持。事实上,在写这本书的过程中,我们发现我们正在期待第四个孩子的到来!我的妻子总是带着微笑和笑话(通常是拿我开玩笑),我很爱她。
第一章:PyTorch 简介
PyTorch 是最受欢迎的深度学习 Python 库之一,广泛被人工智能研究社区使用。许多开发人员和研究人员使用 PyTorch 来加速深度学习研究实验和原型设计。
在本章中,我将简要介绍 PyTorch 是什么以及使其受欢迎的一些特点。我还将向您展示如何在本地机器和云端安装和设置 PyTorch 开发环境。通过本章的学习,您将能够验证 PyTorch 已正确安装并运行一个简单的 PyTorch 程序。
什么是 PyTorch?
PyTorch 库主要由 Facebook 的人工智能研究实验室(FAIR)开发,是一款免费的开源软件,拥有超过 1700 名贡献者。它允许您轻松运行基于数组的计算,在 Python 中构建动态神经网络,并进行自动微分,具有强大的图形处理单元(GPU)加速——这些都是深度学习研究所需的重要功能。尽管有些人用它来加速张量计算,但大多数人用它来进行深度学习开发。
PyTorch 的简单和灵活接口使快速实验成为可能。您可以加载数据,应用转换,并用几行代码构建模型。然后,您可以灵活地编写定制的训练、验证和测试循环,并轻松部署训练好的模型。
它拥有强大的生态系统和庞大的用户社区,包括斯坦福大学等大学和优步、英伟达和 Salesforce 等公司。2019 年,PyTorch 在机器学习和深度学习会议论文中占据主导地位:69%的计算机视觉与模式识别(CVPR)会议论文使用 PyTorch,超过 75%的计算语言学协会(ACL)和北美 ACL 分会(NAACL)使用它,超过 50%的学习表示国际会议(ICLR)和国际机器学习会议(ICML)也使用它。GitHub 上有超过 60000 个与 PyTorch 相关的存储库。
许多开发人员和研究人员使用 PyTorch 来加速深度学习研究实验和原型设计。其简单的 Python API、GPU 支持和灵活性使其成为学术和商业研究机构中的热门选择。自 2018 年开源以来,PyTorch 已经发布了稳定版本,并可以轻松安装在 Windows、Mac 和 Linux 操作系统上。该框架继续迅速扩展,现在可以方便地部署到云端和移动平台的生产环境中。
为什么使用 PyTorch?
如果您正在学习机器学习、进行深度学习研究或构建人工智能系统,您可能需要使用一个深度学习框架。深度学习框架使得执行常见任务如数据加载、预处理、模型设计、训练和部署变得容易。由于其简单性、灵活性和 Python 接口,PyTorch 已经在学术和研究社区中变得非常流行。以下是学习和使用 PyTorch 的一些原因:
PyTorch 很受欢迎
许多公司和研究机构将 PyTorch 作为他们的主要深度学习框架。事实上,一些公司已经在 PyTorch 的基础上构建了他们的自定义机器学习工具。因此,PyTorch 技能需求量大。
PyTorch 得到所有主要云平台的支持,如亚马逊网络服务(AWS)、谷歌云平台(GCP)、微软 Azure 和阿里云
您可以快速启动一个预装有 PyTorch 的虚拟机,进行无摩擦的开发。您可以使用预构建的 Docker 镜像,在云 GPU 平台上进行大规模训练,并在生产规模上运行模型。
PyTorch 得到 Google Colaboratory 和 Kaggle Kernels 的支持
您可以在浏览器中运行 PyTorch 代码,无需安装或配置。您可以通过在内核中直接运行 PyTorch 来参加 Kaggle 竞赛。
PyTorch 是成熟和稳定的
PyTorch 定期维护,现在已经超过 1.8 版本。
PyTorch 支持 CPU、GPU、TPU 和并行处理
您可以使用 GPU 和 TPU 加速训练和推断。张量处理单元(TPUs)是由 Google 开发的人工智能加速的应用特定集成电路(ASIC)芯片,旨在为 NN 硬件加速提供替代 GPU 的选择。通过并行处理,您可以在 CPU 上应用预处理,同时在 GPU 或 TPU 上训练模型。
PyTorch 支持分布式训练
您可以在多台机器上的多个 GPU 上训练神经网络。
PyTorch 支持部署到生产环境
借助新的 TorchScript 和 TorchServe 功能,您可以轻松将模型部署到包括云服务器在内的生产环境中。
PyTorch 开始支持移动部署
尽管目前仍处于实验阶段,但您现在可以将模型部署到 iOS 和 Android 设备上。
PyTorch 拥有庞大的生态系统和一套开源库
诸如 Torchvision、fastai 和 PyTorch Lightning 等库扩展了功能并支持特定领域,如自然语言处理(NLP)和计算机视觉。
PyTorch 还具有 C++前端
尽管本书将重点放在 Python 接口上,但 PyTorch 也支持前端 C++接口。如果您需要构建高性能、低延迟或裸机应用程序,可以使用相同的设计和架构在 C++中编写,就像使用 Python API 一样。
PyTorch 原生支持开放神经网络交换(ONNX)格式
您可以轻松将模型导出为 ONNX 格式,并在 ONNX 兼容的平台、运行时或可视化器中使用它们。
PyTorch 拥有庞大的开发者社区和用户论坛
PyTorch 论坛上有超过 38,000 名用户,通过访问PyTorch 讨论论坛很容易获得支持或发布问题。
入门
如果您熟悉 PyTorch,可能已经安装并设置了开发环境。如果没有,我将在本节中向您展示一些选项。开始的最快方式是使用 Google Colaboratory(或Colab)。Google Colab 是一个免费的基于云的开发环境,类似于 Jupyter Notebook,并已安装了 PyTorch。Colab 提供免费的有限 GPU 支持,并与 Google Drive 接口良好,可用于保存和共享笔记本。
如果您没有互联网访问,或者想在自己的硬件上运行 PyTorch 代码,那么我将向您展示如何在本地机器上安装 PyTorch。您可以在 Windows、Linux 和 macOS 操作系统上安装 PyTorch。我建议您拥有 NVIDIA GPU 进行加速,但不是必需的。
最后,您可能希望使用 AWS、Azure 或 GCP 等云平台开发 PyTorch 代码。如果您想使用云平台,我将向您展示在每个平台上快速入门的选项。
在 Google Colaboratory 中运行
使用 Google Colab,您可以在浏览器中编写和执行 Python 和 PyTorch 代码。您可以直接将文件保存到 Google Drive 帐户,并轻松与他人共享您的工作。要开始,请访问Google Colab 网站,如图 1-1 所示。
图 1-1. Google Colaboratory 欢迎页面
如果您已经登录到您的 Google 帐户,将会弹出一个窗口。单击右下角的“新笔记本”。如果弹出窗口未出现,请单击“文件”,然后从菜单中选择“新笔记本”。您将被提示登录或创建 Google 帐户,如图 1-2 所示。
图 1-2. Google 登录
验证您的配置,导入 PyTorch 库,打印已安装的版本,并检查是否正在使用 GPU,如图 1-3 所示。
图 1-3. 在 Google Colaboratory 中验证 PyTorch 安装
默认情况下,我们的 Colab 笔记本不使用 GPU。您需要从运行时菜单中选择更改运行时类型,然后从“硬件加速器”下拉菜单中选择 GPU 并单击保存,如图 1-4 所示。
图 1-4. 在 Google Colaboratory 中使用 GPU
现在再次运行单元格,选择单元格并按 Shift-Enter。您应该看到is_available()的输出为True,如图 1-5 所示。
图 1-5. 在 Google Colaboratory 中验证 GPU 是否激活
注意
Google 提供了一个付费版本称为 Colab Pro,提供更快的 GPU、更长的运行时间和更多内存。对于本书中的示例,免费版本的 Colab 应该足够了。
现在您已经验证了 PyTorch 已安装,并且您也知道版本。您还验证了您有一个可用的 GPU,并且正确安装和运行了适当的驱动程序。接下来,我将向您展示如何在本地机器上验证您的 PyTorch。
在本地计算机上运行
在某些情况下,您可能希望在本地机器或自己的服务器上安装 PyTorch。例如,您可能希望使用本地存储,或者使用自己的 GPU 或更快的 GPU 硬件,或者您可能没有互联网访问。运行 PyTorch 不需要 GPU,但需要 GPU 加速才能运行。我建议使用 NVIDIA GPU,因为 PyTorch 与用于 GPU 支持的 Compute Unified Device Architecture(CUDA)驱动程序紧密相关。
警告
首先检查您的 GPU 和 CUDA 版本!PyTorch 仅支持特定的 GPU 和 CUDA 版本,许多 Mac 电脑使用非 NVIDIA GPU。如果您使用的是 Mac,请通过单击菜单栏上的苹果图标,选择“关于本机”,然后单击“显示”选项卡来验证您是否有 NVIDIA GPU。如果您在 Mac 上看到 NVIDIA GPU 并希望使用它,您将需要从头开始构建 PyTorch。如果您没有看到 NVIDIA GPU,则应使用 PyTorch 的仅 CPU 版本或选择另一台具有不同操作系统的计算机。
PyTorch 网站提供了一个方便的浏览器工具用于安装,如图 1-6 所示。选择最新的稳定版本,您的操作系统,您喜欢的 Python 包管理器(推荐使用 Conda),Python 语言和您的 CUDA 版本。执行命令行并按照您的配置的说明进行操作。请注意先决条件、安装说明和验证方法。
图 1-6. PyTorch 在线安装配置工具
您应该能够在您喜欢的 IDE(Jupyter Notebook、Microsoft Visual Studio Code、PyCharm、Spyder 等)或终端中运行验证代码片段。图 1-7 显示了如何在 Mac 终端上验证 PyTorch 的正确版本是否已安装。相同的命令也可以用于在 Windows 或 Linux 终端中验证。
图 1-7. 使用 Mac 终端验证 PyTorch
在云平台上运行
如果您熟悉 AWS、GCP 或 Azure 等云平台,您可以在云中运行 PyTorch。云平台为训练和部署深度学习模型提供强大的硬件和基础设施。请记住,使用云服务,特别是 GPU 实例,会产生额外的费用。要开始,请按照感兴趣的平台的在线 PyTorch 云设置指南中的说明进行操作。
设置云环境超出了本书的范围,但我将总结可用的选项。每个平台都提供虚拟机实例以及托管服务来支持 PyTorch 开发。
在 AWS 上运行
AWS 提供多种在云中运行 PyTorch 的选项。如果您更喜欢全面托管服务,可以使用 AWS SageMaker,或者如果您更喜欢管理自己的基础架构,可以使用 AWS 深度学习 Amazon 机器映像(AMI)或容器:
Amazon SageMaker
这是一个全面托管的服务,用于训练和部署模型。您可以从仪表板运行 Jupyter 笔记本,并使用 SageMaker Python SDK 在云中训练和部署模型。您可以在专用 GPU 实例上运行您的笔记本。
AWS 深度学习 AMI
这些是预配置的虚拟机环境。您可以选择 Conda AMI,其中预先安装了许多库(包括 PyTorch),或者如果您更喜欢一个干净的环境来设置私有存储库或自定义构建,可以使用基本 AMI。
AWS 深度学习容器
这些是预先安装了 PyTorch 的 Docker 镜像。它们使您可以跳过从头开始构建和优化环境的过程,主要用于部署。
有关如何入门的更详细信息,请查看“在 AWS 上开始使用 PyTorch”说明。
在 Microsoft Azure 上运行
Azure 还提供多种在云中运行 PyTorch 的选项。您可以使用名为 Azure Machine Learning 的全面托管服务开发 PyTorch 模型,或者如果您更喜欢管理自己的基础架构,可以运行数据科学虚拟机(DSVMs):
Azure Machine Learning
这是一个用于构建和部署模型的企业级机器学习服务。它包括拖放设计器和 MLOps 功能,可与现有的 DevOps 流程集成。
DSVMs
这些是预配置的虚拟机环境。它们预先安装了 PyTorch 和其他深度学习框架以及开发工具,如 Jupyter Notebook 和 VS Code。
有关如何入门的更详细信息,请查看Azure Machine Learning 文档。
在 Google 云平台上运行
GCP 还提供多种在云中运行 PyTorch 的选项。您可以使用名为 AI 平台笔记本的托管服务开发 PyTorch 模型,或者如果您更喜欢管理自己的基础架构,可以运行深度学习 VM 镜像:
AI 平台笔记本
这是一个托管服务,其集成的 JupyterLab 环境允许您创建预配置的 GPU 实例。
深度学习 VM 镜像
这些是预配置的虚拟机环境。它们预先安装了 PyTorch 和其他深度学习框架以及开发工具。
有关如何入门的更详细信息,请查看 Google Cloud 的“AI 和机器学习产品”说明。
验证您的 PyTorch 环境
无论您使用 Colab、本地计算机还是您喜爱的云平台,您都应该验证 PyTorch 是否已正确安装,并检查是否有 GPU 可用。您已经在 Colab 中看到了如何执行此操作。要验证 PyTorch 是否已正确安装,请使用以下代码片段。该代码导入 PyTorch 库,打印版本,并检查是否有 GPU 可用:
import torch
print(torch.__version__)
print(torch.cuda.is_available())
警告
您使用import torch导入库,而不是import pytorch。PyTorch 最初基于torch库,这是一个基于 C 和 Lua 编程语言的开源机器学习框架。保持库命名为torch允许 Torch 代码与更高效的 PyTorch 实现重用。
一个有趣的例子
现在您已经验证了您的环境是否正确配置,让我们编写一个有趣的例子,展示 PyTorch 的一些特性,并演示机器学习中的最佳实践。在这个例子中,我们将构建一个经典的图像分类器,尝试根据 1,000 个可能的类别或选择来识别图像的内容。
您可以从本书的 GitHub 存储库访问此示例并跟随。尝试在 Google Colab、本地计算机或 AWS、Azure 或 GCP 等云平台上运行代码。不用担心理解机器学习的所有概念。我们将在本书中更详细地介绍它们。
注意
在实践中,您将在代码开头导入所有必要的库。然而,在这个例子中,我们将在使用时导入库,这样您就可以看到每个任务需要哪些库。
首先,让我们选择一个我们想要分类的图像。在这个例子中,我们将选择一杯美味的新鲜热咖啡。使用以下代码将咖啡图像下载到您的本地环境:
import urllib.request
url = url = 'https://pytorch.tips/coffee'
fpath = 'coffee.jpg'
urllib.request.urlretrieve(url, fpath)
请注意,代码使用urllib库的urlretrieve()函数从网络获取图像。我们通过指定fpath将文件重命名为coffee.jpg。
接下来,我们使用 Pillow 库(PIL)读取我们的本地图像:
import matplotlib.pyplot as plt
from PIL import Image
img = Image.open('coffee.jpg')
plt.imshow(img)
图 1-8 展示了我们的图像是什么样子的。我们可以使用matplotlib的imshow()函数在我们的系统上显示图像,就像前面的代码所示的那样。
图 1-8。分类器的输入图像
请注意我们还没有使用 PyTorch。这里就是事情变得令人兴奋的地方。接下来,我们将把我们的图像传递给一个预训练的图像分类神经网络(NN)—但在这之前,我们需要预处理我们的图像。在机器学习中,预处理数据是非常常见的,因为 NN 期望输入满足某些要求。
在我们的示例中,图像数据是一个 RGB 1600 × 1200 像素的 JPEG 格式图像。我们需要应用一系列预处理步骤,称为转换,将图像转换为 NN 的正确格式。我们使用 Torchvision 在下面的代码中实现这一点:
import torch
from torchvision import transforms
transform = 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])])
img_tensor = transform(img)
print(type(img_tensor), img_tensor.shape)
# out:
# <class 'torch.tensor'> torch.Size([3, 224, 224])
我们使用Compose()转换来定义一系列用于预处理我们的图像的转换。首先,我们需要调整大小并裁剪图像以适应 NN。图像目前是 PIL 格式,因为我们之前是这样读取的。但是我们的 NN 需要一个张量输入,所以我们将 PIL 图像转换为张量。
张量是 PyTorch 中的基本数据对象,我们将在整个下一章中探索它们。您可以将张量视为 NumPy 数组或带有许多额外功能的数值数组。现在,我们只需将我们的图像转换为一个数字的张量数组,使其准备好。
我们应用了另一个叫做Normalize()的转换,来重新缩放像素值的范围在 0 和 1 之间。均值和标准差(std)的值是基于用于训练模型的数据预先计算的。对图像进行归一化可以提高分类器的准确性。
最后,我们调用transform(img)来将所有的转换应用到图像上。正如你所看到的,img_tensor是一个 3 × 224 × 224 的torch.Tensor,代表着一个 3 通道、224 × 224 像素的图像。
高效的机器学习过程会批处理数据,我们的模型会期望一批数据。然而,我们只有一张图像,所以我们需要创建一个大小为 1 的批次,如下面的代码所示:
batch = img_tensor.unsqueeze(0)
print(batch.shape)
# out: torch.Size([1, 3, 224, 224])
我们使用 PyTorch 的unsqueeze()函数向我们的张量添加一个维度,并创建一个大小为 1 的批次。现在我们有一个大小为 1 × 3 × 224 × 224 的张量,代表一个批次大小为 1 和 3 通道(RGB)的 224 × 224 像素。PyTorch 提供了许多有用的函数,比如unsqueeze()来操作张量,我们将在下一章中探索其中许多函数。
现在我们的图像已经准备好用于我们的分类器 NN 了!我们将使用一个名为 AlexNet 的著名图像分类器。AlexNet 在 2012 年的 ImageNet 大规模视觉识别挑战赛中获胜。使用 Torchvision 很容易加载这个模型,如下面的代码所示:
from torchvision import models
model = models.alexnet(pretrained=True)
我们将在这里使用一个预训练的模型,所以不需要训练它。AlexNet 模型已经使用数百万张图像进行了预训练,并且在分类图像方面表现得相当不错。让我们传入我们的图像,看看它的表现:
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
# out(results will vary): cpu
model.eval()
model.to(device)
y = model(batch.to(device))
print(y.shape)
# out: torch.Size([1, 1000])
GPU 加速是 PyTorch 的一个关键优势。在第一行中,我们使用 PyTorch 的cuda.is_available()函数来查看我们的机器是否有 GPU。这是 PyTorch 代码中非常常见的一行,我们将在第二章和第六章进一步探讨 GPU。我们只对一个图像进行分类,所以这里不需要 GPU,但如果我们有一个巨大的批次,使用 GPU 可能会加快速度。
model.eval()函数配置我们的 AlexNet 模型进行推断或预测(与训练相对)。模型的某些组件仅在训练期间使用,我们不希望在这里使用它们。使用model.to(device)和batch.to(device)将我们的模型和输入数据发送到 GPU(如果可用),执行model(batch.to(device))运行我们的分类器。
输出y包含一个批次的 1,000 个输出。由于我们的批次只包含一个图像,第一维是1,而类的数量是1000,每个类有一个值。值越高,图像包含该类的可能性就越大。以下代码找到获胜的类:
y_max, index = torch.max(y,1)
print(index, y_max)
# out: tensor([967]) tensor([22.3059],
# grad_fn=<MaxBackward0>)
使用 PyTorch 的max()函数,我们看到索引为 967 的类具有最高值 22.3059,因此是获胜者。但是,我们不知道类 967 代表什么。让我们加载包含类名的文件并找出:
url = 'https://pytorch.tips/imagenet-labels'
fpath = 'imagenet_class_labels.txt'
urllib.request.urlretrieve(url, fpath)
with open('imagenet_class_labels.txt') as f:
classes = [line.strip() for line in f.readlines()]
print(classes[967])
# out: 967: 'espresso',
就像我们之前做的那样,我们使用urlretrieve()下载包含每个类描述的文本文件。然后,我们使用readlines()读取文件,并创建一个包含类名的列表。当我们print(classes[967])时,它显示类 967 是espresso!
使用 PyTorch 的softmax()函数,我们可以将输出值转换为概率:
prob = torch.nn.functional.softmax(y, dim=1)[0] * 100
print(classes[index[0]], prob[index[0]].item())
#967: 'espresso', 87.85208892822266
要打印索引处的概率,我们使用 PyTorch 的tensor.item()方法。item()方法经常被使用,并返回张量中包含的数值。结果显示,模型有 87.85%的把握这是一张浓缩咖啡的图像。
我们可以使用 PyTorch 的sort()函数对输出概率进行排序,并查看前五个:
_, indices = torch.sort(y, descending=True)
for idx in indices[0][:5]:
print(classes[idx], prob[idx].item())
# out:
# 967: 'espresso', 87.85208892822266
# 968: 'cup', 7.28359317779541
# 504: 'coffee mug', 4.33521032333374
# 925: 'consomme', 0.36686763167381287
# 960: 'chocolate sauce, chocolate syrup',
# 0.09037172049283981
我们看到模型预测图像是espresso的概率为 87.85%。它还以 7.28%的概率预测cup,以 4.3%的概率预测coffee mug,但它似乎非常确信图像是一杯浓缩咖啡。
您可能现在感觉需要一杯浓缩咖啡。在那个示例中,我们涵盖了很多内容!实际上,实现所有这些的核心代码要短得多。假设您已经下载了文件,您只需要运行以下代码来使用 AlexNet 对图像进行分类:
import torch
from torchvision import transforms, models
transform = 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])])
img_tensor = transform(img)
batch = img_tensor.unsqueeze(0)
model = models.alexnet(pretrained=True)
device = "cuda" if torch.cuda.is_available() else "cpu"
model.eval()
model.to(device)
y = model(batch.to(device))
prob = torch.nn.functional.softmax(y, dim=1)[0] * 100
_, indices = torch.sort(y, descending=True)
for idx in indices[0][:5]:
print(classes[idx], prob[idx].item())
这就是如何使用 PyTorch 构建图像分类器。尝试通过模型运行自己的图像,并查看它们的分类情况。还可以尝试在另一个平台上完成示例。例如,如果您使用 Colab 运行代码,请尝试在本地或云中运行它。
恭喜,您已经验证了您的环境已正确配置,并且可以执行 PyTorch 代码!我们将在本书的其余部分更深入地探讨每个主题。在下一章中,我们将探讨 PyTorch 的基础知识,并提供张量及其操作的快速参考。
第二章:张量
在深入了解 PyTorch 开发世界之前,熟悉 PyTorch 中的基本数据结构torch.Tensor是很重要的。通过理解张量,您将了解 PyTorch 如何处理和存储数据,由于深度学习基本上是浮点数的收集和操作,理解张量将帮助您了解 PyTorch 如何为深度学习实现更高级的功能。此外,在预处理输入数据或在模型开发过程中操作输出数据时,您可能经常使用张量操作。
本章作为理解张量和在代码中实现张量函数的快速参考。我将从描述张量是什么开始,并向您展示如何使用函数来创建、操作和加速 GPU 上的张量操作的一些简单示例。接下来,我们将更广泛地查看创建张量和执行数学操作的 API,以便您可以快速查阅一份全面的张量功能列表。在每个部分中,我们将探讨一些更重要的函数,识别常见的陷阱,并检查它们的使用中的关键点。
张量是什么?
在 PyTorch 中,张量是一种用于存储和操作数据的数据结构。与 NumPy 数组类似,张量是一个包含单一数据类型元素的多维数组。张量可以用来表示标量、向量、矩阵和n维数组,并且是从torch.Tensor类派生的。然而,张量不仅仅是数字数组。从torch.Tensor类创建或实例化张量对象使我们可以访问一组内置的类属性和操作或类方法,提供了一套强大的内置功能。本章详细描述了这些属性和操作。
张量还包括一些附加优势,使它们比 NumPy 数组更适合用于深度学习计算。首先,使用 GPU 加速可以显著加快张量操作的速度。其次,可以使用分布式处理在多个 CPU 和 GPU 上以及跨多个服务器上存储和操作张量。第三,张量跟踪它们的图计算,正如我们将在“自动微分(Autograd)”中看到的,这在实现深度学习库中非常重要。
为了进一步解释张量实际上是什么以及如何使用它,我将从一个简单示例开始,创建一些张量并执行一个张量操作。
简单 CPU 示例
这里有一个简单的示例,创建一个张量,执行一个张量操作,并在张量本身上使用一个内置方法。默认情况下,张量数据类型将从输入数据类型派生,并且张量将分配到 CPU 设备。首先,我们导入 PyTorch 库,然后我们从二维列表创建两个张量x和y。接下来,我们将这两个张量相加,并将结果存储在z中。我们可以在这里使用+运算符,因为torch.Tensor类支持运算符重载。最后,我们打印新的张量z,我们可以看到它是x和y的矩阵和,并打印z的大小。注意,z本身是一个张量对象,size()方法用于返回其矩阵维度,即 2×3:
import torch
x = torch.tensor([[1,2,3],[4,5,6]])
y = torch.tensor([[7,8,9],[10,11,12]])
z = x + y
print(z)
# out:
# tensor([[ 8, 10, 12],
# [14, 16, 18]])
print(z.size())
# out: torch.Size([2, 3])
注意
在旧代码中可能会看到使用torch.Tensor()(大写 T)构造函数。这是torch.FloatTensor默认张量类型的别名。您应该改用torch.tensor()来创建您的张量。
简单 GPU 示例
由于在 GPU 上加速张量操作是张量优于 NumPy 数组的主要优势,我将向您展示一个简单的示例。这是上一节中的相同示例,但在这里,如果有 GPU 设备,我们将将张量移动到 GPU 设备。请注意,输出张量也分配给了 GPU。您可以使用设备属性(例如z.device)来双重检查张量所在的位置。
在第一行中,torch.cuda.is_available()函数将在您的机器支持 GPU 时返回True。这是一种方便的编写更健壮代码的方式,可以在存在 GPU 时加速运行,但在没有 GPU 时也可以在 CPU 上运行。在输出中,device='cuda:0'表示正在使用第一个 GPU。如果您的机器包含多个 GPU,您还可以控制使用哪个 GPU:
device = "cuda" if torch.cuda.is_available()
else "cpu"
x = torch.tensor([[1,2,3],[4,5,6]],
device=device)
y = torch.tensor([[7,8,9],[10,11,12]],
device=device)
z = x + y
print(z)
# out:
# tensor([[ 8, 10, 12],
# [14, 16, 18]], device='cuda:0')
print(z.size())
# out: torch.Size([2, 3])
print(z.device)
# out: cuda:0
在 CPU 和 GPU 之间移动张量
前面的代码使用torch.tensor()在特定设备上创建张量;然而,更常见的是将现有张量移动到设备上,即如果有 GPU 设备的话,通常是 GPU。您可以使用torch.to()方法来实现。当通过张量操作创建新张量时,PyTorch 会在相同设备上创建新张量。在下面的代码中,z位于 GPU 上,因为x和y位于 GPU 上。张量z通过torch.to("cpu")移回 CPU 进行进一步处理。还请注意,操作中的所有张量必须在同一设备上。如果x在 GPU 上,而y在 CPU 上,我们将会收到错误:
device = "cuda" if torch.cuda.is_available()
else "cpu"
x = x.to(device)
y = y.to(device)
z = x + y
z = z.to("cpu")
# out:
# tensor([[ 8, 10, 12],
# [14, 16, 18]])
注意
您可以直接使用字符串作为设备参数,而不是设备对象。以下都是等效的:
-
device="cuda" -
device=torch.device("cuda") -
device="cuda:0" -
device=torch.device("cuda:0")
创建张量
前一节展示了创建张量的简单方法;然而,还有许多其他方法可以实现。您可以从现有的数字数据中创建张量,也可以创建随机抽样。张量可以从存储在类似数组结构中的现有数据(如列表、元组、标量或序列化数据文件)以及 NumPy 数组中创建。
以下代码说明了创建张量的一些常见方法。首先,它展示了如何使用torch.tensor()从列表创建张量。此方法也可用于从其他数据结构(如元组、集合或 NumPy 数组)创建张量:
import numpy
# Created from preexisting arrays
w = torch.tensor([1,2,3]) # ①
w = torch.tensor((1,2,3)) # ②
w = torch.tensor(numpy.array([1,2,3])) # ③
# Initialized by size
w = torch.empty(100,200) # ④
w = torch.zeros(100,200) # ⑤
w = torch.ones(100,200) # ⑥
①
从列表中
②
从元组中
③
从 NumPy 数组中
④
未初始化;元素值不可预测
⑤
所有元素初始化为 0.0
⑥
所有元素初始化为 1.0
如前面的代码示例所示,您还可以使用torch.empty()、torch.ones()和torch.zeros()等函数创建和初始化张量,并指定所需的大小。
如果您想要使用随机值初始化张量,PyTorch 支持一组强大的函数,例如torch.rand()、torch.randn()和torch.randint(),如下面的代码所示:
# Initialized by size with random values
w = torch.rand(100,200) # ①
w = torch.randn(100,200) # ②
w = torch.randint(5,10,(100,200)) # ③
# Initialized with specified data type or device
w = torch.empty((100,200), dtype=torch.float64,
device="cuda")
# Initialized to have the same size, data type,
# and device as another tensor
x = torch.empty_like(w)
①
创建一个 100×200 的张量,元素来自区间 0,1)上的均匀分布。
![2 元素是均值为 0、方差为 1 的正态分布随机数。// # ③
元素是介于 5 和 10 之间的随机整数。
在初始化时,您可以像前面的代码示例中所示指定数据类型和设备(即 CPU 或 GPU)。此外,示例展示了如何使用 PyTorch 创建具有与其他张量相同属性但使用不同数据初始化的张量。带有_like后缀的函数,如torch.empty_like()和torch.ones_like(),返回具有与另一个张量相同大小、数据类型和设备的张量,但初始化方式不同(参见“从随机样本创建张量”)。
注意
一些旧函数,如from_numpy()和as_tensor(),在实践中已被torch.tensor()构造函数取代,该构造函数可用于处理所有情况。
表 2-1 列出了用于创建张量的 PyTorch 函数。您应该使用torch命名空间下的每个函数,例如torch.empty()。您可以通过访问PyTorch 张量文档获取更多详细信息。
表 2-1. 张量创建函数
| 函数 | 描述 |
|---|---|
torch.**tensor**(*data, dtype=None, device=None, requires_grad=False, pin_memory=False*) | 从现有数据结构创建张量 |
torch.**empty**(**size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 根据内存中值的随机状态创建未初始化元素的张量 |
torch.**zeros**(**size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 创建一个所有元素初始化为 0.0 的张量 |
torch.**ones**(**size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 创建一个所有元素初始化为 1.0 的张量 |
torch.**arange**(*start=0, end, step=1, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 使用公共步长值在范围内创建值的一维张量 |
torch.**linspace**(*start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 在start和end之间创建线性间隔点的一维张量 |
torch.**logspace**(*start, end, steps=100, base=10.0, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 在start和end之间创建对数间隔点的一维张量 |
torch.**eye**(*n, m=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 创建一个对角线为 1,其他位置为 0 的二维张量 |
torch.**full**(*size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 创建一个填充了fill_value的张量 |
torch.**load**(*f*) | 从序列化的 pickle 文件中加载张量 |
torch.**save**(*f*) | 将张量保存到序列化的 pickle 文件中 |
PyTorch 文档包含了创建张量的完整函数列表,以及如何使用它们的更详细解释。在创建张量时,请记住一些常见的陷阱和额外的见解:
-
大多数创建函数都接受可选的
dtype和device参数,因此您可以在创建时设置这些参数。 -
使用
torch.arange()而不是已弃用的torch.range()函数。当步长已知时,请使用torch.arange()。当元素数量已知时,请使用torch.linspace()。 -
您可以使用
torch.tensor()从类似数组的结构(如列表、NumPy 数组、元组和集合)创建张量。要将现有张量转换为 NumPy 数组和列表,分别使用torch.numpy()和torch.tolist()函数。
张量属性
PyTorch 受欢迎的一个特点是它非常符合 Python 风格且面向对象。由于张量是自己的数据类型,因此可以读取张量对象本身的属性。现在您可以创建张量了,通过访问它们的属性,可以快速查找有关它们的信息是很有用的。假设x是一个张量,您可以按如下方式访问x的几个属性:
x.dtype
指示张量的数据类型(请参见表 2-2 列出的 PyTorch 数据类型列表)
x.device
指示张量的设备位置(例如,CPU 或 GPU 内存)
x.shape
显示张量的维度
x.ndim
标识张量的维数或秩
x.requires_grad
一个布尔属性,指示张量是否跟踪图计算(参见“自动微分(Autograd)”)
x.grad
如果requires_grad为True,则包含实际的梯度
x.grad_fn
如果requires_grad为True,则存储使用的图计算函数
x.s_cuda,x.is_sparse,x.is_quantized,x.is_leaf,x.is_mkldnn
指示张量是否满足某些条件的布尔属性
x.layout
指示张量在内存中的布局方式
请记但,当访问对象属性时,不要像调用类方法那样包括括号(())(例如,使用x.shape,而不是x.shape())。
数据类型
在深度学习开发中,了解数据及其计算所使用的数据类型非常重要。因此,在创建张量时,应该控制所使用的数据类型。如前所述,所有张量元素具有相同的数据类型。您可以在创建张量时使用dtype参数指定数据类型,或者可以使用适当的转换方法或to()方法将张量转换为新的dtype,如下面的代码所示:
# Specify the data type at creation using dtype
w = torch.tensor([1,2,3], dtype=torch.float32)
# Use the casting method to cast to a new data type
w.int() # w remains a float32 after the cast
w = w.int() # w changes to an int32 after the cast
# Use the to() method to cast to a new type
w = w.to(torch.float64) # ①
w = w.to(dtype=torch.float64) # ②
# Python automatically converts data types during
# operations
x = torch.tensor([1,2,3], dtype=torch.int32)
y = torch.tensor([1,2,3], dtype=torch.float32)
z = x + y # ③
print(z.dtype)
# out: torch.float32
①
传入数据类型。
②
直接使用dtype定义数据类型。
③
Python 会自动将x转换为float32,并将z返回为float32。
请注意,转换和to()方法不会改变张量的数据类型,除非重新分配张量。此外,在执行混合数据类型的操作时,PyTorch 会自动将张量转换为适当的类型。
大多数张量创建函数允许您在创建时使用dtype参数指定数据类型。在设置dtype或转换张量时,请记住使用torch命名空间(例如,使用torch.int64,而不仅仅是int64)。
表 2-2 列出了 PyTorch 中所有可用的数据类型。每种数据类型都会导致不同的张量类,具体取决于张量的设备。相应的张量类分别显示在 CPU 和 GPU 的最右两列中。
表 2-2. 张量数据类型
| 数据类型 | dtype | CPU 张量 | GPU 张量 |
|---|---|---|---|
| 32 位浮点数(默认) | torch.float32或torch.float | torch.FloatTensor | torch.cuda.FloatTensor |
| 64 位浮点数 | torch.float64或torch.double | torch.DoubleTensor | torch.cuda.DoubleTensor |
| 16 位浮点数 | torch.float16或torch.half | torch.HalfTensor | torch.cuda.HalfTensor |
| 8 位整数(无符号) | torch.uint8 | torch.ByteTensor | torch.cuda.ByteTensor |
| 8 位整数(有符号) | torch.int8 | torch.CharTensor | torch.cuda.CharTensor |
| 16 位整数(有符号) | torch.int16或torch.short | torch.ShortTensor | torch.cuda.ShortTensor |
| 32 位整数(有符号) | torch.int32或torch.int | torch.IntTensor | torch.cuda.IntTensor |
| 64 位整数(有符号) | torch.int64或torch.long | torch.LongTensor | torch.cuda.LongTensor |
| 布尔值 | torch.bool | torch.BoolTensor | torch.cuda.BoolTensor |
注意
为了减少空间复杂度,有时您可能希望重用内存并使用就地操作覆盖张量值。要执行就地操作,请在函数名称后附加下划线(_)后缀。例如,函数y.add_(x)将x添加到y,但结果将存储在y中。
从随机样本创建张量
在深度学习开发过程中经常需要创建随机数据。有时您需要将权重初始化为随机值或创建具有指定分布的随机输入。PyTorch 支持一组非常强大的函数,您可以使用这些函数从随机数据创建张量。
与其他创建函数一样,您可以在创建张量时指定 dtype 和 device。表 2-3 列出了一些随机抽样函数的示例。
表 2-3. 随机抽样函数
| 函数 | 描述 |
|---|---|
torch.rand(**size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 从区间[0 到 1]上的均匀分布中选择随机值 |
torch.randn(**size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 从均值为零方差为单位的标准正态分布中选择随机值 |
torch.normal(*mean, std, *, generator=None, out=None*) | 从具有指定均值和方差的正态分布中选择随机数 |
torch.randint(*low=0, high, size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False*) | 在指定的低值和高值之间生成均匀分布的随机整数 |
torch.randperm(*n, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False*) | 创建从 0 到n-1 的整数的随机排列 |
torch.bernoulli(*input, *, generator=None, out=None*) | 从伯努利分布中绘制二进制随机数(0 或 1) |
torch.multinomial(*input, num_samples, replacement=False, *, generator=None, out=None*) | 根据多项分布中的权重从列表中选择一个随机数 |
提示
您还可以创建从更高级分布(如柯西分布、指数分布、几何分布和对数正态分布)中抽样的值张量。为此,使用torch.empty()创建张量,并对分布(例如柯西分布)应用就地函数。请记住,就地方法使用下划线后缀。例如,x = torch.empty([10,5]).cauchy_()创建一个从柯西分布中抽取的随机数张量。
像其他张量一样创建张量
您可能希望创建并初始化一个具有与另一个张量相似属性的张量,包括dtype、device和layout属性,以便进行计算。许多张量创建操作都有一个相似性函数,允许您轻松地执行此操作。相似性函数将具有后缀_like。例如,torch.empty_like(tensor_a)将创建一个具有tensor_a的dtype、device和layout属性的空张量。一些相似性函数的示例包括empty_like()、zeros_like()、ones_like()、full_like()、rand_like()、randn_like()和rand_int_like()。
张量操作
现在您了解如何创建张量,让我们探索您可以对其执行的操作。PyTorch 支持一组强大的张量操作,允许您访问和转换张量数据。
首先我将描述如何访问数据的部分,操作它们的元素,并组合张量以形成新的张量。然后我将向您展示如何执行简单的计算以及高级的数学计算,通常在恒定时间内。PyTorch 提供了许多内置函数。在创建自己的函数之前检查可用的函数是很有用的。
索引、切片、组合和拆分张量
创建张量后,您可能希望访问数据的部分并组合或拆分张量以形成新张量。以下代码演示了如何执行这些类型的操作。您可以像切片和索引 NumPy 数组一样切片和索引张量,如以下代码的前几行所示。请注意,即使数组只有一个元素,索引和切片也会返回张量。在传递给print()等其他函数时,您需要使用item()函数将单个元素张量转换为 Python 值:
x = torch.tensor([[1,2],[3,4],[5,6],[7,8]])
print(x)
# out:
# tensor([[1, 2],
# [3, 4],
# [5, 6],
# [7, 8]])
# Indexing, returns a tensor
print(x[1,1])
# out: tensor(4)
# Indexing, returns a value as a Python number
print(x[1,1].item())
# out: 4
在下面的代码中,我们可以看到我们可以使用与用于切片 Python 列表和 NumPy 数组相同的[*start*:*end*:*step*]格式执行切片。我们还可以使用布尔索引来提取满足某些条件的数据部分,如下所示:
# Slicing
print(x[:2,1])
# out: tensor([2, 4])
# Boolean indexing
# Only keep elements less than 5
print(x[x<5])
# out: tensor([1, 2, 3, 4])
PyTorch 还支持转置和重塑数组,如下面的代码所示:
# Transpose array; x.t() or x.T can be used
print(x.t())
# tensor([[1, 3, 5, 7],
# [2, 4, 6, 8]])
# Change shape; usually view() is preferred over
# reshape()
print(x.view((2,4)))
# tensor([[1, 3, 5, 7],
# [2, 4, 6, 8]])
您还可以使用torch.stack()和torch.unbind()等函数组合或拆分张量,如下面的代码所示:
# Combining tensors
y = torch.stack((x, x))
print(y)
# out:
# tensor([[[1, 2],
# [3, 4],
# [5, 6],
# [7, 8]],
# [[1, 2],
# [3, 4],
# [5, 6],
# [7, 8]]])
# Splitting tensors
a,b = x.unbind(dim=1)
print(a,b)
# out:
# tensor([1, 3, 5, 7]); tensor([2, 4, 6, 8])
PyTorch 提供了一组强大的内置函数,可用于以不同方式访问、拆分和组合张量。表 2-4 列出了一些常用的用于操作张量元素的函数。
表 2-4。索引、切片、组合和拆分操作
| 函数 | 描述 |
|---|---|
torch.cat() | 在给定维度中连接给定序列的张量。 |
torch.chunk() | 将张量分成特定数量的块。每个块都是输入张量的视图。 |
torch.gather() | 沿着由维度指定的轴收集值。 |
torch.index_select() | 使用索引中的条目沿着维度索引输入张量的新张量,索引是LongTensor。 |
torch.masked_select() | 根据布尔掩码(BoolTensor)索引输入张量的新 1D 张量。 |
torch.narrow() | 返回输入张量的窄版本的张量。 |
torch.nonzero() | 返回非零元素的索引。 |
torch.reshape() | 返回一个与输入张量具有相同数据和元素数量但形状不同的张量。使用view()而不是确保张量不被复制。 |
torch.split() | 将张量分成块。每个块都是原始张量的视图或子分区。 |
torch.squeeze() | 返回一个去除输入张量所有尺寸为 1 的维度的张量。 |
torch.stack() | 沿新维度连接一系列张量。 |
torch.t() | 期望输入为 2D 张量并转置维度 0 和 1。 |
torch.take() | 在切片不连续时返回指定索引处的张量。 |
torch.transpose() | 仅转置指定的维度。 |
torch.unbind() | 通过返回已删除维度的元组来移除张量维度。 |
torch.unsqueeze() | 返回一个在指定位置插入大小为 1 的维度的新张量。 |
torch.where() | 根据指定条件从两个张量中的一个返回所选元素的张量。 |
其中一些函数可能看起来多余。然而,重要的是要记住以下关键区别和最佳实践:
-
item()是一个重要且常用的函数,用于从包含单个值的张量返回 Python 数字。 -
在大多数情况下,用
view()代替reshape()来重新塑造张量。使用reshape()可能会导致张量被复制,这取决于其在内存中的布局。view()确保不会被复制。 -
使用
x.T或x.t()是转置 1D 或 2D 张量的简单方法。处理多维张量时,请使用transpose()。 -
torch.squeeze()函数在深度学习中经常用于去除未使用的维度。例如,使用squeeze()可以将包含单个图像的图像批次从 4D 减少到 3D。 -
torch.unsqueeze()函数在深度学习中经常用于添加大小为 1 的维度。由于大多数 PyTorch 模型期望批量数据作为输入,当您只有一个数据样本时,可以应用unsqueeze()。例如,您可以将一个 3D 图像传递给torch.unsqueeze()以创建一个图像批次。
注意
PyTorch 在本质上非常符合 Python 的特性。与大多数 Python 类一样,一些 PyTorch 函数可以直接在张量上使用内置方法,例如x.size()。
其他函数直接使用torch命名空间调用。这些函数以张量作为输入,就像在torch.save(x, 'tensor.pt')中的x一样。
数学张量操作
深度学习开发在很大程度上基于数学计算,因此 PyTorch 支持非常强大的内置数学函数集。无论您是创建新的数据转换、自定义损失函数还是构建自己的优化算法,您都可以通过 PyTorch 提供的数学函数加快研究和开发速度。
本节的目的是快速概述 PyTorch 中许多可用的数学函数,以便您可以快速了解当前存在的内容,并在需要时找到适当的函数。
PyTorch 支持许多不同类型的数学函数,包括逐点操作、缩减函数、比较计算以及线性代数操作,以及频谱和其他数学计算。我们将首先看一下有用的数学操作的第一类是逐点操作。逐点操作在张量中的每个点上执行操作,并返回一个新的张量。
它们对于舍入和截断以及三角和逻辑操作非常有用。默认情况下,这些函数将创建一个新的张量或使用由out参数传递的张量。如果要执行原地操作,请记得在函数名称后附加下划线。
表 2-5 列出了一些常用的逐点操作。
表 2-5. 逐点操作
| 操作类型 | 示例函数 |
|---|---|
| 基本数学 | add(), div(), mul(), neg(), reciprocal(), true_divide() |
| 截断 | ceil(), clamp(), floor(), floor_divide(), fmod(), frac(), lerp(), remainder(), round(), sigmoid(), trunc() |
| 复数 | abs(), angle(), conj(), imag(), real() |
| 三角函数 | acos(), asin(), atan(), cos(), cosh(), deg2rad(), rad2deg(), sin(), sinh(), tan(), tanh() |
| 指数和对数 | exp(), expm1(), log(), log10(), log1p(), log2(), logaddexp(), pow(), rsqrt(), sqrt(), square() |
| 逻辑 | logical_and(), logical_not(), logical_or(), logical_xor() |
| 累积数学 | addcdiv(), addcmul() |
| 位运算符 | bitwise_not(), bitwise_and(), bitwise_or(), bitwise_xor() |
| 错误函数 | erf(), erfc(), erfinv() |
| 伽玛函数 | digamma(), lgamma(), mvlgamma(), polygamma() |
使用 Python 提示或参考 PyTorch 文档以获取有关函数使用的详细信息。请注意,true_divide()首先将张量数据转换为浮点数,应在将整数除以以获得真实除法结果时使用。
注意
大多数张量操作可以使用三种不同的语法。张量支持运算符重载,因此您可以直接使用运算符,例如z = x + y。虽然您也可以使用 PyTorch 函数如torch.add()来执行相同的操作,但这较少见。最后,您可以使用下划线(_)后缀执行原地操作。函数y.add_(x)可以实现相同的结果,但它们将存储在y中。
第二类数学函数是 缩减操作。 缩减操作将一堆数字减少到一个数字或一组较小的数字。 也就是说,它们减少了张量的 维度 或 秩。 缩减操作包括查找最大值或最小值以及许多统计计算的函数,例如查找平均值或标准差。
这些操作在深度学习中经常使用。 例如,深度学习分类通常使用 argmax() 函数将 softmax 输出缩减为主导类。
表 2-6 列出了一些常用的缩减操作。
表 2-6. 缩减操作
| 函数 | 描述 |
|---|---|
torch.argmax(input, dim, keepdim=False, out=None) | 返回所有元素中最大值的索引,或者如果指定了维度,则只返回一个维度上的索引 |
torch.argmin(input, dim, keepdim=False, out=None) | 返回所有元素中最小值的索引,或者如果指定了维度,则只返回一个维度上的索引 |
torch.dist(input, dim, keepdim=False, out=None) | 计算两个张量的 p-范数 |
torch.logsumexp(input, dim, keepdim=False, out=None) | 计算给定维度中输入张量的每行的指数和的对数 |
torch.mean(input, dim, keepdim=False, out=None) | 计算所有元素的平均值,或者如果指定了维度,则只计算一个维度上的平均值 |
torch.median(input, dim, keepdim=False, out=None) | 计算所有元素的中位数或中间值,或者如果指定了维度,则只计算一个维度上的中位数 |
torch.mode(input, dim, keepdim=False, out=None) | 计算所有元素的众数或最频繁出现的值,或者如果指定了维度,则只计算一个维度上的值 |
torch.norm(input, p='fro', dim=None, keepdim=False, out=None, dtype=None) | 计算所有元素的矩阵或向量范数,或者如果指定了维度,则只计算一个维度上的范数 |
torch.prod(input, dim, keepdim=False, dtype=None) | 计算所有元素的乘积,或者如果指定了维度,则只计算输入张量的每行的乘积 |
torch.std(input, dim, keepdim=False, out=None) | 计算所有元素的标准差,或者如果指定了维度,则只计算一个维度上的标准差 |
torch.std_mean(input, unbiased=True) | 计算所有元素的标准差和平均值,或者如果指定了维度,则只计算一个维度上的标准差和平均值 |
torch.sum(input, dim, keepdim=False, out=None) | 计算所有元素的和,或者如果指定了维度,则只计算一个维度上的和 |
torch.unique(input, dim, keepdim=False, out=None) | 在整个张量中删除重复项,或者如果指定了维度,则只删除一个维度上的重复项 |
torch.unique_consecutive(input, dim, keepdim=False, out=None) | 类似于 torch.unique(),但仅删除连续的重复项 |
torch.var(input, dim, keepdim=False, out=None) | 计算所有元素的方差,或者如果指定了维度,则只计算一个维度上的方差 |
torch.var_mean(input, dim, keepdim=False, out=None) | 计算所有元素的平均值和方差,或者如果指定了维度,则只计算一个维度上的平均值和方差 |
请注意,许多这些函数接受 dim 参数,该参数指定多维张量的缩减维度。 这类似于 NumPy 中的 axis 参数。 默认情况下,当未指定 dim 时,缩减会跨所有维度进行。 指定 dim = 1 将在每行上计算操作。 例如,torch.mean(x,1) 将计算张量 x 中每行的平均值。
提示
将方法链接在一起是常见的。 例如,torch.rand(2,2).max().item() 创建一个 2 × 2 的随机浮点数张量,找到最大值,并从结果张量中返回值本身。
接下来,我们将看一下 PyTorch 的比较函数。比较函数通常比较张量中的所有值,或将一个张量的值与另一个张量的值进行比较。它们可以根据每个元素的值返回一个充满布尔值的张量,例如torch.eq()或torch.is_boolean()。还有一些函数可以找到最大或最小值,对张量值进行排序,返回张量元素的顶部子集等。
| torch.svd() | 执行奇异值分解 |
表 2-7. 比较操作
| 操作类型 | 示例函数 |
|---|---|
| 将张量与其他张量进行比较 | eq(), ge(), gt(), le(), lt(), ne() 或 ==, >, >=, <, <=, !=, 分别 |
| 测试张量状态或条件 | isclose(), isfinite(), isinf(), isnan() |
| 返回整个张量的单个布尔值 | allclose(), equal() |
| 查找整个张量或沿给定维度的值 | argsort(), kthvalue(), max(), min(), sort(), topk() |
比较函数似乎很简单;然而,有一些关键点需要记住。常见的陷阱包括以下内容:
-
torch.eq()函数或==返回一个相同大小的张量,每个元素都有一个布尔结果。torch.equal()函数测试张量是否具有相同的大小,如果张量中的所有元素都相等,则返回一个单个布尔值。 -
函数
torch.allclose()也会返回一个单个布尔值,如果所有元素都接近指定值。
接下来我们将看一下线性代数函数。线性代数函数促进矩阵运算,对于深度学习计算非常重要。
许多计算,包括梯度下降和优化算法,使用线性代数来实现它们的计算。PyTorch 支持一组强大的内置线性代数操作,其中许多基于基本线性代数子程序(BLAS)和线性代数包(LAPACK)标准化库。
表 2-8 列出了一些常用的线性代数操作。
表 2-8. 线性代数操作
| 函数 | 描述 |
|---|---|
torch.matmul() | 计算两个张量的矩阵乘积;支持广播 |
torch.chain_matmul() | 计算N个张量的矩阵乘积 |
torch.mm() | 计算两个张量的矩阵乘积(如果需要广播,请使用matmul()) |
torch.addmm() | 计算两个张量的矩阵乘积并将其添加到输入中 |
torch.bmm() | 计算一批矩阵乘积 |
torch.addbmm() | 计算一批矩阵乘积并将其添加到输入中 |
torch.baddbmm() | 计算一批矩阵乘积并将其添加到输入批次 |
torch.mv() | 计算矩阵和向量的乘积 |
torch.addmv() | 计算矩阵和向量的乘积并将其添加到输入中 |
torch.matrix_power | 返回张量的n次幂(对于方阵) |
torch.eig() | 找到实方阵的特征值和特征向量 |
torch.inverse() | 计算方阵的逆 |
torch.det() | 计算矩阵或一批矩阵的行列式 |
torch.logdet() | 计算矩阵或一批矩阵的对数行列式 |
torch.dot() | 计算两个张量的内积 |
torch.addr() | 计算两个张量的外积并将其添加到输入中 |
torch.solve() | 返回线性方程组的解 |
| 表 2-7 列出了一些常用的比较函数供参考。 | |
torch.pca_lowrank() | 执行线性主成分分析 |
torch.cholesky() | 计算 Cholesky 分解 |
torch.cholesky_inverse() | 计算对称正定矩阵的逆并返回 Cholesky 因子 |
torch.cholesky_solve() | 使用 Cholesky 因子解线性方程组 |
表 2-8 中的函数范围从矩阵乘法和批量计算函数到求解器。重要的是要指出,矩阵乘法与torch.mul()或*运算符的逐点乘法不同。
本书不涵盖完整的线性代数研究,但在进行特征降维或开发自定义深度学习算法时,您可能会发现访问一些线性代数函数很有用。请参阅PyTorch 线性代数文档以获取可用函数的完整列表以及如何使用它们的更多详细信息。
我们将考虑的最后一类数学运算是光谱和其他数学运算。根据感兴趣的领域,这些函数可能对数据转换或分析有用。例如,光谱运算如快速傅里叶变换(FFT)在计算机视觉或数字信号处理应用中可能起重要作用。
表 2-9 列出了一些用于频谱分析和其他数学运算的内置操作。
表 2-9. 光谱和其他数学运算
| 操作类型 | 示例函数 |
|---|---|
| 快速、逆、短时傅里叶变换 | fft(), ifft(), stft() |
| 实到复 FFT 和复到实逆 FFT(IFFT) | rfft(), irfft() |
| 窗口算法 | bartlett_window(), blackman_window(), hamming_window(), hann_window() |
| 直方图和箱计数 | histc(), bincount() |
| 累积操作 | cummax(), cummin(), cumprod(), cumsum(), trace()(对角线之和),einsum()(使用爱因斯坦求和的乘积之和) |
| 标准化函数 | cdist(), renorm() |
| 叉积、点积和笛卡尔积 | cross(), tensordot(), cartesian_prod() |
| 创建对角张量的函数,其元素为输入张量的元素 | diag(), diag_embed(), diag_flat(), diagonal() |
| 爱因斯坦求和 | einsum() |
| 矩阵降维和重构函数 | flatten(), flip(), rot90(), repeat_interleave(), meshgrid(), roll(), combinations() |
| 返回下三角形或上三角形及其索引的函数 | tril(), tril_indices, triu(), triu_indices() |
自动微分(Autograd)
一个函数,backward(),值得在自己的子节中调用,因为它是 PyTorch 在深度学习开发中如此强大的原因。backward()函数使用 PyTorch 的自动微分包torch.autograd,根据链式法则对张量进行微分和计算梯度。
这是自动微分的一个简单示例。我们定义一个函数,f = sum(x²),其中 x 是一个变量矩阵。如果我们想要找到矩阵中每个变量的 df / dx,我们需要为张量x设置requires_grad = True标志,如下面的代码所示:
x = torch.tensor([[1,2,3],[4,5,6]],
dtype=torch.float, requires_grad=True)
print(x)
# out:
# tensor([[1., 2., 3.],
# [4., 5., 6.]], requires_grad=True)
f = x.pow(2).sum()
print(f)
# tensor(91., grad_fn=<SumBackward0>)
f.backward()
print(x.grad) # df/dx = 2x
# tensor([[ 2., 4., 6.],
# [ 8., 10., 12.]])
f.backward()函数对f进行微分,并将df / dx存储在x.grad属性中。对微积分微分方程的快速回顾将告诉我们f相对于x的导数,df / dx = 2x。对x的值评估df / dx的结果显示为输出。
注意
只有浮点dtype的张量可以需要梯度。
训练神经网络需要我们在反向传播中计算权重梯度。随着我们的神经网络变得更深更复杂,这个功能可以自动化复杂的计算。有关 autograd 工作原理的更多信息,请参阅Autograd 教程。
本章提供了一个快速参考,用于创建张量和执行操作。现在您已经对张量有了良好的基础,我们将重点讨论如何使用张量和 PyTorch 来进行深度学习研究。在下一章中,我们将回顾深度学习开发过程,然后开始编写代码。