pytorch
pytorch迅速复习
基础
pytorch是一个开源的深度学习框架,提供了灵活的张量操作和自动求导机制,广泛应用于计算机视觉、自然语言处理等领域。重点是针对张量进行操作。在pytorch语法中,会发现许多函数有类似的名字,只是差了一个下划线,比如add和add_,前者是返回一个新的张量,而后者是原地操作,会修改原始张量的值。
张量操作
- 张量创建:可以使用
torch.tensor()、torch.zeros()、torch.ones()等函数创建张量。1 2 3 4
zeros = torch.zeros(3, 4) # 3x4全零张量 ones = torch.ones(2,3) # 2x3全一张量 range_tensor = torch.range(0,9) # [0,1,2,...,9] identity = torch.eye(3) # 3x3单位矩阵
- 内存布局:PyTorch中的张量可以是连续的(contiguous)或非连续的(non-contiguous)。连续张量在内存中以行优先(row-major)或列优先(column-major)的方式存储,而非连续张量则可能由于转置、切片等操作而导致内存布局不连续。 Stride 定义了:为了在某个维度上移动到下一个元素,需要在内存中跳过多少个逻辑位置。 shape 定义了:每个维度的大小。
1 2 3
x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x.shape) # 输出: torch.Size([2, 3]) print(x.stride()) # 输出: (3, 1)
contiguous()函数:将张量转换为内存中连续存储的格式,返回一个新的张量。
1 2 3 4 5
x = torch.tensor([[1, 2, 3], [4, 5, 6]]) y = x.t() # 转置操作 print(y.is_contiguous()) # 输出: False z = y.contiguous() # 转换为连续存储格式 print(z.is_contiguous()) # 输出: True
为什么转置后就不连续了:因为转置操作并不会挪动数据的位置,而是改变了张量的视图,比如上面的例子,他会将stride从(3,1)变成(1,3),这一位这在最低维度移动一次,在内存中要移动3个位置。
- 广播机制:当进行张量操作时,如果两个张量的形状不完全相同,但满足某些条件,PyTorch会自动进行广播,使得它们具有相同的形状。
1 2 3 4
a = torch.tensor([1, 2, 3]) # 形状为 (3,) b = torch.tensor([[4], [5], [6]]) # 形状为 (3, 1) c = a + b # 广播机制使得a和b具有相同的形状 (3, 3) print(c) # 输出: tensor([[5, 6, 7], [6, 7, 8], [7, 8, 9]])
- 高级索引:PyTorch支持多种高级索引方式,包括布尔索引、整数数组索引等。
1 2 3 4 5 6 7 8 9 10 11 12 13
x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) # 布尔索引 mask = x > 5 print(x[mask]) # 输出: tensor([6, 7, 8, 9]) # 整数数组索引 indices = torch.tensor([0, 2]) print(x[indices]) # 输出: tensor([[1, 2, 3], [7, 8, 9]]) # 任务3: 使用where,小于等于5的变成0,大于5的保持原值 # TODO: 你的代码 conditional = torch.where(x>5,x,0) # 提示: torch.where(condition, x, 0) print(conditional) # 输出: tensor([[0, 0, 0], [0, 0, 6], [7, 8, 9]])
- view and reshape:用于改变张量的形状,view要求张量是连续的,而reshape则不要求。 ```python “””
- view: 要求张量必须是contiguous的,否则报错
- reshape: 会自动处理non-contiguous的情况(可能复制数据)
- contiguous: 返回一个连续内存的张量副本
任务:
- 创建一个张量并转置(变成non-contiguous)
- 尝试用view重塑(会失败)
- 用reshape重塑(会成功)
- 先contiguous再view(会成功) “”” x = torch.arange(6).reshape(2, 3) x_t = x.t() # 转置后变成 non-contiguous
# 任务1: 检查x_t是否contiguous # TODO: 你的代码 is_contig = x_t.is_contiguous() # 应该是 False
# 任务2: 尝试用view,应该会失败 view_success = False try: # TODO: 尝试 x_t.view(6) x_t.view(6) # YOUR CODE HERE view_success = True except RuntimeError: view_success = False
# 任务3: 用reshape(会成功) # TODO: 你的代码 reshaped = x_t.reshape(6) # x_t.reshape(6)
# 任务4: 先contiguous再view # TODO: 你的代码 viewed = x_t.contiguous().view(6) # x_t.contiguous().view(6) ```
自动求导
PyTorch提供了自动求导机制,通过torch.autograd模块实现。
- 基本梯度计算: 通过设置
requires_grad=True来跟踪张量的计算历史,调用backward()方法计算梯度。1 2 3 4 5
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y = x * 2 + 1 z = y.sum() z.backward() print(x.grad) # 输出: tensor([2., 2., 2.])
在设置requires_grad=True后,PyTorch会自动跟踪对该张量的所有操作,在调用backward()时,PyTorch会自动根据因变量进行链式法则求导,计算出自变量的梯度,并存储在自变量.grad属性中。
- 梯度累积:默认情况下,每次调用backward()时,计算的梯度会累积到.grad属性中,而不是覆盖之前的值。每次backward会将新梯度加到已有梯度上。 这就是为什么训练循环中需要 optimizer.zero_grad()
1 2 3 4 5 6 7 8
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y1 = x * 2 y2 = x * 3 y1.backward(torch.ones_like(x)) # 计算y1的梯度 print(x.grad) # 输出: tensor([2., 2., 2.]) y2.backward(torch.ones_like(x)) # 计算y2的梯度,累积到x.grad中 print(x.grad) # 输出: tensor([5., 5., 5.]) (2 + 3)
为了保留在某一时刻的梯度,可以使用x.grad.clone(),为了清零梯度,可以使用x.grad.zero_()或者optimizer.zero_grad()。
- detach 和 no_grad: detach()方法可以创建一个新的张量,该张量与原始张量共享数据但不跟踪计算历史。no_grad上下文管理器可以在其作用范围内禁止梯度计算。
1 2 3 4 5 6 7 8 9 10 11 12
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y = x * 2 z = y.sum() # 使用detach创建一个新的张量,不跟踪计算历史 y_detached = y.detach() print(y_detached.requires_grad) # 输出: False # 在no_grad上下文中进行操作,不计算梯度 with torch.no_grad(): y_no_grad = x * 3 print(y_no_grad.requires_grad) # 输出: False
- 计算图和反向传播:PyTorch会动态构建计算图,只有在调用backward()时才会进行反向传播计算梯度。
1 2 3 4 5 6 7 8 9 10 11 12
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) y = x * 2 + 1 z = y.sum() # 计算图在这里被构建 print(z.grad_fn) # 输出: <SumBackward0 object at ...> z.backward(retain_graph=True) # 反向传播计算梯度 print(x.grad) # 输出: tensor([2., 2., 2.]) z.grad.zero_() # 清零z的梯度 z.backward() # 再次反向传播计算梯度 print(x.grad) # 输出: tensor([2., 2., 2.]) (因为z的梯度被清零了,所以x.grad没有累积)
如果不添加retain_graph=True,第一次调用backward()后计算图会被释放,第二次调用backward()时会报错,因为计算图已经不存在了。
- 梯度钩子:可以使用register_hook()方法在张量上注册一个钩子函数,在每次计算梯度时被调用。
1 2 3 4 5 6 7 8 9 10 11 12
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) # 定义一个钩子函数,打印梯度 def print_grad(grad): print("Gradient:", grad) # 在x上注册钩子函数 x.register_hook(print_grad) y = x * 2 + 1 z = y.sum() z.backward() # 每次计算x的梯度时,都会调用print_grad函数