PyTorch 教程-快速上手指南

文章目录

  • PyTorch Quickstart
    • 1.处理数据
    • 2.创建模型
    • 3.优化模型参数
    • 4.保存模型
    • 5.加载模型
  • PyTorch 基础入门
    • 1.Tensors
      • 1.1初始化张量
      • 1.2张量的属性
      • 1.3张量运算
        • 1.3.1张量的索引和切片
        • 1.3.2张量的连接
        • 1.3.3算术运算
        • 1.3.4单元素张量转变为Python数值
      • 1.4Tensor与NumPy的桥接
        • 1.4.1Tensor to NumPy array
        • 1.4.2NumPy array to Tensor
    • 2.在PyTorch中加载数据集
      • 2.1装载数据集
      • 2.2迭代和可视化数据集
      • 2.3为文件创建自定义数据集
      • 2.4使用DataLoader为训练准备数据
      • 2.5遍历数据加载器
    • 3.Transforms
    • 4.构建一个神经网络
      • 4.1导入包
      • 4.2检查GPU是否可用
      • 4.3定义类
      • 4.4模型层
        • 4.4.1nn.Flatten
        • 4.4.2nn.Linear
        • 4.4.3nn.ReLU
        • 4.4.4nn.Sequential
        • 4.4.5nn.Softmax
      • 4.5模型参数
    • 5.使用 torch.autograd 进行自动微分
      • 5.1张量,函数与计算图
      • 5.2计算梯度
      • 5.3禁用梯度跟踪
      • 5.4计算图更多信息
      • 5.5张量梯度和雅可比积(可选)
    • 6.优化模型参数
      • 6.1前提代码
      • 6.2超参数
      • 6.3优化循环
      • 6.4损失函数
      • 6.5优化器
      • 6.6完整实现
    • 7.模型保存和加载
      • 7.1模型权重的保存和加载
      • 7.2保存和加载模型结构
  • 参考

说明:本教程翻译自 Pytorch 官方教程: Introduction to PyTorch ,适合对 Python 和深度学习具有一定基础的同学学习,是 Pytorch 的入门教程。 😃

PyTorch Quickstart

1.处理数据

PyTorch有两个处理数据的基本操作:torch.utils.data.DataLoadertorch.utils.data.DatasetDataset用于存储样本及其对应的标签,而DataLoader则围绕Dataset包装了一个可迭代的数据加载器。

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

PyTorch 提供特定于领域的库,如 TorchText, TorchVision 和 TorchAudio,所有这些库都包含数据集。对于本教程,将使用 TorchVision 数据集。

torchvision.datasets模块包含了许多真实世界视觉数据的 Dataset对象,比如 CIFAR、 COCO (完整列表在这里)。在本教程中,我们使用 FashionMNIST 数据集。每个 TorchVision Dataset都包含两个参数: transformtarget_transform,分别用于转换样本和标签。

# 从开源数据集下载训练数据。
training_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor(),
)# 从开源数据集下载测试数据。
test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=ToTensor(),
)

Dataset作为参数传递给DataLoader。这将在数据集上包装一个迭代器,并支持自动批处理、采样、随机打乱和多进程数据加载。 在这里,定义了一个大小为64的批处理**,即 DataLoader 迭代器中的每个元素都会返回一个由64个特征和标签组成的批次数据**。

batch_size = 64# 创建数据加载器
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)for X, y in test_dataloader:# N 表示批量大小(batch size),C 表示通道数(channels),H 表示图像高度(height),W 表示图像宽度(width)。print(f"Shape of X [N, C, H, W]: {X.shape}")print(f"Shape of y: {y.shape} {y.dtype}")break

image-20240327120328647

更详细的内容请查看 loading data in PyTorch 。

2.创建模型

为了在 PyTorch 中定义一个神经网络,需要创建一个继承自 nn.Module 的自定义类。在 __init__ 方法中定义网络的层次结构,并在 forward 方法中指定数据将如何通过网络的各个层。为了加速神经网络中的操作,我们将其移动到 GPU 或 MPS (如果有的话)。

# 获取 cpu, gpu 或 mps 设备用于加速训练.
device = ("cuda"if torch.cuda.is_available()else "mps"if torch.backends.mps.is_available()else "cpu"
)
print(f"Using {device} device")# 定义神经网络
class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 512),nn.ReLU(),nn.Linear(512, 512),nn.ReLU(),nn.Linear(512, 10))def forward(self, x):x = self.flatten(x)logits = self.linear_relu_stack(x)return logitsmodel = NeuralNetwork().to(device)
print(model)

image-20240327121434246

更详细的内容请查看 building neural networks in PyTorch 。

3.优化模型参数

为了训练一个模型,我们需要一个 loss function 和一个optimizer 。

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

在单个训练循环中,模型对训练数据集(以批次 batch 输入)进行预测,并反向传播预测误差以调整模型的参数。

def train(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)model.train()for batch, (X, y) in enumerate(dataloader):# 将数据移动到 GPU 上X, y = X.to(device), y.to(device)# 计算预测值与损失pred = model(X)loss = loss_fn(pred, y)# 反向传播loss.backward()optimizer.step()optimizer.zero_grad()if batch % 100 == 0:loss, current = loss.item(), (batch + 1) * len(X)print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

我们还针对测试数据集检查模型的性能,以确保它正在学习。

def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)model.eval()test_loss, correct = 0, 0with torch.no_grad():for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)test_loss += loss_fn(pred, y).item()correct += (pred.argmax(1) == y).type(torch.float).sum().item()test_loss /= num_batchescorrect /= sizeprint(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练过程在多个迭代(epoch)中进行。在每个 epoch 中,模型学习参数以做出更好的预测。我们在每个 epoch 打印模型的准确度和损失;我们希望看到准确度随着每个 epoch 的增加而增加,损失随着每个 epoch 的增加而减少

epochs = 5
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train(train_dataloader, model, loss_fn, optimizer)test(test_dataloader, model, loss_fn)
print("Done!")

image-20240327123032431

更详细的内容请查看 Training your model 。

4.保存模型

保存模型的常见方法是序列化内部状态字典(包含模型参数)。

torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")

image-20240327152101353

5.加载模型

加载模型的过程包括重新创建模型结构并将状态字典加载到其中。

model = NeuralNetwork().to(device)
model.load_state_dict(torch.load("model.pth"))

image-20240327152608393

这个模型现在可以用来做预测。

classes = ["T-shirt/top","Trouser","Pullover","Dress","Coat","Sandal","Shirt","Sneaker","Bag","Ankle boot",
]model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():x = x.to(device)pred = model(x)predicted, actual = classes[pred[0].argmax(0)], classes[y]print(f'Predicted: "{predicted}", Actual: "{actual}"')

image-20240327153439393

更详细的内容请查看 Saving & Loading your model 。


PyTorch 基础入门

1.Tensors

张量是一种专门的数据结构,非常类似于数组和矩阵。在PyTorch中,我们使用张量来编码模型的输入和输出,以及模型的参数。张量类似于NumPy的ndarrays,唯一的区别在于张量可以在GPU或其他硬件加速器上运行。实际上,张量和NumPy数组通常可以共享相同的底层内存,消除了复制数据的需要。张量还针对自动微分进行了优化。

import torch
import numpy as np

1.1初始化张量

张量可以用各种方式初始化。请看下面的例子:

Directly from data

张量可以直接从数据中创建。数据类型是自动推断的。

data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)

From a NumPy array

张量可以从NumPy数组中创建(反之亦然——参见NumPy桥接)。

np_array = np.array(data)
x_np = torch.from_numpy(np_array)

From another tensor

新张量保留参数张量的属性(形状、数据类型),除非被显式覆盖。

x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")

Out:

image-20240313153231525

With random or constant values

shape是张量维度的元组。在下面的函数中,它决定了输出张量的维数。

shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")

Out:

image-20240313153905686

1.2张量的属性

张量属性描述了它们的形状(shape)、数据类型(datatype)和存储它们的设备。

tensor = torch.rand(3,4)print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

Out:

image-20240313154443643

1.3张量运算

在这里,详细描述了超过100种张量操作,包括算术、线性代数、矩阵操作(转置、索引、切片)、采样等。每个操作都可以在GPU上运行(通常比在CPU上运行速度更快)。

默认情况下,张量是在CPU上创建的。需要使用.to方法将张量明确地移动到GPU上(在检查GPU是否可用后)。请注意,跨设备复制大型张量可能会在时间和内存方面产生昂贵的开销!

# We move our tensor to the GPU if available
if torch.cuda.is_available():tensor = tensor.to('cuda')

尝试列表中的一些操作。如果你熟悉NumPy API,你会发现张量API使用起来轻而易举。

1.3.1张量的索引和切片
tensor = torch.ones(4, 4)
print('First row: ',tensor[0])
print('First column: ', tensor[:, 0])
print('Last column:', tensor[..., -1])
tensor[:,1] = 0
print(tensor)

Out:

image-20240313163729709

1.3.2张量的连接

你可以使用torch.cat沿着给定的维度连接一系列张量。另外,你还可以了解torch.stack,它是另一种张量拼接操作,与torch.cat有一些微妙的区别。

t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)

Out:

image-20240313164329491

1.3.3算术运算
# 这计算两个张量之间的矩阵乘法。y1,y2,y3将具有相同的值
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)y3 = torch.rand_like(tensor)
torch.matmul(tensor, tensor.T, out=y3)# 这将计算元素乘积。z1,z2,z3将具有相同的值
z1 = tensor * tensor
z2 = tensor.mul(tensor)z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
1.3.4单元素张量转变为Python数值

如果你有一个只包含一个元素的张量,例如将张量中的所有值聚合成一个值,你可以使用 item() 方法将其转换为 Python 数值。

agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))

Out:

image-20240313165543899

In-place操作

In-place操作是将结果存储到操作数中的操作。它们用 _ 后缀表示。例如:x.copy_(y)x.t_(),将改变 x。

print(tensor, "\n")
tensor.add_(5)
print(tensor)

Out:

image-20240313170112276

【注:In-place操作节省了一些内存,但在计算导数时可能会有问题,因为会立即丢失历史记录。因此,不鼓励使用它们。】

1.4Tensor与NumPy的桥接

Tensor和NumPy之间进行数据交换的机制:在 CPU 上的张量和 NumPy 数组可以共享它们的底层内存位置,改变其中一个将会改变另一个。

1.4.1Tensor to NumPy array
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")

Out:

image-20240313171528418

张量的变化反映在NumPy数组中。

t.add_(1)
print(f"t: {t}")
print(f"n: {n}")

Out:

image-20240313171534865

1.4.2NumPy array to Tensor
n = np.ones(5)
t = torch.from_numpy(n)

NumPy数组的变化反映在张量中。

np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")

Out:

image-20240313172820522

2.在PyTorch中加载数据集

处理数据样本的代码可能会变得混乱且难以维护;理想情况下,我们希望数据集代码与模型训练代码解耦,以提高可读性和模块化性。PyTorch 提供了两个数据原语:torch.utils.data.DataLoadertorch.utils.data.Dataset,它们允许你使用预加载的数据集以及你自己的数据。Dataset 存储样本及其对应的标签,而 DataLoader 则在 Dataset 周围包装了一个可迭代对象,以便轻松访问样本。

PyTorch 领域库提供了许多预加载的数据集(例如 FashionMNIST),它们是 torch.utils.data.Dataset 的子类,并实现了特定于特定数据的功能。你可以用它们来原型设计和评估模型性能。您可以在这里找到这些数据集:图像数据集、文本数据集和音频数据集。

2.1装载数据集

这是一个如何从TorchVision加载Fashion-MNIST数据集的示例。Fashion-MNIST是由Zalando公司提供的包含6万个训练样本和1万个测试样本的服装图像数据集。每个样本包含一个28x28的灰度图像和对应的10个类别之一的标签。

我们使用以下参数加载FashionMNIST Dataset:

  • root是存储训练/测试数据的路径,

  • train指定是训练数据集还是测试数据集,

  • download=True如果在root路径中不存在数据,则从网络下载数据。

  • transformtarget_transform分别指定对特征和标签的转换。

import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plttraining_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor()
)test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=ToTensor()
)

Out:

image-20240313203856636

2.2迭代和可视化数据集

我们可以像处理列表一样手动索引数据集training_data[index]。我们使用matplotlib来可视化训练数据中的一些样本。

labels_map = {0: "T-Shirt",1: "Trouser",2: "Pullover",3: "Dress",4: "Coat",5: "Sandal",6: "Shirt",7: "Sneaker",8: "Bag",9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):sample_idx = torch.randint(len(training_data), size=(1,)).item()img, label = training_data[sample_idx]figure.add_subplot(rows, cols, i)plt.title(labels_map[label])plt.axis("off")plt.imshow(img.squeeze(), cmap="gray")
plt.show()

下载

2.3为文件创建自定义数据集

自定义的 Dataset 类必须实现三个函数: __init__, __len__, and __getitem__。让我们看看这个实现;FashionMNIST图像存储在img_dir目录中,而它们的标签则单独存储在CSV文件annotations_file中。

import os
import pandas as pd
from torchvision.io import read_imageclass CustomImageDataset(Dataset):def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):self.img_labels = pd.read_csv(annotations_file)self.img_dir = img_dirself.transform = transformself.target_transform = target_transformdef __len__(self):return len(self.img_labels)def __getitem__(self, idx):img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])image = read_image(img_path)label = self.img_labels.iloc[idx, 1]if self.transform:image = self.transform(image)if self.target_transform:label = self.target_transform(label)return image, label

接下来,将分解每个函数中发生的事情:

  • __init__函数。类的构造函数,在实例化 Dataset 对象时运行。
  • __len__函数。返回数据集中的样本数。
  • __getitem__函数。从数据集中的给定索引idx加载并返回一个示例。根据索引,它识别图像在磁盘上的位置,使用read_image将其转换为张量,从self.img_labels中的csv数据中检索相应的标签,调用它们上的转换函数(如果适用),并以元组形式返回张量图像和相应的标签。

2.4使用DataLoader为训练准备数据

Dataset 检索数据集的特征,并一次标记一个样本。在训练模型时,通常希望以“小批处理minibatches”的形式传递样本,在每个epoch重新洗牌数据以减少模型过度拟合,并使用Python的 multiprocessing 来加快数据检索。

DataLoader是一个 iterable,它用一个简单的API为我们抽象了这种复杂性。

from torch.utils.data import DataLoadertrain_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

2.5遍历数据加载器

上面已将该数据集加载到 DataLoader中,并可根据需要遍历该数据集。下面的每次迭代都会返回一批 train_featurestrain_labels(分别包含 batch_size=64 个特征和标签)。由于指定了 shuffle=True,因此在遍历完所有批次后,数据将被重新洗牌(如需对数据加载顺序进行更精细的控制,请查看 Samplers )。

# Display image and label.
# 从train_dataloader中获取一个批次的图像数据和对应的标签
train_features, train_labels = next(iter(train_dataloader))# 打印图像数据和标签的形状
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")# 获取第一个图像,并去除可能存在的批次维度
img = train_features[0].squeeze()# 获取第一个标签
label = train_labels[0]# 显示图像
plt.imshow(img, cmap="gray")
plt.show()# 打印标签
print(f"Label: {label}")

image-20240325220149553

3.Transforms

数据并不总是以训练机器学习算法所需的最终处理形式出现。这时使用transforms来执行数据的一些操作,并使其适合于训练。所有 TorchVision 数据集都有两个参数:transform 用于修改特征, target_transform 用于修改标签 。它们接受包含转换逻辑的可调用对象。torchvision.transforms 模块提供了几种常用的转换,可以直接使用。

FashionMNIST 的特征以 PIL 图像格式提供,而标签则为整数。在训练过程中,需要将特征转换为标准化的张量,并将标签转换为 one-hot 编码的张量。为了进行这些转换,使用 ToTensorLambda

import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambdads = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor(),target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)

ToTensor()函数:ToTensor 将 PIL 图像或 NumPy ndarray 转换为 FloatTensor,并将图像的像素强度值缩放到范围 [0., 1.] 内。

Lambda Transforms:Lambda transforms 应用用户定义的 lambda 函数。在这里,定义了一个函数将整数转换为 one-hot 编码的张量。它首先创建一个大小为 10 的零张量(数据集中标签的数量),然后调用 scatter_ 函数,该函数将值为 1 分配到由标签 y 给出的索引上。

4.构建一个神经网络

神经网络由执行数据操作的层/模块组成。 torch.nn 命名空间提供了构建自己的神经网络所需的所有构建模块。PyTorch 中的每个模块都是 nn.Module 的子类。神经网络本身也是一个模块,它由其他模块(层)组成。

接下来将构建一个神经网络来对FashionMNIST数据集中的图像进行分类。

4.1导入包

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

4.2检查GPU是否可用

希望能够在可用的硬件加速器上(如GPU或MPS)上训练我们的模型。接下来检查torch.cuda or torch.backends.mps 是否可用。

device = ("cuda"if torch.cuda.is_available()else "mps"if torch.backends.mps.is_available()else "cpu"
)
print(f"Using {device} device")

4.3定义类

通过继承 nn.Module 来定义我们自己的神经网络,并在 __init__ 中初始化神经网络的层。每个 nn.Module 子类都在 forward 方法中实现对输入数据的操作。

import torch.nn as nnclass NeuralNetwork(nn.Module):def __init__(self):# 初始化神经网络结构super().__init__()  # 调用父类的构造函数self.flatten = nn.Flatten()  # 将输入图像展平为一维向量self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 512),  # 输入大小为28*28,输出大小为512的全连接层nn.ReLU(),  # ReLU激活函数nn.Linear(512, 512),  # 输入和输出大小均为512的全连接层nn.ReLU(),  # ReLU激活函数nn.Linear(512, 10),  # 输入大小为512,输出大小为10的全连接层(用于10个类别的分类任务))def forward(self, x):# 定义前向传播过程x = self.flatten(x)  # 将输入图像展平logits = self.linear_relu_stack(x)  # 经过线性层和ReLU激活函数的堆叠return logits  # 返回最终输出

接下来创建一个 NeuralNetwork 的实例,并将其移动到 GPU 设备上,并打印其结构。

model = NeuralNetwork().to(device)
print(model)

image-20240326120042685

要使用模型,需要将输入数据传递给它。这将执行模型的 forward 方法,以及一些 background operations 。不要直接调用 model.forward()

调用模型对输入进行处理会返回一个二维张量,dim=0 对应于每个类别的 10 个原始预测值,dim=1 对应于每个输出的单个值。通过将其传递给 nn.Softmax 模块的实例,我们可以得到预测概率。

X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits) # 表明Softmax操作应该沿着张量logits的第2个维度进行
y_pred = pred_probab.argmax(1) # argmax(1)表示沿着张量的第2个维度找到最大值所在的索引,这样就可以确定每个样本预测的类别。
print(f"Predicted class: {y_pred}")

image-20240326121147879

4.4模型层

接下来,让我们逐层分解 FashionMNIST 模型中的层。为了说明这一点,将取一个大小为 28x28 的样本小批量,其中包含 3 张图像,并看看当我们将其通过网络时会发生什么。

input_image = torch.rand(3,28,28)
print(input_image.size())

image-20240326151011251

4.4.1nn.Flatten

我们初始化 nn.Flatten 层,将每个 2D 的 28x28 图像转换为一个包含 784 个像素值的连续数组(小批量维度保持(在 dim=0 处))。

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())

image-20240326151531746

4.4.2nn.Linear

linear layer 是一个模块,它使用其存储的权重和偏置对输入进行线性变换。

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())

image-20240326151826395

4.4.3nn.ReLU

非线性激活函数是创建模型输入和输出之间复杂映射的关键。它们在线性变换之后应用,引入非线性,帮助神经网络学习各种现象。

在这个模型中,我们在线性层之间使用 nn.ReLU ,但还有其他激活函数可以引入模型的非线性。

print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")

image-20240326152033250

4.4.4nn.Sequential

nn.Sequential 是一个模块的有序容器。数据按照定义的顺序通过所有模块。可以使用顺序容器来组合一个类似于 seq_modules 的快速网络。

seq_modules = nn.Sequential(flatten,layer1,nn.ReLU(),nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
4.4.5nn.Softmax

神经网络的最后一个线性层返回的是 logits,即在 [-infty, infty] 范围内的原始值,这些值会被传递到 nn.Softmax 模块。logits 会被缩放到 [0, 1] 范围内的值,表示每个类别的模型预测概率。dim参数指示值必须在其指定的维度上求和为 1。

softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)

4.5模型参数

神经网络中的许多层都是参数化的,即在训练期间进行优化的相关权重和偏置。通过对 nn.Module 进行子类化,自动跟踪模型对象中定义的所有字段,并使所有参数可以通过模型的 parameters()named_parameters() 方法访问。

接下来,我们遍历每个参数,并打印其大小和值的预览。

print(f"Model structure: {model}\n\n")for name, param in model.named_parameters():print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

image-20240326164430796

5.使用 torch.autograd 进行自动微分

在训练神经网络时,最常用的算法是反向传播。在这个算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。要计算这些梯度,PyTorch 有一个内置的微分引擎叫做 torch.autograd。它支持对任何计算图进行梯度的自动计算。

考虑最简单的单层神经网络,其中包含输入 x、参数 w b,以及一些损失函数。可以在 PyTorch 中如下定义它:

import torchx = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

5.1张量,函数与计算图

此代码定义以下计算图:

img

在这个网络中,wb 是需要优化的参数。因此,我们需要能够计算损失函数相对于这些变量的梯度。为了实现这一点,我们将这些张量的 requires_grad 属性设置为 True。(注:可以在创建张量时设置 requires_grad 的值,或者稍后使用 x.requires_grad_(True) 方法进行设置。)

我们应用于张量以构建计算图的函数实际上是 Function 类的对象。该对象知道如何在前向方向计算函数,也知道在反向传播步骤中如何计算它的导数。对于反向传播函数的引用存储在张量的 grad_fn 属性中

print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")

image-20240326171634054

5.2计算梯度

为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即在一些固定的 xy 值下,需要计算 ∂ l o s s ∂ w a n d ∂ l o s s ∂ b {\frac{\partial loss}{\partial w}}{\mathrm{~and~}}{\frac{\partial loss}{\partial b}} wloss and bloss。为了计算这些导数,我们调用 loss.backward(),然后从 w.gradb.grad 中检索值:

loss.backward()
print(w.grad)
print(b.grad)

image-20240326172351590

[!CAUTION]

  • 我们只能获取计算图的叶节点的 grad 属性,这些叶节点的 requires_grad 属性设置为 True。对于计算图中的所有其他节点,梯度将不可用。

  • 出于性能原因,我们只能在给定图上执行一次backward的梯度计算。如果我们需要在同一图上进行多次backward传播调用,则需要在 backward 调用中传递 retain_graph=True

5.3禁用梯度跟踪

默认情况下,所有 requires_grad=True 的张量都在跟踪它们的计算历史并支持梯度计算。然而,有些情况下我们不需要这样做,例如,当我们已经训练好模型,只想将其应用到一些输入数据时,即我们只想通过网络进行前向计算。可以通过将我们的计算代码包裹在 torch.no_grad() 块中来停止跟踪计算:

z = torch.matmul(x, w)+b
print(z.requires_grad)with torch.no_grad():z = torch.matmul(x, w)+b
print(z.requires_grad)

image-20240326173308389

实现相同结果的另一种方法是使用张量的 detach() 方法:

z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)

image-20240326173857077

有一些原因你可能想要禁用梯度跟踪:

  1. 将神经网络中的一些参数标记为冻结参数( frozen parameters.)。
  2. 当你只进行前向传递时,加快计算速度,因为不跟踪梯度的张量上的计算会更高效。

5.4计算图更多信息

概念上,autograd 在一个由 Function 对象组成的有向无环图(DAG)中记录了数据(张量)和所有执行的操作(以及生成的新张量)。在这个 DAG 中,叶节点是输入张量,根节点是输出张量。通过从根节点到叶节点追踪这个图,可以使用链式法则自动计算梯度。

在前向传播中,autograd 同时执行两件事情:

  1. 运行请求的操作以计算结果张量。
  2. 在 DAG 中维护操作的梯度函数。

当在 DAG 根节点上调用 .backward() 时,反向传播开始。然后,autograd

  1. 从每个 .grad_fn 计算梯度。
  2. 将它们累积在各自张量的 .grad 属性中。
  3. 使用链式法则一直传播到叶节点张量。

在 PyTorch 中,DAGs 是动态的。一个重要的事情要注意的是,图是从头开始重新创建的;在每次 .backward() 调用之后,autograd 开始填充一个新图。这正是允许您在模型中使用控制流语句的原因;如果需要,您可以在每次迭代中更改形状、大小和操作。

5.5张量梯度和雅可比积(可选)

在许多情况下,我们有一个标量损失函数,并且我们需要计算相对于一些参数的梯度。然而,有些情况下输出函数是任意张量。在这种情况下,PyTorch 允许您计算所谓的雅可比积(Jacobian product),而不是实际的梯度。

对于向量函数 y ⃗ = f ( x ⃗ ) \vec{y}=f(\vec{x}) y =f(x ),其中 x ⃗ = ⟨ x 1 , … , x n ⟩ \vec{x}=\langle x_1,\dots,x_n\rangle x =x1,,xn y ⃗ = ⟨ y 1 , … , y m ⟩ \vec{y}=\langle y_1,\dots,y_m\rangle y =y1,,ym y ⃗ \vec{y} y 相对于 x ⃗ \vec{x} x 的梯度由雅可比矩阵给出:

J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ) J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) J= x1y1x1ymxny1xnym

而不是计算雅可比矩阵本身,PyTorch 允许您计算给定输入向量 v = ( v 1 , … , v m ) \mathbf{v}=(v_1,\dots,v_m) v=(v1,,vm)雅可比积 v T ⋅ J \mathbf{v}^T\cdot J vTJ。这可以通过使用 v \mathbf{v} v 作为参数调用 backward 来实现。 v \mathbf{v} v 的大小应与我们想要计算乘积的原始张量的大小相同:

# 创建了一个大小为4x5的单位矩阵张量 inp,并将其设置为需要梯度信息。
inp = torch.eye(4, 5, requires_grad=True)# 定义了一个计算图,将输入张量 inp 的每个元素加1,然后对结果取平方,并转置得到输出张量 out。
out = (inp+1).pow(2).t()# 执行反向传播,计算输出张量 out 对自身的梯度。这里使用了全1的梯度张量作为参数,表示将梯度传播到 out 中的每个元素。retain_graph=True 保留了计算图,以便后续再次执行反向传播。
out.backward(torch.ones_like(out), retain_graph=True)
# 打印第一次反向传播后输入张量 inp 的梯度。由于PyTorch会累积梯度信息,因此这里会显示第一次反向传播后的梯度值。
print(f"First call\n{inp.grad}")# 再次执行反向传播,计算输出张量 out 对自身的梯度。由于之前已经调用过一次反向传播,所以这里会继续累积梯度信息。
out.backward(torch.ones_like(out), retain_graph=True)
# 打印第二次反向传播后输入张量 inp 的梯度。这里会显示第一次和第二次反向传播后的梯度值的累积结果。
print(f"\nSecond call\n{inp.grad}")# 将输入张量 inp 的梯度信息清零,以便后续重新累积梯度。
inp.grad.zero_()# 再次执行反向传播,计算输出张量 out 对自身的梯度。由于之前调用了 inp.grad.zero_(),所以这里的梯度信息会重新累积。
out.backward(torch.ones_like(out), retain_graph=True)
# 打印清零梯度后的结果,显示输入张量 inp 的梯度信息已经被重新累积。
print(f"\nCall after zeroing gradients\n{inp.grad}")

image-20240326181903800

注意,当我们第二次使用相同的参数调用 backward 时,梯度的值是不同的。这是因为在进行 backward 传播时,PyTorch 累积梯度,即计算的梯度值被加到计算图的所有叶节点的 grad 属性中。如果您想要计算正确的梯度,您需要在此之前将 grad 属性清零。在实际训练中,优化器帮助我们完成这一点。

注意:之前我们没有带参数调用 backward() 函数。这本质上等同于调用 backward(torch.tensor(1.0)),这是在标量值函数(例如神经网络训练中的损失)情况下计算梯度的一种有用方式。

6.优化模型参数

既然我们已经有了模型和数据,现在应该训练、验证、测试我们的模型(基于我们的数据来优化参数)。训练一个模型也是一个迭代的过程;在每次迭代中(又称为 epoch),模型会对输出中进行一次预测,计算这个预测的误差(损失值),收集这些误差相对于参数的导数,然后通过梯度下降的方式来优化这些参数。关于这个过程的更详细的介绍,可以看3Blue1Brown制作的《反向传播演算》这个视频。

6.1前提代码

将之前的模块(数据集和数据加载器、构建模型)的代码拿过来。

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensortraining_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor()
)test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=ToTensor()
)train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)class NeuralNetwork(nn.Module):def __init__(self):super(NeuralNetwork, self).__init__()self.flatten = nn.Flatten()self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 512),nn.ReLU(),nn.Linear(512, 512),nn.ReLU(),nn.Linear(512, 10),)def forward(self, x):x = self.flatten(x)logits = self.linear_relu_stack(x)return logitsmodel = NeuralNetwork()

6.2超参数

超参数是你用来控制模型优化过程的、可以调整的参数。不同的超参数取值能够影响模型训练和收敛的速度(更多关于调整超参数的内容)

定义以下用于训练的超参数:

  • Number of Epochs - 迭代数据集的次数
  • Batch Size - 在参数更新之前通过网络传播的数据样本数量。
  • Learning Rate - 学习率, 每 Batch/Epoch 次更新模型参数的幅度。较小的值会产生较慢的学习速度,较大的值可能会在训练过程中产生无法预料的行为
learning_rate = 1e-3
batch_size = 64
epochs = 5

6.3优化循环

设置完超参数后,接下来我们在一个优化循环中训练并优化我们的模型。优化循环的每次迭代叫做一个 Epoch(时期、纪元)

每个 Epoch 由两个主要部分构成:

  • 训练循环 在训练数据集上遍历,尝试收敛到最优的参数。
  • 验证/测试循环 在测试数据集上遍历,以检查模型效果是否在提升。

接下来,让我们简单的熟悉一下在训练循环中使用的一些概念。

6.4损失函数

拿到一些训练数据的时候,我们的模型不太可能给出正确答案。损失函数能衡量获得的结果相对于目标值的偏离程度,我们希望在训练中能够最小化这个损失函数。我们对给定的数据样本做出预测然后和真实标签数据对比来计算损失。

常见的损失函数包括给回归任务用的 nn.MSELoss(Mean Square Error, 均方误差)、给分类任务使用的 nn.NLLLoss(Negative Log Likelihood, 负对数似然)、nn.CrossEntropyLoss(交叉熵损失函数)结合了 nn.LogSoftmaxnn.NLLLoss.

我们把模型输出的 logits 传递给 nn.CrossEntropyLoss, 它会正则化 logits 并计算预测误差。

# 初始化损失函数
loss_fn = nn.CrossEntropyLoss()

6.5优化器

优化是在每一个训练步骤中调整模型参数来减小模型误差的过程优化算法定义了这个过程应该如何进行(在这个例子中,我们使用 Stochastic Gradient Descent-即SGD,随机梯度下降)。所有优化的逻辑都被封装在 optimizer 这个对象中。这里,我们使用 SGD 优化器。除此之外,在 PyTorch 中还有很多其他可用的优化器,比如 ADAM 和 RMSProp 在不同类型的模型和数据上表现得更好。

通过注册需要训练的模型参数、然后传递学习率这个超参数来初始化优化器。

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练循环内部, 优化在三个步骤上发生:

  • 调用 optimizer.zero_grad() 来重置模型参数的梯度。梯度会默认累加,为了防止重复计算(梯度),我们在每次迭代中显式的清空(梯度累加值)。
  • 调用 loss.backward() 来反向传播预测误差。PyTorch 对每个参数分别存储损失梯度。
  • 获取到梯度后,调用 optimizer.step() 来根据反向传播中收集的梯度来调整参数。

6.6完整实现

定义 train_loop 为优化循环的代码,test_loop 为根据测试数据来评估模型表现的代码。

def train_loop(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)# 将模型设置为训练模式-对于 batch normalization 和 dropout 层很重要# 在这种情况下不需要,但为最佳实践添加了model.train()for batch, (X, y) in enumerate(dataloader):# Compute prediction and losspred = model(X)  # 计算模型的预测值loss = loss_fn(pred, y)  # 计算损失# Backpropagationloss.backward()  # 反向传播,计算梯度optimizer.step()  # 根据梯度更新模型参数optimizer.zero_grad()  # 清除梯度if batch % 100 == 0:# 打印损失信息loss, current = loss.item(), (batch + 1) * len(X)print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")def test_loop(dataloader, model, loss_fn):# 将模型设置为评估模式-对于 batch normalization 和 dropout 层很重要# 在这种情况下不需要,但为最佳实践添加了model.eval()size = len(dataloader.dataset)num_batches = len(dataloader) test_loss, correct = 0, 0# 使用 torch.no_grad() 对模型进行评估,确保在测试模式下不计算梯度# 同时也减少了对 requires_grad=True 的张量进行不必要的梯度计算和内存使用with torch.no_grad():for X, y in dataloader:pred = model(X)test_loss += loss_fn(pred, y).item()  # 计算测试集上的损失correct += (pred.argmax(1) == y).type(torch.float).sum().item()  # 统计正确预测的数量test_loss /= num_batches  # 计算平均损失correct /= size  # 计算准确率print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

初始化损失函数和优化器,传递给 train_looptest_loop。可以随意地修改 epochs 的数量来跟踪模型表现的进步情况。

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)epochs = 10
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train_loop(train_dataloader, model, loss_fn, optimizer)test_loop(test_dataloader, model, loss_fn)
print("Done!")

image-20240327110435881

7.模型保存和加载

在这个章节我们会学习如何持久化模型状态来保存、加载和执行模型预测。

import torch
import torchvision.models as models

7.1模型权重的保存和加载

PyTorch 将模型学习到的参数存储在一个内部状态字典中,叫 state_dict。它们可以通过 torch.save 方法来持久化。

# 使用 models.vgg16(weights='IMAGENET1K_V1') 加载一个预训练的 VGG16 模型,
# 该模型使用 ImageNet 数据集进行了训练。
model = models.vgg16(weights='IMAGENET1K_V1')# 调用 torch.save() 函数来保存这个模型的参数到一个文件中,
# 文件名为 'model_weights.pth'。
torch.save(model.state_dict(), 'model_weights.pth')

要加载模型权重,需要先创建一个跟要加载权重的模型结构一样的模型,然后使用 load_state_dict() 方法加载参数。

# 创建一个未经训练的 VGG16 模型,不指定 ``weights``
model = models.vgg16()# 加载之前保存的模型参数到模型中
model.load_state_dict(torch.load('model_weights.pth'))# 将模型设置为评估模式
model.eval()

image-20240327112830500

注意: 请确保在进行推理前调用 model.eval() 方法来将 dropout 层和 batch normalization 层设置为评估模式(evaluation模式)。如果不这么做的话会产生并不一致的推理结果。

7.2保存和加载模型结构

在加载模型权重的时候,需要首先实例化一个模型类,因为模型类定义了神经网络的结构。然而,我们也想把模型类结构和模型一起保存,那就可以通过将 model 传递给保存函数(而不是 model.state_dict())。

torch.save(model, 'model.pth')

可以这样载入模型:

model = torch.load('model.pth')

这种方法在序列化模型时使用 Python 的 pickle 模块,因此在加载模型时依赖于实际的类定义。


参考

  • Pytorch 官方教程
  • Pytorch 官方文档 (用于查阅各种 torch API 操作)

😃😃😃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/293006.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统慢查询的思考

系统慢查询的思考 在一个系统中发现慢查询的功能或很卡的现象。你是怎么思考的?从哪几个方面去思考?会用什么工具? 一个系统使用了几年后都可能会出现这样的问题。原因可能有以下几点。 数据量的增加。系统中平时的使用中数据量是有一个累…

【AXIS】AXI-Stream FIFO设计实现(四)——异步时钟

前文介绍了几种同步时钟情况下的AXI Stream FIFO实现方式,一般来说,FIFO也需要承担异步时钟域模块间数据传输的功能,本文介绍异步AXIS FIFO的实现方式。 如前文所说,AXI-Stream FIFO十分类似于FWFT异步FIFO,推荐参考前…

MIPI CSI-2 Low Level Protocol解读

一、Low Level Protocol介绍 LLP 是一种面向字节的基于数据包的协议,支持使用短数据包和长数据包格式传输任意数据。为简单起见,本节中的所有示例均为单通道配置。 LLP特性: 传输任意数据(与有效载荷无关) 8 位字大…

Chatgpt掘金之旅—有爱AI商业实战篇(二)

演示站点: https://ai.uaai.cn 对话模块 官方论坛: www.jingyuai.com 京娱AI 一、前言: 成为一名商业作者是一个蕴含着无限可能的职业选择。在当下数字化的时代,作家们有着众多的平台可以展示和推广自己的作品。无论您是对写书、文…

MSPF5438数据卫星透传

最近在网上找了个项目来做,实现功能简单描述就是通过Lora模块E30-170T27D接收上位机发送的数据包,并对接收数据包进行正确性校验,若数据包校验成功则将其储存在W25Q125FV 中,待上位机发送数据包传输完毕指令后,单片机启…

Docker配置Mysql

1.首页搜索mysql镜像 2.选择对应版本的MySQL,点击pull 3.pull完成以后,点击images,这里可以看到刚刚pull完成的mysql版本 4.打开命令界面,运行命令 docker images ,查看当前已经pull的images 5.运行命令设置mysql docker run -it…

【有芯职说】数字芯片BES工程师

一、 数字芯片BES工程师简介 今天来聊聊数字芯片BES工程师,其中BES是Back End Support的缩写,就是后端支持的意思。其实这个岗位是数字IC前端设计和数字IC后端设计之间的一座桥,完成从寄存器传输级设计到具体工艺的mapping和实现。这个岗位在…

Linux文件(系统)IO(含动静态库的链接操作)

文章目录 Linux文件(系统)IO(含动静态库的链接操作)1、C语言文件IO操作2、三个数据流stdin、stdout、stderr3、系统文件IO3.1、相关系统调用接口的使用3.2、文件描述符fd3.3、文件描述符的分配规则3.3、重定向3.4、自制shell加入重…

HCIP【GRE VPN、MGRE VPN与PPP验证综合实验】

目录 实验要求: 实验拓扑图: 实验思路: 实验步骤: 一、配IP地址 (1)配置所有设备接口的IP地址: (2)配置私网与公网接口的缺省路由使得公网可通: 二、P…

前端面试题---->JavaScript

const声明的对象属性和数组的值可以被修改吗?为什么 原因:当使用const声明一个对象或数组时,实际上是保证了对象或数组的引用不会被修改,但对象或数组本身的属性或元素是可以被修改的。这是因为const只能保证指向的内存地址不变&a…

操作教程|在MeterSphere中通过SSH登录服务器的两种方法

MeterSphere开源持续测试平台拥有非常强大的插件集成机制,用户可以通过插件实现平台能力的拓展,借助插件或脚本实现多种功能。在测试过程中,测试人员有时需要通过SSH协议登录至服务器,以获取某些配置文件和日志文件,或…

ES学习日记(四)-------插件head安装和一些配套插件下载

前言 接上节,第三方插件选择了时间久,功能丰富,长得丑的head,head 插件在ES 5版本以前开箱即用非常简单,ES 5版本以后需要运行在node环境下,所以我们要先准备一下环境 一.安装Git yum -y install git 二.安装node 安装包位置node for linux下载 解压…

CaT论文翻译

CaT: Balanced Continual Graph Learning with Graph Condensation CaT:通过图压缩实现平衡的连续图学习 Abstract 持续图学习(CGL)的目的是通过以流方式输入图数据来持续更新图模型。由于模型在使用新数据进行训练时很容易忘记以前学到的知识,因此灾…

什么是检索增强生成(Retrieval-Augmented Generation,RAG)

什么是RAG? 检索增强生成(Retrieval-Augmented Generation,RAG),是指为大模型提供外部知识源的概念。能够让大模型生成准确且符合上下文的答案,同时能够减少模型幻觉。 用最通俗的语言描述:在已…

向量点乘有哪些作用呢

如下: 1.找到两个向量之间的夹角(不用多说) 2.求一个向量投影在另一个向量的投影: 我们把图中b的在a上的投影向量称作b1吧,因为b1就在a上,所以只需要求出b1的大小,然后乘以a的单位向量,我们就得到向量b1了…

Unity 渲染

渲染的三个阶段 1:应用阶段 1.1 数据的准备 遮挡剔除,层级剔除。 渲染顺序,UI在Herachy窗口按照层级渲染,其余物体由大概按照先近后远。 打包渲染数据发送给显存,主要包括有模型信息,变换矩阵&#xff0c…

归并排序---分治法

1、算法的概念 归并排序:是创建在归并操作上的一种有效的排序算法。算法是采用分治法的一个非常典型的应用,且各层分治递归可以同时进行。归并排序的思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序&…

MoonBit MeetUp回顾——张正、宗喆:编程语言在云原生与区块链领域的技术探索

宗喆和张正分别给我们带了 KCL 相关的最新进展,由蚂蚁集团开发的 Rust 编写的开源 DSL,目标是优化云原生策略配置和用户体验。它通过引入动态配置管理、配置校验和基础设施抽象等核心概念,解决开发者认知负担、配置膨胀和标准化工具缺乏的问题…

[Windows]服务注册工具(nssm)

文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景:例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接:https://pan.baidu.com/s/111fkBWIS7CTlWIj80Kc8Sg?pwdanan 提取码…

python_2

文章目录 题目一运行结果 题目二运行结果 题目一 代码如下: def merge():ls_0 input("输入一个列表(空格隔开):").split()ls_1 []for i in ls_0:ls_1.append(i)ls_1.sort()if ls_0 ls_1:print("这是一个有序列表")else:print(&qu…