Skip to content

第 1 章 认识 PyTorch

学习目标

  • 知道 PyTorch 是什么、解决什么问题
  • 完成 PyTorch 的安装并验证版本
  • 会用 import torch 导入并创建第一个张量
  • 理解 torch.Tensor 的三个基本属性:维数、形状、数据类型
  • 理解「向量化」:张量运算作用于每个元素,不需要写循环

1.1 PyTorch 是什么

PyTorch 是 Facebook AI Research(现 Meta AI)开源的深度学习框架,核心是一个 Python 库。它的名字由两部分组成:Py(Python)和 Torch(火炬,一种早期科学计算框架的名字)。

PyTorch 提供三件套:

  1. 张量(tensor):一个存储同类型元素、支持整体运算的多维容器,用法与 NumPy 的数组类似,但默认数据类型是 32 位浮点数,且可以运行在 GPU 上。
  2. 自动求导(autograd):自动计算「损失对参数」的梯度——这是训练神经网络的核心机制,第 6 章展开。
  3. 神经网络库(torch.nn):搭建网络、损失函数、优化器的现成组件,第 7~11 章展开。

它解决一个具体问题:用 Python 从零写深度学习又慢又啰嗦。比如把 100 万个数字每个加 1,用列表要写循环:

python
# 列表写法:必须逐个元素处理
nums = list(range(1_000_000))
result = [x + 1 for x in nums]

用张量,一次运算作用于整个张量:

python
import torch
nums = torch.arange(1_000_000)
result = nums + 1

nums + 1 内部用编译好的代码批量执行。在本机实测,100 万个元素各加 1,张量用时约 0.002 秒,列表循环约 0.045 秒——快 20 倍以上(具体数值随机器波动)。这种「对整块数据做运算,而不是逐个元素循环」的方式,叫向量化(vectorization)。

本书所有示例基于 PyTorch 2.x(写作时最新为 2.13.0)。代码统一在交互环境(REPL)或脚本文件中运行,约定运行方式用 uv run python(见 1.2)。

1.2 安装 PyTorch

1.2.1 用 uv 安装(推荐)

本书配套仓库使用 uv 管理 Python 环境。在项目根目录:

$ uv sync

完成后验证:

$ uv run python -c "import torch; print(torch.__version__)"
2.13.0+cu130

版本号末尾的 +cu130 表示这是带 CUDA 13.0 支持的构建。是否真的能用 GPU,以 1.3 的 torch.cuda.is_available() 为准。

1.2.2 用 pip 安装

如果你已有自己的 Python 环境,直接:

$ pip install torch
$ python3 -c "import torch; print(torch.__version__)"

输出 2.x.x 即安装成功。后续所有示例,把 uv run python 换成你自己的 python3 即可。

1.3 导入 PyTorch 与创建第一个张量

导入是用 import 语句把库加载进当前程序。PyTorch 社区约定统一简写为 torch:

python
import torch

张量torch.tensor 从列表创建。torch.tensor(列表) 把列表转换成一个 torch.Tensor:

python
import torch

scores = torch.tensor([85, 92, 78, 90, 88])
print(scores)

输出:

tensor([85, 92, 78, 90, 88])

注意 tensor(...) 是张量的打印格式,与列表的 [...] 不同。

张量可以和数直接做算术,结果仍是张量:

python
print(scores + 10)     # 每个分数加 10
print(scores * 2)      # 每个分数乘 2

输出:

tensor([ 95, 102,  88, 100,  98])
tensor([170, 184, 156, 180, 176])

+* 逐元素作用于整个张量,这就是 1.1 说的向量化。对比列表:列表的 + 是拼接,* 是重复——语义完全不同,这是新手最容易混淆的地方。

1.4 张量的三个基本属性

每个 torch.Tensor 都有三个必知属性:ndim(维数)、shape(形状)、dtype(数据类型)。

维数(ndim)是张量有几层方括号:一维是向量,二维是矩阵。

形状(shape)是一个 torch.Size 对象,每个元素是该维度上的长度。

数据类型(dtype)是张量里元素的类型。torch.Tensor 要求所有元素类型一致。

python
import torch

a = torch.tensor([1, 2, 3])          # 一维
b = torch.tensor([[1, 2], [3, 4]])   # 二维

print("a:", a.ndim, a.shape, a.dtype)
print("b:", b.ndim, b.shape, b.dtype)

输出:

a: 1 torch.Size([3]) torch.int64
b: 2 torch.Size([2, 2]) torch.int64

逐项解读:

  • a.ndim1b.ndim2:分别是一维、二维。
  • a.shapetorch.Size([3]):长度为 3 的一维张量。
  • b.shapetorch.Size([2, 2]):2 行 2 列。
  • dtypetorch.int64:64 位整数。元素里只要有一个小数,整个张量就是浮点型:
python
import torch

c = torch.tensor([1.5, 2.5])
print(c.dtype)
print(c)

输出:

torch.float32
tensor([1.5000, 2.5000])

这里有一个与 NumPy 的关键区别:PyTorch 默认浮点类型是 float32(32 位),不是 float64。深度学习用 32 位浮点就够,而且省一半内存、在 GPU 上更快。代价是精度略低,1.6 会看到实例。

也可以在创建时用 dtype 参数指定类型:

python
import torch

d = torch.tensor([1, 2, 3], dtype=torch.float64)
print(d)
print(d.dtype)

输出:

tensor([1., 2., 3.], dtype=torch.float64)
torch.float64

注意两点:打印浮点张量时每个数带一个小数点(1.);当 dtype 不是默认的 float32 时,打印会额外标注 dtype=torch.float64

1.5 二维张量的打印格式

二维张量打印成矩阵形状,自动换行对齐:

python
import torch

grades = torch.tensor([[85, 90], [78, 88], [92, 95]])
print(grades)
print("形状:", grades.shape)

输出:

tensor([[85, 90],
        [78, 88],
        [92, 95]])
形状: torch.Size([3, 2])

形状 torch.Size([3, 2]) 读作「3 行 2 列」:3 个学生,每人 2 门成绩。这个「先认识数据长什么样,再学怎么操作它」的顺序,贯穿全书。

1.6 张量的简单统计

torch.Tensor 自带常用的统计方法,无需循环。统计平均值时,整数张量不能直接求 mean(PyTorch 会报错),所以这里用浮点张量:

python
import torch

scores = torch.tensor([85.0, 92.0, 78.0, 90.0, 88.0], dtype=torch.float64)
print("总和:", scores.sum().item())
print("平均:", scores.mean().item())
print("最大:", scores.max().item())
print("最小:", scores.min().item())
print("个数:", scores.numel())

输出:

总和: 433.0
平均: 86.6
最大: 92.0
最小: 78.0
个数: 5

.item() 把 0 维张量(标量)转成 Python 数字。没有 .item() 时,scores.sum() 打印成 tensor(433.)——它仍然是张量,不是普通数字,直接拿去做字符串拼接会出错。

如果不指定 dtype=torch.float64,默认的 float32 算平均值会有可见误差:

python
import torch

scores32 = torch.tensor([85.0, 92.0, 78.0, 90.0, 88.0])
print(scores32.mean().item())

输出:

86.5999984741211

精确值应该是 86.6,float32 只保留约 7 位有效十进制数字。深度学习场景通常不在乎这种误差,但做数值计算时要意识到它。

1.7 读懂第一条报错

PyTorch 报错和 Python 报错格式一样,是 traceback。常见的第一条报错是形状不匹配

在交互环境中输入:

>>> import torch
>>> torch.tensor([1, 2, 3]) + torch.tensor([1, 2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 0

解读:错误类型是 RuntimeError(运行时错误),信息是 The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 0(张量 a 的大小 3 必须与张量 b 的大小 2 在非单例维度 0 上匹配)。两个张量长度不同(3 和 2),不能逐元素相加。某些形状不同的张量其实可以相加——这叫「广播」,是第 4 章的主题,现在只需记住:两个形状不兼容的张量做运算会报 RuntimeError

读 PyTorch 报错三步:

  1. 最后一行:错误类型 + 一句话说明,形状错误的信息里一定带有 size ... must matchshapes ...
  2. 中间的行:出错的是哪条语句、哪个文件。
  3. 核对参与运算的张量形状:用 print(张量.shape) 确认。

动手实践

  1. 在项目根目录运行 uv run python 进入交互环境,验证 torch.__version__torch.cuda.is_available()
  2. 创建你自己的身高张量 torch.tensor([...]),打印它,再打印 ndimshapedtype
  3. 把张量每个元素减 5 并打印,验证结果仍是张量;再用 Python 列表做同样的 - 5,观察报错——这直观展示了张量与列表运算语义的区别。
  4. 故意让 torch.tensor([1, 2, 3])torch.tensor([1, 2]) 相加,用「读报错三步」说出:错误类型是什么、两个操作数的形状各是什么。
  5. 对整型分数张量 torch.tensor([85, 92, 78]) 调用 .mean(),观察报错,再用 dtype=torch.float64 重试——亲眼确认「整数张量不能直接求平均」。

常见错误

错误写法现象原因
torch.tensor(1, 2, 3)TypeError: tensor() takes 1 or 2 positional arguments but 3 were giventensor 只接受一个序列参数,应写成 torch.tensor([1, 2, 3])
[1, 2, 3] + [4, 5, 6]结果是 [1, 2, 3, 4, 5, 6]列表的 + 是拼接,不是逐元素相加;要用 torch.tensor
torch.tensor([1, 2, 3]) + torch.tensor([1, 2])RuntimeError: The size of tensor a (3) must match ...形状不兼容,不能逐元素运算
import torch 后直接用 tensor([1, 2])NameError: name 'tensor' is not defined导入简写后要用 torch.tensor(...);或写成 from torch import tensor
torch.tensor([85, 92]).mean()RuntimeError: mean(): could not infer output dtype. Input dtype must be either a floating point or complex dtype. Got: Long整数张量不能求平均,要先转浮点(如 dtype=torch.float64)
print("平均:", scores.mean())打印 平均: tensor(86.6)mean() 返回 0 维张量,要用 .item() 转成 Python 数字

章末练习

基础

  1. 写一段代码:创建张量 [10, 20, 30, 40, 50],分别打印它的 ndimshapedtype
  2. 创建二维张量 [[1, 2, 3], [4, 5, 6]],打印形状并说明它代表几行几列。
  3. 验证安装:用一行命令输出你本机 PyTorch 的版本号,并输出 torch.cuda.is_available()

提高

  1. 用列表 [1, 2, 3] 分别做 [1, 2, 3] * 2torch.tensor([1, 2, 3]) * 2,打印两个结果,并解释为什么不同。
  2. 创建张量 torch.tensor([1.0, 2, 3])(混入一个小数),打印它的 dtype,再解释为什么整个张量变成浮点型;接着打印 torch.tensor([1, 2, 3], dtype=torch.float64) 并说明打印格式与默认浮点的差异。

挑战

  1. 不运行代码,预测 torch.tensor([1, 2, 3], dtype=torch.float64).mean().item() 的输出,再运行验证;然后用列表完成同样任务(需要循环或 sum(lst)/len(lst)),比较两种写法。
  2. torch.arange(1_000_000) 创建 100 万个整数,执行 +1 运算;用列表推导式完成同样的 +1,用 time 模块分别计时,感受向量化的速度差异(提示:列表版本大约慢 20 倍以上)。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. PyTorch 的核心数据结构是什么?
    • A. list
    • B. ndarray
    • C. Tensor
    • D. Series
  2. PyTorch 社区约定的导入写法是?
    • A. import pytorch as pt
    • B. import torch
    • C. from torch import *
    • D. import torch as pytorch
  3. torch.tensor([1, 2, 3]).shape 的结果是?
    • A. 3
    • B. torch.Size([3])
    • C. torch.Size([1, 3])
    • D. [3]
  4. torch.tensor([1.5, 2]).dtype 的结果是?
    • A. torch.int64
    • B. torch.float64
    • C. torch.float32
    • D. torch.float16
  5. torch.tensor([[1, 2], [3, 4]])ndim 是?
    • A. 1
    • B. 2
    • C. 4
    • D. torch.Size([2, 2])
  6. torch.tensor([1, 2, 3]) + 10 的结果是?
    • A. [11, 12, 13](列表)
    • B. [1, 2, 3, 10]
    • C. tensor([11, 12, 13])
    • D. 报错
  7. PyTorch 默认浮点数据类型是?
    • A. float64
    • B. float32
    • C. int64
    • D. float16
  8. torch.tensor([1, 2, 3]) + torch.tensor([1, 2]) 会?
    • A. 返回 tensor([2, 4, 3])
    • B. 返回 tensor([2, 4])
    • C. 报 RuntimeError
    • D. 返回列表
  9. 下列哪个是查看张量元素总数的属性或方法?
    • A. len()(内置函数)
    • B. shape
    • C. ndim
    • D. numel()
  10. 关于向量化,正确的说法是?
    • A. 必须用 for 循环处理张量
    • B. 一次运算作用于整个张量,内部用编译代码批量执行
    • C. 只有列表才支持向量化
    • D. 向量化会让程序更慢