pytorch笔记-重制版

张量

1.1 创建张量(Tensor)

张量(Tensor)是 PyTorch 的核心数据结构,类似于 NumPy 的 ndarray,但支持 GPU 加速和自动求导。

1. 从数据直接创建

1
2
3
4
5
import torch

# 从列表或数组创建
x = torch.tensor([1, 2, 3]) # 默认 dtype 根据输入推断
x = torch.tensor([[1., 2.], [3., 4.]]) # float 类型

2. 创建特定值的张量

1
2
3
4
5
6
torch.empty(2, 3)        # 未初始化的张量(含随机垃圾值)
torch.zeros(2, 3) # 全 0 张量
torch.ones(2, 3) # 全 1 张量
torch.full((2, 3), 5) # 全为指定值(如 5)
torch.eye(n, m=None, out=None) # 返回一个2维张量,对角线位置全1,其它位置全0
torch.diag(torch.tensor([1,2,3])) #对角矩阵

3. 创建数值序列

1
2
torch.arange(0, 10, 2)   # [0, 2, 4, 6, 8],步长为 2
torch.linspace(0, 1, 5) # 在 [0,1] 之间均匀取 5 个点:[0.00, 0.25, 0.50, 0.75, 1.00]

4. 随机张量

1
2
3
4
torch.rand(2, 3)         # [0,1) 均匀分布
torch.randn(2, 3) # 标准正态分布(均值 0,标准差 1)
torch.randint(low=0, high=10, size=(2, 3)) # 整数随机
torch.normal(mean = torch.zeros(3,3), std = torch.ones(3,3)) #正态分布随机

5. 指定数据类型和设备

1
x = torch.zeros(2, 3, dtype=torch.float32, device='cpu')  # 可指定 device='cuda'(若可用)

6. 从 NumPy 数组创建(共享内存)

1
2
3
import numpy as np
np_array = np.array([1, 2, 3])
x = torch.from_numpy(np_array) # 共享内存,修改一个会影响另一个

可以用numpy方法从Tensor得到numpy数组,也可以用torch.from_numpy从numpy数组得到 Tensor。

这两种方法关联的Tensor和numpy数组是共享数据内存的。

如果改变其中一个,另外一个的值也会发生改变。

如果有需要,可以用张量的clone方法拷贝张量,中断这种关联。

此外,还可以使用item方法从标量张量得到对应的Python数值。

使用tolist方法从张量得到对应的Python数值列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#例1-1-4 张量和numpy数组
import numpy as np
import torch
#torch.from_numpy函数从numpy数组得到Tensor
arr = np.zeros(3)
tensor = torch.from_numpy(arr)
print("before add 1:")
print(arr)
print(tensor)
print("\nafter add 1:")
np.add(arr,1, out = arr) #给 arr增加1,tensor也随之改变
print(arr)
print(tensor)

out:
before add 1:
[0. 0. 0.]
tensor([0., 0., 0.], dtype=torch.float64)

after add 1:
[1. 1. 1.]
tensor([1., 1., 1.], dtype=torch.float64)





# numpy方法从Tensor得到numpy数组
tensor = torch.zeros(3)
arr = tensor.numpy()
print("before add 1:")
print(tensor)
print(arr)
print("\nafter add 1:")
#使用带下划线的方法表示计算结果会返回给调用 张量
tensor.add_(1) #给 tensor增加1,arr也随之改变
#或: torch.add(tensor,1,out = tensor)
print(tensor)
print(arr)

out:
before add 1:
tensor([0., 0., 0.])
[0. 0. 0.]

after add 1:
tensor([1., 1., 1.])
[1. 1. 1.]





# 可以用clone() 方法拷贝张量,中断这种关联
tensor = torch.zeros(3)
#使用clone方法拷贝张量, 拷贝后的张量和原始张量内存独立
arr = tensor.clone().numpy() # 也可以使用tensor.data.numpy()
print("before add 1:")
print(tensor)
print(arr)
print("\nafter add 1:")
#使用 带下划线的方法表示计算结果会返回给调用 张量
tensor.add_(1) #给 tensor增加1,arr不再随之改变
print(tensor)
print(arr)

out:
before add 1:
tensor([0., 0., 0.])
[0. 0. 0.]

after add 1:
tensor([1., 1., 1.])
[0. 0. 0.]





# item方法和tolist方法可以将张量转换成Python数值和数值列表
scalar = torch.tensor(1.0)
s = scalar.item()
print(s)
print(type(s))
tensor = torch.rand(2,2)
t = tensor.tolist()
print(t)
print(type(t))

out:
1.0
<class 'float'>
[[0.5407917499542236, 0.08548498153686523], [0.8822196125984192, 0.5270139575004578]]
<class 'list'>

7. 张量属性

1
2
3
4
x.shape      # 形状
x.dtype # 数据类型
x.device # 所在设备(CPU/GPU)
x.size() # 等价于 shape

8. 创建与另一个张量相同属性的新张量

1
2
y = torch.zeros_like(x)    # 形状、dtype、device 与 x 相同,值为 0
y = torch.ones_like(x)

💡 小贴士

  • 使用 torch.tensor()拷贝数据,而 torch.from_numpy()共享内存
  • 在训练模型时,通常使用 requires_grad=True 来启用自动求导(后续章节)。

当然可以,祺!以下是与前一节格式一致的 1.2 张量的数据类型、维度与尺寸 学习笔记:

1.2 张量的数据类型、维度与尺寸

张量的数据类型(dtype)维度(dimension)尺寸(shape) 是理解和操作张量的基础,直接影响计算效率、内存占用和模型兼容性。

1. 数据类型(dtype)

PyTorch 支持多种数据类型,常见如下:

类型名(Python) 对应 dtype 说明
32 位浮点数 torch.float32torch.float 默认浮点类型,常用
64 位浮点数 torch.float64torch.double 高精度计算
16 位浮点数 torch.float16torch.half 节省内存,用于训练加速
32 位整数 torch.int32 较少用
64 位整数 torch.int64torch.long 常用于分类标签(如 CrossEntropyLoss)
8 位无符号整数 torch.uint8 图像像素常用(0–255)
布尔类型 torch.bool 用于掩码(mask)操作

⚠️ 注意:

  • 某些操作(如 torch.nn.CrossEntropyLoss)要求标签为 torch.long
  • GPU 上不支持所有 dtype(如 torch.double 在部分操作中受限)。

2. 查看与转换数据类型

1
2
3
4
5
6
7
x = torch.tensor([1, 2, 3])
print(x.dtype) # torch.int64

# 转换类型
y = x.float() # 转为 float32
z = x.to(torch.float64) # 显式指定 dtype
w = x.type(torch.bool) # 转为布尔型(非零为 True)

3. 维度(Dimension)与形状(Shape)

  • 维度(dim):张量的“轴”数。例如:
    • 标量:0 维(torch.tensor(5)
    • 向量:1 维([1, 2, 3] → shape (3,)
    • 矩阵:2 维([[1,2],[3,4]] → shape (2, 2)
    • 批量图像:4 维([batch, channel, height, width]
  • 形状(shape):每个维度的大小,用元组表示。
1
2
3
4
5
6
7
x = torch.randn(2, 3, 4)
print(x.dim()) # 3
print(x.shape) # torch.Size([2, 3, 4])
print(x.size()) # 等价于 shape
print(x.size(1)) # 第 1 维的大小 → 3

x.numel() # 总元素个数 → 2×3×4 = 24

可以使用view方法改变张量的尺寸。

如果view方法改变尺寸失败,可以使用reshape方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 # 使用view可以改变张量尺寸
vector = torch.arange(0,12)
print(vector)
print(vector.shape)
matrix34 = vector.view(3,4)
print(matrix34)
print(matrix34.shape)
matrix43 = vector.view(4,-1) #-1表示该位置长度由程序自动推断
print(matrix43)
print(matrix43.shape)

out:
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
torch.Size([12])
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
torch.Size([3, 4])
tensor([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
torch.Size([4, 3])


# 有些操作会让张量存储结构扭曲,直接使用view会失败,可以用reshape方法
matrix26 = torch.arange(0,12).view(2,6)
print(matrix26)
print(matrix26.shape)
# 转置操作让张量存储结构扭曲
matrix62 = matrix26.t()
print(matrix62.is_contiguous())
# 直接使用view方法会失败,可以使用reshape方法
#matrix34 = matrix62.view(3,4) #error!
matrix34 = matrix62.reshape(3,4) #等价于matrix34 =
matrix62.contiguous().view(3,4)
print(matrix34)

out:
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
torch.Size([2, 6])
False
tensor([[ 0, 6, 1, 7],
[ 2, 8, 3, 9],
[ 4, 10, 5, 11]])

4. 张量的内存布局与连续性(进阶)

  • is_contiguous():判断张量在内存中是否连续存储。
  • 某些操作(如 view())要求张量是连续的,否则需先调用 .contiguous()
1
2
3
4
x = torch.randn(2, 3)
y = x.t() # 转置,可能不连续
print(y.is_contiguous()) # False
z = y.contiguous() # 转为连续

💡 小贴士

  • 使用 x.shape 而不是 x.size() 可读性更好(两者等价)。
  • 在模型输入前,务必检查张量的 dtypeshape 是否符合预期。

5. 实用技巧

  1. 统一类型以避免错误
    不同 dtype 的张量不能直接运算:
1
2
3
4
a = torch.tensor([1, 2], dtype=torch.float32)
b = torch.tensor([3, 4], dtype=torch.int64)
# c = a + b # ❌ 报错!
c = a + b.float() # ✅ 先转换
  1. 检查张量是否在 GPU 上
1
2
if x.is_cuda:
print("在 GPU 上")
  1. 创建与某张量相同属性的新张量
1
y = torch.zeros_like(x)  # 自动继承 x 的 dtype、device、shape

1.3 张量的基本操作

1. 算术运算

PyTorch 支持逐元素(element-wise)的数学运算,支持广播(broadcasting)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
x = torch.tensor([1., 2., 3.])
y = torch.tensor([4., 5., 6.])

# 基本运算
z = x + y # torch.add(x, y)
z = x - y # torch.sub(x, y)
z = x * y # torch.mul(x, y)
z = x / y # torch.div(x, y)

# 幂与开方
z = x ** 2 # 平方
z = torch.sqrt(x)
z = torch.exp(x)
z = torch.log(x)

# 原地操作(带下划线 _)
x.add_(1) # x = x + 1,直接修改原张量

⚠️ 注意:原地操作(如 add_)会覆盖原数据,可能影响自动求导,训练中慎用。

2. 索引与切片

与 Python 列表和 NumPy 类似,支持负索引、切片、高级索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
x = torch.randn(3, 4)

# 基础索引
x[0] # 第 0 行
x[:, 1] # 第 1 列
x[1, 2] # 第 1 行第 2 列的元素(标量)

# 切片
x[0:2, :] # 前两行
x[:, -1] # 最后一列

# 布尔索引与高级索引
mask = x > 0
x[mask] # 返回所有 >0 的元素(1D 张量)

# 使用索引修改值
x[0, 0] = 999

#省略号可以表示多个冒号
print(x[...,1])

💡 提示:索引操作返回的张量通常与原张量共享内存,修改可能影响原张量。

3. 形状变换(Reshape)

改变张量的形状而不改变数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
x = torch.arange(12)           # shape: (12,)

# reshape / view(要求张量连续)
x_reshaped = x.view(3, 4) # shape: (3, 4)
x_reshaped = x.reshape(2, 6) # reshape 更安全(自动处理非连续)

# 展平
x_flat = x.flatten() # 等价于 x.view(-1)

# 增/删维度
x = torch.tensor([1, 2, 3])
x_unsq = x.unsqueeze(0) # shape: (1, 3) → 增加第 0 维
x_sq = x_unsq.squeeze(0) # shape: (3,) → 删除大小为 1 的维度

# 转置
x = torch.randn(2, 3)
x_t = x.t() # 仅适用于 2D
x_t = x.transpose(0, 1) # 适用于任意维度
x_t = x.permute(1, 0) # 更通用的维度重排(类似 NumPy transpose)

4. 拼接与拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = torch.tensor([[1, 2]])
b = torch.tensor([[3, 4]])

# 拼接(concatenate)
torch.cat((a, b), dim=0) # 纵向拼接 → shape (2, 2)
torch.cat((a, b), dim=1) # 横向拼接 → shape (1, 4)

# 堆叠(创建新维度)
torch.stack((a, b), dim=0) # shape (2, 1, 2)

# 拆分
x = torch.arange(12).view(3, 4)
torch.chunk(x, chunks=2, dim=1) # 按列拆成 2 块
torch.split(x, split_size_or_sections=2, dim=0) # 按行每 2 行拆一次

5. 其他常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
x = torch.randn(3, 4)

# 最值与统计
torch.max(x) # 全局最大值
torch.max(x, dim=0) # 沿 dim=0 求最大值(返回值和索引)
torch.sum(x, dim=1)
torch.mean(x)

# 排序
values, indices = torch.sort(x, dim=1)

# 克隆(断开计算图)
y = x.clone() # 深拷贝,保留梯度信息(若 x 有 requires_grad)

💡 小贴士

  • view()reshape() 功能相似,但 reshape() 更安全(可处理非连续张量)。
  • 拼接用 cat,堆叠用 stack —— 后者会新增一个维度。
  • 索引和切片操作在调试和数据采样中极为常用,务必熟练掌握。

torch.cat

torch.cat(inputs, dimension=0) → Tensor

在给定维度上对输入的张量序列seq 进行连接操作。

参数:

inputs (sequence of Tensors) – 可以是任意相同Tensor 类型的python 序列

dimension (int, optional) – 沿着此维连接张量序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> x = torch.randn(2, 3)
>>> x

0.5983 -0.0341 2.4918
1.5981 -0.5265 -0.8735
[torch.FloatTensor of size 2x3]

>>> torch.cat((x, x, x), 0)

0.5983 -0.0341 2.4918
1.5981 -0.5265 -0.8735
0.5983 -0.0341 2.4918
1.5981 -0.5265 -0.8735
0.5983 -0.0341 2.4918
1.5981 -0.5265 -0.8735
[torch.FloatTensor of size 6x3]

>>> torch.cat((x, x, x), 1)

0.5983 -0.0341 2.4918 0.5983 -0.0341 2.4918 0.5983 -0.0341 2.4918
1.5981 -0.5265 -0.8735 1.5981 -0.5265 -0.8735 1.5981 -0.5265 -0.8735
[torch.FloatTensor of size 2x9]

torch.squeeze

torch.squeeze(input, dim=None, out=None)

将输入张量形状中的1 去除并返回。 如果输入是形如(A×1×B×1×C×1×D)

,那么输出形状就为: (A×B×C×D)

当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: (A×1×B)

, squeeze(input, 0) 将会保持张量不变,只有用 squeeze(input, 1),形状会变成 (A×B)

注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。

参数:

input (Tensor) – 输入张量

dim (int, optional) – 如果给定,则input只会在给定维度挤压

out (Tensor, optional) – 输出张量

1
2
3
4
5
6
7
8
9
10
11
12
>>> x = torch.zeros(2,1,2,1,2)
>>> x.size()
(2L, 1L, 2L, 1L, 2L)
>>> y = torch.squeeze(x)
>>> y.size()
(2L, 2L, 2L)
>>> y = torch.squeeze(x, 0)
>>> y.size()
(2L, 1L, 2L, 1L, 2L)
>>> y = torch.squeeze(x, 1)
>>> y.size()
(2L, 2L, 1L, 2L)

torch.unsqueeze

torch.unsqueeze(input, dim, out=None)

返回一个新的张量,对输入的制定位置插入维度 1

注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。

参数:

  • tensor (Tensor) – 输入张量
  • dim (int) – 插入维度的索引
  • out (Tensor, optional) – 结果张量
1
2
3
4
5
6
7
8
9
10
>>> x = torch.Tensor([1, 2, 3, 4])
>>> torch.unsqueeze(x, 0)
1 2 3 4
[torch.FloatTensor of size 1x4]
>>> torch.unsqueeze(x, 1)
1
2
3
4
[torch.FloatTensor of size 4x1]

当然可以,祺!以下是格式统一、结构清晰的 第二章 自动求导(Autograd)与自动微分机制 学习笔记:


自动微分

2.1 自动求导(Autograd)与自动微分机制

1. 基本原理

  • PyTorch 使用动态计算图(Dynamic Computation Graph):计算过程即构建图,每次前向传播都会重新构建。
  • 只有当张量的 requires_grad=True 时,PyTorch 才会追踪其计算历史,用于后续求导。
1
2
3
4
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 # y = x^2
y.backward() # 自动计算 dy/dx
print(x.grad) # 输出: tensor(4.0) → 因为 d(x²)/dx = 2x = 4

✅ 关键点:

  • requires_grad=True 是开启自动求导的前提。
  • backward() 触发反向传播,梯度累积到 .grad 属性中。

2. 多变量与向量求导

对于向量或矩阵输出,backward() 需要传入与输出同形的“梯度权重”(gradient tensor)。

1
2
3
4
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x ** 2 # y = [1, 4, 9]
y.backward(torch.tensor([1.0, 1.0, 1.0])) # 相当于对每个分量求导后求和
print(x.grad) # tensor([2., 4., 6.])

💡 说明:
y 是标量(如 loss),可直接调用 y.backward()
y 是向量,则需提供 gradient 参数,通常用于加权求和。

3. 计算图与梯度累积

  • 每次调用 backward()累加梯度.grad 中,而非覆盖。
  • 训练时需在每次迭代前清零梯度:
1
2
3
optimizer.zero_grad()   # 等价于 model.zero_grad() 或 x.grad.zero_()
loss.backward()
optimizer.step()

手动清零示例:

1
2
if x.grad is not None:
x.grad.zero_()

4. 停止梯度追踪(常用场景)

有时希望某些操作不参与求导(如冻结部分网络、推理阶段):

1
2
3
4
5
6
7
8
# 方法1:设置 requires_grad=False
x = torch.tensor(2.0, requires_grad=True)
y = x.detach() # 返回一个新张量,不参与梯度计算

# 方法2:使用 torch.no_grad() 上下文(推荐用于推理)
with torch.no_grad():
z = x ** 2 # z 不会被追踪,无梯度
print(z.requires_grad) # False

⚠️ 注意:

  • detach() 返回的张量与原张量共享数据,但断开计算图。
  • no_grad() 是上下文管理器,适用于批量操作。

5. 梯度检查与调试

  • 使用 retain_graph=True 可多次调用 backward()(默认会释放计算图):
1
2
3
y = x ** 2
y.backward(retain_graph=True) # 第一次反向
y.backward() # 第二次反向(需 retain_graph=True)
  • 检查是否需要梯度:
1
2
print(x.requires_grad)   # True/False
print(y.grad_fn) # 查看该张量是由哪个函数生成的(如 PowBackward0)

6. 自定义梯度(进阶)

可通过 torch.autograd.Function 自定义前向和反向传播逻辑(如实现新算子):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyReLU(torch.autograd.Function):
@staticmethod
def forward(ctx, input):
ctx.save_for_backward(input)
return input.clamp(min=0)

@staticmethod
def backward(ctx, grad_output):
input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0
return grad_input

# 使用
x = torch.randn(2, 2, requires_grad=True)
y = MyReLU.apply(x)

数据加载

Pytorch通常使用Dataset和DataLoader这两个工具类来构建数据管道。

Dataset:定义了数据集的内容,它相当于一个类似列表的数据结构,具有确定的长度,能够用索 引获取数据集中的元素。

DataLoader:定义了按batch加载数据集的方法,它是一个实现了 iter 方法的可迭代对 象,每次迭代输出一个batch的数据。能够控制batch的大小,batch中元素的采样方法,以及将batch结果整理成模型所需 输入形式的方法,并且能够使用多进程读取数据。

在绝大部分情况下,用户只需实现Dataset的 len 方法和 getitem 方法,就可以轻松构 建自己的数据集,并用默认数据管道进行加载

Dataset 与 DataLoader 基础

Dataset:定义数据集

Dataset 是一个抽象类,需继承并实现两个方法:

  • __len__(self):返回数据集大小
  • __getitem__(self, idx):返回第 idx 个样本(通常为 (data, label) 元组)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from torch.utils.data import Dataset

class CustomDataset(Dataset):
def __init__(self, data, labels):
self.data = data
self.labels = labels

def __len__(self):
return len(self.data)

def __getitem__(self, idx):
sample = self.data[idx]
label = self.labels[idx]
return torch.tensor(sample, dtype=torch.float32), torch.tensor(label, dtype=torch.long)

✅ 提示:

  • 可在 __init__ 中加载文件路径、进行初步预处理;
  • 实际数据读取(如图像解码)通常放在 __getitem__ 中,实现按需加载。

PyTorch 还提供内置数据集(如 torchvision.datasets.MNIST),用法类似。

DataLoader:批量加载与并行

DataLoaderDataset 封装为可迭代对象,支持:

  • 批量(batching)
  • 打乱(shuffling)
  • 多进程加载(num_workers)
  • 自定义采样(sampler)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from torch.utils.data import DataLoader

dataset = CustomDataset(data, labels)
dataloader = DataLoader(
dataset,
batch_size=32,
shuffle=True, # 训练时通常为 True
num_workers=4, # 多进程加载(Windows 注意使用 if __name__ == '__main__':)
pin_memory=True # 锁页内存,加速 GPU 传输(当使用 GPU 时)
)

# 使用
for batch_data, batch_labels in dataloader:
# batch_data.shape: [32, ...]
# batch_labels.shape: [32]
pass

关键参数说明

参数 作用
batch_size 每批样本数
shuffle 是否打乱顺序(训练集设为 True,验证集设为 False
num_workers 加载数据的子进程数(0 表示主进程加载)
pin_memory 若为 True,数据加载到 CUDA 锁页内存,加快 GPU 传输
drop_last 若最后一批不足 batch_size,是否丢弃(默认 False

⚠️ 注意事项:

  • 在 Windows 或 Jupyter 中使用 num_workers > 0 时,需将数据加载代码包裹在 if __name__ == '__main__': 中,避免多进程 fork 错误。
  • pin_memory=True 仅在使用 GPU 时有意义,且会增加 CPU 内存占用。

示例:完整训练数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 定义数据集
train_dataset = CustomDataset(train_x, train_y)
val_dataset = CustomDataset(val_x, val_y)

# 2. 创建 DataLoader
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=2)

# 3. 训练循环中使用
for epoch in range(epochs):
model.train()
for x_batch, y_batch in train_loader:
x_batch, y_batch = x_batch.to(device), y_batch.to(device)
optimizer.zero_grad()
loss = criterion(model(x_batch), y_batch)
loss.backward()
optimizer.step()

💡 小贴士

  • 自定义 Dataset 是处理私有数据的标准方式;
  • 对于大型数据集(如图像),建议在 __getitem__ 中动态读取文件,避免内存溢出;
  • 使用 torchvision.transforms 可方便地集成图像预处理(见后续小节)。

torchvision.transforms 数据增强与预处理

在计算机视觉任务中,原始数据(如图像)通常需要进行标准化、裁剪、翻转、归一化等操作。torchvision.transforms 提供了一系列可组合的变换工具,无缝集成到 Dataset 中,支持高效、灵活的数据预处理和增强。

基本用法:组合变换(Compose

多个变换可通过 transforms.Compose 串联,按顺序执行:

1
2
3
4
5
6
7
8
9
from torchvision import transforms

transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.RandomHorizontalFlip(p=0.5), # 50% 概率水平翻转(数据增强)
transforms.ToTensor(), # 转为 Tensor,并将像素值缩放到 [0, 1]
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # ImageNet 标准化
])

✅ 说明:

  • ToTensor() 必须在 Normalize 之前,因为后者要求输入为 [0,1] 的 Tensor;
  • Normalizemeanstd 通常使用 ImageNet 统计量(迁移学习时保持一致)。

常用变换分类

图像格式转换
变换 作用
ToTensor() PIL Image 或 NumPy → torch.Tensor(HWC → CHW,值域 [0,1])
ToPILImage() Tensor → PIL Image(用于可视化)
几何变换(数据增强)
变换 说明
RandomHorizontalFlip(p) 随机水平翻转
RandomVerticalFlip(p) 随机垂直翻转
RandomRotation(degrees) 随机旋转(如 degrees=15
RandomCrop(size) 随机裁剪(常用于训练)
CenterCrop(size) 中心裁剪(常用于验证)
Resize(size) 调整尺寸(如 224(224, 224)
颜色与亮度调整
变换 说明
ColorJitter(brightness, contrast, saturation, hue) 随机改变颜色属性
RandomGrayscale(p) 以概率 p 转为灰度图
张量级操作
变换 说明
Normalize(mean, std) 逐通道标准化:output = (input - mean) / std
RandomErasing(p, scale, ratio) 随机遮挡图像区域(正则化手段)

在自定义 Dataset 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ImageDataset(Dataset):
def __init__(self, img_paths, labels, transform=None):
self.img_paths = img_paths
self.labels = labels
self.transform = transform

def __len__(self):
return len(self.img_paths)

def __getitem__(self, idx):
img = Image.open(self.img_paths[idx]).convert('RGB') # 确保三通道
label = self.labels[idx]

if self.transform:
img = self.transform(img)

return img, label

# 使用
train_transform = transforms.Compose([...]) # 含增强
val_transform = transforms.Compose([...]) # 无随机增强

train_dataset = ImageDataset(train_paths, train_labels, transform=train_transform)
val_dataset = ImageDataset(val_paths, val_labels, transform=val_transform)

⚠️ 注意:

  • 训练集使用随机增强(如 RandomHorizontalFlip),
  • 验证/测试集应使用确定性变换(如 Resize + CenterCrop),保证结果可复现。

高级技巧

  • 条件变换:通过自定义函数实现复杂逻辑(如仅对特定类别增强)。
  • Lambda 表达式
1
transforms.Lambda(lambda x: x.unsqueeze(0))  # 添加维度
  • MixUp / CutMix:需在 DataLoader 之后、送入模型前实现(不属于 transforms 内置)。

可视化验证变换效果(调试用)

1
2
3
4
5
import matplotlib.pyplot as plt

img, _ = train_dataset[0]
plt.imshow(img.permute(1, 2, 0)) # CHW → HWC
plt.show()

💡 小贴士

  • 数据增强只在训练阶段使用,避免验证集“失真”;
  • 过度增强可能损害模型性能(如旋转对数字识别不利);
  • 对非图像数据(如表格、文本),需自行实现预处理逻辑,不依赖 torchvision.transforms

神经网络

神经网络模块(torch.nn

torch.nn 是 PyTorch 构建神经网络的核心模块,提供了层(Layer)损失函数(Loss)容器(Container) 等组件,配合 torch.optim 可快速搭建和训练模型。

基本结构:nn.Module

所有神经网络模型都继承自 nn.Module,必须实现 __init__forward 方法。如果Pytorch的内置模型层不能够满足需求,我们也可以通过继承nn.Module基类构建自定义的模型层。 实际上,pytorch不区分模型和模型层,都是通过继承nn.Module进行构建。 因此,我们只要继承nn.Module基类并实现forward方法即可自定义模型层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch
import torch.nn as nn

class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.linear1 = nn.Linear(10, 5) # 全连接层:输入10维,输出5维
self.relu = nn.ReLU()
self.linear2 = nn.Linear(5, 1)

def forward(self, x):
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
return x

model = SimpleNet()
print(model)

✅ 关键点:

  • __init__ 中定义网络层(作为属性)。
  • forward 中定义数据流向(PyTorch 自动构建计算图)。

常用网络层(Layers)

说明
nn.Linear(in_features, out_features) 全连接层(仿射变换)。 参数数量 = in × out(权重) + out(偏置)
nn.Flatten(start_dim=1) 将多维张量从 start_dim 开始压平为一维,常用于 CNN 后接全连接层前
nn.BatchNorm1d(num_features) 一维批归一化。对 batch 维度做标准化,通常置于激活函数之前。 可通过 affine=False 禁用可学习参数
nn.BatchNorm2d/3d 二维/三维批归一化,分别用于图像和视频数据
nn.Dropout(p=0.5) 随机将输入张量的部分元素置零(概率 p),用于正则化 Dropout2d/3d 按通道/体素整体丢弃,适用于卷积特征图
nn.Threshold(threshold, value) 限幅函数:若 x <= threshold,输出 value;否则输出 x
nn.ConstantPad2d(padding, value) 二维常数填充(如 padding=(1,1,1,1) 表示上下左右各填1)
nn.ReplicationPad1d/2d 边缘复制填充(重复边界值)
nn.ZeroPad2d(padding) 二维零填充
nn.GroupNorm(num_groups, num_channels) 组归一化:将通道分为 num_groups 组分别归一化,不受 batch size 限制,在小 batch 场景优于 BatchNorm
nn.LayerNorm(normalized_shape) 层归一化:对单个样本的所有特征做归一化,常用于 Transformer
nn.InstanceNorm2d(num_features) 实例归一化:对每个样本的每个通道单独归一化,常用于风格迁移

卷积与池化层

说明
nn.Conv1d(in_ch, out_ch, kernel_size) 一维卷积,常用于文本或时序数据 参数数量 = in_ch × kernel_size × out_ch + out_ch
nn.Conv2d(in_ch, out_ch, kernel_size, ...) 二维卷积,图像任务核心 参数数量 = in_ch × k_h × k_w × out_ch + out_chdilation > 1 → 空洞卷积(扩大感受野) • groups > 1 → 分组卷积 • groups = in_ch → 深度卷积(Depthwise Conv) • 深度卷积 + 1×1 卷积 = 深度可分离卷积(Separable Conv)
nn.Conv3d 三维卷积,用于视频或体数据
nn.MaxPool2d(kernel_size, stride, ...) 二维最大池化,无参数,常用下采样手段
nn.AvgPool2d 二维平均池化
nn.AdaptiveMaxPool2d(output_size) 自适应池化:自动计算 stride/padding,使输出尺寸恒为 output_size(如 (1,1) 用于全局池化)
nn.AdaptiveAvgPool2d 自适应平均池化
nn.FractionalMaxPool2d 分数最大池化:支持非整数缩放比,具随机性,有一定正则效果
nn.ConvTranspose2d 转置卷积(“反卷积”),常用于上采样(如语义分割) ⚠️ 并非数学逆运算,但可恢复空间尺寸
nn.Upsample(size/scale_factor, mode='nearest') 插值上采样,mode 可选 'nearest''bilinear'
nn.Unfold / nn.Fold 滑动窗口提取与重构。卷积操作可等价表示为:Unfold → Linear → Fold

循环网络层

说明
nn.Embedding(num_embeddings, embedding_dim) 词嵌入层:将离散索引(如单词 ID)映射为稠密向量,参数可学习
nn.LSTM(input_size, hidden_size, num_layers, ...) 长短时记忆网络,缓解梯度消失,支持长期依赖 • bidirectional=True → 双向 LSTM • 默认输入形状:(seq_len, batch, feature) • 设 batch_first=True 可改为 (batch, seq_len, feature)
nn.GRU 门控循环单元,结构比 LSTM 简单,参数更少,训练更快
nn.RNN 基础循环网络,易梯度消失,较少使用
nn.LSTMCell / GRUCell / RNNCell 单步循环单元,适用于自定义循环逻辑(一般不直接使用)

Transformer 相关层

说明
nn.MultiheadAttention(embed_dim, num_heads) 多头注意力机制,Transformer 核心组件
nn.TransformerEncoderLayer 编码器层:包含多头注意力 + 前馈网络 + 残差连接 + LayerNorm
nn.TransformerDecoderLayer 解码器层:含自注意力、编码器-解码器注意力、前馈网络
nn.TransformerEncoder 由多个 TransformerEncoderLayer 堆叠而成
nn.TransformerDecoder 由多个 TransformerDecoderLayer 堆叠而成
nn.Transformer 完整 Transformer 模型(含编码器+解码器),适用于序列到序列任务

💡 小贴士

  • BatchNorm 在小 batch(<8)时不稳定,可改用 GroupNormLayerNorm
  • Depthwise Separable Conv(深度可分离卷积)大幅减少参数量,是 MobileNet 等轻量模型的核心;
  • Transformer 默认不包含位置编码,需手动添加(如 nn.Embedding 或正弦编码);
  • 所有层的参数(如权重、偏置)会自动注册到模型的 .parameters() 中,无需手动管理。

激活函数(非线性)

激活函数通常作为独立层使用:

1
2
3
4
5
nn.ReLU()
nn.Sigmoid()
nn.Tanh()
nn.LeakyReLU(negative_slope=0.01)
nn.Softmax(dim=1) # 常用于多分类输出

⚠️ 注意:

  • Softmax 通常CrossEntropyLoss 一起使用(因后者内部已包含 LogSoftmax)。

容器(Containers)

用于组织多层结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Sequential:按顺序执行
model = nn.Sequential(
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 1)
)

# ModuleList:可迭代的模块列表(支持 for 循环)
self.layers = nn.ModuleList([nn.Linear(i, i+1) for i in range(3)])

# ModuleDict:字典形式管理模块
self.blocks = nn.ModuleDict({
'conv': nn.Conv2d(3, 64, 3),
'relu': nn.ReLU()
})

参数管理

  • 所有 nn.Parameter(如权重、偏置)会自动注册为模型参数。
  • 可通过 .parameters() 获取所有可训练参数:
1
2
3
4
5
6
for name, param in model.named_parameters():
print(name, param.shape)

# 冻结部分参数(如微调时)
for param in model.linear1.parameters():
param.requires_grad = False

设备与数据类型一致性

模型和输入张量必须在同一设备(CPU/GPU)和兼容 dtype 上:

1
2
3
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
x = x.to(device)

模型保存与加载

1
2
3
4
5
6
7
8
9
10
# 保存整个模型(不推荐)
torch.save(model, 'model.pth')

# 推荐:仅保存状态字典(state_dict)
torch.save(model.state_dict(), 'model_weights.pth')

# 加载
model = SimpleNet()
model.load_state_dict(torch.load('model_weights.pth'))
model.eval() # 设置为评估模式(影响 Dropout、BatchNorm 等)

✅ 最佳实践:

  • 训练时用 model.train(),推理时用 model.eval()
  • 保存/加载使用 state_dict,更灵活、安全。

优化器与训练循环(torch.optim

训练神经网络的核心流程包括:前向传播 → 计算损失 → 反向传播 → 参数更新torch.optim 提供了多种优化算法,简化了参数更新过程。

基本训练循环结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch
import torch.nn as nn
import torch.optim as optim

# 模型、损失函数、优化器
model = SimpleNet()
criterion = nn.CrossEntropyLoss() # 分类任务常用
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 假设 data_loader 是训练数据迭代器
for epoch in range(num_epochs):
for inputs, labels in data_loader:
inputs, labels = inputs.to(device), labels.to(device)

# 1. 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels)

# 2. 反向传播
optimizer.zero_grad() # 清零梯度(关键!)
loss.backward() # 计算梯度

# 3. 参数更新
optimizer.step() # 根据梯度更新参数

✅ 关键三步:

  • zero_grad():防止梯度累积(除非故意累积)
  • backward():自动求导
  • step():执行优化算法更新权重

常用优化器(Optimizers)

优化器 说明 典型用法
SGD 随机梯度下降,可加动量 optim.SGD(params, lr=0.01, momentum=0.9)
Adam 自适应学习率,收敛快 optim.Adam(params, lr=0.001, betas=(0.9, 0.999))
RMSprop 适合非平稳目标 optim.RMSprop(params, lr=0.01, alpha=0.99)
AdamW Adam + 权重衰减解耦(推荐) optim.AdamW(params, lr=3e-4, weight_decay=0.01)

💡 建议:

  • 初学者可首选 AdamAdamW
  • 微调大模型时,AdamW + warmup + linear decay 是常见组合。

学习率调度(Learning Rate Scheduler)

动态调整学习率可提升收敛性和泛化能力:

1
2
3
4
5
6
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# 每10个epoch,学习率 ×0.1

for epoch in range(num_epochs):
train(...)
scheduler.step() # 更新学习率

其他常用调度器:

  • ReduceLROnPlateau:当 loss 不下降时降低 lr(需传入 metrics
  • CosineAnnealingLR:余弦退火
  • LinearWarmup + CosineDecay:常用于 Transformer 类模型

损失函数(Loss Functions)

torch.nn 提供常用损失函数,需根据任务选择:

任务类型 损失函数 注意事项
多分类 nn.CrossEntropyLoss() 输入未归一化 logits,标签为 long 类型
二分类 nn.BCEWithLogitsLoss() 输入为 logits,内部含 sigmoid
回归 nn.MSELoss() / nn.L1Loss() 注意输出维度匹配
对比学习 nn.CosineEmbeddingLoss() 用于向量相似性

⚠️ 重要区别:

  • CrossEntropyLoss = LogSoftmax + NLLLoss不要在模型最后加 Softmax
  • BCEWithLogitsLossBCELoss 更数值稳定(推荐使用前者)。

训练 vs 评估模式

  • 训练模式model.train() → 启用 Dropout、BatchNorm 更新统计量
  • 评估模式model.eval() → 关闭 Dropout,使用固定统计量(如均值/方差)
1
2
3
4
5
6
7
# 验证阶段示例
model.eval()
with torch.no_grad(): # 禁用梯度计算,节省内存
for inputs, labels in val_loader:
outputs = model(inputs)
loss = criterion(outputs, labels)
# 计算准确率等指标

✅ 最佳实践:

  • 验证/测试时务必使用 model.eval() + torch.no_grad()

梯度裁剪(Gradient Clipping)

防止梯度爆炸(尤其在 RNN 或大模型中):

1
2
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 在 optimizer.step() 之前调用

💡 小贴士

  • 每次迭代必须调用 optimizer.zero_grad(),否则梯度会累加。
  • 使用 loss.item() 获取标量损失值(避免内存泄漏)。
  • 训练日志建议记录:epoch、loss、lr、评估指标(如准确率)。