Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

PyTorch——Tensor的内存布局Layout


整体说明

  • 在 PyTorch 里,张量的 layout 属性主要用于表明内存的组织形式(tensor.layout 属性可查看张量当前的布局类型)
  • 张量的存储主要分为稀疏布局(Sparse Layout)和稠密布局(Dense Layout)两种
    • 稠密布局适合进行常规的张量运算
    • 稀疏布局在处理大规模稀疏数据时,能够显著减少内存占用和计算量
  • 在使用稀疏张量进行计算时,需要注意:
    • 并非所有的 PyTorch 操作都支持稀疏张量,部分操作在执行前可能需要先将稀疏张量转换为稠密张量

稠密布局(torch.strided)

  • torch.strided 是标准的多维数组布局,采用连续的内存存储方式
  • torch.strided 在每一个维度都具备步长(stride)属性,借助该属性可以计算出内存中的偏移量
  • 在 PyTorch 1.13 及后续版本中,很多张量创建函数(如 torch.ones 等)的参数的默认值是都是 torch.strided
  • 稠密布局示例:
    1
    2
    x = torch.tensor([[1, 2, 3], [4, 5, 6]])
    print(x.layout) # 输出:torch.strided

稀疏 CSR 布局(torch.sparse_csr)

  • torch.sparse_csr 布局适用于存储稀疏矩阵,能有效节省内存
  • torch.sparse_csr 运用压缩稀疏行(Compressed Sparse Row)格式,借助三个张量来表示:
    • crow_indices:记录每行在 col_indices 和 values 中的起始位置
    • col_indices:存储非零元素所在的列索引
    • values:存放非零元素的值
  • 稀疏 CSR 布局示例:
    1
    2
    3
    4
    5
    crow_indices = torch.tensor([0, 2, 3])
    col_indices = torch.tensor([0, 1, 2])
    values = torch.tensor([1, 2, 3])
    sparse_tensor = torch.sparse_csr_tensor(crow_indices, col_indices, values, (2, 3))
    print(sparse_tensor.layout) # 输出:torch.sparse_csr

其他稀疏布局

  • PyTorch 还支持多种稀疏布局,以满足不同的访问和计算需求
  • 稀疏 COO(torch.sparse_coo)布局
  • 稀疏 CSC(torch.sparse_csc)布局

布局转换方法示例

  • 可以使用以下方法在不同布局之间进行转换:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 从稠密布局转换为稀疏 COO 布局
    dense_tensor = torch.tensor([[0, 1], [2, 0]])
    sparse_coo = dense_tensor.to_sparse() # 默认为 COO 格式

    # 从稀疏 COO 布局转换回稠密布局
    dense_tensor = sparse_coo.to_dense()

    # 稀疏布局之间的转换
    sparse_csr = sparse_coo.to_sparse_csr()

PyTorch——Random相关状态管理


整体说明

  • PyTorch 中,包含很多随机操作,比如
    • 可以使用 torch.rand() 等函数获取随机数
    • 可以使用 torch.nn.functional.dropout() 实现随机 drop 一些神经元
    • 可以使用 tensor.random_() 等函数随机初始化参数
  • 这些涉及随机数/采样的方法均受限于一个随机状态管理

torch Seed 打印

  • torch Seed 打印代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 打印 torch 的随机种子情况
    def print_torch_seeds():
    print("=" * 30 + "PyTorch Random Seeds Status")
    print("=" * 30)
    cpu_seed = torch.initial_seed()
    print(f"[CPU] Seed: {cpu_seed}")

    if torch.cuda.is_available():
    try:
    gpu_seed = torch.cuda.initial_seed()
    current_device = torch.cuda.current_device()
    device_name = torch.cuda.get_device_name(current_device)

    print(f"[GPU] Seed: {gpu_seed}")
    print(f" Device: {current_device} ({device_name})")
    except Exception as e:
    print(f"[GPU] Error getting seed: {e}")
    else:
    print("[GPU] CUDA is not available.")

    print("=" * 30)
    print_torch_seeds()

torch Seed 设置

  • 全局 torch Seed 设置代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import torch

    # 固定CPU种子
    torch.manual_seed(42)

    # 固定所有GPU的种子(单GPU/多GPU通用)
    if torch.cuda.is_available():
    torch.cuda.manual_seed_all(42) # 替代 torch.cuda.manual_seed(42)(单GPU)

    # GPU上生成随机排列
    perm = torch.randperm(10, device="cuda") # 注意:需要指定 "cuda" 才会在 GPU 上执行
    print("GPU随机排列:", perm) # 每次运行结果一致
    print("draw a random number:", torch.rand()) # 每次运行结果一致
  • 使用独立的 torch 生成器(独立管理自己的随机生成器):

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

    # 创建独立的生成器并设置种子
    generator = torch.Generator()
    generator.manual_seed(42)
    # generator = torch.Generator().manual_seed(42) # 等价实现

    # 生成随机排列时指定生成器
    perm1 = torch.randperm(10, generator=generator)
    perm2 = torch.randperm(10, generator=generator)

    print("独立生成器-第一次:", perm1) # tensor([2, 7, 3, 1, 0, 9, 4, 5, 8, 6])
    print("独立生成器-第二次:", perm2) # tensor([2, 0, 7, 9, 8, 4, 3, 6, 1, 5])

    # 重置生成器种子,结果重复
    generator.manual_seed(42)
    perm3 = torch.randperm(10, generator=generator)
    print("重置生成器后:", perm3) # tensor([2, 7, 3, 1, 0, 9, 4, 5, 8, 6])(和perm1一致)
    • 说明:torch.Generator 是 PyTorch 中统一的随机数生成器(RNG)核心对象,几乎所有 PyTorch 内置的随机操作都支持通过 generator 参数指定该生成器

附录:torch.Generator 详细说明

  • torch.Generator 是 PyTorch 中统一的随机数生成器(RNG)核心对象 ,几乎所有 PyTorch 内置的随机操作都支持通过 generator 参数指定该生成器,仅极少数场景不支持(或无需支持)
  • torch.Generator 的核心作用是隔离随机状态 :任何依赖 PyTorch 内置随机数生成的操作,只要支持 generator 参数,就能通过该生成器控制随机行为;无 generator 参数的操作,要么不依赖随机数,要么复用全局生成器(CPU/CUDA)
  • 所有需要随机逻辑的场景均支持 torch.Generator 的随机操作(全场景)
  • 注:无随机逻辑的操作本身无随机行为,因此不需要(也无法)指定 generator:
    • 张量基础操作:torch.ones()、torch.zeros()、torch.arange()、torch.cat()、torch.matmul() 等
    • 数学运算:torch.sin()、torch.exp()、torch.mean()、torch.argmax() 等
    • 索引/切片:x[:, 0]、x.index_select() 等
    • 设备/类型转换:x.to('cuda')、x.float() 等

随机逻辑的场景示例

  • 所有操作均可通过 generator 参数指定自定义 torch.Generator,实现随机状态隔离
  • 基础随机数生成
    函数/方法 用途 示例
    torch.rand() 均匀分布随机数 torch.rand(3, generator=g)
    torch.randn() 标准正态分布随机数 torch.randn(2, 4, generator=g)
    torch.randint() 整数随机数 torch.randint(0, 10, (3,), generator=g)
    torch.randperm() 随机排列 torch.randperm(5, generator=g)
    torch.rand_like() 按形状生成均匀随机数 torch.rand_like(torch.ones(2), generator=g)
    torch.randn_like() 按形状生成正态随机数 torch.randn_like(torch.ones(2), generator=g)
    torch.normal() 自定义均值/方差正态分布 torch.normal(0, 1, (3,), generator=g)
    torch.poisson() 泊松分布随机数 torch.poisson(torch.ones(3), generator=g)
    torch.exponential() 指数分布随机数 torch.exponential(1.0, (3,), generator=g)
    torch.cauchy() 柯西分布随机数 torch.cauchy(0, 1, (3,), generator=g)
    torch.log_normal() 对数正态分布随机数 torch.log_normal(0, 1, (3,), generator=g)
    torch.multinomial() 多项分布采样 torch.multinomial(torch.ones(5), 3, generator=g)
    torch.bernoulli() 伯努利分布(0/1) torch.bernoulli(torch.ones(3)*0.5, generator=g)
    • 注:指定参数 generator 时,前面的参数也需要指定(Python 本身的规则)
  • 张量随机初始化
    函数/方法 用途 示例
    tensor.random_() 原地随机初始化(整数) tensor.random_(generator=g)
    tensor.uniform_() 原地均匀分布初始化 tensor.uniform_(0, 1, generator=g)
    tensor.normal_() 原地正态分布初始化 tensor.normal_(0, 1, generator=g)
    tensor.cauchy_() 原地柯西分布初始化 tensor.cauchy_(0, 1, generator=g)
  • 随机采样/变换(数据增强等)
    函数/方法 用途 示例
    torch.utils.data.RandomSampler 数据集随机采样 RandomSampler(dataset, generator=g)
    torch.nn.functional.dropout() Dropout层随机失活 F.dropout(x, p=0.5, generator=g)
    torch.nn.functional.dropout2d() 2D Dropout F.dropout2d(x, p=0.5, generator=g)
    torch.nn.functional.dropout3d() 3D Dropout F.dropout3d(x, p=0.5, generator=g)
    torchvision.transforms 中的随机变换 图像随机增强(如RandomCrop) transforms.RandomCrop(32, generator=g)(需torchvision)
    torch.distributions 分布采样 概率分布采样(如Normal、Uniform) dist = Normal(0, 1); dist.sample((3,), generator=g)

特殊说明:随机场景但不支持 torch.Generator 的场景

  • 有随机逻辑但不支持自定义 generator 的场景;依赖随机数,但 PyTorch 未开放 generator 参数,只能复用全局生成器(CPU/CUDA):
    操作 原因 替代方案
    torch.shuffle() 底层绑定全局生成器 用 torch.randperm(generator=g) 手动实现洗牌
    torch.nn.Dropout 模块(默认) 模块初始化时未绑定生成器 改用 F.dropout(generator=g) 或自定义模块绑定生成器
    部分第三方库的随机操作(如某些数据增强) 未适配 generator 参数 替换为 PyTorch 原生实现或手动设置全局种子
    torch.multiprocessing 多进程随机 进程间生成器隔离限制 每个进程内重新初始化 generator
  • 实践思考:
    • 1)凡是生成随机数的 PyTorch 原生函数,优先检查是否有 generator 参数,有则建议使用(隔离随机状态)
    • 2)对不支持 generator 的随机操作,要么手动实现(如用 randperm 替代 shuffle),要么临时设置全局种子并尽快恢复
    • 3)CUDA 场景务必创建对应设备的 generator,避免跨设备混用导致随机状态混乱

最佳实践:torch.Generator 隔离随机状态

  • 多个 torch.Generator 隔离随机状态示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import torch

    # 创建两个独立生成器
    g1 = torch.Generator().manual_seed(42)
    g2 = torch.Generator().manual_seed(42)

    # 用g1生成随机数(消耗g1的状态)
    a = torch.rand(2, generator=g1)
    b = torch.rand(2, generator=g1)

    # 用g2生成随机数(g2状态未被消耗,结果和g1初始一致)
    c = torch.rand(2, generator=g2)

    print("a (g1第一次):", a) # tensor([0.8823, 0.9150])
    print("b (g1第二次):", b) # tensor([0.3829, 0.9593])
    print("c (g2第一次):", c) # tensor([0.8823, 0.9150])(和a一致)

附录:GPU 下的 torch.Generator

  • torch.Generator 必须与操作的设备(CPU/CUDA)对齐 ,否则会导致隐式设备拷贝、性能损耗,甚至随机状态混乱
    • CPU 操作时使用 CPU 生成器
    • CUDA 操作时使用对应 CUDA 设备的生成器
    • 核心目的:避免隐式跨设备拷贝,保证随机状态的隔离性和可复现性
  • 所有支持 CUDA 的随机操作(如 torch.rand(3, device='cuda', generator=g)),需指定与生成器同设备的 generator
  • CUDA 生成器的随机状态与 CPU 生成器完全隔离,互不干扰
生成器的设备属性
  • torch.Generator 可通过 device 参数绑定具体设备,默认是 CPU:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import torch

    # CPU生成器(默认)
    g_cpu = torch.Generator() # 等价于 torch.Generator(device="cpu")
    print("CPU生成器设备:", g_cpu.device) # 输出:cpu

    # CUDA生成器(需显式指定)
    if torch.cuda.is_available():
    g_cuda = torch.Generator(device="cuda:0") # 绑定cuda:0
    print("CUDA生成器设备:", g_cuda.device) # 输出:cuda:0
对齐 vs 不对齐的示例对比
  • 正确:生成器设备 等于 操作 device(推荐)

    1
    2
    3
    4
    5
    6
    7
    8
    if torch.cuda.is_available():
    # 创建cuda:0的生成器
    g_cuda = torch.Generator(device="cuda:0").manual_seed(42)
    # 操作指定device=cuda:0,与生成器对齐
    perm = torch.randperm(10, device="cuda:0", generator=g_cuda)

    print("结果设备:", perm.device) # cuda:0
    print("无隐式拷贝,效率最高")
  • 错误:生成器设备 不等于 操作 device(性能坑)

    1
    2
    3
    4
    5
    6
    7
    if torch.cuda.is_available():
    # 生成器是cuda:0,但操作指定 device=cpu
    g_cuda = torch.Generator(device="cuda:0").manual_seed(42)
    perm = torch.randperm(10, device="cpu", generator=g_cuda)

    print("结果设备:", perm.device) # cpu
    print("隐式拷贝:GPU生成随机数 拷贝到CPU(额外开销)")
  • 更隐蔽的错误:CUDA操作用CPU生成器

    1
    2
    3
    4
    5
    6
    7
    if torch.cuda.is_available():
    # 生成器是CPU,操作指定device=cuda
    g_cpu = torch.Generator().manual_seed(42)
    perm = torch.randperm(10, device="cuda", generator=g_cpu)

    print("结果设备:", perm.device) # cuda:0
    print("隐式拷贝:CPU生成随机数 拷贝到GPU(额外开销)")
为什么必须对齐?
  • 随机数生成器的硬件绑定 :
    • CPU 生成器依赖 CPU 的随机数算法
    • CUDA 生成器依赖 GPU 的 cuRAND 库,直接在 GPU 显存生成随机数;
      • 跨设备使用时,PyTorch 会先在生成器设备生成随机数,再通过 PCIe 总线拷贝到操作指定的设备,产生额外耗时
  • 随机状态的隔离性(容易因为误用而出错) :
    • CUDA生成器的随机状态(get_state())和 CPU 生成器完全隔离,若跨设备使用,会导致“生成器状态和操作设备不匹配”,破坏随机种子的可复现性:
      1
      2
      3
      4
      5
      6
      7
      if torch.cuda.is_available():
      g_cuda = torch.Generator(device="cuda").manual_seed(42)
      # 第一次:跨设备使用(cuda生成器 到 cpu操作)
      perm1 = torch.randperm(10, device="cpu", generator=g_cuda)
      # 第二次:直接用cuda生成器 到 cuda操作
      perm2 = torch.randperm(10, device="cuda", generator=g_cuda)
      # perm2的结果不等于“重新seed后cuda操作的结果”(状态已被跨设备操作消耗)
torch.Generator 的最佳实践
  • 创建生成器时显式指定设备 :

    • 不要依赖默认的CPU生成器,GPU场景务必创建 device="cuda" 的生成器
  • 封装成函数,强制对齐 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def get_generator(device: str = "cpu", seed: int = 42):
    g = torch.Generator(device=device)
    g.manual_seed(seed)
    return g

    # 使用
    if torch.cuda.is_available():
    g = get_generator(device="cuda:0", seed=42)
    perm = torch.randperm(10, device="cuda:0", generator=g)
  • 多GPU场景:每个GPU对应独立生成器 :

    1
    2
    3
    4
    5
    6
    7
    if torch.cuda.is_available() and torch.cuda.device_count() > 1:
    # 为cuda:0和cuda:1分别创建生成器
    g0 = torch.Generator(device="cuda:0").manual_seed(42)
    g1 = torch.Generator(device="cuda:1").manual_seed(100)

    perm0 = torch.randperm(10, device="cuda:0", generator=g0)
    perm1 = torch.randperm(10, device="cuda:1", generator=g1)

PyTorch——backward函数详细解析

本文主要介绍PyTorch中backward函数和grad的各种用法


梯度的定义

  • \(y\) 对 \(x\) 的梯度可以理解为: 当 \(x\) 增加1的时候, \(y\) 值的增加量
  • 如果 \(x\) 是矢量(矩阵或者向量等),那么计算时也需要看成是多个标量的组合来计算,算出来的值表示的也是 \(x\) 当前维度的值增加1的时候, \(y\) 值的增加量

backward基础用法

  • tensorflow是先建立好图,在前向过程中可以选择执行图的某个部分(每次前向可以执行图的不同部分,前提是,图里必须包含了所有可能情况)
  • pytorch是每次前向过程都会重新建立一个图,反向(backward)的时候会释放,每次的图可以不一样, 所以在Pytorch中可以随时使用if, while等语句
    • tensorflow中使用if, while就得在传入数据前(构建图时)告诉图需要构建哪些逻辑,然后才能传入数据运行
    • PyTorch中由于不用在传入数据前先定义图(图和数据一起到达,图构建的同时开始计算数据?)
  • backward操作会计算梯度并将梯度直接加到变量的梯度上,所以为了保证梯度准确性,需要使用optimizer.zero_grad()清空梯度

计算标量对标量的梯度

  • 结构图如下所示

  • 上面图的代码构建如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import torch
    from torch.autograd import Variable

    w1 = Variable(torch.Tensor([2]),requires_grad=True)
    w2 = Variable(torch.Tensor([3]),requires_grad=True)
    w3 = Variable(torch.Tensor([5]),requires_grad=True)
    x = w1 + w2
    y = w2*w3
    z = x+y
    z.backward()
    print(w1.grad)
    print(w2.grad)
    print(w3.grad)
    print(x.grad)
    print(y.grad)

    # output:
    tensor([1.])
    tensor([6.])
    tensor([3.])
    None
    None
    • 从图中的推导可知,梯度符合预期
    • \(x, y\) 不是叶节点,没有梯度存储下来,注意可以理解为梯度计算了,只是没有存储下来,PyTorch中梯度是一层层计算的

计算标量对矢量的梯度

  • 修改上面的构建为

    • 增加变量 \(s = z.mean\),然后直接求取 \(s\) 的梯度
  • 结构图如下:

  • 代码如下:

    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
    import torch
    from torch.autograd import Variable

    w1 = Variable(torch.ones(2,2)*2,requires_grad=True)
    w2 = Variable(torch.ones(2,2)*3,requires_grad=True)
    w3 = Variable(torch.ones(2,2)*5,requires_grad=True)
    x = w1 + w2
    y = w2*w3
    z = x+y
    # z.backward()
    s = z.mean()
    s.backward()
    print(w1.grad)
    print(w2.grad)
    print(w3.grad)
    print(x.grad)
    print(y.grad)
    # output:
    tensor([[0.2500, 0.2500],
    [0.2500, 0.2500]])
    tensor([[1.5000, 1.5000],
    [1.5000, 1.5000]])
    tensor([[0.7500, 0.7500],
    [0.7500, 0.7500]])
    None
    None
    • 显然推导结果符合代码输出预期
    • 梯度的维度与原始自变量的维度相同,每个元素都有自己对应的梯度,表示当当前元素增加1的时候, 因变量值的增加量

计算矢量对矢量的梯度

  • 还以上面的结构图为例

  • 直接求中间节点 \(z\) 关于自变量的梯度

  • 代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import torch
    from torch.autograd import Variable

    w1 = Variable(torch.ones(2,2)*2, requires_grad=True)
    w2 = Variable(torch.ones(2,2)*3, requires_grad=True)
    w3 = Variable(torch.ones(2,2)*5, requires_grad=True)
    x = w1 + w2
    y = w2*w3
    z = x+y
    z_w1_grad = torch.autograd.grad(outputs=z, inputs=w1, grad_outputs=torch.ones_like(z))
    print(z_w1_grad)
    • 在因变量是矢量时,grad_outputs参数不能为空,标量时可以为空(grad_outputs为空时和grad_outputs维度为1时等价)
    • grad_outputs的维度必须和outputs参数的维度兼容

关于autograd.grad函数

  • 参考博客: https://blog.csdn.net/qq_36556893/article/details/91982925
grad_outputs参数详解
  • 在因变量是矢量时,grad_outputs参数不能为空,标量时可以为空(grad_outputs为空时和grad_outputs维度为1时等价)
  • grad_outputs的维度必须和outputs参数的维度兼容
    [待更新]

backward 对计算图的清空

  • 在 PyTorch 中,backward() 方法在计算反向梯度后,不仅不会保存中间节点(非叶子张量)的梯度,还会释放对应的计算图

    • 所以,多次调用 backward() 方法是会报错的 RuntimeError: Trying to backward through the graph a second time
  • 如果想要多次调用 backward() 方法,需要使用 backward(retain_graph=True)

  • 多次调用 backward() 的错误示例:

    1
    2
    3
    4
    5
    6
    7
    8
    import torch

    x = torch.tensor(2.0, requires_grad=True)
    y = x**3
    z = y + 1

    z.backward() # 第一次调用,正常运行
    z.backward() # 报错:RuntimeError: Trying to backward through the graph a second time...
  • 多次调用 backward() 的正确示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import torch

    x = torch.tensor(2.0, requires_grad=True)
    y = x**3
    z = y + 1

    z.backward(retain_graph=True) # 第一次调用,保留计算图
    print(x.grad) # 输出:tensor(12.)

    z.backward() # 第二次调用,此时可以不保留计算图
    print(x.grad) # 输出:tensor(24.),梯度累加了

自定义算子的前向后向实现

  • PyTorch 中,自定义算子适用于一些 PyTorch 内置函数无法满足的场景:
    • 实现个性化运算算子:当需要使用 PyTorch 未内置的数学运算(如特殊激活函数、自定义卷积等),且需支持自动求导时
    • 优化计算效率:对特定操作手动编写前向 / 反向逻辑,可能比内置函数更高效(如简化冗余计算)
    • 整合外部库:可用于将 C++、CUDA 或其他语言实现的算法接入 PyTorch,并支持自动求导
  • 自定义算子是 Function 类需继承 torch.autograd.Function,并实现两个静态方法 :
    • forward():定义前向传播逻辑,输入为张量,输出为计算结果
    • backward():定义反向传播梯度计算逻辑,输入为上游梯度,输出为各输入的梯度
  • PyTorch 自定义实现代码 Demo:
    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
    import torch

    class SquareSumFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, y):
    """
    :param ctx: 可用于保存需要在反向传播中使用的张量,torch.autograd.function.BackwardCFunction 的实例
    :param x: 输入张量
    :param y: 输入张量
    :return: 输出张量
    """
    ctx.save_for_backward(x, y) # ctx 用于存储反向传播所需的中间变量
    output = x **2 + y **2
    return output

    @staticmethod
    def backward(ctx, grad_output): # backward 输入参数和 forward 输出参数数量一致
    """
    :param ctx: 获取在 forward() 中保存的中间变量
    :param grad_output: 上游梯度,计算到当前算子的梯度后,链式法则乘上这个梯度就行,形式为torch.Tensor,第一次反向操作传入值为常量1
    :return: 返回多个值,依次对齐 forward 过程输入的张量的梯度
    """
    x, y = ctx.saved_tensors # 取出前向保存的变量
    grad_x = grad_output * 2 * x # 计算这个算子的 forward 输出 对x的梯度,然后按照链式法则乘上上游梯度
    grad_y = grad_output * 2 * y
    return grad_x, grad_y # backward 输出 和 forward 输入参数一一对齐

    x = torch.tensor([2.0], requires_grad=True)
    y = torch.tensor([3.0], requires_grad=True)
    z = SquareSumFunction.apply(x, y) # 通过 apply 调用
    z.backward() # 计算梯度
    print(x.grad, y.grad) # 输出:tensor([4.]) tensor([6.]),符合预期
    print(z.grad_fn)
    # 若计算图没有被清空,可以用下面的式子主动调用算子的 backward 函数(注意这里不会自动给 x.grad和y.grad赋值)
    # print(z.grad_fn.apply(1)) # 传入常量1作为上游梯度 `grad_output`,会返回两个梯度值+grad_fn的 tuple (tensor([4.], grad_fn=<MulBackward0>), tensor([6.], grad_fn=<MulBackward0>)),

PyTorch——使用问题记录


PyTorch和torchvision版本不兼容

  • 问题描述:
    RuntimeError: Couldn't load custom C++ ops. This can happen if your PyTorch and torchvision versions are incompatible, or if you had errors while compiling torchvision from source. For further information on the compatible versions, check https://github.com/pytorch/vision#installation for the compatibility matrix. Please check your PyTorch version with torch.version and your torchvision version with torchvision.version and verify if they are compatible, and if not please reinstall torchvision so that it matches your PyTorch install.

  • 解决方案:

    1
    pip install torch torchvision --upgrade

PyTorch——nn.Parameter类


整体说明

  • nn.Parameter 是 torch.Tensor 的一个子类,其定义方式如下:

    1
    2
    3
    class Parameter(torch.Tensor, metaclass=_ParameterMeta):
    """..."""
    def __init__ ...
  • nn.Parameter是最常用的模型参数类,其他许多高级封装的层(如nn.Linear和nn.Conv2d等)都包含着nn.Parameter对象作为参数


torch.Tensor和nn.Parameter的却别

  • 两者主要区别是是否自动注册为模型参数,具体逻辑见下文:
    特性 nn.Parameter torch.Tensor
    是否自动注册为模型参数 是 否
    是否默认启用梯度计算 是 (requires_grad=True) 否 (requires_grad=False)
    是否被优化器自动更新 是 否(需手动添加到优化器)
    适用场景 可训练参数(如权重、偏置) 中间结果或固定值
  • 在构建神经网络时,通常使用 nn.Parameter 来定义可训练参数,而 torch.Tensor 更适合存储不需要训练的数据

是否自动注册为模型参数

  • nn.Parameter :

    • 当 nn.Parameter 被赋值给 nn.Module 的属性时,它会自动注册为模型的可训练参数
    • 可以通过 model.parameters() 访问这些参数,优化器会自动更新它们
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      import torch.nn as nn

      class MyModel(nn.Module):
      def __init__(self):
      super(MyModel, self).__init__()
      self.weight = nn.Parameter(torch.randn(2, 2)) # 自动注册为模型参数

      def forward(self, x):
      return x @ self.weight

      model = MyModel()
      for param in model.parameters():
      print(param) # 可以访问到 self.weight

      # Parameter containing:
      # tensor([[-0.1866, 0.6549],
      # [-0.2559, -0.4768]], requires_grad=True)
  • torch.Tensor :

    • 直接使用 torch.Tensor 初始化的张量不会被自动注册为模型参数
    • 无法通过 model.parameters() 访问,优化器也不会更新它
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import torch.nn as nn

      class MyModel(nn.Module):
      def __init__(self):
      super(MyModel, self).__init__()
      self.weight = torch.randn(2, 2) # 只是一个普通的张量,不会注册为参数

      def forward(self, x):
      return x @ self.weight

      model = MyModel()
      for param in model.parameters():
      print(param) # 不会输出 self.weight

      # 无任何输出

是否支持自动梯度计算

  • nn.Parameter :

    • nn.Parameter 是 torch.Tensor 的子类,默认启用梯度计算(requires_grad=True)
    • 在反向传播时,PyTorch 会自动计算其梯度
  • torch.Tensor :

    • 默认情况下,torch.Tensor 的 requires_grad=False,不会计算梯度
    • 如果需要计算梯度,必须手动设置 requires_grad=True
    • 示例:
      1
      self.weight = torch.randn(2, 2, requires_grad=True)  # 手动启用梯度计算

优化器是否能更新

  • nn.Parameter :

    • 优化器可以通过 model.parameters() 获取 nn.Parameter 并更新其值
  • torch.Tensor :

    • 普通的 torch.Tensor 不会被优化器识别,除非手动将其添加到优化器的参数列表中
    • 示例:
      1
      optimizer = torch.optim.SGD([self.weight], lr=0.01)  # 手动添加到优化器

使用场景

  • nn.Parameter :

    • 适用于定义模型的可训练参数(如权重、偏置等)
    • 是构建神经网络时的标准做法
  • torch.Tensor :

    • 适用于存储不需要训练的中间结果或固定值(如常量张量)
    • 如果需要训练,必须手动设置 requires_grad=True 并注册到优化器

示例对比

  • 对比代码:
    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
    import torch
    import torch.nn as nn

    # 使用 nn.Parameter
    class ModelWithParameter(nn.Module):
    def __init__(self):
    super(ModelWithParameter, self).__init__()
    self.weight = nn.Parameter(torch.randn(2, 2)) # 自动注册为参数

    def forward(self, x):
    return x @ self.weight

    # 使用 torch.Tensor
    class ModelWithTensor(nn.Module):
    def __init__(self):
    super(ModelWithTensor, self).__init__()
    self.weight = torch.randn(2, 2, requires_grad=True) # 需要手动设置 requires_grad

    def forward(self, x):
    return x @ self.weight

    # 比较
    model1 = ModelWithParameter()
    print(list(model1.parameters())) # 输出: [Parameter containing...]

    model2 = ModelWithTensor()
    print(list(model2.parameters())) # 输出: []

nn.Linear和nn.Parameter的关系

  • nn.Parameter 是 torch.Tensor 的子类,用于表示模型中的可训练参数,当它被赋值给 nn.Module 的属性时,会自动注册为模型参数,参与反向传播和优化

  • nn.Linear 内部包含两个 nn.Parameter,分别用于存储权重和偏置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Linear(Module):
    """...comments..."""
    __constants__ = ['in_features', 'out_features']
    in_features: int
    out_features: int
    weight: Tensor
    def __init__(self, in_features: int, out_features: int, bias: bool = True,
    device=None, dtype=None) -> None:
    factory_kwargs = {'device': device, 'dtype': dtype}
    super(Linear, self).__init__()
    self.in_features = in_features
    self.out_features = out_features
    self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs)) # Parameter定义
    if bias:
    self.bias = Parameter(torch.empty(out_features, **factory_kwargs)) # Parameter定义
    else:
    self.register_parameter('bias', None)
    self.reset_parameters()
  • 对上面代码的其他解读:

    • __constants__ = ['in_features', 'out_features']: __constants__ 是 PyTorch 的一个特殊属性,用于声明哪些属性是常量。这些常量在 TorchScript(PyTorch 的 JIT 编译器)中会被优化,并且不会保存在模型的 state_dict 中
    • in_features: int: 是一个类型注解,声明 in_features 是一个整数类型的类属性。它表示输入特征的数量,即输入向量的维度,这个注解的主要作用是提高代码的可读性和类型检查(例如,使用静态类型检查工具如 mypy)
    • out_features: int: 同样是一个类型注解,声明 out_features 是一个整数类型的类属性。它表示输出特征的数量,即输出向量的维度,这个注解的主要作用是提高代码的可读性和类型检查
    • weight: Tensor: 是一个类型注解,声明 weight 是一个 Tensor 类型的类属性。weight 是线性层的权重矩阵,形状为 (out_features, in_features)。它会在 __init__ 方法中被初始化为一个可学习的参数(通过 torch.nn.Parameter 封装)

torch模型参数都是nn.Parameter类吗?

  • 在PyTorch中,模型参数通常是nn.Parameter类的实例,但并非所有模型中的可学习参数都必须是nn.Parameter

  • 模型中不是nn.Parameter类的一些特例和说明:

    • Buffer(缓冲区) :有些模型需要存储一些状态,但这些状态不是可学习的参数。这些状态可以通过register_buffer方法注册为缓冲区,而不是nn.Parameter。缓冲区不会被优化器更新,但会随模型一起保存和加载

      1
      self.register_buffer('running_mean', torch.zeros(num_features))
    • 非可学习参数 :有些参数虽然是模型的一部分,但不需要通过反向传播进行更新。这些参数可以是普通的torch.Tensor,而不是nn.Parameter

    • 自定义参数 :在某些情况下,开发者可能会手动管理参数,而不是使用nn.Parameter。例如,直接使用torch.Tensor并在需要时手动更新

    • 动态生成的临时变量 :在某些复杂的模型中,可能会有动态生成的参数或临时变量,这些可能不是nn.Parameter

    • 注:子模块中间接包含了nn.Parameter对象 :子模块(如nn.Linear、nn.Conv2d等)中包含的nn.Parameter对象参数会自动被识别并注册为模型参数,开发者可以通过parameters()方法访问这些参数。

PyTorch——激活函数调用的最佳实践


整体说明

  • 在PyTorch中,调用激活函数有几种常见方法:

    • 方法一 :通过 torch.relu() 直接调用激活函数【几乎不会使用,常用torch.nn.functional.relu()方式替代】
      • 使用时不需要实例化对象,适合用于简单的函数式调用
      • 它接受一个张量作为输入,并对这个张量应用ReLU操作
    • 方法二 :使用 torch.nn.functional.relu()(通常简写为 F)直接调用激活函数,一种函数式的方式来应用ReLU激活函数
      • 使用时不需要实例化对象,适合用于简单的函数式调用
      • 和torch.relu()类似,但它提供了更多的灵活性,比如你可以通过参数控制是否进行原地操作(inplace)等
      • 从技术角度来看,使用 torch.relu 和 F.relu 最终调用了相同的底层实现
    • 方法三 :通过 torch.nn.ReLU() 创建对象后调用
      • 这是ReLU作为一个层(layer)的形式出现,属于torch.nn模块。当你需要将ReLU作为一个网络的一部分时使用
      • 在使用前需要先实例化一个ReLU对象,然后可以像其他层一样调用这个对象。这种方式更适合于构建神经网络模型的架构中,因为它遵循了面向对象的设计理念,可以方便地集成到模型定义中
  • 总结来说,如果你只是想应用ReLU而不考虑网络结构,可以直接使用torch.relu()或torch.nn.functional.relu()。若你在构建一个复杂的神经网络并且希望以层的形式组织你的激活函数,则推荐使用torch.nn.ReLU()。对于更细粒度的控制需求,如执行原地操作来节省内存,torch.nn.functional.relu()是更好的选择


使用 torch.nn.functional 调用激活函数

  • 设计目的 : torch.nn.functional 提供了一系列函数式接口,适用于直接对张量执行操作,比如激活函数、池化等。这种方式非常适合用于需要灵活地应用不同操作的场景
  • 典型使用场景 : 当你需要在模型外部或者在自定义的前向传播逻辑中灵活地应用某些操作时,F 模块下的函数是非常有用的。例如,在定义一个自定义的 forward 方法时,你可以直接对输入张量调用 F.relu()
    1
    2
    3
    4
    5
    import torch
    import torch.nn.functional as F

    input_tensor = torch.randn(2, 3)
    output_tensor = F.relu(input_tensor)

使用 torch.nn.ReLU 调用激活函数

  • 设计目的 : 尽管 torch 下也有可以直接调用的激活函数(如 torch.relu),但这种做法并不常见
  • 典型使用场景 : 实际上,对于激活函数这类操作,更推荐使用 torch.nn.functional 或者对应的 nn 模块中的类(例如 torch.nn.ReLU)。这是因为它们提供了更清晰的设计模式,并且与PyTorch的整体设计理念更加一致
    1
    2
    3
    4
    5
    6
    import torch
    import torch.nn as nn

    relu_layer = nn.ReLU()
    input_tensor = torch.randn(2, 3)
    output_tensor = relu_layer(input_tensor)

总结

  • F 模块提供了一种更为灵活的方式来调用激活函数和其他层操作,因为它允许你直接将这些操作应用于任何张量,而不需要先将其包装在一个模块中
  • 使用 torch.nn.functional 或者对应的 nn 模块中的类来调用激活函数有助于保持代码的一致性和提高代码的可读性,这是由于PyTorch社区普遍采用这样的编码风格
  • 因此,尽管从技术角度来看,使用 torch.relu 和 F.relu 最终调用了相同的底层实现,但为了遵循最佳实践和保持代码的一致性,推荐使用 F.relu 或者 nn.ReLU 来应用ReLU激活函数。这样做不仅使得代码更具可读性,也更容易维护

PyTorch——计算机视觉torchvision

PyTorch中有个torchvision包,里面包含着很多计算机视觉相关的数据集(datasets),模型(models)和图像处理的库(transforms)等
本文主要介绍数据集中(ImageFolder)类和图像处理库(transforms)的用法


PyTorch预先实现的Dataset

  • ImageFolder

    1
    from torchvision.datasets import ImageFolder
  • COCO

    1
    from torchvision.datasets import coco
  • MNIST

    1
    from torchvision.datasets import mnist
  • LSUN

    1
    from torchvision.datasets import lsun
  • CIFAR10

    1
    from torchvision.datasets import CIFAR10

ImageFolder

  • ImageFolder假设所有的文件按照文件夹保存,每个文件夹下面存储统一类别的文件,文件夹名字为类名

  • 构造函数

    1
    ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
    • root:在root指定的路径下寻找图片,root下面的每个子文件夹就是一个类别,每个子文件夹下面的所有文件作为当前类别的数据
    • transform:对PIL Image进行的转换操作,transform的输入是使用loader读取图片的返回对象
      • PIL是 Python Imaging Library 的简称,是Python平台上图像处理的标准库
    • target_transform:对label的转换, 默认会自动编码
      • 默认编码为从0开始的数字,如果我们自己将文件夹命名为从0开头的数字,那么将按照我们的意愿命名,否则命名顺序不确定
      • 测试证明,如果文件夹下面是root/cat/, root/dog/两个文件夹,则自动编码为{‘cat’: 0, ‘dog’: 1}
      • class_to_idx属性存储着文件夹名字和类别编码的映射关系,dict
      • classes属性存储着所有类别,list
    • loader:从硬盘读取图片的函数
      • 不同的图像读取应该用不同的loader
      • 默认读取为RGB格式的PIL Image对象
      • 下面是默认的loader
        1
        2
        3
        4
        5
        6
        def default_loader(path):
        from torchvision import get_image_backend
        if get_image_backend() == 'accimage':
        return accimage_loader(path)
        else:
        return pil_loader(path)

transfroms详解

  • 包导入

    1
    from torchvision.transforms import transforms
  • transforms包中包含着很多封装好的transform操作

    • transforms.Scale(size):将数据变成制定的维度
    • transforms.ToTensor():将数据封装成PyTorch的Tensor类
    • transforms.Normalize(mean, std): 将数据标准话,具体标准化的参数可指定
  • 可将多个操作组合到一起,同时传入 ImageFolder 等对数据进行同时操作,每个操作被封装成一个类

    1
    2
    3
    4
    simple_transform = transforms.Compose([transforms.Resize((224,224))
    ,transforms.ToTensor()
    ,transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
    train = ImageFolder('dogsandcats/train/',simple_transform)
  • torchvision.transforms.transforms包下的操作类都是基于torchvision.transforms.functional下的函数实现的

    • 导入torchvision.transforms.functional的方式
      1
      from torchvision.transforms import functional

PyTorch——GPU管理

本文主要介绍PyTorch相关的GPU管理


GPU管理整体说明

  • PyTorch的GPU管理包含以下接口:
    • cuda设备可用性判断:torch.cuda.is_available()
    • 设备对象创建和获取:torch.device('cuda')
    • 张量切换设备:tensor.to(gpu_device)
    • 模型切换设备:model.to(gpu_device)
    • 模型参数设备打印:print(f"参数所在设备: {param.device}") for param in model.parameters(): # model.parameters()
    • cuda设备可用数量查看:torch.cuda.device_count()
    • 当前环境cuda设备管理:torch.cuda.device(1)
    • 当前环境cuda设备号查看:torch.cuda.current_device()

Torch的默认指定GPU设定

  • PyTorch 会默认选择一个 GPU 作为当前设备(通常是编号为 0 的 GPU),原因如下:
    • 单 GPU 环境 :如果系统中只有一块 GPU,PyTorch 会默认使用它(编号为 0)
    • 多 GPU 环境 :如果有多个 GPU,PyTorch 仍然会默认使用编号为 0 的 GPU,除非显式地指定使用其他 GPU
    • 简化开发 :默认设备机制使得开发者在不显式指定设备的情况下,代码仍然可以正常运行
  • 默认 GPU 的设定是为了方便开发者,避免每次都需要手动指定设备

GPU管理先关概念

设备编号(Device Index)

  • PyTorch 使用从 0 开始的整数编号来标识 GPU
  • 例如,如果有 4 块 GPU,它们的编号分别是 0、1、2、3
  • 可以通过 torch.cuda.device_count() 获取当前可用的 GPU 数量

当前设备(Current Device)

  • PyTorch 会跟踪当前正在使用的 GPU,称为「当前设备」
  • 默认情况下,当前设备是编号为 0 的 GPU
  • 可以通过 torch.cuda.current_device() 获取当前设备的编号

切换设备简单示例

  • 可以使用 torch.cuda.set_device(device_id) 来切换当前设备

    1
    2
    torch.cuda.set_device(1)  # 切换到编号为 1 的 GPU
    # 接下来的操作会默认在指定GPU上
  • 也可以通过 torch.device 来指定设备:

    1
    2
    device = torch.device("cuda:1")  # 使用编号为 1 的 GPU
    tensor = torch.tensor([1, 2, 3]).to(device)

GPU高阶管理

  • 如果没有显式指定设备,PyTorch 会将张量和模型放在默认 GPU(通常是 cuda:0)上

  • 例如:

    1
    x = torch.tensor([1, 2, 3]).cuda()  # 默认放在 cuda:0 上
  • 可以通过环境变量 CUDA_VISIBLE_DEVICES 控制哪些 GPU 对 PyTorch 可见。例如:

    1
    export CUDA_VISIBLE_DEVICES=1,2  # 只让 GPU 1 和 2 对 PyTorch 可见

    在这种情况下,PyTorch 会将可见的 GPU 重新编号为 0 和 1

代码示例

  • 使用示例:
    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
    ## 设备管理
    # 创建一个 CPU 设备对象
    cpu_device = torch.device('cpu')
    # 创建一个 GPU 设备对象,如果有多个 GPU,可以指定编号,如 'cuda:1'
    gpu_device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

    ## 检查GPU是否可用
    # if torch.cuda.is_available()

    ## 获取GPU的数量
    num_gpus = torch.cuda.device_count()

    ## 张量切换设备
    # 将张量移动到 GPU 设备
    tensor = tensor.to(gpu_device)
    # 或者使用 .cuda() 方法
    tensor = tensor.cuda() if torch.cuda.is_available() else tensor
    # 将张量移动到 CPU 设备
    tensor = tensor.cpu()
    ## 检查张量所在设备
    print(f"张量所在设备: {tensor.device}")

    ## 模型切换设备(跟张量操作一致)
    model = torch.nn.Linear(10, 10)
    # 将模型移动到 GPU 设备
    model = model.to(gpu_device)
    # 或者使用 .cuda() 方法
    model = model.cuda() if torch.cuda.is_available() else model
    # 将模型移动到 CPU 设备
    model = model.cpu()
    ## 检查参数所在设备
    for param in model.parameters(): # model.parameters()
    print(f"参数所在设备: {param.device}")

    ## 当前环境的多设备管理
    # 指定GPU设备方案一
    if torch.cuda.is_available() and num_gpus > 1:
    with torch.cuda.device(1): # 指定使用第二个 GPU
    tensor = torch.randn(3, 3).cuda()
    print(f"张量所在设备: {tensor.device}")
    # 指定GPU设备方案二
    if torch.cuda.is_available() and num_gpus > 1:
    torch.cuda.set_device(1) # 指定使用第二个 GPU
    # 输出设备号
    if torch.cuda.is_available():
    current_device = torch.cuda.current_device()
    print(f"当前使用的 GPU 编号: {current_device}")

    ## 更清晰的设备管理方式
    # 将 tensor 移动到 cuda:0
    gpu_device0 = torch.device('cuda:0')
    tensor0 = tensor.to(gpu_device0)

    # 将 tensor 移动到 cuda:1
    gpu_device1 = torch.device('cuda:1')
    tensor1 = tensor.to(gpu_device1)

PyTorch——各种常用函数总结

PyTorch使用笔记,函数持续更新


torch.max()

  • 注:torch.min与torch.max用法类似

单参数(取全局最大值)

  • 用法

    1
    torch.max(input) -> Tensor
    • input: 一个Tensor的对象
    • return: 返回input变量中的最大值

多参数(按维度取最大值)

  • 用法

    1
    torch.max(input, dim, keepdim=False, out=None) -> tuple[Tensor, Tensor]
    • input: 一个Tensor的对象
    • 返回是一个包含 values 和 indices 的对象,其中 values 是最大值,indices 是其索引
    • dim: 指明维度,生成的结果中,indices 用于替换第 0 维度(这对应 gather() 的检索方式)
    • keepdim: 是否保持输出张量的维度与输入张量一致(默认值为 False)
      • 如果为 True,输出张量在指定维度上的大小为 1;
      • 如果为 False,输出张量将减少一个维度
  • keepdim=False示例(包含索引使用示例):

    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
    import torch
    x = torch.tensor([[3, 2, 1],
    [4, 5, 6]])
    # 获取每行的最大值
    values, indices = torch.max(x, dim=1) # 默认在最后一个维度,即 dim=1 上操作,按行取最大值(生成的index是指定dim=1维度的索引)
    print("values:\n", values)
    print("indices:\n", indices)
    # 用 indices 从 x 中检索出 values,方法一:gather 方法
    retrieved_values = torch.gather(x, dim=1, index=indices.unsqueeze(1)) # 用 indices 替换 dim=1 索引维度即可抽取到对应的值
    print("retrieved_values:\n", retrieved_values)
    print("retrieved_values.squeeze():\n", retrieved_values.squeeze())
    print("---")
    # 用 indices 从 x 中检索出 values,方法二:高级索引
    retrieved_values = x[torch.arange(2), indices]
    print("retrieved_values:\n", retrieved_values)

    # values:
    # tensor([3, 6])
    # indices:
    # tensor([0, 2])
    # retrieved_values:
    # tensor([[3],
    # [6]])
    # retrieved_values.squeeze():
    # tensor([3, 6])
    # ---
    # retrieved_values:
    # tensor([3, 6])
  • keepdim=True示例(包含索引使用示例):

    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
    import torch
    x = torch.tensor([[3, 2, 1],
    [4, 5, 6]])
    # 获取每行的最大值,保留维度
    values, indices = torch.max(x, dim=1, keepdim=True) # 默认在最后一个维度,即 dim=1 上操作,按行取最大值(生成的index是指定dim=1维度的索引)
    print("values:\n", values)
    print("indices:\n", indices)
    # 用 indices 从 x 中检索出 values,方法一:gather 方法
    retrieved_values = torch.gather(x, dim=1, index=indices) # 用 indices 替换 dim=1 索引维度即可抽取到对应的值
    print("retrieved_values:\n", retrieved_values)
    print("---")
    # 用 indices 从 x 中检索出 values,方法二:高级索引
    retrieved_values = x[torch.arange(2), indices.squeeze()]
    print("retrieved_values:\n", retrieved_values)
    print("retrieved_values.unsqueeze(dim=1):\n", retrieved_values.unsqueeze(dim=1))

    # values:
    # tensor([[3],
    # [6]])
    # indices:
    # tensor([[0],
    # [2]])
    # retrieved_values:
    # tensor([[3],
    # [6]])
    # ---
    # retrieved_values:
    # tensor([3, 6])
    # retrieved_values.unsqueeze(dim=1):
    # tensor([[3],
    # [6]])

torch.backward() & torch.no_grad()

  • 用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch
    x = torch.ones(2, 2, requires_grad=True)
    m = torch.ones(2, 2, requires_grad=True)
    with torch.no_grad(): # 这两句合起来
    z = x + 1 # 等价于z = x.detach() + 1
    y = z * 2 + m
    out = y.mean()
    out.backward()
    print(x.grad) # output None,因为梯度在z = x+1处断开了
    print(m.grad) # output tensor([[0.2500, 0.2500],
    # [0.2500, 0.2500]])
    print(y.grad) # output None,因为y只是中间操作节点,不是叶子Tensor变量(leaf Tensor)
  • 以上代码中,x不会被计算梯度,因为z = x + 1处梯度断开了,等价于z = x.detach() + 1

  • 注意,如果调用torch.backward()时,没有任何可以计算的梯度,会报错,如下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch
    x = torch.ones(2, 2, requires_grad=True)
    m = torch.ones(2, 2, requires_grad=True)
    with torch.no_grad(): # 这两句合起来
    z = x + 1 # 等价于z = x.detach() + 1
    y = z * 2 + m.detach() # 相对上面的改动点,仅这里
    out = y.mean()
    out.backward()
    print(x.grad) # output None,因为梯度在z = x+1处断开了
    print(m.grad) # output tensor([[0.2500, 0.2500],
    # [0.2500, 0.2500]])
    print(y.grad) # output None,因为y只是中间操作节点,不是叶子Tensor变量(leaf Tensor)
    • 仅修改了m.detach()后out.backward()就开始报错RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn,因为没有任何梯度可以计算了
    • 其他说明:如果仅修改m = torch.ones(2, 2, requires_grad=True)为m = torch.ones(2, 2, requires_grad=False),也一样会导师没有任何梯度可以计算了,执行out.backward()也会报相同错误

torch.nn.Module

  • 直接打印torch.nn.Module类对象print(model),默认情况下这个方法会调用model.__str__(),进一步调用model.__repr__(),这个函数会给出一个包含所有被定义为类属性的层的可读表示,但不会包括那些被定义为简单属性或方法的对象,这个方法可以用来查看模型包含哪些网络层
  • 举例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import torch
    import torch.nn as nn
    class MLPModel(nn.Module):
    def __init__(self):
    super(MLPModel, self).__init__()
    self.fc1 = nn.Linear(2, 2) # 会被打印
    self.fc2 = nn.Linear(2, 1) # 会被打印
    self.activation = torch.relu # 不会被打印

    def forward(self, x):
    x = self.activation(self.fc1(x))
    x = self.fc2(x)
    return x
    model = MLPModel()
    print(model) # 等价于print(model.__str__())和print(model.__repr__())

    # MLPModel(
    # (fc1): Linear(in_features=2, out_features=2, bias=True)
    # (fc2): Linear(in_features=2, out_features=1, bias=True)
    # )

tensor.item()

  • 用于抽取一个tensor对象中的单一数值(注意,只能是单一数值,否则会报错)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import torch
    # 单元素张量
    tensor = torch.tensor([5]) # 形状为 (1,)
    value = tensor.item() # 提取出 Python 的 int 值
    print(value) # 输出: 5
    print(type(value)) # 输出: <class 'int'>

    tensor = torch.tensor([1, 2, 3])
    value = tensor.item() # 报错: ValueError: only one element tensors can be converted to Python scalars

    # 强化学习中,常常用于抽取动作,方便和环境交互
    action = action_dist.sample() # 采样一个动作,action 是一个单元素张量
    action_value = action.item() # 提取出动作的标量值

torch.autograd.grad(outputs,inputs,grad_outputs)

  • 功能:outputs对inputs求导
  • 关键点:
    • 当grad_outputs取默认值时,要求outputs必须是标量张量(一维)
    • 当outputs不是标量张量时,要求grad_outputs与outputs维度一致,指明outputs中每个值对最终梯度的权重,且此时不可省略该参数(否则会报错:“RuntimeError: grad can be implicitly created only for scalar outputs”)
  • Demo展示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import torch
    # 创建需要梯度的张量
    x = torch.tensor([2.0, 3.0], requires_grad=True)

    # 定义一个多维张量 y = [x[0]**2, x[1]**3]
    y = torch.stack([x[0]**2+x[1]**2, x[0]*3+x[1]**2]) # y 是一个形状为 (2,) 的张量

    # 计算 y 对 x 的梯度
    # 需要指定 grad_outputs 作为 y 的每个元素的权重(默认情况下,grad_outputs 是全 1 的张量)
    grads = torch.autograd.grad(outputs=y, inputs=x,grad_outputs=torch.ones_like(y))

    print("x:", x)
    print("y:", y)
    print("Gradients of y with respect to x:", grads)

    ### output:
    # x: tensor([2., 3.], requires_grad=True)
    # y: tensor([13., 15.], grad_fn=<StackBackward0>)
    # Gradients of y with respect to x: (tensor([ 7., 12.]),)
    # 其中 7 = 2 * x[0] + 3; 12 = 2 * x[1] + 2 * x[1]

torch.cat、stack、hstack、vstack

  • torch.cat和torch.stack两者都是用来拼接tensor的函数,主要区别是使用 torch.cat 在现有维度上拼接,使用 torch.stack 在新维度上拼接

    • 维度变化 :
      • torch.cat 不新增维度,只在现有维度上拼接
      • torch.stack 会新增一个维度,并在该维度上拼接,torch.stack的工作需要分成两步,第一步是增加维度(比如(3,)经过dim=0会变成(1,3)),第二步是将该维度拼接
    • 形状要求 :
      • torch.cat 要求非拼接维度上的形状相同
      • torch.stack 要求所有张量的形状完全一致
  • 使用特别说明 :一般使用 torch.cat和torch.stack就够了,torch.hstack和torch.vstack基本可以被torch.cat和torch.stack替代,所以不常用

  • torch.cat Demo 展示:

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

    a = torch.tensor([[1, 2], [3, 4]])
    b = torch.tensor([[5, 6], [7, 8]])

    # 沿第0维拼接
    c = torch.cat((a, b), dim=0)
    print(c)
    # 输出:
    # tensor([[1, 2],
    # [3, 4],
    # [5, 6],
    # [7, 8]])

    # 沿第1维拼接
    d = torch.cat((a, b), dim=1)
    print(d)
    # 输出:
    # tensor([[1, 2, 5, 6],
    # [3, 4, 7, 8]])
  • torch.stack Demo 展示:

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

    a = torch.tensor([1, 2, 3])
    b = torch.tensor([4, 5, 6])

    # 在第0维新增维度并拼接: (3,) -> (1,3) -> (2,3)
    c = torch.stack((a, b), dim=0)
    print(c)
    # 输出:
    # tensor([[1, 2, 3],
    # [4, 5, 6]])

    # 在第1维新增维度并拼接: (3,) -> (3,1) -> (3,2)
    d = torch.stack((a, b), dim=1)
    print(d)
    # 输出:
    # tensor([[1, 4],
    # [2, 5],
    # [3, 6]])
    • stack的动作分两步,第一步是在指定维度增加一维,比如:
      • 上面的式子(3,)经过dim=0后变成(1,3),进一步地两个(1,3)堆叠变成(2,3)
      • 上面的式子(3,)经过dim=1后变成(3,1),进一步地两个(3,1)堆叠变成(3,2)
  • torch.hstack和torch.vstack函数:

    • torch.hstack:在水平方向(dim=1)处拼接张量,不新增维度
      • 对于 1 维张量会特殊处理,直接将 1 维张量变长(此时相当于在 dim=0处拼接)
    • torch.vstack:在垂直方向(第 0 维)拼接张量,除了原始输入为 1 维时,不新增维度
      • 对于 1 维张量会特殊处理,拼接后变成 2 维(此时相当于先在dim=0增加一维再按照这个维度拼接)
    • 不同函数在输入1维和5维向量时的Demo:
      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
      import torch
      tensors_1d = [torch.tensor([1, 3, 4]), torch.tensor([1, 3, 4])]
      tensors_5d = [torch.randn(1, 3, 4, 5, 6), torch.randn(1, 3, 4, 5, 6)]
      # 使用 torch.stack
      stacked_1d = torch.stack(tensors_1d)
      stacked_5d = torch.stack(tensors_5d, dim=2)
      # 使用 torch.hstack
      hstacked_1d = torch.hstack(tensors_1d)
      hstacked_5d = torch.hstack(tensors_5d)
      # 使用 torch.vstack
      vstacked_1d = torch.vstack(tensors_1d)
      vstacked_5d = torch.vstack(tensors_5d)
      # 使用 torch.cat
      cat_1d = torch.cat(tensors_1d)
      cat_5d = torch.cat(tensors_5d, dim=2)

      print("tensors_1d shape:", tensors_1d[0].shape)
      print("tensors_5d shape:", tensors_5d[0].shape)
      print("torch.stack 1D shape:", stacked_1d.shape)
      print("torch.stack 5d shape(dim=2):", stacked_5d.shape)
      print("torch.hstack 1D shape:", hstacked_1d.shape)
      print("torch.hstack 5d shape:", hstacked_5d.shape)
      print("torch.vstack 1D shape:", vstacked_1d.shape)
      print("torch.vstack 5d shape:", vstacked_5d.shape)
      print("torch.cat 1D shape:", cat_1d.shape)
      print("torch.cat 5d shape(dim=2):", cat_5d.shape)

      # tensors_1d shape: torch.Size([3])
      # tensors_5d shape: torch.Size([1, 3, 4, 5, 6])
      # torch.stack 1D shape: torch.Size([2, 3])
      # torch.stack 5d shape(dim=2): torch.Size([1, 3, 2, 4, 5, 6])
      # torch.hstack 1D shape: torch.Size([6])
      # torch.hstack 5d shape: torch.Size([1, 6, 4, 5, 6])
      # torch.vstack 1D shape: torch.Size([2, 3])
      # torch.vstack 5d shape: torch.Size([2, 3, 4, 5, 6])
      # torch.cat 1D shape: torch.Size([6])
      # torch.cat 5d shape(dim=2): torch.Size([1, 3, 8, 5, 6])

tensor.repeat()函数

  • 基本用法

    1
    tensor.repeat(*sizes)
    • *sizes:一个整数序列,表示每个维度上重复的次数
    • 返回值:一个新的张量,其形状是原张量形状的每个维度乘以对应的重复次数
  • 注意事项

    • 新维度的顺序 :repeat 会按照输入的顺序对每个维度进行扩展
    • 内存共享 :repeat 不会复制数据,而是通过视图(view)来实现重复。这意味着返回的张量与原始张量共享底层数据
    • 维度扩展规则 :如果输入的 sizes 长度大于张量的维度,则会在前面补充新的维度,比如(2,)的原始张量调用repeat(2,3)后,会先扩展成(1,2)的张量,再执行repeat(2,3),但是不建议补充这个功能
  • 函数调用Demo:

    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
    import torch
    x = torch.tensor([1, 2])
    print("Original tensor:", x)

    # 沿第 0 维重复 3 次
    result = x.repeat(3)
    print("Repeated tensor(3):", result)
    # 添加一个新维度,并在两个维度上重复
    result = x.repeat(2, 3)
    print("Repeated tensor shape(2,3):", result.shape)
    print("Repeated tensor(2,3):\n", result)

    x = torch.tensor([[1, 2, 3], [4, 5, 6]])
    print("Original tensor:\n", x)

    # 在第 0 维重复 2 次,第 1 维重复 3 次
    result = x.repeat(2, 3)
    print("Repeated tensor:\n", result)

    # Original tensor: tensor([1, 2])
    # Repeated tensor(3): tensor([1, 2, 1, 2, 1, 2])
    # Repeated tensor shape(2,3): torch.Size([2, 6])
    # Repeated tensor(2,3):
    # tensor([[1, 2, 1, 2, 1, 2],
    # [1, 2, 1, 2, 1, 2]])
    # Original tensor:
    # tensor([[1, 2, 3],
    # [4, 5, 6]])
    # Repeated tensor:
    # tensor([[1, 2, 3, 1, 2, 3, 1, 2, 3],
    # [4, 5, 6, 4, 5, 6, 4, 5, 6],
    # [1, 2, 3, 1, 2, 3, 1, 2, 3],
    # [4, 5, 6, 4, 5, 6, 4, 5, 6]])

parameters() & named_parameters()

  • model.parameters():返回模型中所有参数,不返回参数名称
  • model.named_parameters():返回模型中所有参数,同时返回参数名称
  • Demo
    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
    # 定义一个简单的全连接神经网络
    class SimpleNN(nn.Module):
    def __init__(self):
    super(SimpleNN, self).__init__()
    self.fc1 = nn.Linear(2, 2) # 输入层到隐藏层
    self.fc2 = nn.Linear(2, 1) # 隐藏层到输出层
    for param in self.fc2.parameters(): # 可指定设置某些参数不参与梯度更新(Frozen参数)
    param.requires_grad = False

    def forward(self, x):
    x = torch.relu(self.fc1(x)) # ReLU 激活函数
    x = self.fc2(x)
    return x

    # 创建网络实例
    model = SimpleNN()

    # 打印模型结构
    print(model)

    print("\nmodel.named_parameters():")
    # 遍历模型的所有参数并打印那些 requires_grad=True 的参数
    for name, param in model.named_parameters():
    if param.requires_grad:
    print(f"Parameter {name} requires gradient: {param}")
    else:
    print(f"Parameter {name} don't require gradient: {param}")

    print("\nmodel.parameters():")
    # 遍历模型的所有参数并打印那些 requires_grad=True 的参数
    for param in model.parameters():
    print(param.requires_grad)
    # SimpleNN(
    # (fc1): Linear(in_features=2, out_features=2, bias=True)
    # (fc2): Linear(in_features=2, out_features=1, bias=True)
    # )
    #
    # model.named_parameters():
    # Parameter fc1.weight requires gradient: Parameter containing:
    # tensor([[-0.6769, 0.7008],
    # [-0.6705, 0.2912]], requires_grad=True)
    # Parameter fc1.bias requires gradient: Parameter containing:
    # tensor([-0.6091, -0.2306], requires_grad=True)
    # Parameter fc2.weight don't require gradient: Parameter containing:
    # tensor([[ 0.5131, -0.4101]])
    # Parameter fc2.bias don't require gradient: Parameter containing:
    # tensor([-0.0788])
    #
    # model.parameters():
    # True
    # True
    # False
    # False

torch.empty()创建变量

  • torch.empty() 是 PyTorch 中用于创建一个新的张量(tensor)的函数,该张量不会被初始化,即它的元素值是未定义的。这意味着张量中的元素可能是任意值,取决于分配给张量的内存块之前的状态
  • 具体用途:
    • 快速创建张量 :当你需要一个具有特定形状的张量,但不关心初始值时,可以使用 torch.empty() 来快速创建它。这对于只需要分配内存而不必初始化数据的情况非常有用
    • 占位符张量 :在某些情况下,你可能想要创建一个张量作为占位符,稍后将用实际的数据填充它。例如,在实现算法或构建计算图时,你可能提前知道所需的空间大小,但暂时没有具体数值
    • 性能优化 :由于不需要初始化张量中的数据,torch.empty() 可以比 torch.zeros() 或 torch.ones() 更快地分配内存,因为它跳过了设置默认值的过程
    • 显存管理 :在GPU上操作时,有时会利用 torch.empty() 来预分配显存,避免后续操作中可能出现的内存碎片化问题

torch.linspace() & torch.arange()

  • torch.linspace 和 torch.arange 都是 PyTorch 中用于生成数值序列的函数,但它们在生成数值的方式和使用场景上有一些关键的不同
    • torch.linspace : 返回一个一维张量,包含从 start 到 end(包括两端点)之间等间距分布的 steps 个点
    • torch.arange : 返回一个一维张量,包含从 start 开始到 end 结束(不包括 end),以步长 step 增长的连续整数或浮点数
  • Demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch

    # 使用 linspace
    linspace_tensor = torch.linspace(0, 2, steps=3)
    print("Linspace:", linspace_tensor)

    # 使用 arange
    arange_tensor = torch.arange(0, 10, step=2)
    print("Arange:", arange_tensor)

    # Linspace: tensor([0.0000, 0.6667, 1.3333, 2.0000])
    # Arange: tensor([0, 2, 4, 6, 8])

tensor.is_contiguous()

  • tensor.is_contiguous() 用于检查一个张量(Tensor)是否是连续的(contiguous)。在PyTorch中,张量的存储方式可以分为连续和非连续。一个张量被认为是连续的,如果它的元素在内存中是按行优先顺序(row-major order)依次存放的,也就是说,在内存中这些元素是连续排列的
  • 连续排列对于需要高效访问或操作张量数据的操作非常重要,因为许多底层实现(例如,CUDNN库中的函数)要求输入的数据必须是连续存储的。如果一个张量不是连续的,即使它包含相同的数据,其内存布局也可能导致性能下降或者某些操作无法执行
  • 当调用 tensor.is_contiguous() 方法时,如果返回值为 True,则表示该张量是连续的;如果返回 False,则表示张量当前不是以连续的方式存储的。如果你需要将一个非连续的张量转换为连续的,可以使用 .contiguous() 方法来创建一个新的、与原张量具有相同数据但存储方式为连续的张量副本
  • 示例代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch

    x = torch.tensor([[1, 2, 3], [4, 5, 6]])
    print(x.is_contiguous()) # 输出: True

    # 假设进行了转置操作,通常会导致张量变为非连续
    x_t = x.t()
    print(x_t.is_contiguous()) # 输出: False

    # 使用 contiguous 方法将非连续张量转换为连续
    x_t_contig = x_t.contiguous()
    print(x_t_contig.is_contiguous()) # 输出: True

x.squeeze(dim) vs x.unsqueeze(dim)

  • unsqueeze:在指定维度添加一个维度,且该长度为 1
  • squeeze:去掉指定长度为 1 的维度
    • 如果指定的不是长度为 1 的维度,则不做任何修改
    • 如果不指定任何维度,则将所有长度为 1 的维度都去掉;
    • 如果不指定任何维度,且没有长度为 1 的维度,则不做任何修改
  • unsqueeze() 有等价形式,如对于二维的张量:
    • tensor.unsqueeze(dim=0) 等价于 tensor[None] 或 tensor[None,:,:]
    • tensor.unsqueeze(dim=1) 等价于 tensor[:,None] 或 tensor[:,None,:]
    • tensor.unsqueeze(dim=0) 等价于 tensor[:,:,None]
  • unsqueeze() 代码示例:
    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
    import torch
    import numpy as np
    x = torch.tensor([[3, 2, 1],
    [4, 5, 6]], dtype=torch.float32)

    y = x[None] # 等价于 y = x[None,:,:] 和 y = x.unsqueeze(dim=0)
    print(y)
    print(y.shape)

    y = x[:,None] # 等价于 y = x[:,None,:] 和 y = x.unsqueeze(dim=1)
    print(y)
    print(y.shape)

    y = x[:,:,None] # 和 y = x.unsqueeze(dim=2)
    print(y)
    print(y.shape)

    # tensor([[[3., 2., 1.],
    # [4., 5., 6.]]])
    # torch.Size([1, 2, 3])
    # tensor([[[3., 2., 1.]],
    # [[4., 5., 6.]]])
    # torch.Size([2, 1, 3])
    # tensor([[[3.],
    # [2.],
    # [1.]],
    # [[4.],
    # [5.],
    # [6.]]])
    # torch.Size([2, 3, 1])

torch.flatten() 和 torch.unflatten()

  • torch.flatten() 基本用法:

    1
    torch.flatten(input, start_dim=0, end_dim=-1)
    • input:需要被展平的输入张量
    • start_dim:从哪个维度开始展平,默认值为0(即从第0维开始)
    • end_dim:到哪个维度结束展平,默认值为-1(即到最后一维结束)
  • torch.unflatten() 基本用法

    1
    torch.unflatten(input, dim, unflattened_size)
    • input:需要被处理的输入张量
    • dim:需要被拆分的维度索引
    • unflattened_size:指定拆分后的维度大小,可以是元组或列表
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import torch

    x = torch.tensor([[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]])
    print(x.shape) # 输出: torch.Size([1, 2, 2, 2])

    # 展平整个张量
    y = torch.flatten(x)
    print(y.shape) # 输出: torch.Size([8])
    print(y) # 输出: tensor([1, 2, 3, 4, 5, 6, 7, 8])

    # 展平张量的中间两个维度
    z = torch.flatten(x, start_dim=1, end_dim=2)
    print(z.shape) # 输出: torch.Size([1,4,2])

    # 使用 unflatten 拆分维度
    w = z.unflatten(dim=1, sizes=(2,2))
    print(w) # 输出: torch.Size([1, 2, 2, 2])

tensor.numpy() vs torch.from_numpy()

  • torch.from_numpy(ndarray):将 NumPy 数组转换为张量,两者共享内存 ,修改NumPy变量会改变Tensor变量,反之亦然

  • tensor.numpy():将张量转换为 NumPy 数组(仅限 CPU 张量),两者共享内存 ,修改NumPy变量会改变Tensor变量,反之亦然

  • 代码示例:

    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
    import torch
    import numpy as np

    numpy_array = np.array([[1.0, 2, 3], [4, 5, 6]])
    tensor_from_numpy = torch.from_numpy(numpy_array)
    numpy_array[0, 0] = 100
    print("\n修改后的 NumPy 数组:\n", numpy_array.dtype,numpy_array)
    print("PyTorch 张量也会同步变化:\n", tensor_from_numpy)

    tensor = torch.tensor([[7, 8, 9], [10, 11, 12]], dtype=torch.float32)
    numpy_from_tensor = tensor.numpy()
    tensor[0, 0] = 77
    print("\n修改后的 PyTorch 张量:\n", tensor)
    print("NumPy 数组也会同步变化:\n", numpy_from_tensor)


    numpy_array = np.array([[1, 2, 3], [4, 5, 6]])
    tensor_from_numpy = torch.Tensor(numpy_array)
    numpy_array[0, 0] = 100
    print("\n修改后的 NumPy 数组:\n", numpy_array)
    print("PyTorch 张量不会同步变化:\n", tensor_from_numpy)

    # 修改后的 NumPy 数组:
    # float64 [[100. 2. 3.]
    # [ 4. 5. 6.]]
    # PyTorch 张量也会同步变化:
    # tensor([[100., 2., 3.],
    # [ 4., 5., 6.]], dtype=torch.float64)
    #
    # 修改后的 PyTorch 张量:
    # tensor([[77., 8., 9.],
    # [10., 11., 12.]])
    # NumPy 数组也会同步变化:
    # [[77. 8. 9.]
    # [10. 11. 12.]]
    #
    # 修改后的 NumPy 数组:
    # [[100 2 3]
    # [ 4 5 6]]
    # PyTorch 张量不会同步变化:
    # tensor([[1., 2., 3.],
    # [4., 5., 6.]])
  • 特别注意(其他共享内存相关函数)

    • 使用 torch.Tensor(numpy_array) 得到的变量只有在 numpy_array 的类型为 np.float32 时共享,否则不共享内存的(注意:torch.Tensor() 是无法指定数据类型的,默认类型就是 torch.float32)
    • 使用 torch.tensor(numpy_array) 得到的变量总是不共享内存,都是副本

inplace参数的使用

  • 在PyTorch中,inplace参数允许你指定操作是否直接在输入张量上进行而不需要额外的内存分配来存储结果。这对于减少内存使用特别有用,但需要注意的是,这也会覆盖原始数据。下面通过具体的例子说明如何使用inplace参数,以torch.nn.functional.relu为例:

示例代码

  • 代码示例:

    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
    import torch
    import torch.nn.functional as F

    # 创建一个随机张量
    input_tensor = torch.randn(2, 3)
    print("Original tensor:")
    print(input_tensor)

    # 不使用inplace模式(默认)
    output_tensor = F.relu(input_tensor)
    print("\nOutput tensor (not inplace):")
    print(output_tensor)
    print("\nOriginal tensor after not inplace operation:")
    print(input_tensor) # 原始张量未被修改

    # 使用inplace模式
    F.relu(input_tensor, inplace=True)
    print("\nOriginal tensor after inplace operation:")
    print(input_tensor) # 原始张量被修改,应用了ReLU

    # Original tensor:
    # tensor([[-0.7269, -0.2997, 0.1654],
    # [ 1.2219, 1.2698, -0.5245]])
    #
    # Output tensor (not inplace):
    # tensor([[0.0000, 0.0000, 0.1654],
    # [1.2219, 1.2698, 0.0000]])
    #
    # Original tensor after not inplace operation:
    # tensor([[-0.7269, -0.2997, 0.1654],
    # [ 1.2219, 1.2698, -0.5245]])
    #
    # Original tensor after inplace operation:
    # tensor([[0.0000, 0.0000, 0.1654],
    # [1.2219, 1.2698, 0.0000]])
    • 输出解释
      • 不使用inplace:当你调用F.relu(input_tensor)时,默认情况下不会修改原始的input_tensor,而是返回一个新的张量output_tensor作为ReLU操作的结果
      • 使用inplace=True:当你指定inplace=True,如F.relu(input_tensor, inplace=True),此时ReLU操作会直接作用于input_tensor本身,即它将所有负值设置为0,并且这个修改是直接在原来的张量上进行的,不会创建新的张量。因此,在执行完这个操作后,原始的input_tensor已经被修改

关于inplace的其他说明

  • 使用inplace操作可以节省一些内存,因为不需要为输出分配新的空间
    • 它会直接修改原张量的数据,在需要保留原数据的情况下要谨慎使用
    • 并不是所有的函数都支持inplace参数,具体的支持情况可以查阅相关函数的文档说明
  • 这种方式同样适用于其他许多具有inplace选项的操作,比如激活函数、归一化等,理解并正确使用inplace可以帮助更有效地管理和优化你的模型训练过程
  • PyTorch 中,常常用以 _ 结尾的函数实现 inplace 操作

torch.device() 和 tensor.to()函数

  • 设备获取:torch.device('cuda')用于设备获取
  • tensor迁移:tensor = tensor.to(device)
  • 模型迁移:model = model.to(device)
    • 会将模型的所有可学习参数以及模型中的缓冲区移动到指定的 device 设备上
    • 所有可学习参数 :也就是模型的权重和偏置,模型的可学习参数由 model.parameters() 对象来表示(这里面包含模型的所有nn.Parameter对象)。这些参数一般是在模型的 __init__ 方法中定义的
    • 模型中的缓冲区 :即不需要反向传播更新的张量,例如 BatchNorm 层中的运行统计量,详细来说,例如 torch.nn.BatchNorm2d 层中的 running_mean 和 running_var

tensor.view() 和 tensor.reshape()

  • 在 PyTorch 中,tensor.view() 和 tensor.reshape() 都可以用来改变张量的形状,但它们之间有一些关键的区别
  • tensor.view() :
    • 它要求数据在内存中是连续存储的。如果 tensor 不是连续的(例如,经过 transpose、permute 或其他操作后),使用 .view() 会抛出错误。这时你需要先调用 .contiguous() 来确保 tensor 在内存中是连续的,然后才能使用 .view()
    • .view() 的性能可能更好,因为它直接改变了对原始数据的视图而没有复制数据
  • tensor.reshape() :
    • tensor.reshape() 等价于 tensor.contiguous().view()
    • tensor.reshape()可以在张量不是连续的情况下工作,因为它会在必要时创建张量的一个副本
      • 如果张量是连续的,不会创建副本
    • .reshape() 是自 PyTorch 0.4.0 版本引入的一个函数,旨在提供一种更一致的方式来处理形状变换。无论张量是否连续,它都可以工作
  • 一句话总结:
    • 如果确定张量是连续的 ,并且希望明确表达避免不必要的数据复制以提高性能 ,那么可以选择使用 .view()
    • 然而,如果不确定张量是否连续或者你不关心额外的数据复制 ,.reshape() 提供了一个更为方便和灵活的选择
      • 在大多数情况下,为了代码的健壮性和易读性,推荐使用 .reshape()
      • 在连续存储下, .reshape() 和 .view() 是等价的,无需担心性能

tensor.topk()函数

  • torch.topk() 用于获取张量中指定维度上的前 k 个最大/最小值及其索引,常用于分类任务中获取 top-k 的预测结果,其函数定义如下:

    1
    torch.topk(input, k, dim=None, largest=True, sorted=True, *, out=None)
  • 参数说明:

    • input : 输入的张量
    • k : 需要返回的最大(或最小)值的数量
    • dim : 沿着哪个维度进行操作。如果未指定,则默认在最后一个维度上操作
    • largest : 如果为 True,则返回最大的 k 个值;如果为 False,则返回最小的 k 个值。默认为 True
    • sorted : 如果为 True,则返回的结果会按照大小排序;如果为 False,则返回的结果顺序不确定。默认为 True
    • out : 可选的输出元组,用于存储结果
  • 返回值

    • values : 前 k 个最大(或最小)的值
    • indices : 这些值在输入张量中的索引
  • 用法示例

    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
    import torch
    x = torch.tensor([[3, 2, 1],
    [4, 5, 6]])
    # 获取每行的前 2 个最大值,下面的式子等价于 values, indices = torch.topk(x, k=2, dim=1)
    values, indices = torch.topk(x, k=2) # 默认在最后一个维度,即 dim=1 上操作,按行取最大值(生成的index是指定dim=1维度的索引)
    # 用 indices 从 x 中检索出 values
    retrieved_values = torch.gather(x, dim=1, index=indices) # 用 indices 替换 dim=1 索引维度即可抽取到对应的值
    print("values:\n", values)
    print("indices:\n", indices)
    print("retrieved_values:\n", retrieved_values)

    print("---")
    # 获取每列的前 1 个最大值
    values, indices = torch.topk(x, k=1, dim=0) # 在 dim=0 上操作,生成的index是指定dim=0维度的索引
    # 用 indices 从 x 中检索出 values
    retrieved_values = torch.gather(x, dim=0, index=indices)
    print("values:\n", values)
    print("indices:\n", indices)
    print("retrieved_values:\n", retrieved_values)

    # values:
    # tensor([[3, 2],
    # [6, 5]])
    # indices:
    # tensor([[0, 1],
    # [2, 1]])
    # retrieved_values:
    # tensor([[3, 2],
    # [6, 5]])
    # ---
    # values:
    # tensor([[4, 5, 6]])
    # indices:
    # tensor([[1, 1, 1]])
    # retrieved_values:
    # tensor([[4, 5, 6]])

nn.Module.register_buffer

  • 在PyTorch中,self.register_buffer 是一个用于向模块(nn.Module)注册持久化缓冲区(buffer)的方法。它的主要作用是告诉PyTorch某些张量是模型的一部分,但不属于可训练参数(即不需要梯度更新),但这些张量在模型保存或加载时需要被包含进来
  • 非可训练参数:通过 register_buffer 注册的张量不会被优化器更新(不像 nn.Parameter)
    • 注:在BatchNorm中,running_mean 和 running_var 是统计量,需要跟踪但不参与梯度计算,也属于这一类
  • 持久化:注册的buffer会被包含在 model.state_dict() 中,因此当调用 torch.save 和 torch.load 时,它们会被自动保存和加载
  • 设备移动:当调用 model.to(device) 时,这些buffer会自动移动到对应的设备(如CPU/GPU),与模型的其他参数一致
  • 获取值:被包含在 model.state_dict()中
  • 常见用法:一些固定常量参数或者统计量
  • 使用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyModel(nn.Module):
    def __init__(self):
    super().__init__()
    self.weight = nn.Parameter(torch.randn(3, 3))
    # 注册一个buffer(非可训练但需要持久化)
    self.register_buffer('buffer_a', torch.zeros(3))
    model = MyModel()
    print(model.state_dict())
    # 输出包含:weight 和 buffer_a

PyTorch中的对数运算

  • 以 e 为底:x.log() == torch.log(x)
  • 以 2 为底:x.log2() == torch.log2(x)
  • 以 10 为底:x.log10() == torch.log10(x)
  • 以其他数字为底的通过对数换底公式实现:
    $$ \log_5 x = \frac{\ln x}{\ln 5} $$
    • 实现代码
      1
      2
      3
      4
      5
      import torch

      def log5(x):
      """计算以5为底的对数,输入为Tensor"""
      return torch.log(x) / torch.log(torch.tensor(5.0))

torch.clip 和 torch.clamp

  • 在 PyTorch 里,torch.clip 和 torch.clamp 功能完全一样,都能用于将输入张量的数值限制在指定的区间内,从英文含义上看,两者分别是截取和限制的含义,都差不多,但理论上 clip 会更符合原本这个函数“裁剪”的含义
  • torch.clamp 是 PyTorch 从早期就存在的传统函数,一直被广泛运用
  • torch.clip 是在版本 1.7.0 时新增的函数,目的是和 NumPy 的 np.clip 保持 API 一致,方便用户从 NumPy 迁移过来
  • 推荐使用 torch.clip,方便阅读,但为了和更早的 torch 版本兼容,还有很多人使用 clamp

torch.allclose() 函数

  • 用于比较两个张量是否在给定的误差范围内“几乎相等”

  • 基本语法

    1
    torch.allclose(input, other, rtol=1e-05, atol=1e-08, equal_nan=False)
  • 特别注意:

  • other 与 input 形状必须相同

  • equal_nan:若为True,则NaN值被视为相等;若为False,则NaN会导致比较失败

  • 若所有元素的差异都在容差范围内,则返回True;否则返回False。对于每个元素,比较条件为:

    1
    |input - other| ≤ atol + rtol * |other|
  • 容易混淆的相关方法的比较(以下两个方法都是逐个元素比较,返回逐元素结果的)

    • torch.isclose() :逐元素比较两个张量是否接近,返回与输入形状相同的布尔张量(注意不是只返回一个值,是逐元素比较结果)

      1
      torch.isclose(a, b, rtol=1e-5, atol=1e-8, equal_nan=False)
    • torch.eq() :严格逐元素相等比较(不考虑容差),返回与输入形状相同的布尔张量


torch.chuck 函数用法

  • torch.chunk 是 PyTorch 中用于将张量按照指定维度拆分成多个子张量的函数,返回一个包含拆分后子张量的元组

  • torch.chunk 拆分后的数据与原张量共享内存(浅拷贝),修改子张量会影响原张量

  • 函数定义为:

    1
    torch.chunk(input, chunks, dim=0)
    • input:待拆分的输入张量(torch.Tensor)
    • chunks:拆分的数量(int)。需注意:若输入张量在 dim 维度的大小不能被 chunks 整除,最后一个子张量的大小会略小(其余子张量大小相等)
    • dim:指定拆分的维度(int,默认值为 0)
    • 返回:一个元组(tuple),包含 chunks 个子张量(或最后一个子张量略小)
  • torch.chunk 与 torch.split 的区别:

    • torch.chunk 按“数量”拆分(chunks 参数),子张量大小尽可能平均;
    • torch.split 按“指定大小”拆分(如 split_size_or_sections=2 表示每个子张量大小为 2)

torch.chunk 的一些示例

  • 基本用法(1D 张量)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import torch

    # 能整除的情况
    x = torch.tensor([1, 2, 3, 4, 5, 6])
    chunks = torch.chunk(x, chunks=3, dim=0) # 沿第0维拆分成3份
    print(chunks)
    # 输出:(tensor([1, 2]), tensor([3, 4]), tensor([5, 6]))

    # 不能整除的情况
    x = torch.tensor([1, 2, 3, 4, 5])
    chunks = torch.chunk(x, chunks=2, dim=0) # 5不能被2整除,最后一个子张量多1个元素
    print(chunks)
    # 输出:(tensor([1, 2]), tensor([3, 4, 5]))
  • 高维张量拆分(2D 张量)示例

    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
    x = torch.arange(12).reshape(3, 4)  # 形状为 (3, 4) 的矩阵
    print("原始张量:\n", x)
    # 原始张量:
    # tensor([[ 0, 1, 2, 3],
    # [ 4, 5, 6, 7],
    # [ 8, 9, 10, 11]])

    # 沿 dim=0(行维度)拆分成2份
    chunks_dim0 = torch.chunk(x, chunks=2, dim=0)
    print("沿行拆分:\n", chunks_dim0)
    # 沿行拆分:
    # (tensor([[0, 1, 2, 3],
    # [4, 5, 6, 7]]),
    # tensor([[ 8, 9, 10, 11]]))

    # 沿 dim=1(列维度)拆分成2份
    chunks_dim1 = torch.chunk(x, chunks=2, dim=1)
    print("沿列拆分:\n", chunks_dim1)
    # 沿列拆分:
    # (tensor([[0, 1],
    # [4, 5],
    # [8, 9]]),
    # tensor([[ 2, 3],
    # [ 6, 7],
    # [10, 11]]))

torch.where 函数和 tensor.where 函数

  • torch.where() 是 PyTorch 中用于基于条件对张量元素进行选择性替换的方法,类似于“三目运算符”的向量版,语法如下:

    1
    torch.where(condition, x, y)
    • condition:布尔型张量(与原张量同形状),用于判断每个元素是否满足条件
    • x:当 condition 为 True 时,保留或使用 x 的值(可与原张量同形状,或为标量)
    • y:当 condition 为 False 时,使用 y 的值(可与原张量同形状,或为标量)
    • 返回值:一个新张量,每个元素根据 condition 从 x 或 y 中取值
  • 注:x.where(condition, y) 是实例方法,等价于全局函数 torch.where(condition, x, y)

tensor.where(condition, y)(实例方法)

  • 张量对象的实例方法,语法为:

    1
    result = tensor.where(condition, y)
    • condition(必选):布尔型张量(torch.BoolTensor),形状必须与 tensor 相同(或可广播为相同形状)
      • 用于判断每个元素是否满足条件,决定最终取值来源
    • y(必选): 张量(与 tensor 同数据类型)或标量(如 int、float)
      • 若为张量,形状必须与 tensor 相同(或可广播为相同形状);若为标量,会自动广播到 tensor 的形状
        • 当 condition 为 False 时,使用 y 的值(或对应位置的元素)替换 tensor 中的元素

torch.where(condition, x, y)(全局函数)

  • PyTorch 的全局函数,语法为:

    1
    result = torch.where(condition, x, y)
    • condition(必选):布尔型张量(torch.BoolTensor),形状必须与 x、y 相同(或可广播为相同形状)
      • 用于判断每个元素是否满足条件,决定从 x 或 y 中取值
    • x(必选):张量(与 y 同数据类型)或标量
      • 若为张量,形状必须与 condition 相同(或可广播为相同形状);若为标量,会自动广播到对应形状
      • 当 condition 为 True 时,使用 x 的值(或对应位置的元素)
    • y(必选):张量(与 x 同数据类型)或标量
      • 若为张量,形状必须与 condition 相同(或可广播为相同形状);若为标量,会自动广播到对应形状
      • 当 condition 为 False 时,使用 y 的值(或对应位置的元素)

示例

  • 代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import torch

    tensor = torch.tensor([1, 5, 3, 8, 2, 7])
    condition = tensor % 2 == 0
    y = torch.tensor([-1, -1, -1, -1, -1, -1])

    # 方法1:tensor.where(condition, y) —— 实例方法
    result_instance = tensor.where(condition, y)

    # 等价方法:torch.where(condition, tensor, y) —— 函数
    result_function = torch.where(condition, tensor, y)

    print("tensor.where() 结果:", result_instance) # tensor([-1, -1, -1, 8, 2, -1])
    print("torch.where() 结果:", result_function) # tensor([-1, -1, -1, 8, 2, -1])

torch.size() 函数 和 tensor.size(n)

tensor.size() 函数

  • 返回张量的完整形状 ,描述张量在每个维度上的元素个数
  • 返回值 torch.Size 可直接当作 tuple 使用(支持索引、len() 等),例如 len(t2.size()) 会返回张量的维度数(2)
  • 等价写法:tensor.shape(属性,功能与 size() 完全一致,更简洁),例如 t2.shape 与 t2.size() 结果相同

tensor.size(n) 函数

  • 返回张量第 n 维的元素个数(维度索引从 0 开始)

  • 用法:

    1
    tensor.size(dim)  # dim:指定维度的索引(0 表示第1维,1 表示第2维,以此类推)
    • 如 tensor.size(1) 等价于 tensor.size()[1] 或 tensor.shape[1]
  • 维度索引从 0 开始:

    • size(0) 是第 1 维
    • size(1) 是第 2 维
    • size(-1) 表示最后一维(常用技巧)
  • 若指定的维度索引超出张量的维度范围 会抛出 IndexError

    • 例如 1D 张量用 size(1) 会抛异常

对比 torch.size() 函数 和 tensor.size(n)

  • size():快速查看张量整体形状(例如确认输入数据是否符合模型要求)
  • size(1):常用于提取矩阵的列数、文本张量的词向量维度等(例如 batch_size, seq_len = tensor.size(0), tensor.size(1))
  • 使用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import torch

    # 2D 张量(矩阵)整体输出
    t2 = torch.tensor([[1, 2], [3, 4], [5, 6]])
    print(t2.size()) # 输出: torch.Size([3, 2]),2维,3行2列

    # 1D 张量:没有第1维(索引1超出范围),会报错
    t1 = torch.tensor([1, 2, 3])
    # print(t1.size(1)) # 报错:IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)

    # 2D 张量:第1维(索引1)是列数
    print(t2.size(1)) # 输出: 2,对应 shape [3, 2] 的第2个元素(列数)
    print(t2.size()[1]) # 等价写法,输出: 2

torch.roll() 函数

  • torch.roll 用于循环移位(滚动)张量元素 ,支持沿指定维度对张量元素进行循环平移,移位后超出边界的元素会从另一侧补回(类似“循环队列”的逻辑)

  • 函数签名为:

    1
    torch.roll(input, shifts, dims=None) -> Tensor
    • input:Tensor,输入张量(任意维度) |
    • shifts:int / 序列,移位步数:
      • 正数:沿维度从后向前移(向右/向下);
      • 负数:沿维度从前向后移(向左/向上);
      • 序列形式(如 (2, -1)):需与 dims 一一对应,为每个维度指定独立移位步数
    • dims:int / 序列 / None,移位维度:
      • None(默认):先将张量展平为 1D 再移位,最后恢复原形状;
      • 单个 int:仅沿该维度移位;
      • 序列形式(如 (0, 1)):需与 shifts 长度一致,对多个维度依次移位
    • 返回值:与 input 形状、数据类型完全相同的新张量(原张量不改变,除非用 in-place 版本 torch.roll_)

使用示例

  • 1D 张量示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import torch

    x = torch.tensor([0, 1, 2, 3, 4])

    # 向右移位 2 步(正数=向后移)
    roll1 = torch.roll(x, shifts=2)
    print(roll1) # tensor([3, 4, 0, 1, 2])

    # 向左移位 1 步(负数=向前移,等价于 shifts=len(x)-1)
    roll2 = torch.roll(x, shifts=-1)
    print(roll2) # tensor([1, 2, 3, 4, 0])
  • 2D 张量示例(注:dim=0(行方向,上下移)、dim=1(列方向,左右移))

    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
    # 仅单维度移位示例
    x = torch.tensor([[1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]])

    # 沿 dim=0(行)向下移 1 步(最后一行绕回开头)
    roll_row = torch.roll(x, shifts=1, dims=0)
    print("行移位:")
    print(roll_row)
    # tensor([[7, 8, 9],
    # [1, 2, 3],
    # [4, 5, 6]])

    # 沿 dim=1(列)向左移 1 步(第一列绕回末尾)
    roll_col = torch.roll(x, shifts=-1, dims=1)
    print("列移位:")
    print(roll_col)
    # tensor([[2, 3, 1],
    # [5, 6, 4],
    # [8, 9, 7]])

    # 多维度同时移位:需保证 `shifts` 和 `dims` 长度一致,分别对应每个维度的移位步数
    x = torch.tensor([[1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]])

    # 沿 dim=0 向下移 1 步,沿 dim=1 向右移 2 步
    roll_multi = torch.roll(x, shifts=(1, 2), dims=(0, 1))
    print(roll_multi)
    # 步骤解析:
    # 1. dim=0 移位后:[[7,8,9],[1,2,3],[4,5,6]]
    # 2. 再对 dim=1 移位 2 步:[[8,9,7],[2,3,1],[5,6,4]]
    # 最终结果:
    # tensor([[8, 9, 7],
    # [2, 3, 1],
    # [5, 6, 4]])
  • dims=None(展平后移位,再恢复原来 shape):默认将张量展平为 1D 移位,再恢复原形状,慎用(可能导致元素顺序混乱)

    1
    2
    3
    4
    5
    x = torch.tensor([[1, 2], [3, 4]])

    # 展平为 [1,2,3,4],移位 1 步 -> [4,1,2,3],再恢复为 (2,2)
    roll_flat = torch.roll(x, shifts=1, dims=None)
    print(roll_flat) # tensor([[4, 1], [2, 3]])

使用注意事项

  • 移位步数可以超界(但不建议) :步数会自动对维度长度取模(如长度为 5 的维度,移位 7 步等价于移位 7%5=2 步)

    1
    2
    x = torch.tensor([0,1,2,3,4])
    print(torch.roll(x, shifts=7)) # 等价于 shifts=2 -> tensor([3,4,0,1,2])
  • 原张量不改变 :torch.roll 是“非 in-place”操作,如需修改原张量,使用 torch.roll_(末尾加下划线)

    1
    2
    3
    x = torch.tensor([0,1,2])
    x.roll_(shifts=1) # 原张量被修改
    print(x) # tensor([2, 0, 1])
  • 梯度传播 :支持自动求导(梯度会跟随移位逻辑反向传播),可用于神经网络层中


torch.quantile() 函数

  • torch.quantile 是用于计算张量分位数的函数,支持多维张量、指定维度计算、线性插值等功能,适用于统计分析、异常值检测等场景

  • torch.quantile 支持多维张量、批量分位数、多种插值模式,核心是通过 dim 控制计算维度、q 指定分位数、interpolation 调整插值逻辑

  • torch.quantile 函数签名

    1
    2
    3
    4
    5
    6
    7
    8
    torch.quantile(
    input: Tensor,
    q: Union[float, Tensor],
    dim: Optional[Union[int, Tuple[int, ...]]] = None,
    keepdim: bool = False,
    interpolation: str = 'linear',
    out: Optional[Tensor] = None
    ) -> Tensor
    • input:Tensor
      • 输入张量(支持任意维度,如 1D/2D/3D 等)
    • q:float 或 Tensor
      • 分位数(范围:[0, 1])
      • 可以是单个值(如 0.5 表示中位数)或张量(批量计算多个分位数)
    • dim:int 或 Tuple[int, ...]
      • 计算分位数的维度(可选)。默认 None 表示对整个张量展平后计算
    • keepdim:bool
      • 是否保留计算维度(默认 False,即压缩维度;True 则维度数不变)
    • interpolation:str
      • 分位数插值方式(默认 'linear'),支持 5 种模式(下文详细说明)
        • 'linear'(默认),线性插值:q = i + f 时,结果 = (1-f)*x[i] + f*x[i+1](最常用)
        • 'lower',取下界:结果 = x[floor(i + f)](即小于等于目标位置的最大元素)
        • 'higher',取上界:结果 = x[ceil(i + f)](即大于等于目标位置的最小元素)
        • 'nearest',取最近邻:结果 = x[round(i + f)](四舍五入到最近索引)
        • 'midpoint',中点插值:结果 = (x[floor(i + f)] + x[ceil(i + f)]) / 2(上下界平均)

使用示例

  • 1D 张量(展平计算),计算单个分位数(中位数)和多个分位数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch

    x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])

    # 1. 计算中位数(0.5 分位数)
    median = torch.quantile(x, q=0.5)
    print("中位数:", median) # 输出:tensor(3.)

    # 2. 计算多个分位数(0.25、0.5、0.75 四分位数)
    q_list = torch.tensor([0.25, 0.5, 0.75])
    quantiles = torch.quantile(x, q=q_list)
    print("四分位数:", quantiles) # 输出:tensor([2., 3., 4.])
  • 多维张量(指定维度计算):对 2D 张量的行/列计算分位数,控制 dim 和 keepdim:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float32)

    # 1. 按列(dim=0)计算中位数,保留维度(keepdim=True)
    col_median = torch.quantile(x, q=0.5, dim=0, keepdim=True)
    print("按列中位数(保留维度):\n", col_median)
    # 输出:tensor([[4., 5., 6.]])(形状:[1, 3])

    # 2. 按行(dim=1)计算多个分位数(0.25、0.75),不保留维度
    row_quantiles = torch.quantile(x, q=[0.25, 0.75], dim=1)
    print("按行四分位数(不保留维度):\n", row_quantiles)
    # 输出:tensor([[2., 5., 8.], [3., 6., 9.]])(形状:[2, 3])

注意事项

  • input 需为浮点型(float32/float64),整数型张量会自动转换为浮点型
  • q 必须在 [0, 1] 内,否则会抛出 ValueError
  • torch.quantile 的参数(如 interpolation)与 numpy.quantile 基本一致,可无缝迁移

torch.nn.functional.pad()(简称 F.pad())

  • F.pad() 是用于张量填充的核心函数,支持任意维度的对称/非对称填充、多种填充模式(常数、反射、复制等),

  • F.pad() 函数签名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import torch.nn.functional as F

    F.pad(
    input: torch.Tensor,
    pad: Sequence[int],
    mode: str = 'constant',
    value: float = 0.0,
    **kwargs
    ) -> torch.Tensor
    • input:torch.Tensor
      • 输入张量(支持任意维度:1D/2D/3D/4D/5D 等)
    • pad:Sequence[int]
      • 填充尺寸配置(关键参数),格式为 (left0, right0, left1, right1, left2, right2, ...),对应张量从最后一维到第一维的左右填充数
      • left0, right0 表示 dim=-1 的维度(最后一维)的左边和右边分别 pad 数量配置
      • 对于 N 维张量,pad 需包含 2*N 个整数(若某维度无需填充,填 0)
      • 每个维度的填充格式为 (left_pad, right_pad),即 左边填充数,右边填充数
      • 示例:
        • 1D 张量(shape [L]):pad=(left, right) ,填充后 shape [left + L + right];
        • 2D 张量(shape [H, W]):pad=(left_w, right_w, left_h, right_h),填充后 shape [left_h + H + right_h, left_w + W + right_w]
        • 3D 张量(shape [D, H, W]):pad=(left_w, right_w, left_h, right_h, left_d, right_d),填充后 shape [left_d + D + right_d, left_h + H + right_h, left_w + W + right_w]
    • mode:str
      • 填充模式(默认 'constant'),支持 6 种模式
        • 'constant'(默认):用固定值 value 填充(如 0 填充)
        • 'reflect',反射填充:以张量边缘为对称轴反射(不包含边缘本身);注意,此时张量边缘是对称轴
        • 'replicate',复制填充:用张量边缘的元素填充(重复边缘值)
        • 'circular',循环填充:将张量视为循环结构,用对侧的元素填充(环绕填充)
        • 'edge',等价于 'replicate'(兼容旧版本 PyTorch)
        • 'symmetric',对称填充:以张量边缘为对称轴反射(包含边缘本身)
    • value:float
      • 仅 mode='constant' 时有效,指定填充的常数(默认 0)
    • **kwargs:
      • 额外参数(如 reflect/replicate 模式下的 padding_mode 兼容参数,较少用)

不同模式效果对比

  • 以 2D 张量 [[1,2],[3,4]] 为例,左/右/上/下各填充 1 个元素,对比核心模式:
    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
    import torch
    import torch.nn.functional as F

    x = torch.tensor([[1,2],[3,4]], dtype=torch.float32)
    pad = (1,1,1,1) # (left_w=1, right_w=1, left_h=1, right_h=1)

    # 不同模式填充结果简单对比
    print("constant (value=0):")
    print(F.pad(x, pad, mode='constant', value=0))
    # 输出:
    # tensor([[0., 0., 0., 0.],
    # [0., 1., 2., 0.],
    # [0., 3., 4., 0.],
    # [0., 0., 0., 0.]])

    print("\nreflect:")
    print(F.pad(x, pad, mode='reflect'))
    # 输出(反射不包含边缘):
    # tensor([[4., 3., 4., 3.],
    # [2., 1., 2., 1.],
    # [4., 3., 4., 3.],
    # [2., 1., 2., 1.]])

    print("\nreplicate:")
    print(F.pad(x, pad, mode='replicate'))
    # 输出(复制边缘):
    # tensor([[1., 1., 2., 2.],
    # [1., 1., 2., 2.],
    # [3., 3., 4., 4.],
    # [3., 3., 4., 4.]])

注意事项

  • pad 维度顺序 :必须是「最后一维 到 第一维」的左右填充,容易混淆(如 2D 张量先 W 后 H),填错会导致尺寸异常
  • F.pad 是可微分操作,填充的常数部分梯度为 0,反射/复制部分梯度会反向传播到原张量边缘元素
  • 'constant'/'replicate' 模式效率最高,'reflect'/'symmetric' 稍慢,'circular' 因循环逻辑效率较低(大张量建议提前优化)
  • 与 nn.ZeroPad2d 等层的区别 :nn.ZeroPad2d/nn.ReflectionPad2d 是封装好的层(仅支持特定维度),F.pad 更灵活(支持任意维度和模式),功能完全覆盖前者

torch.unbind 函数

  • torch.unbind 是 PyTorch 中用于拆分张量维度的核心函数,作用是将一个张量沿着指定维度(dim)“解绑”(拆分)为多个独立的张量,返回这些张量的元组

  • 简单理解:假设有一个 shape 为 (batch_size, seq_len, hidden_dim) 的张量

    • 沿着 dim=0(batch 维度)unbind 后,会得到 batch_size 个 shape 为 (seq_len, hidden_dim) 的张量;
    • 沿着 dim=1(seq_len 维度)unbind 后,会得到 seq_len 个 shape 为 (batch_size, hidden_dim) 的张量
  • 函数签名与核心参数

    1
    torch.unbind(input, dim=0) -> tuple[Tensor, ...]
    • input:待拆分的输入张量(任意维度)
    • dim:指定拆分的维度(默认 0,即第一个维度)
    • 返回值:拆分后的张量组成的元组,元组长度 = 原张量在 dim 维度上的大小

tensor.flip(dims) 函数

  • tensor.flip(dims) 用于沿着指定维度翻转张量(reverse the order of elements),仅改变指定维度上元素的顺序,不改变张量的形状、数据类型和设备

    • dims 是整数或整数元组,指定要翻转的维度(0=第1维,1=第2维,以此类推)
    • 沿 dims 维度反转元素顺序,非指定维度保持不变
    • 张量的 shape、dtype、device、requires_grad 均不改变
    • 支持自动微分(梯度会正确传播)
  • 注:flip 还有个实现是参数可以是 可变位置参数(即展开的列表/元组)

    1
    2
    3
    4
    @overload
    def flip(self, dims: _size) -> Tensor: ...
    @overload
    def flip(self, *dims: _int) -> Tensor: ... # 可变位置参数
  • 使用示例

    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
    import torch

    # 1D 整数张量
    a = torch.tensor([1, 2, 3, 4])
    a_flip = a.flip(dims=[0]) # 或 a.flip(0),沿维度0翻转(唯一维度),等价于 `tensor[::-1]`
    print("1D flip结果:", a_flip) # 输出: tensor([4, 3, 2, 1])
    print("形状不变:", a_flip.shape == a.shape) # 输出: True

    # 2D 浮点数张量,可指定沿行(维度0)、列(维度1)或同时沿行列翻转
    b = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])

    b_flip_0 = b.flip(dims=[0]) # 沿行翻转(上下颠倒) 等价于 b.flip(0)
    print("沿行翻转:\n", b_flip_0)
    # 输出:
    # tensor([[5., 6.],
    # [3., 4.],
    # [1., 2.]])

    b_flip_1 = b.flip(dims=[1]) # 沿列翻转(左右颠倒)
    print("沿列翻转:\n", b_flip_1)
    # 输出:
    # tensor([[2., 1.],
    # [4., 3.],
    # [6., 5.]])

    b_flip_01 = b.flip(dims=[0, 1]) # 同时沿行和列翻转 等价于 b.flip(0,1)
    print("沿行列翻转:\n", b_flip_01)
    # 输出:
    # tensor([[6., 5.],
    # [4., 3.],
    # [2., 1.]])

    # 3D 张量(模拟2个2×2的图像),可指定任意维度翻转(如batch维度、高度维度、宽度维度)
    c = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # shape: (2, 2, 2)

    c_flip_2 = c.flip(dims=[2]) # 沿宽度维度(第3维)翻转
    print("3D沿宽度翻转:\n", c_flip_2)
    # 输出:
    # tensor([[[2, 1],
    # [4, 3]],
    # [[6, 5],
    # [8, 7]]])

    c_flip_0 = c.flip(dims=[0]) # 沿batch维度翻转
    print("3D沿batch翻转:\n", c_flip_0)
    # 输出:
    # tensor([[[5, 6],
    # [7, 8]],
    # [[1, 2],
    # [3, 4]]])

torch.nn.utils.rnn.pad_sequence 函数

  • torch.nn.utils.rnn.pad_sequence 是 PyTorch 中处理变长序列(Variable-length Sequences) 的工具,用于将一批长度不同的张量(序列)填充到相同长度,以便批量输入 RNN、Transformer 等模型

  • 核心功能是将一个 张量列表(每个张量对应一条变长序列)填充为一个 统一长度的二维/高维张量 ,填充值默认是 0(可自定义);其填充规则为:

    • 短序列在 末尾(右侧) 补零(默认,可通过 batch_first 调整返回值维度顺序);
    • 最终长度由列表中 最长序列的长度 决定
  • 函数签名

    1
    2
    3
    4
    5
    torch.nn.utils.rnn.pad_sequence(
    sequences, # 变长序列的列表
    batch_first=False, # 输出张量的维度顺序:是否为 (batch_size, seq_len, ...)
    padding_value=0.0 # 填充值(默认0)
    ) -> Tensor
    • sequences:List[Tensor]
      • 必须是张量列表 ,每个张量的形状需满足:
        • 1D 张量:(seq_len_i,)(如单特征序列);
        • 2D 张量:(seq_len_i, feature_dim)(如带特征维度的序列);
        • 高维张量:(seq_len_i, d1, d2, ...)(如序列+多特征维度);
        • 注意 :所有张量的除了 第一个 维度(要求第一个维度为待对齐的序列长度)外,其余维度必须一致
    • batch_first:bool
      • 控制输出张量的维度顺序:
        • False(默认):输出形状 (max_seq_len, batch_size, ...)(适配 PyTorch RNN 层默认输入格式);
        • True:输出形状 (batch_size, max_seq_len, ...)(更直观,适合自定义模型)
    • padding_value:float
      • 虽然写的是 float,但可以填充整数,默认 0 或 0.0,(类型需与输入张量 dtype 兼容)

用法示例

  • 1D 变长序列(单特征)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import torch
    from torch.nn.utils.rnn import pad_sequence

    # 生成3条长度不同的1D序列(seq_len分别为2、3、1)
    seq1 = torch.tensor([1, 2]) # shape: (2,)
    seq2 = torch.tensor([3, 4, 5]) # shape: (3,)
    seq3 = torch.tensor([6]) # shape: (1,)
    sequences = [seq1, seq2, seq3]

    # 1. 默认参数(batch_first=False,padding_value=0)
    padded = pad_sequence(sequences)
    print("默认输出 shape:", padded.shape) # (max_seq_len=3, batch_size=3)
    print("默认输出:\n", padded)
    # 输出:
    # tensor([[1, 3, 6],
    # [2, 4, 0],
    # [0, 5, 0]])
  • 2D 变长序列(带特征维度):适用于每条序列的每个元素是一个特征向量(如词嵌入后的序列):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 2D序列:shape (seq_len_i, feature_dim),feature_dim=2(统一)
    seq1 = torch.tensor([[1, 10], [2, 20]]) # (2, 2)
    seq2 = torch.tensor([[3, 30], [4, 40], [5, 50]]) # (3, 2)
    seq3 = torch.tensor([[6, 60]]) # (1, 2)
    sequences = [seq1, seq2, seq3]

    padded = pad_sequence(sequences, batch_first=True)
    print("2D序列填充后 shape:", padded.shape) # (3, 3, 2)(batch_size=3, max_seq_len=3, feature_dim=2)
    print("2D序列填充后:\n", padded)
    # 输出:
    # tensor([[[ 1, 10],
    # [ 2, 20],
    # [ 0, 0]], # 短序列补零(特征维度均补0)
    #
    # [[ 3, 30],
    # [ 4, 40],
    # [ 5, 50]],
    #
    # [[ 6, 60],
    # [ 0, 0],
    # [ 0, 0]]])

torch.randperm 函数

  • torch.randperm 函数 的原型为:

    1
    torch.randperm(n, *, generator=None, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False) -> Tensor
  • torch.randperm 函数用于生成 0 到 n-1 的随机排列

    • 返回一个长度为 n、包含不重复整数的 1D 张量 ,常用于数据打乱(如训练集 shuffle、批次采样)
    • 注意返回的是索引 indices,并不改变按原始数据

torch.argsort 函数

  • torch.argsort 用于返回张量元素排序后索引的函数

    • 核心作用是:不改变原张量,仅返回一个索引张量,该索引对应将原张量按指定规则排序后各元素的原始位置
  • torch.argsort 函数原型为:

    1
    torch.argsort(input, dim=-1, descending=False, stable=False, *, out=None)
    • input:输入张量(任意维度,如 1D、2D、3D 等)
    • dim :指定排序的维度(负号表示倒数维度,如 dim=-1 表示最后一维);默认 -1
    • descending:是否降序排序(True 降序,False 升序);默认 False
    • stable:是否使用稳定排序(相同元素保留原始顺序,仅对 CPU 有效,GPU 不支持);默认 False
    • out:输出张量(可选,用于指定结果存储位置);默认 None
    • 返回值:一个与 input 形状完全相同的整数张量 ,元素为原张量排序后的索引
  • 1D 张量示例:对一维张量排序,返回排序后元素的原始索引(升序/降序)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import torch

    # 1D 输入张量
    x = torch.tensor([3, 1, 4, 1, 5])

    # 升序排序(默认 descending=False)
    idx_asc = torch.argsort(x)
    print("升序索引:", idx_asc) # 输出:tensor([1, 3, 0, 2, 4])
    # 验证:x[idx_asc] 即为排序后的张量
    print("升序结果:", x[idx_asc]) # 输出:tensor([1, 1, 3, 4, 5])

    # 降序排序(descending=True)
    idx_desc = torch.argsort(x, descending=True)
    print("降序索引:", idx_desc) # 输出:tensor([4, 2, 0, 3, 1])
    print("降序结果:", x[idx_desc]) # 输出:tensor([5, 4, 3, 1, 1])

结合 torch.randperm 函数 和 torch.argsort 函数 实现打乱再恢复

  • 举个直观例子:

    • 原索引:[0,1,2,3,4](对应 x 的位置)
    • randperm(5) 生成 shuffle_idx = [3,0,4,1,2](随机打乱)
    • x_shuffled = x[3], x[0], x[4], x[1], x[2]
    • argsort(shuffle_idx) 计算:
      • 将 shuffle_idx 升序排序为 [0,1,2,3,4],其元素对应在 shuffle_idx 中的位置是 [1,3,4,0,2](存储为 restore_idx)
    • 验证:x_shuffled[restore_idx] = x[0],x[1],x[2],x[3],x[4](完全恢复原顺序)
  • 通过 torch.randperm 打乱 + torch.argsort(shuffle_idx) 恢复的核心是 “索引映射的可逆性”

    • 打乱:用随机索引映射到乱序;
    • 恢复:用 argsort 还原该映射,与张量值无关,高效且稳定
  • 1D 张量示例:

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

    # 1. 生成原张量(长度为 5)
    x = torch.tensor([10, 20, 30, 40, 50]) # 原张量:[10,20,30,40,50]
    n = len(x)

    # 2. 用 randperm 打乱顺序
    shuffle_idx = torch.randperm(n) # 生成 0~4 的随机排列(例:[3,0,4,1,2])
    x_shuffled = x[shuffle_idx] # 打乱后的张量:x[3],x[0],x[4],x[1],x[2] -> [40,10,50,20,30]
    print("打乱索引 shuffle_idx:", shuffle_idx)
    print("打乱后 x_shuffled:", x_shuffled)

    # 3. 用 argsort 恢复顺序(核心:对 shuffle_idx 做 argsort)
    restore_idx = torch.argsort(shuffle_idx) # 恢复索引:[1,3,4,0,2](示例)
    x_restored = x_shuffled[restore_idx] # 恢复原张量
    print("恢复索引 restore_idx:", restore_idx)
    print("恢复后 x_restored:", x_restored)
    print("是否完全恢复:", torch.equal(x, x_restored)) # 输出 True
  • 注意(对 index 做排序的优点):

    • 如果原张量有重复值,直接对 x_shuffled 做 argsort 可能因值排序导致恢复失败,但对 shuffle_idx 做 argsort 不受值影响(仅依赖索引映射)

len(dataloader) 函数

  • len(dataloader) 得到的是 训练数据加载器(DataLoader)的批次数(batch count),即整个训练数据集被划分为多少个批次(batch)
  • 注:len(dataloader) 可以在任意时刻使用,不会影响 dataloader 数据集的 迭代操作
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import torch
    from torch.utils.data import DataLoader, TensorDataset

    # 构造简单数据集和dataloader
    dataset = TensorDataset(torch.randn(1000, 10)) # 1000个样本
    train_dataloader = DataLoader(dataset, batch_size=32, drop_last=False)

    # 定义get_batch(基于dataloader迭代器)
    def get_batch(loader):
    loader_iter = iter(loader) # 每次调用都重新初始化迭代器(关键)
    return next(loader_iter)

    # 第一步:调用len()
    print(len(train_dataloader)) # 输出32(仅计算批次数,不碰数据)

    # 第二步:正常get_batch
    batch = get_batch(train_dataloader)
    print(batch[0].shape) # 输出torch.Size([32, 10])(成功获取批次,无任何影响)

tensor.expand() 和 tensor.expand_as() 函数

  • 核心:Expand 是“视图扩展”,不是“数据复制”
  • expand() 和 expand_as() 的核心作用是将张量在维度上进行扩展(广播) ,但它并不会复制新的数据,而是返回原张量的一个“视图(view)”
    • 扩展后的张量和原张量共享内存(修改原张量,扩展后的张量也会变;)
    • 只有在维度大小为 1 的位置才能被扩展

tensor.expand() 用法

  • 基本语法

    1
    tensor.expand(*sizes)
    • *sizes:传入想要扩展后的张量形状(元组/多个整数)
    • 规则:
      • 对于原张量中大小为 1 的维度,可以扩展为任意正整数;
      • 对于原张量中大小大于 1 的维度,必须和原维度大小一致(不能改);
      • 可以用 -1 表示“保持原维度大小不变”;
      • 支持“维度扩展”(比如从 2D 扩展为 3D),只需在前面/后面加 1 再扩展
  • 代码示例

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

    # 扩展维度(可新增维度)
    x = torch.tensor([1, 2, 3]) # shape: [3]
    x_expand = x.expand(5, 3) # 扩展为 [5, 3]
    print("原张量x:", x)
    print("扩展后x_expand:", x_expand) # 复制 5 行
    print("x_expand形状:", x_expand.shape) # torch.Size([5, 3])

    # 用-1保持原维度
    y = torch.tensor([[1], [2], [3]]) # shape: [3, 1]
    y_expand = y.expand(3, 4) # 扩展第2维为4,shape: [3,4]
    y_expand2 = y.expand(-1, 4) # -1 等价于保持3不变,结果和上面一致
    print("\ny_expand:", y_expand)
    print("y_expand2形状:", y_expand2.shape) # torch.Size([3, 4])

    # 错误示例:非1维度不能修改
    z = torch.tensor([1, 2]) # shape: [2]
    # z_error = z.expand(3, 4) # 原第2维是2,不能扩展为4,会报错

tensor.expand_as() 用法

  • 基本语法

    1
    tensor.expand_as(other_tensor)
    • 作用:等价于 tensor.expand(other_tensor.size()),即把当前张量扩展为和 other_tensor 相同的形状
    • 规则:和 expand() 完全一致,只是不需要手动写形状,直接复用另一个张量的形状
  • 代码示例

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

    # 原张量
    a = torch.tensor([[1], [2], [3]]) # shape: [3, 1]
    # 目标形状的张量
    b = torch.randn(3, 4) # shape: [3, 4]

    # 扩展a为b的形状
    a_expand = a.expand_as(b)
    print("a_expand形状:", a_expand.shape) # torch.Size([3, 4])
    print("a_expand:", a_expand)
    # 输出:
    # tensor([[1, 1, 1, 1],
    # [2, 2, 2, 2],
    # [3, 3, 3, 3]])

    # 等价写法
    a_expand2 = a.expand(b.size())
    print(torch.equal(a_expand, a_expand2)) # True(两个结果完全一致)

torch.ge 函数

  • torch.ge 是 用于逐元素比较 的基础函数,核心功能是判断第一个输入张量的元素是否大于或等于第二个输入张量的对应元素,最终返回一个与输入同形状的布尔型张量(dtype=torch.bool),元素值为 True/False 表示对应位置的比较结果
  • 完整形式:torch.ge(input, other, *, out=None)
    • input:torch.Tensor(必选),第一个输入张量(比较的左操作数)
    • other: torch.Tensor, 数值型(int/float)(必选), 第二个输入(比较的右操作数),支持广播机制
  • 其他等价名称:torch.greater_equal(与 torch.ge 功能完全一致,可互换使用)
  • 注意输出类型:逐元素执行 input >= other 比较,输出布尔张量(与广播后形状一致),数据类型固定为 torch.bool,每个元素对应 input 和 other 对应位置的 >= 比较结果

rsqrt()

  • rsqrt(),是 reciprocal square root(平方根的倒数)
  • 用法:tensor.rsqrt()
  • 为什么需要 rsqrt()?
    • 性能优势:乘法运算通常比除法运算更快,预先计算 1/std 并复用,避免重复除法
    • 数值稳定性:在某些硬件(如 GPU)上,.rsqrt() 有专门的优化指令,PyTorch 的 .rsqrt() 通常比 1 / sqrt() 更稳定

torch.Tensor() 和 torch.tensor() 用法辨析

  • 在 PyTorch 中,torch.Tensor 和 torch.tensor 看似相似,但核心定位、用法和行为有明显区别

    特性 torch.Tensor (类) torch.tensor (函数)
    默认数据类型 固定为 float32(FloatTensor) 自动推断(如整数 -> int64,浮点数 -> float32)
    空输入行为 传入数字 N 会创建长度为 N 的空 float32 张量 传入数字 N 会报错(必须是可迭代对象,如列表/元组)
    推荐程度 不推荐(PyTorch 官方更推荐函数式接口) 推荐(标准化的创建方式,更易维护)
  • torch.tensor 函数原型:

    1
    2
    3
    4
    5
    6
    7
    8
    def torch.tensor(
    data,
    dtype: Optional[torch.dtype] = None,
    device: Optional[Union[torch.device, str]] = None,
    requires_grad: bool = False,
    pin_memory: bool = False,
    ) -> torch.Tensor:
    ...
  • torch.Tensor 类原型:

    1
    2
    3
    class torch.Tensor(torch._C._TensorBase):
    def __init__(self, *sizes, dtype=None, device=None, requires_grad=False) -> None:
    ...

torch.Tensor() 和 torch.tensor(..., dtype=torch.float32) 的区别

  • 在大多数常规场景下,torch.Tensor() 和 torch.tensor(..., dtype=torch.float32) 的最终张量结果(数值+数据类型) 是一样的,但部分场景下二者的底层逻辑、边界行为有区别

    • 常规场景(传入列表/元组):二者的张量结果(数值+float32类型)完全一致
    • 边界场景(传入单个数字):torch.Tensor(N) 创建长度为N的空张量,torch.tensor(N, dtype=float32) 直接报错,行为完全不同
  • 最佳实践:建议优先用 torch.tensor(..., dtype=torch.float32),语义更清晰、输入校验更严格,能避免因 “数字被解析为长度” 导致的隐蔽 bug

    • torch.Tensor几乎不需要亲自调用
  • 1. 常规输入场景(传入可迭代数据) 时,二者的表面结果一致(都是 float32 张量):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch

    # 方式1:torch.Tensor 构造函数
    a = torch.Tensor([1, 2, 3])
    print(a.dtype, a) # torch.float32 tensor([1., 2., 3.])

    # 方式2:torch.tensor 指定 dtype=float32
    b = torch.tensor([1, 2, 3], dtype=torch.float32)
    print(b.dtype, b) # torch.float32 tensor([1., 2., 3.])

    # 数值和类型完全一致
    print(torch.equal(a, b)) # True
  • 2. 关键差异:边界输入场景 ,在非可迭代输入(比如单独的数字),两者不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 场景1:传入单个数字 N
    # torch.Tensor(N):创建长度为 N 的空 float32 张量(初始化值为随机/0,取决于环境)
    c = torch.Tensor(5)
    print(c) # tensor([0., 0., 0., 0., 0.]) (长度5的float32张量)

    # torch.tensor(N, dtype=float32):直接报错(要求输入是可迭代对象)
    try:
    d = torch.tensor(5, dtype=torch.float32)
    except TypeError as err:
    print(err) # data must be a sequence (got int)
    • 底层逻辑差异
      • torch.Tensor():作为类的构造函数,行为更“宽松”,对输入的解析规则是 PyTorch 早期设计的逻辑(比如数字被解析为“张量长度”)
      • torch.tensor(..., dtype=torch.float32):作为标准化函数,对输入的校验更严格,必须传入可迭代数据(列表/元组/数组等),语义更清晰(“基于给定数据创建指定类型的张量”)
  • 3. 其他额外注意:数据拷贝逻辑

    • torch.tensor() 会总是拷贝输入数据(即使输入已是张量),而 torch.Tensor() 可能复用内存(极少数场景)
      1
      2
      3
      4
      # 输入是已有张量时的拷贝差异
      x = torch.tensor([1,2,3])
      a = torch.Tensor(x) # 可能浅拷贝(依赖底层实现)
      b = torch.tensor(x, dtype=torch.float32) # 强制深拷贝

Centos——挖矿病毒kdevtmpfsi查杀经历


挖矿病毒kdevtmpfsi查杀经历

问题简介

  • 服务器: 阿里云主机服务器
  • 系统: Centos7
  • 表现: kdevtmpfsi进程占用400%(8核心处理器)CPU
  • 时间: 2020-01-07报警, 2020-01-08处理

查杀经过

  • 使用clamscan命令搜索所有文件, clamav详情见我之前的博客clamav安装与杀毒

    1
    nohup clamscan / -r --infected -l clamscan.log > clamscan.out &
    • 注:这一步比较花时间
  • 查看扫描结果

    1
    cat clamscan.log | grep FOUND

    /var/lib/docker/overlay/bdd049c71596d743907224a8dd6fdb3fb4ca76e3af8dfd6eee2d034de2be45a1/merged/tmp/kdevtmpfsi: Multios.Coinminer.Miner-6781728-2 FOUND
    /var/lib/docker/overlay/bdd049c71596d743907224a8dd6fdb3fb4ca76e3af8dfd6eee2d034de2be45a1/merged/tmp/red2.so: Unix.Trojan.Gafgyt-6981174-0 FOUND
    /var/lib/docker/overlay/bdd049c71596d743907224a8dd6fdb3fb4ca76e3af8dfd6eee2d034de2be45a1/upper/tmp/kdevtmpfsi: Multios.Coinminer.Miner-6781728-2 FOUND
    /var/lib/docker/overlay/bdd049c71596d743907224a8dd6fdb3fb4ca76e3af8dfd6eee2d034de2be45a1/upper/tmp/red2.so: Unix.Trojan.Gafgyt-6981174-0 FOUND

  • 删除这四个文件,这里直接到相关目录下查看发现../tmp目录下往往都是病毒文件(与kinsing相关,全部删除)

  • top查看CPU信息确定挖矿进程kdevtmpfsi的进程号[pid]

  • 确定启动信息中启动命令,并删除(在这里查到的信息是文件已经被删除了)

    1
    ls /proc/[pid] -ali
  • 查找父进程进程号

    1
    systemctl status [pid]

    ● docker-be9fcab033e6158f8ff7d6ac07d28cfd918375178c27e016aa800cbeef985161.scope - libcontainer container be9fcab033e6158f8ff7d6ac07d28cfd918375178c27e016aa800cbeef985161
    Loaded: loaded (/run/systemd/system/docker-be9fcab033e6158f8ff7d6ac07d28cfd918375178c27e016aa800cbeef985161.scope; static; vendor preset: disabled)
    Drop-In: /run/systemd/system/docker-be9fcab033e6158f8ff7d6ac07d28cfd918375178c27e016aa800cbeef985161.scope.d

          └─50-BlockIOAccounting.conf, 50-CPUAccounting.conf, 50-DefaultDependencies.conf, 50-Delegate.conf, 50-Description.conf, 50-MemoryAccounting.conf, 50-Slice.conf

    Active: active (running) since Mon 2019-11-11 11:24:17 UTC; 1 months 27 days ago

      Tasks: 38

    Memory: 2.3G
    CGroup: /system.slice/docker-be9fcab033e6158f8ff7d6ac07d28cfd918375178c27e016aa800cbeef985161.scope

          ├─ 4475 redis-server *:6379
          ├─ 8528 sh -c /tmp/.ICEd-unix/vJhOU
          ├─ 8529 /tmp/.ICEd-unix/vJhOU
          └─22822 /tmp/kdevtmpfsi

    Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

  • 杀死不需要的相关进程,如上面的4475,8528, 8529, 22822

  • 查看是否还有需要杀死的进程,如果有,则杀死该进程

    1
    ps -ef | grep kinsing
  • top确定挖矿进程已经被杀死

总结

  • 查杀病毒两个小时未发现服务器有明显异常
  • 第二天出现了,需要进一步查看,重启机器后问题没有再出现
1…333435…63
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

630 posts
53 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4