文章

pytorch

pytorch迅速复习

pytorch

基础

pytorch是一个开源的深度学习框架,提供了灵活的张量操作和自动求导机制,广泛应用于计算机视觉、自然语言处理等领域。重点是针对张量进行操作。在pytorch语法中,会发现许多函数有类似的名字,只是差了一个下划线,比如add和add_,前者是返回一个新的张量,而后者是原地操作,会修改原始张量的值。

张量操作

  1. 张量创建:可以使用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单位矩阵
    
  2. 内存布局: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个位置。

  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]])
    
  4. 高级索引: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]])
    
  5. view and reshape:用于改变张量的形状,view要求张量是连续的,而reshape则不要求。 ```python “””
    • view: 要求张量必须是contiguous的,否则报错
    • reshape: 会自动处理non-contiguous的情况(可能复制数据)
    • contiguous: 返回一个连续内存的张量副本

    任务:

    1. 创建一个张量并转置(变成non-contiguous)
    2. 尝试用view重塑(会失败)
    3. 用reshape重塑(会成功)
    4. 先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模块实现。

  1. 基本梯度计算: 通过设置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属性中。

  2. 梯度累积:默认情况下,每次调用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()。

  3. 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
    
  4. 计算图和反向传播: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()时会报错,因为计算图已经不存在了。

  5. 梯度钩子:可以使用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函数
    
本文由作者按照 CC BY 4.0 进行授权