跳转至

Pytorch 1 :Basic Components

导言

Pytorch基础知识,包括张量的使用,和自动微分。

Pytorch简介

Pytorch前生Torch

Torch是一个有大量机器学习算法支持的科学计算框架,是一个与Numpy类似的张量(Tensor) 操作库,其特点是特别灵活,但因其采用了小众的编程语言是Lua,所以流行度不高,这也就有了PyTorch的出现。所以其实Torch是 PyTorch的前身,它们的底层语言相同,只是使用了不同的上层包装语言。

PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。它主要由Facebookd的人工智能小组开发,不仅能够 实现强大的GPU加速,同时还支持动态神经网络,这一点是现在很多主流框架如TensorFlow都不支持的。 PyTorch提供了两个高级功能:

  • 具有强大的GPU加速的张量计算(如Numpy)
  • 包含自动求导系统的深度神经网络

主要定位两类人群:

  1. NumPy 的替代品,可以利用 GPU 的性能进行计算。
  2. 深度学习研究平台拥有足够的灵活性和速度

优势

  • 支持GPU
  • 灵活,支持动态神经网络
    • TensorFlow和Caffe都是命令式的编程语言,而且是静态的,首先必须构建一个神经网络,然后一次又一次使用相同的结构,如果想要改变网络的结构,就必须从头开始。
    • 但是对于PyTorch,通过反向求导技术,可以让你零延迟地任意改变神经网络的行为,而且其实现速度快。正是这一灵活性是PyTorch对比TensorFlow的最大优势。
  • 底层代码易于理解
  • 命令式体验
  • 自定义扩展

劣势

对比TensorFlow,有些处于劣势,目前PyTorch

  • 还不支持快速傅里叶、沿维翻转张量和检查无穷与非数值张量;
  • 针对移动端、嵌入式部署以及高性能服务器端的部署其性能表现有待提升;
  • 其次因为这个框 架较新,使得他的社区没有那么强大,在文档方面其C库大多数没有文档。

安装和使用

官网 选择对应cuda版本下载即可

from __future__ import print_function
import torch
好的!张量(Tensors)是PyTorch的核心数据结构,类似于NumPy中的多维数组(ndarray),但张量支持GPU加速计算,因此在深度学习中非常高效。下面我将详细展开讲解张量的创建、操作以及一些常用功能。


1. 什么是张量?

张量是一个多维数组,可以表示标量、向量、矩阵以及更高维的数据结构: - 0维张量:标量(Scalar),例如 5 - 1维张量:向量(Vector),例如 [1, 2, 3] - 2维张量:矩阵(Matrix),例如 [[1, 2], [3, 4]] - 3维及以上张量:高维数组,例如图像数据通常是3维张量(通道×高度×宽度)。

PyTorch中的张量支持高效的数学运算,并且可以自动求导,是构建神经网络的基础。


2. 创建张量

PyTorch提供了多种创建张量的方式,以下是一些常见的创建方法:

(1) 从Python列表或NumPy数组创建

import torch

# 从列表创建张量
tensor_from_list = torch.tensor([1, 2, 3])
print(tensor_from_list)  # 输出: tensor([1, 2, 3])

# 从NumPy数组创建张量
import numpy as np
numpy_array = np.array([4, 5, 6])
tensor_from_numpy = torch.from_numpy(numpy_array)
print(tensor_from_numpy)  # 输出: tensor([4, 5, 6], dtype=torch.int32)

(2) 创建特定形状的张量

# 创建全零张量
zeros_tensor = torch.zeros(2, 3)  # 2行3列的全零矩阵
print(zeros_tensor)

# 创建全一张量
ones_tensor = torch.ones(2, 3)  # 2行3列的全一矩阵
print(ones_tensor)

# 创建随机张量
rand_tensor = torch.rand(2, 3)  # 2行3列的随机值矩阵(值在0到1之间)
print(rand_tensor)

(3) 创建特定范围的张量

# 创建等差序列张量
arange_tensor = torch.arange(0, 10, 2)  # 从0开始,步长为2,小于10
print(arange_tensor)  # 输出: tensor([0, 2, 4, 6, 8])

# 创建线性间隔张量
linspace_tensor = torch.linspace(0, 1, 5)  # 从0到1,均匀分成5个数
print(linspace_tensor)  # 输出: tensor([0.0000, 0.2500, 0.5000, 0.7500, 1.0000])

arange

torch.arange 是 PyTorch 中用于生成等间隔数值序列的函数。它可以接受一个、两个或三个参数,具体用法如下:

  • 一个参数torch.arange(end)
  • 生成从 0 到 end-1 的整数序列。
  • 示例:torch.arange(5) 生成张量 [0, 1, 2, 3, 4]

  • 两个参数torch.arange(start, end)

  • 生成从 startend-1 的整数序列。
  • 示例:torch.arange(2, 5) 生成张量 [2, 3, 4]

  • 三个参数torch.arange(start, end, step)

  • 生成从 startend-1 的序列,步长为 step
  • 示例:torch.arange(0, 1, 0.2) 生成张量 [0.0, 0.2, 0.4, 0.6, 0.8]

(4) 创建与现有张量形状相同的张量

existing_tensor = torch.tensor([[1, 2], [3, 4]])
new_tensor = torch.zeros_like(existing_tensor)  # 创建与existing_tensor形状相同的全零张量
print(new_tensor)

3. 张量的属性

每个张量都有一些重要的属性:

  • 形状(Shape):张量的维度大小。
  • 数据类型(dtype):张量中元素的数据类型(如float32int64等)。
  • 设备(device):张量存储在CPU还是GPU上。
tensor = torch.rand(2, 3)
print("Shape:", tensor.shape)  # 输出: torch.Size([2, 3])
print("Data type:", tensor.dtype)  # 输出: torch.float32
print("Device:", tensor.device)  # 输出: cpu (默认)

4. 张量的操作

PyTorch提供了丰富的张量操作,以下是一些常见的操作:

(1) 索引与切片

tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(tensor[0, 1])  # 输出: tensor(2) (第0行第1列)
print(tensor[:, 1])  # 输出: tensor([2, 5]) (第1列的所有行)

(2) 改变形状

tensor = torch.arange(6)
reshaped_tensor = tensor.view(2, 3)  # 将1维张量变为2行3列
print(reshaped_tensor)

维度-1,自动推断维度大小

在使用 viewreshape 方法时,如果其中一个维度的大小为 -1,那么 PyTorch 会自动推断这个维度的大小。

注意:

  • 元素总数必须匹配:使用 view 变形时,新形状的元素总数必须与原张量的元素总数相同。否则会抛出错误。
  • 连续性要求:view 操作要求张量是连续存储的(即内存布局是连续的)。如果张量不是连续的,可以先调用 .contiguous() 方法将其变为连续的再进行变形。

(3) 数学运算

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

# 加法
print(a + b)  # 输出: tensor([5, 7, 9])

# 矩阵乘法
matrix_a = torch.tensor([[1, 2], [3, 4]])
matrix_b = torch.tensor([[5, 6], [7, 8]])
print(torch.matmul(matrix_a, matrix_b))  # 输出: tensor([[19, 22], [43, 50]])

torch.einsum

torch.einsum 用于执行基于爱因斯坦求和约定(Einstein summation convention)的张量操作。爱因斯坦求和约定是一种表示张量运算的简洁方式,可以用于矩阵乘法、外积、转置等多种操作。

爱因斯坦求和约定通过使用索引标签来指定张量的维度,并通过重复索引标签来表示求和操作。具体来说:

  • 单个索引标签:表示一个维度。
  • 重复的索引标签:表示在这些维度上进行求和。
  • 不同的索引标签:表示新的维度。

torch.einsum(equation, *operands) 其中: - equation 是一个字符串,描述了输入张量如何组合以生成输出张量。 - *operands 是输入张量。

矩阵转置
import torch

# 定义矩阵 A
A = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)

# 使用 einsum 进行矩阵转置
A_T = torch.einsum("ij->ji", A)
print(A_T)
矩阵乘法

假设你有两个矩阵 AB,你想计算它们的矩阵乘法 C = A @ B

import torch

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

# 使用 einsum 进行矩阵乘法
C = torch.einsum('ik,kj->ij', A, B)
print(C)

输出:

tensor([[19, 22],
        [43, 50]])

解释: - ik 表示矩阵 A 的维度。 - kj 表示矩阵 B 的维度。 - ij 表示结果矩阵 C 的维度。 - 重复的索引 k 表示在该维度上进行求和。

外积(Outer Product)

外积(Outer Product)是一种张量运算,用于生成一个新的张量,其维度是输入张量维度的组合。具体来说,外积操作将两个向量扩展为一个矩阵,其中每个元素是两个向量对应元素的乘积。

给定两个向量 \( \mathbf{a} \)\( \mathbf{b} \),它们的外积 \( \mathbf{C} \) 定义为:

\[ \mathbf{C}_{ij} = \mathbf{a}_i \times \mathbf{b}_j \]

其中 \( \mathbf{a} \) 是一个 \( m \)-维向量,\( \mathbf{b} \) 是一个 \( n \)-维向量,结果矩阵 \( \mathbf{C} \) 是一个 \( m \times n \) 的矩阵。

假设我们有两个向量:

[ \mathbf{a} = \begin{bmatrix} 1 \ 2 \ 3 \end{bmatrix} ] [ \mathbf{b} = \begin{bmatrix} 4 \ 5 \end{bmatrix} ]

它们的外积 \( \mathbf{C} \) 计算如下:

\[ \mathbf{C} = \mathbf{a} \otimes \mathbf{b} = \begin{bmatrix} 1 \times 4 & 1 \times 5 \\ 2 \times 4 & 2 \times 5 \\ 3 \times 4 & 3 \times 5 \end{bmatrix} = \begin{bmatrix} 4 & 5 \\ 8 & 10 \\ 12 & 15 \end{bmatrix} \]
torch.einsum 计算外积
freqs = torch.einsum("i,j->ij", t, inv_freq).to(dtype)
  • t 是一个一维张量(向量),假设其形状为 (m,)
  • inv_freq 是另一个一维张量(向量),假设其形状为 (n,)
  • "i,j->ij" 是 einsum 的方程字符串:
  • i 表示 t 的维度。
  • j 表示 inv_freq 的维度。
  • ij 表示结果张量 freqs 的维度。
  • 结果 freqs 是一个形状为 (m, n) 的二维张量,其中每个元素 freqs[i, j] = t[i] * inv_freq[j]

假设 tinv_freq 的值如下:

import torch

t = torch.tensor([1, 2, 3], dtype=torch.float32)
inv_freq = torch.tensor([0.1, 0.2], dtype=torch.float32)

freqs = torch.einsum("i,j->ij", t, inv_freq)
print(freqs)

输出:

tensor([[0.1000, 0.2000],
        [0.2000, 0.4000],
        [0.3000, 0.6000]])

解释: - t 是一个形状为 (3,) 的向量。 - inv_freq 是一个形状为 (2,) 的向量。 - freqs 是一个形状为 (3, 2) 的矩阵,其中每个元素是 tinv_freq 对应元素的乘积。

(4) 广播机制

PyTorch支持广播机制,允许对不同形状的张量进行运算:

a = torch.tensor([[1, 2, 3]])
b = torch.tensor([1, 2, 3])
print(a + b)  # 输出: tensor([[2, 4, 6]])

(5) 张量的拼接与分割

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

# 分割
tensor = torch.arange(6)
chunks = torch.chunk(tensor, 2)  # 将张量分成2块
print(chunks)  # 输出: (tensor([0, 1, 2]), tensor([3, 4, 5]))
cat 用法

torch.cat 是 PyTorch 中用于在给定维度上拼接张量的函数。以下是 torch.cat 的使用说明和 dim 参数的解释:

torch.cat(tensors, dim=0, *, out=None) -> Tensor
  • 参数说明:

  • tensors: 需要拼接的张量序列,可以是元组或列表。

  • dim: 指定在哪个维度上进行拼接,默认值为 0。dim=-1 表示最后一个维度。dim=-2 表示倒数第二个维度,依此类推。

示例 一维张量拼接

import torch

a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
result = torch.cat((a, b), dim=0)
print(result)  # 输出: tensor([1, 2, 3, 4, 5, 6])

二维张量拼接

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

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

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

通过这些示例可以看出,dim 参数决定了拼接的方向和方式。


5. 张量与GPU

PyTorch支持将张量移动到GPU上进行加速计算:

# 检查是否有可用的GPU
if torch.cuda.is_available():
    device = torch.device("cuda")  # 使用GPU
else:
    device = torch.device("cpu")  # 使用CPU

# 将张量移动到GPU
tensor = torch.tensor([1, 2, 3])
tensor = tensor.to(device)
print(tensor.device)  # 输出: cuda:0 (如果GPU可用)

自动求导机制(Autograd)

PyTorch 的自动求导机制(Autograd)是它的一个核心功能,能够自动计算梯度,从而简化了反向传播和优化过程。它通过 torch.autograd 模块来实现,主要用于自动计算张量的梯度,并执行链式法则(即,反向传播)。

In-place 操作的正确性检查

在 PyTorch 中,In-place 操作指的是直接修改张量内容的操作,而不是创建一个新张量。例如,tensor.add_()tensor.mul_() 等函数就是典型的 in-place 操作。In-place 操作有一个潜在的问题:它们会修改原始数据,这可能会影响到计算图,从而导致梯度计算出现问题。

在训练过程中,PyTorch 会构建一个计算图,其中包含了所有的操作和依赖关系。在进行反向传播(backward)时,PyTorch 会依照计算图计算梯度。如果你对某个张量进行了 in-place 操作,可能会破坏计算图的结构,导致反向传播失败。

因此,PyTorch 会检查你是否在计算图中正在被使用的 Variable 上执行了 in-place 操作。如果发生这种情况,PyTorch 会在反向传播时抛出错误。这确保了,如果没有错误,反向传播中的梯度计算一定是正确的。

举个例子,如果你有一个张量 x,并对它执行了 x.add_(5)(in-place 操作),然后在反向传播时 PyTorch 会发现这个张量已经被修改,导致错误。所以 PyTorch 会提醒你避免这种情况发生。

基本概念

  1. 张量(Tensor)

  2. PyTorch 中的数据类型叫做 Tensor,它可以在计算图中存储数据,并支持梯度计算。

  3. 当一个张量需要计算梯度时,requires_grad 参数必须设置为 True
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)

  1. 计算图(Computation Graph)

  2. 在 PyTorch 中,所有的操作(加法、乘法、矩阵乘法等)都会构成一个计算图,这个图记录了张量之间的运算关系。

    • 只有 requires_grad=True 的张量及其操作会被记录在计算图中
    • requires_grad=True 的张量(如默认的 requires_grad=False)不会被追踪,它们的计算不影响计算图和梯度的计算。
  3. 每个操作都会被记录为一个节点,边表示张量之间的关系。
  4. 当我们执行反向传播时,PyTorch 会根据这个计算图逐步计算梯度。

  1. 梯度(Gradients)

  2. 梯度是用来指导模型学习(模型参数通过梯度+优化器更新)的,通常在训练过程中通过反向传播来计算。

  3. PyTorch 的 autograd 会自动追踪每个张量的操作,并计算梯度。

主要功能和操作

1. requires_grad 参数

  • 当你创建一个张量时,如果你想计算它的梯度,必须设置 requires_grad=True, 否则默认值是 False
  • 如果 requires_gradTrue,PyTorch 会追踪该张量的所有操作。
x = torch.randn(2, 2, requires_grad=True)
y = x * 2
z = y.mean()

在这个例子中,x 被标记为需要计算梯度,因此 yz 也会被自动记录到计算图中。

什么张量需要计算梯度

  • 固定张量是不需要计算梯度的,比如不变的输入数据、计算中常量的、模型中的常量参数。
  • 需要迭代更新的张量是需要计算梯度的,一般是模型的可训练参数(如 nn.Linear 层的 weight 和 bias)。可以通过优化器和自动计算的梯度来更新这些张量。
# model.parameters():每个 nn.Module 子类(例如 Linear)都有 parameters() 方法,
# 返回该层的所有可训练参数(例如权重和偏置)。这些参数的梯度会在反向传播中自动计算。
optimizer = optim.SGD(model.parameters(), lr=0.01)  # 随机梯度下降优化器

# 输入数据和目标
x = torch.randn(1, 2)  # 假设输入是2维数据
y_true = torch.randn(1, 1)  # 目标输出

# 前向传播
y_pred = model(x)

# 计算损失
loss = criterion(y_pred, y_true)

# 反向传播
loss.backward()

# 更新参数
optimizer.step()

2. backward() 方法

当计算图建立完毕并且需要进行反向传播时,调用 backward() 方法,它会计算梯度并存储在对应张量的 .grad 属性中。

z.backward()  # 反向传播
print(x.grad)  # 打印梯度

这个 backward() 方法会计算 z 相对于 x 的梯度,并将结果存储在 x.grad 中。你可以通过 x.grad 访问梯度。

3. 禁用梯度计算(torch.no_grad()

在某些情况下,我们可能不希望追踪梯度,比如在模型推理(inference)时。可以通过 torch.no_grad() 上下文管理器来禁用梯度计算。

with torch.no_grad():
    y = x * 2

这样,y 的计算不会记录到计算图中,节省了内存和计算。

对于不需要计算梯度的张量

当你不需要某个张量参与梯度计算时,可以使用 .detach() 方法将该张量从计算图中分离出来,这样它就不会影响到后续的梯度计算。

detach() 方法返回一个新的张量,这个张量和原来的张量共享数据,但不会跟踪历史操作,也不会被用于计算梯度。

举个例子:

x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 2  # y 是计算图的一部分,会记录操作
z = y.detach()  # z 不会记录操作,不会参与梯度计算

当你不再需要计算梯度时,可以使用 detach() 将不需要的部分从计算图中“脱离”,例如在某些特定的推理任务中,你可能只需要计算某些张量的值,而不希望它们参与梯度计算。

这样做的好处是节省内存和计算资源,尤其在训练和推理分离的情况下非常常用。

4. retain_graph 参数

默认情况下,反向传播计算完成后,计算图会被销毁。但如果你需要多次反向传播(例如,在多次计算中反向传播),可以使用 retain_graph=True 来保留计算图。

z.backward(retain_graph=True)

5. grad_fn 属性

每个张量都包含一个 grad_fn 属性,记录了产生该张量的操作。如果你对一个张量进行了操作,PyTorch 会自动为该操作创建一个节点,并将它赋值给 grad_fn

print(x.grad_fn)  # None,因为 x 是一个原始张量
print(y.grad_fn)  # <MulBackward0 object at ...>

6. 多张量反向传播

在某些情况下,可能有多个输出需要计算梯度,可以通过传递一个梯度输入来指定反向传播的起点。

y.backward(torch.ones_like(y))

示例:简单的自动求导

下面是一个简单的例子,展示了如何使用 autograd

import torch

# 创建一个张量,开启求导
x = torch.ones(2, 2, requires_grad=True)

# 执行一些操作
y = x * 2
z = y.mean()

# 反向传播计算梯度
z.backward()

# 查看梯度
print(x.grad)

这段代码中,x 是一个 2x2 的张量,经过乘法操作后,y 是一个包含 x * 2 的新张量,最后计算了 y 的平均值 z。调用 z.backward() 会计算 z 相对于 x 的梯度,结果存储在 x.grad 中。

复杂组件的自动求导

复杂组件的可训练参数都是requires_grad=True
import torch
import torch.nn as nn

# 定义一个简单的线性层
linear = nn.Linear(2, 1)

# 查看 weight 和 bias 的 requires_grad
print(linear.weight.requires_grad)  # 输出:True
print(linear.bias.requires_grad)    # 输出:True

示例:高层组件的自动求导

  • 在构建神经网络时会使用 nn.Module 类及其子类(如 nn.Linear),但是自动微分的机制在这些高层组件背后依然起作用。
  • torch.nn.Module 是所有神经网络层(例如 Linear、Conv2d 等)和模型的基类。每个 nn.Module 组件都有内部的张量(如权重和偏置),并且会执行一些计算。即使是这些高层组件,自动微分机制依然会工作,跟我们直接操作 Tensor 时的机制是相同的。
import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的神经网络模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.linear = nn.Linear(2, 1)  # 输入2个特征,输出1个预测值

    def forward(self, x):
        return self.linear(x)

# 创建模型、损失函数和优化器
model = SimpleModel()
criterion = nn.MSELoss()  # 均方误差损失

# model.parameters():每个 nn.Module 子类(例如 Linear)都有 parameters() 方法,
# 返回该层的所有可训练参数(例如权重和偏置)。这些参数的梯度会在反向传播中自动计算。
optimizer = optim.SGD(model.parameters(), lr=0.01)  # 随机梯度下降优化器

# 输入数据和目标
x = torch.randn(1, 2)  # 假设输入是2维数据
y_true = torch.randn(1, 1)  # 目标输出

# 前向传播
y_pred = model(x)

# 计算损失
loss = criterion(y_pred, y_true)

# 反向传播
loss.backward()

# 查看参数的梯度
print(model.linear.weight.grad)  # 权重的梯度
print(model.linear.bias.grad)    # 偏置的梯度

# 更新参数
optimizer.step()

可训练参数

识别

一般来说,

  • 神经网络层(如 nn.Linearnn.Conv2dnn.BatchNorm2d 等)通常会有可训练参数,
  • 而像激活函数(sigmoidReLU 等)和归一化层(nn.MaxPool2dnn.AvgPool2d 等)中并不是所有的操作都有可训练参数。

可以通过以下几种方法来判断一个层或模块是否有可训练参数:

1. parameters() 检查
  • 每个 PyTorch nn.Module(例如 nn.Linearnn.Conv2d 等)都有一个 parameters() 方法,它返回该模块的所有可训练参数。
  • 如果该模块有可训练的参数(即 requires_grad=True),这些参数就会出现在 parameters() 中。

示例代码"

import torch
import torch.nn as nn

# 创建不同的层
linear_layer = nn.Linear(2, 1)
conv_layer = nn.Conv2d(1, 1, 3)
sigmoid_layer = nn.Sigmoid()

# 打印线性层的可训练参数
print("Linear layer parameters:")
for param in linear_layer.parameters():
    print(param.shape)

# 打印卷积层的可训练参数
print("\nConv2d layer parameters:")
for param in conv_layer.parameters():
    print(param.shape)

# 打印 Sigmoid 层的可训练参数
print("\nSigmoid layer parameters:")
for param in sigmoid_layer.parameters():
    print(param.shape)

输出

Linear layer parameters:
torch.Size([1, 2])  # weight
torch.Size([1])      # bias

Conv2d layer parameters:
torch.Size([1, 1, 3, 3])  # weight
torch.Size([1])            # bias

Sigmoid layer parameters:

  • 对于 nn.Linearnn.Conv2d 层,parameters() 方法返回了 权重(weight)偏置(bias),这些都是可训练参数。
  • 对于 nn.Sigmoid,它没有任何可训练参数,parameters() 返回的是空的。
通过 named_parameters() 方法检查

named_parameters() 方法类似于 parameters(),但它会返回每个参数的名称,帮助你更清晰地看到每个参数的作用。

# 打印线性层的可训练参数名称和形状
print("Linear layer named parameters:")
for name, param in linear_layer.named_parameters():
    print(f"{name}: {param.shape}")

输出

Linear layer named parameters:
weight: torch.Size([1, 2])
bias: torch.Size([1])

这样,你可以更清楚地看到每个参数的名称(例如 weightbias)。

通过 requires_grad 属性检查

如果你只是想检查某个参数是否是可训练的,可以直接查看它的 requires_grad 属性。

# 检查是否有可训练参数
for param in linear_layer.parameters():
    print(param.requires_grad)  # 输出 True,表示这些参数会计算梯度

自动注册参数

PyTorch 的 nn.Module 会自动管理你定义的层的可训练参数(如 weightbias),这是通过 Python 中的 torch.nn.Parameter 类型实现的。

torch.nn.Parameter

  • torch.nn.Parameter:当你定义模型的参数时(比如权重 weight 或偏置 bias),如果你将它们定义为 nn.Parameter 对象,它们会自动注册为该模块的可训练参数。PyTorch 会把这些 Parameter 自动放入模块的 parameters() 返回的列表中。
  • nn.Module 会追踪这些 Parameter:一旦你将这些 Parameter 注册到 nn.Module 中,nn.Module 会自动管理它们的 requires_grad 属性,并将它们添加到模块的参数集合中。

nn.Linear 层的实现

class Linear(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super(Linear, self).__init__()
        # weight 会自动成为 nn.Parameter 类型
        self.weight = Parameter(torch.Tensor(out_features, in_features))
        if bias:
            # bias 会自动成为 nn.Parameter 类型
            self.bias = Parameter(torch.Tensor(out_features))
        else:
            self.bias = None

        self.reset_parameters()

在这个代码中:

  • self.weightself.bias 都是 nn.Parameter 对象,因此它们会被自动注册为该 Linear 层的可训练参数。
  • nn.Module 会通过调用 self.parameters()self.named_parameters() 来收集这些 Parameter 对象。

注册流程

  • 当你实例化一个层(例如 nn.Linear)时,PyTorch 会自动为该层的权重和偏置(如果存在)创建 nn.Parameter 对象,并将它们添加到模块的参数列表中。
  • 这样,所有继承自 nn.Module 的层都会自动将它们的可训练参数注册到父类的 parameters() 方法中。

只有self.xxx才会被注册

这段代码实际上不会按你预期的方式注册层的参数。让我们来分析一下为什么:

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()

    def forward(self, x):
        tmp1 = nn.Linear(2, 2)
        tmp2 = nn.Conv2d(1, 1, 3)
        return tmp2(tmp1(x))

forward 方法中,你在每次执行前向传播时,创建了 tmp1tmp2 两个层(nn.Linearnn.Conv2d)。但是问题是:

  1. tmp1tmp2 是局部变量:它们仅在 forward 方法内部存在,无法像类属性那样在类实例中持久保存。每次调用 forward 时,都会重新创建这些层的实例。
  2. 即使梯度被跟踪了,优化器也更新了参数,但是下次forward执行时也用不了,会重新创建新的参数。

管理

PyTorch 会通过内部机制来管理层的参数。具体来说:

  • 当你调用 parameters()named_parameters() 时,nn.Module 会返回它管理的所有 nn.Parameter 对象。
  • 这些 Parameter 对象的 requires_grad 属性会自动设置为 True,除非你显式地设置为 False
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layer1 = nn.Linear(2, 2)
        self.layer2 = nn.Conv2d(1, 1, 3)

    def forward(self, x):
        return self.layer2(self.layer1(x))

model = MyModel()

# 查看 model 中所有的可训练参数
for param in model.parameters():
    print(param.shape)

parameters() 背后的实现机制

PyTorch 的实现并不会把所有层的参数直接放入一个 parameters() 列表,而是通过一种递归方式来查找所有子模块的参数。每当你在模型中创建一个子模块(如 nn.Linearnn.Conv2d 等),PyTorch 会自动注册该模块的 Parameter 对象,并将这些对象添加到父模块的参数集合中。

具体来说,nn.Module 类有一个 _modules 属性,这个属性用来存储该模块的所有子模块(例如 self.layer1self.layer2)。在调用 parameters() 时,nn.Module 会递归地查找所有子模块,并返回所有 Parameter 对象。