pytorch基础!🍧
Pytorch基础
1.Tensors张量
张量是一种专门的数据结构,与数组和矩阵非常相似。在 PyTorch 中,我们使用张量对模型的输入和输出以及模型的参数进行编码。
张量类似于 NumPy 的 ndarrays,不同之处在于张量可以在 GPU 或其他硬件加速器上运行。事实上,张量和 NumPy 数组通常可以共享相同的底层内存,从而消除了复制数据的需要。
1.1 初始化张量
1 |
|
张量可以通过多种方式进行初始化。
直接来自数据
1 |
|
从 NumPy 数组
1 |
|
从另一个张量
新张量保留参数张量的属性(形状、数据类型),除非显式覆盖。
1 |
|
输出:
1 |
|
使用随机值或常量值
shape
是张量维度的元组。在下面的函数中,它决定了输出张量的维数。
1 |
|
tips:在给定形状(shape)时,逗号在 Python 中通常用于表示元组。在这种情况下,shape = (2, 3,)
中的逗号实际上是一个元组的标志,即使在没有逗号的情况下,它也是一个合法的元组。
这种写法是为了确保在定义元组时即使只有一个元素也使用逗号,以避免与普通的括号运算符产生歧义。例如,如果写成 shape = (2, 3)
,它将被解释为一个包含两个整数的表达式,而不是一个包含一个元组的表达式。
在 Python 中,单个元素的元组需要在元素后面添加逗号,以明确表示这是一个元组。这是为了区分元组和括号内的表达式。所以,(2, 3,)
和 (2, 3)
在这里是等价的,都表示一个包含两个整数的元组。
输出:
1 |
|
1.2 张量的属性
张量属性描述它们的形状、数据类型和存储它们的设备。
1 |
|
输出:
1 |
|
1.3 张量操作
https://pytorch.org/docs/stable/torch.html
这里全面描述了 100 多种张量运算,包括算术、线性代数、矩阵操作(转置、索引、切片)、采样等。这些操作中的每一个都可以在 GPU 上运行(速度通常高于在 CPU 上)。
默认情况下,张量是在 CPU 上创建的。我们需要显式地将张量移动到 GPU using .to
方法(在检查 GPU 可用性之后)。
1 |
|
索引和切片
1 |
|
输出:
1 |
|
tips:省略符号的作用是省略掉其余的维度。例如:
1 |
|
...
等价于:,:,:
连接张量 可用于 torch.cat
沿给定维度连接一系列张量。
1 |
|
输出:
1 |
|
tips:dim表示连接的维度。
切片操作:
1 |
|
输出结果为:
1 |
|
tips:
若在维度上使用0:3:2
,表示从索引 0 开始到索引 3 结束。举例:
1 |
|
输出结果为:
1 |
|
算术运算
1 |
|
- 矩阵乘法(Matrix Multiplication):
y1 = tensor @ tensor.T
:这是Python中使用@
运算符进行矩阵乘法的简洁写法,它计算了tensor
与其转置的矩阵相乘,并将结果存储在y1
中。y2 = tensor.matmul(tensor.T)
:这是torch.Tensor
类的matmul
方法的调用方式,实现了与@
运算符相同的功能,将两个张量相乘。y3 = torch.rand_like(y1)
和torch.matmul(tensor, tensor.T, out=y3)
:这两行代码将矩阵乘法的结果存储在预先分配的张量y3
中。
- 元素级乘法(Element-wise Multiplication):
z1 = tensor * tensor
:这是Python中进行元素级乘法的简洁写法,它将tensor
中的每个元素与另一个tensor
中对应位置的元素相乘,并将结果存储在z1
中。z2 = tensor.mul(tensor)
:这是torch.Tensor
类的mul
方法的调用方式,实现了与*
运算符相同的功能,进行元素级乘法。z3 = torch.rand_like(tensor)
和torch.mul(tensor, tensor, out=z3)
:这两行代码将元素级乘法的结果存储在预先分配的张量z3
中。
单元素张量
如果你有一个单元素张量,例如通过将张量的所有值聚合为一个值,你可以使用以下命令 item()
将其转换为 Python 数值:
1 |
|
输出:
1 |
|
tips:agg也是一个张量,为单元素张量。
就地操作
将结果存储到操作数中的操作称为就地操作。它们由 _
后缀表示。例如: x.copy_(y)
、、 x.t_()
将更改 x
。
1 |
|
输出:
1 |
|
常见的PyTorch就地操作:
- **add_、sub_、mul_、div_**:
add_()
:就地执行张量的加法。sub_()
:就地执行张量的减法。mul_()
:就地执行张量的乘法。div_()
:就地执行张量的除法。
- 其他数学函数:
abs_()
:就地执行张量的绝对值操作。neg_()
:就地执行张量的取负操作。pow_()
:就地执行张量的指数操作。clamp_()
:就地执行张量的截断操作。
- 归约操作:
sum_()
:就地计算张量的元素之和。mean_()
:就地计算张量的平均值。max_()
:就地计算张量的最大值。min_()
:就地计算张量的最小值。
- 其他操作:
fill_()
:用指定的标量值填充张量。zero_()
:将张量的所有元素设置为0。fill_diagonal_()
:将张量的对角线元素填充为指定值。
这些就地操作都是在函数名后面添加下划线_
来表示的,例如add_()
、mul_()
等。在使用时需要小心,因为它们会直接修改原始的张量,可能会导致不可预测的结果或难以调试的错误。
1.4 使用 NumPy 桥接
CPU 上的张量和 NumPy 数组可以共享其底层内存位置,更改一个将更改另一个。
Tensor 到 NumPy 数组
1 |
|
输出:
1 |
|
张量的变化反映在 NumPy 数组中。
1 |
|
输出:
1 |
|
NumPy 数组转 Tensor
1 |
|
NumPy 数组中的更改会反映在张量中。
1 |
|
输出:
1 |
|
2.Datasets & DataLoaders 数据集和数据加载器
用于处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望我们的数据集代码与模型训练代码解耦,以获得更好的可读性和模块化。PyTorch 提供了两个数据原语: torch.utils.data.DataLoader
和torch.utils.data.Dataset
允许你使用预加载的数据集以及你自己的数据。
Dataset 存储样本及其相应的标签,而 DataLoader 则在 Dataset 周围封装了一个可迭代器,以方便访问样本。
2.1 加载数据集
1 |
|
root
是存储训练/测试数据的路径,train
指定训练或测试数据集,download=True
如果数据在 上不可用root
,则从 Internet 下载数据。transform
和target_transform
指定要素和标注转换
2.2 数据加载器
检索 Dataset
数据集的特征,并一次标记一个样本。在训练模型时,我们通常希望以“小批量”的方式传递样本,在每个时期重新洗牌数据以减少模型过拟合,并使用 Python multiprocessing
来加快数据检索速度。
DataLoader
是一个可迭代的对象,它通过一个简单的 API 为我们抽象了这种复杂性。
1 |
|
我们已将该数据集加载到DataLoader
中,并可以根据需要遍历该数据集。下面的每次迭代都会返回一批 train_features
and train_labels
(分别包含 batch_size=64
特征和标签)。因为我们指定 shuffle=True
了 ,在我们遍历所有批次后,数据会被洗牌。
1 |
|
tips:tensor.shape和tensor.size()获取到的内容相同,但一个是属性,一个是方法。可以把tensor看成是一个类。
3.构建神经网络
神经网络由对数据执行操作的层/模块组成。torch.nn 命名空间提供了构建自己的神经网络所需的所有构建块。PyTorch 中的每个模块都对 nn.模块。神经网络本身是由其他模块(层)组成的模块。这种嵌套结构允许轻松构建和管理复杂的架构。
3.1 获取用于训练的设备
1 |
|
3.2 定义类
我们通过子类化来定义我们的神经网络 nn.Module
,并在 __init__
中初始化神经网络层。每个 nn.Module
子类都实现对方法中输入数据的 forward
操作。
1 |
|
我们创建一个 NeuralNetwork
的实例,并将其移动到 device
中,并打印其结构。
1 |
|
结果:
1 |
|
为了使用模型,我们将输入数据传递给它。这将执行模型的 forward
,以及一些后台操作。不要直接调用model.forward()
!
tips:
- 在 PyTorch 中,实现了
nn.Module
的子类中的forward
方法是一个特殊的约定。当您调用模型的实例(例如model
)时,PyTorch 会自动调用forward
方法,而不需要显式地调用model.forward()
。 - 这是因为
torch.nn.Module
类中已经定义了__call__
方法,而该方法内部实际上会调用forward()
方法。 - 在Python中,
__call__
是一个特殊方法,允许类的实例像函数一样被调用。
下面是一个简单的例子,演示了如何使用__call__
方法:
1 |
|
在输入上调用模型将返回一个二维张量,其中 dim=0 对应于每个类的 10 个原始预测值的每个输出,dim=1 对应于每个输出的单个值。我们通过传递模块的 nn.Softmax
实例来获取预测概率。
1 |
|
结果:
1 |
|
tips:
tensor.argmax(1)
是对 PyTorch 张量进行操作,用于沿指定维度找到张量中最大值的索引。参数是0表示沿着列找最大值,1表示沿着行找最大值。nn.Softmax(dim=1)
是PyTorch等深度学习框架中常用的函数。该函数用于沿着指定的维度计算张量的 softmax 激活。在这里,dim=1
表示 softmax 沿着输入张量的第二个维度(从 0 开始索引)进行操作。
3.2 模型层
nn.Flatten
nn.Flatten
是 PyTorch 中的一个层(Layer),用于将输入的多维张量(例如,具有多个轴或维度的张量)转换为一个具有单个轴的张量(通常是一维张量)。其作用是将输入的数据“展平”成一个一维向量,以便于后续的神经网络层(如全连接层)处理。
1 |
|
结果:
在许多情况下,当将卷积层的输出传递给全连接层时,需要使用 nn.Flatten
来将卷积层的输出展平为一维张量,以便于后续的全连接层处理。
nn.Linear
nn.Linear
是 PyTorch 中的一个线性层(Linear Layer),也称为全连接层(Fully Connected Layer)或仿射层(Affine Layer)。这个层将输入张量与权重矩阵相乘,然后加上偏置向量(可选),最后应用激活函数(也可选)。
在神经网络中,全连接层通常用于将输入数据与权重相乘,并加上偏置,从而产生新的特征表示,这些特征表示被传递给下一层。全连接层的作用是将输入数据映射到输出空间中。
以下是 nn.Linear
的基本用法示例:
1 |
|
结果:
在训练神经网络时,权重矩阵和偏置向量是可学习的参数,它们会根据反向传播算法进行优化,以最小化损失函数。
tips:
权重矩阵和偏置向量是随机的。
nn.ReLU
非线性激活是在模型的输入和输出之间创建复杂映射的原因。它们在线性变换后应用以引入非线性,帮助神经网络学习各种现象。
1 |
|
结果:
tips:
- ReLU(Rectified Linear Unit)是一种常用的非线性激活函数,被广泛应用于深度神经网络中。ReLU函数定义为:
f(x)=max(0,x)
,即,当输入 x 大于等于0时,ReLU函数返回输入 x;当输入 x 小于0时,ReLU函数返回0。如上图结果所示。 - 常见的非线性激活函数包括:
- Sigmoid函数:将输入映射到0到1之间的连续范围,常用于输出层的二分类问题。
- Tanh函数:类似于Sigmoid函数,但将输入映射到-1到1之间的连续范围,也常用于隐藏层。
- ReLU(Rectified Linear Unit)函数:对于正数输入,输出等于输入;对于负数输入,输出为0。ReLU函数在深度学习中得到了广泛应用,因为它的计算简单且在训练过程中可以加速收敛。
- Leaky ReLU函数:与ReLU类似,但对负数输入有小的线性斜率,可以避免ReLU中的“死亡神经元”问题。
- Softmax函数:常用于多分类问题的输出层,将输入转换成一个概率分布,使得输出的所有值都在0到1之间且总和为1。
nn.Sequential
nn.Sequential 是模块的有序容器。数据以与定义的相同的顺序传递到所有模块。
1 |
|
效果与之前的定义相同。
nn.Softmax
Softmax函数是一种常用的激活函数,通常用于多分类问题的输出层,将原始的网络输出转换为表示概率分布的形式。Softmax函数将输入向量 z 的每个元素转换为一个介于0和1之间的实数,同时确保所有元素的总和为1,因此可以看作是对输入向量的归一化。
1 |
|
结果:
tips:
dim为1表示行的和为1,dim为0表示列的和为 1。
3.3 模型参数
神经网络中的许多层都是参数化的,即具有相关的权重和偏差,这些权重和偏差在训练过程中得到优化。子类 nn.Module
会自动跟踪模型对象中定义的所有字段,并使所有参数都可以使用模型 parameters()
或 named_parameters()
方法访问。
1 |
|
结果:
tips:
偏差会加在每一行上。
4.torch.autograd自动微分
在训练神经网络时,最常用的算法是反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。
为了计算这些梯度,PyTorch 有一个内置的微分引擎,称为 torch.autograd
。它支持自动计算任何计算图的梯度。
考虑最简单的单层神经网络,具有输入 x
、参数 w
, b
和一些损失函数。可以在 PyTorch 中按以下方式定义它:
1 |
|
PyTorch中实现自动计算梯度的机制是通过动态计算图实现的。当你在PyTorch中定义张量并进行操作时,PyTorch会构建一个计算图,该计算图描述了数据流经过的操作,并且知道每个操作涉及的张量之间的依赖关系。这个计算图是动态的,因为它在每次执行时都会重新构建。
此代码定义以下计算图:
为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数。为了计算这些导数,我们调用 loss.backward()
,然后从 w.grad
和 b.grad
中检索值:
1 |
|
简而言之,PyTorch实现自动计算梯度的主要步骤如下:
- 定义张量,并在需要计算梯度的张量上设置
requires_grad=True
。 - 执行计算操作,PyTorch会跟踪这些操作并构建计算图。
- 当需要计算梯度时,调用
.backward()
方法。PyTorch会根据计算图自动计算梯度,并将梯度累积到相应的张量的.grad
属性中。
tips:
我们只能获取计算图的叶节点的属性,这些节点的 grad
requires_grad
属性设置为 True
。对于图中的所有其他节点,梯度将不可用。
5.优化模型参数
现在我们有了模型和数据,是时候通过优化模型的参数来训练、验证和测试我们的模型了。训练模型是一个迭代过程;在每次迭代中,模型对输出进行猜测,计算其猜测中的误差(损失),收集误差相对于其参数的导数(如我们在上一节中看到的),并使用梯度下降优化这些参数。
5.1 超参数
超参数是可调整的参数,可用于控制模型优化过程。不同的超参数值会影响模型训练和收敛率
我们定义以下用于训练的超参数:
- Number of Epochs - 遍历数据集的次数
- Batch Size 批量大小 - 在更新参数之前通过网络传播的数据样本数,参数.grad会累计一个batch的梯度
- Learning Rate 学习率 - 在每个批次/周期更新模型参数的程度。较小的值会导致学习速度较慢,而较大的值可能会导致训练期间出现不可预测的行为。
5.2 优化循环
一旦我们设置了超参数,我们就可以使用优化循环来训练和优化我们的模型。优化循环的每次迭代称为一个纪元。
每个纪元由两个主要部分组成:
- The Train Loop 训练循环 - 遍历训练数据集并尝试收敛到最佳参数。
- **The Validation/Test Loop **验证/测试循环 - 遍历测试数据集,以检查模型性能是否正在提高。
5.3 损失函数
当呈现一些训练数据时,我们未经训练的网络可能不会给出正确的答案。损失函数衡量获得的结果与目标值的差异程度,它是我们在训练过程中想要最小化的损失函数。为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。
5.4 优化
优化是调整模型参数以减少每个训练步骤中的模型误差的过程。优化算法定义了此过程的执行方式。所有优化逻辑都封装在对象中 optimizer
。在这里,我们使用 SGD 优化器;此外,PyTorch 中还有许多不同的优化器,例如 ADAM 和 RMSProp,它们更适合不同类型的模型和数据。
我们通过注册需要训练的模型参数并传入学习率超参数来初始化优化器。
1 |
|
在训练循环中,优化分三个步骤进行:
- 调用
optimizer.zero_grad()
以重置模型参数的梯度。默认情况下,渐变相加;为了防止重复计算,我们在每次迭代时都明确地将它们归零。 - 通过调用
loss.backward()
反向传播预测损失。PyTorch 将损失的梯度与每个参数交汇。 - 一旦我们有了梯度,我们就会调用
optimizer.step()
通过向后传递中收集的梯度来调整参数。
tips:
model.parameters()为每一层的权重和偏差。比如上述例子中的w,b
举例:
5.5 全面实施
我们在优化代码上定义 train_loop
循环,并 test_loop
根据我们的测试数据评估模型的性能。
1 |
|
我们初始化损失函数和优化器,并将其传递给 train_loop
和 test_loop
。随意增加 epoch 的数量以跟踪模型的改进性能。
1 |
|
tips:
model.train()
和 model.eval()
是 PyTorch 中用于控制模型模式的两个方法,它们的主要区别在于模型处于不同的运行模式,具体如下:
model.train()
: 调用model.train()
将模型设置为训练模式。在训练模式下,模型中的一些特定层,比如 dropout 和 batch normalization,会以不同的方式处理输入数据。例如,dropout 在训练时会随机丢弃部分节点,以防止过拟合;而 batch normalization 在训练时会根据当前 mini-batch 的统计数据来标准化输入数据。因此,在训练模式下,这些层会执行相应的训练操作。model.eval()
: 调用model.eval()
将模型设置为评估模式。在评估模式下,模型的行为会发生变化。例如,dropout 层不再随机丢弃节点,而是将所有节点保留,以便获取更加稳定的预测结果;batch normalization 也会使用固定的统计数据进行标准化,而不是使用当前 mini-batch 的统计数据。评估模式下,模型的行为更接近于实际使用场景。
总的来说,model.train()
将模型设置为训练模式,用于训练过程中;model.eval()
将模型设置为评估模式,用于测试、验证或推断过程中,以获得更稳定和可靠的输出结果。
5.6 保存并加载模型
在本节中,我们将了解如何通过保存、加载和运行模型预测来持久化模型状态。
1 |
|
保存和加载模型权重
PyTorch 模型将学习到的参数存储在名为 state_dict
的内部状态字典中。这些可以通过以下 torch.save
方法持久化:
1 |
|
要加载模型权重,您需要先创建同一模型的实例,然后使用 load_state_dict()
method 加载参数。
1 |
|
tips:
请务必在推理前调用 model.eval()
方法,将 dropout 和 Batch 归一化层设置为评估模式。如果不这样做,将产生不一致的推理结果。
Saving and Loading Models with Shapes
在加载模型权重时,我们需要先实例化模型类,因为该类定义了网络的结构。我们可能希望将此类的结构与模型一起保存,在这种情况下,我们可以传递model
(而不是 model.state_dict()
)到保存函数:
1 |
|
然后,我们可以像这样加载模型:
1 |
|