Pytorch框架从入门到精通
目录
一、Tensors
1.1 初始化一个Tensor
1)赋值初始化
2)从 NumPy 数组初始化
3)从另一个张量
4)使用随机值或常量值
1.2 Tensor 的属性
1.3 对 Tensor 的操作
1.3.1 总体介绍
1.3.2 索引和切片
1.3.3 算术运算
矩阵乘法
元素乘法
1.3.4 tensor值求和后转变为python数值
1.3.5 就地操作
1.4 使用 NumPy 桥接
1.4.1 Tensor 到 NumPy 数组
1.4.2 NumPy 数组到 Tensor
二、数据集和数据加载器--torch.utils.data API 接口
2.1 加载数据集
2.2 迭代和可视化数据集
2.3 创建自定义数据集
__init__
__len__
__getitem__
2.4 准备数据以使用 DataLoader 进行训练
2.5 遍历 DataLoader
三、变换--torchvision.transforms API 接口
3.1 ToTensor()
3.2 Lambda 转换
四、构建神经网络--torch.nn API
4.1 获取训练设备
4.2 定义类
4.3 模型层
1)nn.Flatten
2)nn.Linear
3)nn.ReLU
4)nn.Sequential
5)nn.Softmax
4.4 模型参数
五、自动微分--Autograd Mechanics
5.1 张量、函数与计算图
5.2 计算梯度
5.3 禁用梯度跟踪
5.4 更多关于计算图的知识
5.5 张量梯度与雅可比积
六、优化模型参数
6.1 超参数
6.2 优化循环
6.3 损失函数
6.4 优化器
6.5 完整实现
6.6 拓展阅读--损失函数,torch.optim,热启动训练
七、保存和加载模型
7.1 保存和加载模型权重
7.2 保存和加载带有形状的模型
(上述7.1和7.2的更详细相关教程)
7.3 运行模型预测
7.4 拓展阅读--检查点方面相关技巧
( 想要快速入门pytorch,请看这里哦~:PyTorch 快速入门-CSDN博客)
一、Tensors
数据结构
Tensors 是一种与数组和矩阵类似的数据结构,用于存储和操作多维数据。在 PyTorch 中的作用
用于编码模型的输入、输出和参数。
是 PyTorch 中最基本的数据单元。
与 NumPy 的相似性
Tensors 类似于 NumPy 的 ndarrays。
两者可以共享相同的底层内存,无需复制数据。
硬件加速支持
Tensors 可以在 GPU 或其他硬件加速器上运行,从而加速计算。
这使得 Tensors 特别适合深度学习和大规模数值计算。
自动微分优化
Tensors 针对自动微分进行了优化,便于在深度学习中进行梯度计算和反向传播。
灵活性
Tensors 支持多种数据类型和操作,适用于各种科学计算和机器学习任务。
先导包:torch,numpy
import torch
import numpy as np
1.1 初始化一个Tensor
1)赋值初始化
data = [[1, 2],[3, 4]]
print(data)
x_data = torch.tensor(data)
print(x_data)
>> 运行结果
>> [[1, 2], [3, 4]]
>> tensor([[1, 2],
[3, 4]])
2)从 NumPy 数组初始化
np_array = np.array(data)
print(np_array)
x_np = torch.from_numpy(np_array)
print(x_np)
>> 结果:
>> [[1 2]
[3 4]]
>> tensor([[1, 2],
[3, 4]])
3)从另一个张量
新张量保留参数张量的属性(形状、数据类型),除非显式覆盖。
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")
>> 结果:
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.7093, 0.8628],
[0.4379, 0.9094]])
4)使用随机值或常量值
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}")
>> 结果:
Random Tensor:
tensor([[0.3727, 0.1424, 0.1199],
[0.0873, 0.0711, 0.3417]])
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
1.2 Tensor 的属性
Tensor 属性描述其形状、数据类型和存储它们的设备。
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}")
>> 结果
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
1.3 对 Tensor 的操作
1.3.1 总体介绍
1)张量运算:涵盖算术运算、线性代数、矩阵操作(如转置、索引、切片)以及采样等,这些运算的详细描述可在PyTorch官方文档中查看。
2)运算设备支持
不仅可以在CPU上运行,还支持在各种加速器上运行,如CUDA、MPS、MTIA或XPU等,以提升计算效率
3)张量默认创建位置及移动
默认情况下,张量是在CPU上创建的。若需将张量移动到加速器上,需在检查加速器可用性之后,使用.to方法显式进行移动,例如:
# We move our tensor to the current accelerator if available
if torch.accelerator.is_available():
tensor = tensor.to(torch.accelerator.current_accelerator())
1.3.2 索引和切片
:操作:表示省略某个维度
...操作:表示从该位置往前的维度都省略
形状计算步骤
假设 tensor
的形状为 (d1, d2, ..., dn)
,其中 d1, d2, ..., dn
分别表示每个维度的大小。
tensor[...,-1]操作后最终结果的形状为 (d1, d2, ..., dn-1),
tensor[...,:2](取前两个元素)操作后最终结果的形状为 (d1, d2, ..., dn-1,2)
tensor = torch.ones(2, 3, 1)
print(tensor)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}") #等价于tensor[:,:,-1]
tensor[:,1] = 0
print(tensor)
>> 结果
tensor([[[1.],
[1.],
[1.]],
[[1.],
[1.],
[1.]]])
First row: tensor([[1.],
[1.],
[1.]])
First column: tensor([[1.],
[1.]])
Last column: tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[[1.],
[0.],
[1.]],
[[1.],
[0.],
[1.]]])
1.3.3 联接张量
可用于沿给定维度连接一系列张量。
法一:torch.stack--沿新维度连接一系列张量。
torch.stack(tensors, dim=0, *, out=None)
参数:
- tensors:要连接的张量序列。tensors中所有tensor大小都要相同
- dim (int, optional):要插入的维度。必须介于 0 和 维度数字(包括)。默认值:0
- 关键字参数out (Tensor, optional) - 输出张量。
例子:
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]])
>>> torch.stack((x, x)) # same as torch.stack((x, x), dim=0)
tensor([[[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]],
[[ 0.3367, 0.1288, 0.2345],
[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x)).size()
torch.Size([2, 2, 3])
>>> torch.stack((x, x), dim=1)
tensor([[[ 0.3367, 0.1288, 0.2345],
[ 0.3367, 0.1288, 0.2345]],
[[ 0.2303, -1.1229, -0.1863],
[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x), dim=2)
tensor([[[ 0.3367, 0.3367],
[ 0.1288, 0.1288],
[ 0.2345, 0.2345]],
[[ 0.2303, 0.2303],
[-1.1229, -1.1229],
[-0.1863, -0.1863]]])
>>> torch.stack((x, x), dim=-1)
tensor([[[ 0.3367, 0.3367],
[ 0.1288, 0.1288],
[ 0.2345, 0.2345]],
[[ 0.2303, 0.2303],
[-1.1229, -1.1229],
[-0.1863, -0.1863]]])
stack形状计算步骤
假设 tensor
的形状为 (d1, d2, ..., dn)
,其中 d1, d2, ..., dn
分别表示每个维度的大小,
dim = 1。
- 如果2个tensor拼接,则最终结果的形状为
(d1, 2, d2, ..., dn)
,即在维度1上增加一个大小为2的新维度; - 如果3个tensor拼接,则最终结果的形状为
(d1, 3, d2, ..., dn)
,即在维度1上增加一个大小为3的新维度; - 如果4个tensor拼接,则最终结果的形状为
(d1, 4, d2, ..., dn)
,即在维度1上增加一个大小为4的新维度;
法二:torch.cat--沿现有维度连接给定序列。
tensor = torch.ones(2, 3)
t1 = torch.cat([tensor, tensor, tensor], dim=1)
t2 = torch.stack([tensor, tensor, tensor], dim=1)
print(t1)
print(t1.shape)
print(t2)
print(t2.shape)
>> 结果
tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1., 1., 1.]])
torch.Size([2, 9])
tensor([[[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]],
[[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]]])
torch.Size([2, 3, 3])
cat形状计算步骤
假设 tensor
的形状为 (d1, d2, ..., dn)
,其中 d1, d2, ..., dn
分别表示每个维度的大小,
dim = 1。
- 如果2个tensor拼接,则最终结果的形状为
(d1, d2*2, ..., dn)
,即在维度1上增加一个大小为2的新维度; - 如果3个tensor拼接,则最终结果的形状为
(d1, d2*3, ..., dn)
,即在维度1上增加一个大小为3的新维度; - 如果4个tensor拼接,则最终结果的形状为
(d1, d2*4, ..., dn)
,即在维度1上增加一个大小为4的新维度;
1.3.3 算术运算
矩阵乘法
@
运算符:张量的矩阵乘法。
matmul
函数:
y2 = tensor.matmul(tensor.T)
:使用matmul
函数计算tensor
和其转置tensor.T
的矩阵乘法。
matmul
函数是 PyTorch 中用于矩阵乘法的函数,与@
运算符功能相同。
matmul
函数的out
参数:
torch.matmul(tensor, tensor.T, out=y3)
:将tensor
和其转置tensor.T
的矩阵乘法结果直接写入已存在的 y3 张量中。
out
参数允许将运算结果直接写入指定的张量,避免额外的内存分配。
元素乘法
*
运算符:张量的元素乘法,即对应位置的元素相乘。
mul
函数:
z2 = tensor.mul(tensor)
:使用mul
函数计算tensor
与其自身的元素乘法。
mul
函数是 PyTorch 中用于元素乘法的函数,与*
运算符功能相同。
mul
函数的out
参数:
torch.mul(tensor, tensor, out=z3)
:将tensor
与其自身的元素乘法结果直接写入已存在的z3张量中。
out
参数允许将运算结果直接写入指定的张量,避免额外的内存分配。
tensor = torch.ones(2, 3)
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` returns the transpose of a tensor
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)
# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
>> 结果
tensor([[0.0132, 2.5215, 1.2758],
[3.7079, 0.0481, 1.4282]])
1.3.4 tensor值求和后转变为python数值
通过item()可以将tensor聚合后的所有值转换为一个值
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
>> 结果
3.5523605346679688 <class 'float'>
1.3.5 就地操作
是指直接在原始张量上进行修改,而不创建新的张量。这些操作通常以 _
结尾,例如 add_
、mul_
、fill_
等。
print(f"{tensor} \n")
tensor.add_(5)
print(tensor)
>> 结果
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.]])
1.4 使用 NumPy 桥接
CPU 和 NumPy 数组上的张量可以共享其底层内存 locations 的 Locations,更改一个位置将更改另一个位置。
1.4.1 Tensor 到 NumPy 数组
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
>> 结果
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
此时如果张量发生变化,那么numpy数组也会发生变化
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
>> 结果
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
1.4.2 NumPy 数组到 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}")
>> 结果
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
二、数据集和数据加载器--torch.utils.data API 接口
处理数据样本的代码容易混乱,难以维护。所以PyTorch提供了Dataset和DataLoader这两个工具,把数据集代码和模型训练代码分开,提高可读性和模块化。
- torch.utils.data.Dataset:抽象数据集对象,负责封装样本和标签的存储
- torch.utils.data.DataLoader:构建在Dataset之上的可迭代包装器,实现高效数据加载
2.1 加载数据集
从 TorchVision 加载 Fashion-MNIST 数据集。
Fashion-MNIST 是 Zalando 的文章图像数据集,由 60,000 个训练示例和 10,000 个测试示例组成。 每个示例都包含一个 28×28 灰度图像和一个来自 10 个类之一的关联标签。
我们使用以下参数加载 FashionMNIST 数据集:
-
root
是存储训练/测试数据的路径, -
train
指定训练或测试数据集, -
download=True
从 Internet 下载数据(如果 .root
上没有数据) -
transform
指定特征和标签转换target_transform
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
>> 结果
0%| | 0.00/26.4M [00:00<?, ?B/s]
0%| | 65.5k/26.4M [00:00<01:12, 361kB/s]
1%| | 229k/26.4M [00:00<00:38, 680kB/s]
3%|3 | 885k/26.4M [00:00<00:10, 2.53MB/s]
7%|7 | 1.93M/26.4M [00:00<00:06, 4.08MB/s]
25%|##4 | 6.52M/26.4M [00:00<00:01, 15.2MB/s]
38%|###7 | 9.96M/26.4M [00:00<00:00, 17.1MB/s]
59%|#####8 | 15.5M/26.4M [00:01<00:00, 26.5MB/s]
73%|#######2 | 19.2M/26.4M [00:01<00:00, 24.5MB/s]
93%|#########3| 24.6M/26.4M [00:01<00:00, 31.4MB/s]
100%|##########| 26.4M/26.4M [00:01<00:00, 19.3MB/s]
0%| | 0.00/29.5k [00:00<?, ?B/s]
100%|##########| 29.5k/29.5k [00:00<00:00, 327kB/s]
0%| | 0.00/4.42M [00:00<?, ?B/s]
1%|1 | 65.5k/4.42M [00:00<00:12, 361kB/s]
5%|5 | 229k/4.42M [00:00<00:06, 679kB/s]
21%|## | 918k/4.42M [00:00<00:01, 2.57MB/s]
44%|####3 | 1.93M/4.42M [00:00<00:00, 4.07MB/s]
100%|##########| 4.42M/4.42M [00:00<00:00, 6.06MB/s]
0%| | 0.00/5.15k [00:00<?, ?B/s]
100%|##########| 5.15k/5.15k [00:00<00:00, 30.8MB/s]
2.2 迭代和可视化数据集
可以像列表一样手动索引,用于可视化训练数据中的一些样本。
Datasets:training_data[index]
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__ 和 __getitem__。
存储 FashionMNIST 图像 在 directory 中,并且它们的标签单独存储在 CSV file 中。
import os
import pandas as pd
from torchvision.io import read_image
class 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_dir
self.transform = transform
self.target_transform = target_transform
def __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__
__init__ 函数在实例化 Dataset 对象时运行一次。我们初始化 包含图像、annotations 文件和两个转换的目录
labels.csv 文件如下:
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
__len__
返回数据集中的样本数
__getitem__
__getitem__函数根据给定的索引从数据集中加载并返回一个样本。基于该索引,它识别图像在磁盘上的位置,并将其转换为张量,从CSV数据中检索相应的标签,对它们调用转换函数,并以元组的形式返回张量图像和相应的标签。
2.4 准备数据以使用 DataLoader 进行训练
一次检索数据集的一个样本的特征和标签。在训练模型时,我们通常希望以“小批量”的形式传递样本,在每个epoch时重新洗牌数据以减少模型过拟合,并使用Python的多线程来加快数据检索速度。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
2.5 遍历 DataLoader
将该数据集加载到DataLoader中,并可以根据需要遍历数据集。下面的每次迭代都会返回一个批次的feature和label(分别包含特征和标签)。由于指定了batchs,所以在遍历完所有批次后,数据会被打乱(若要更精细地控制数据加载顺序,可以查看[相关文档])。
# Display image and label.
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}")
>> 结果
Feature batch shape: torch.Size([64, 1, 28, 28])
Labels batch shape: torch.Size([64])
Label: 5
三、变换--torchvision.transforms API 接口
在训练机器学习算法时,数据并不总是以所需的最终处理形式出现。我们使用 transform 来执行一些数据操作使其适合训练。
所有 TorchVision 数据集都有两个参数
- transform:修改特征和修改标签 ;
- target_transform:接受包含转换逻辑的可调用对象。
torchvision.transforms 模块提供 几个开箱即用的常用转换。 FashionMNIST 功能采用 PIL 图像格式,标签为整数。 对于训练,我们需要将特征作为标准化张量,将标签作为独热编码张量。 为了进行这些转换,我们使用 ToTensor ()和 Lambda
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = 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))
)
>> 结果
0%| | 0.00/26.4M [00:00<?, ?B/s]
0%| | 65.5k/26.4M [00:00<01:12, 363kB/s]
1%| | 197k/26.4M [00:00<00:45, 575kB/s]
3%|2 | 721k/26.4M [00:00<00:12, 2.04MB/s]
6%|5 | 1.57M/26.4M [00:00<00:07, 3.33MB/s]
21%|##1 | 5.64M/26.4M [00:00<00:01, 13.2MB/s]
35%|###5 | 9.27M/26.4M [00:00<00:01, 16.4MB/s]
56%|#####5 | 14.7M/26.4M [00:01<00:00, 25.6MB/s]
70%|####### | 18.5M/26.4M [00:01<00:00, 24.4MB/s]
90%|########9 | 23.7M/26.4M [00:01<00:00, 30.9MB/s]
100%|##########| 26.4M/26.4M [00:01<00:00, 19.3MB/s]
0%| | 0.00/29.5k [00:00<?, ?B/s]
100%|##########| 29.5k/29.5k [00:00<00:00, 328kB/s]
0%| | 0.00/4.42M [00:00<?, ?B/s]
1%|1 | 65.5k/4.42M [00:00<00:12, 361kB/s]
5%|5 | 229k/4.42M [00:00<00:06, 678kB/s]
21%|##1 | 950k/4.42M [00:00<00:01, 2.18MB/s]
87%|########6 | 3.83M/4.42M [00:00<00:00, 7.58MB/s]
100%|##########| 4.42M/4.42M [00:00<00:00, 6.06MB/s]
0%| | 0.00/5.15k [00:00<?, ?B/s]
100%|##########| 5.15k/5.15k [00:00<00:00, 36.4MB/s]
3.1 ToTensor()
ToTensor()是PyTorch中torchvision.transforms模块的一个常用函数,主要用于将输入数据(如图像、数组等)转换为张量的形式。
数据类型转换:将PIL图像或NumPy ndarray转换为FloatTensor。
像素值归一化:将图像的像素强度值从0,255范围缩放至0.,1.范围内。
通道顺序调整:将形状为(H, W, C)的多维数组或PIL图像对象转换为形状为(C, H, W)的张量。
3.2 Lambda 转换
Lambda
转换可以对输入数据应用任意的自定义函数。这个自定义函数可以是任何可调用对象,例如Python函数、类的实例等。通过Lambda
转换,用户可以实现各种复杂的转换逻辑,而无需编写额外的转换类。
在这里,我们定义了一个匿名函数,将整数转换为one-hot编码张量。具体步骤如下:
-
创建一个长度为10的零张量(因为我们数据集中有10个标签)。
-
使用
scatter_
方法将指定位置的值设置为1。
( 匿名函数:
使用lambda
关键字定义
lambda arguments: expression
• arguments:函数的参数,可以有多个,用逗号分隔。
• expression:函数的返回值,是一个表达式。
)
四、构建神经网络--torch.nn API
神经网络由对数据执行操作的层/模块组成。torch.nn 命名空间提供了构建您自己的神经网络所需的所有构建块。PyTorch 中的每个模块都是 nn.Module 的子类。神经网络本身也是一个模块,由其他模块(层)组成。这种嵌套结构使得构建复杂的架构变得容易。
下边构建一个神经网络来对 FashionMNIST 数据集中的图像进行分类。
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
4.1 获取训练设备
我们希望能够在 GPU 或 MPS 等硬件加速器上训练我们的模型(如果可用)。让我们检查一下 torch.cuda 或 torch.backends.mps 是否可用,否则我们使用 CPU。
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
print(f"Using {device} device")
4.2 定义类
我们通过继承 nn.Module
来定义我们的神经网络,并在 __init__
中初始化神经网络层。每个 nn.Module
子类都在 forward
方法中实现对输入数据的操作。
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 logits
我们创建一个 NeuralNetwork 的实例,并将其移动到 device 上,然后打印其结构。
model = NeuralNetwork().to(device)
print(model)
>> 结果
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
要使用模型,我们将输入数据传递给它。这将执行模型的 forward 方法,以及一些后台操作。不要直接调用 model.forward()!
在输入上调用模型会返回一个 10 维张量,其中包含每个类的原始预测值。我们通过 nn.Softmax 模块的实例来获得预测概率。
X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")
>> 结果
Predicted class: tensor([9], device='cuda:0')
4.3 模型层
让我们分解 FashionMNIST 模型中的层。为了说明这一点,我们将取一个包含 3 张 28x28 图像的样本小批量,看看通过网络时会发生什么。
input_image = torch.rand(3, 28, 28)
print(input_image.size())
>> 结果
torch.Size([3, 28, 28])
1)nn.Flatten
我们初始化 nn.Flatten 层,将每个 2D 的 28x28 图像转换为 784 个像素值的连续数组(保持小批量维度(dim=0))。
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
>> 结果
torch.Size([3, 784])
2)nn.Linear
线性层 使用其存储的权重和偏置对输入应用线性变换。
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
>> 结果
torch.Size([3, 20])
3)nn.ReLU
非线性激活函数在模型的输入和输出之间创建复杂的映射。它们在线性变换后应用以引入非线性,帮助神经网络学习各种现象。
在这个模型中,我们在线性层之间使用 nn.ReLU,但还有其他激活函数可以在模型中引入非线性。
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
>> 结果:负数全部变成0.0000,正数不变
Before ReLU: tensor([[ 0.0399, -0.0856, 0.1098, -0.0472, -0.1217, 0.1842, -0.3489, 0.3911,
-0.2160, -0.2117, 0.3714, -0.2562, 0.0076, 0.2177, -0.1724, 0.2404,
0.1411, 0.1120, -0.0715, 0.0376],
[ 0.0208, -0.0104, 0.0881, -0.0955, -0.0816, 0.1917, -0.3871, 0.3878,
-0.1827, -0.2110, 0.3427, -0.2707, 0.0063, 0.2005, -0.1583, 0.2273,
0.1337, 0.0921, -0.0562, 0.0126],
[ 0.0909, -0.1817, 0.0926, -0.0005, -0.1026, 0.2360, -0.3727, 0.4387,
-0.2225, -0.2530, 0.3822, -0.2901, 0.0119, 0.2223, -0.1836, 0.2681,
0.1432, 0.0975, -0.0892, 0.0478]], grad_fn=<AddmmBackward0>)
After ReLU: tensor([[0.0399, 0.0000, 0.1098, 0.0000, 0.0000, 0.1842, 0.0000, 0.3911, 0.0000,
0.0000, 0.3714, 0.0000, 0.0076, 0.2177, 0.0000, 0.2404, 0.1411, 0.1120,
0.0000, 0.0376],
[0.0208, 0.0000, 0.0881, 0.0000, 0.0000, 0.1917, 0.0000, 0.3878, 0.0000,
0.0000, 0.3427, 0.0000, 0.0063, 0.2005, 0.0000, 0.2273, 0.1337, 0.0921,
0.0000, 0.0126],
[0.0909, 0.0000, 0.0926, 0.0000, 0.0000, 0.2360, 0.0000, 0.4387, 0.0000,
0.0000, 0.3822, 0.0000, 0.0119, 0.2223, 0.0000, 0.2681, 0.1432, 0.0975,
0.0000, 0.0478]], grad_fn=<ReluBackward0>)
4)nn.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)
5)nn.Softmax
神经网络的最后一个线性层返回 logits
——原始值在 [-infty, infty] 范围内——传递给 nn.Softmax 模块。logits 被缩放到 [0, 1] 范围内的值,表示模型对每个类的预测概率。dim
参数指示值必须总和为 1 的维度。
softmax = nn.Softmax(dim=1) #这里指第二维列特征值和为1
pred_probab = softmax(logits)
4.4 模型参数
神经网络内的许多层都是参数化的,即具有在训练期间优化的相关权重和偏置。承 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")
>> 结果
Model structure: NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[-0.0075, 0.0333, -0.0135, ..., 0.0331, -0.0281, 0.0350],
[ 0.0315, -0.0007, 0.0277, ..., -0.0087, -0.0335, -0.0144]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0154, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[-0.0417, 0.0111, 0.0067, ..., -0.0365, -0.0205, -0.0344],
[ 0.0071, -0.0414, -0.0253, ..., -0.0147, -0.0413, -0.0335]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0481, -0.0060], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0025, -0.0210, -0.0270, ..., -0.0199, -0.0220, -0.0364],
[ 0.0213, -0.0376, -0.0220, ..., -0.0360, -0.0225, -0.0227]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0150, -0.0261], device='cuda:0', grad_fn=<SliceBackward0>)
五、自动微分--Autograd Mechanics
在训练神经网络时,最常用的算法是反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。
为了计算这些梯度,PyTorch 内置了一个名为 torch.autograd 的微分引擎。它支持任何计算图的梯度自动计算。
考虑最简单的单层神经网络,具有输入 x
、参数 w
和 b
,以及一些损失函数。它可以通过以下方式在 PyTorch 中定义:
import torch
x = torch.ones(5) # 输入张量
y = torch.zeros(3) # 期望输出
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 张量、函数与计算图
上段代码定义了以下计算图:
在这个网络中,w
和 b
是参数,我们需要对其进行优化。因此,我们需要能够计算损失函数相对于这些变量的梯度。为了做到这一点,我们设置了这些张量的 requires_grad
属性。
(注:您可以在创建张量时设置 requires_grad
的值,或者稍后使用 x.requires_grad_(True)
方法。)
我们应用于张量以构建计算图的函数实际上是 Function
类的对象。该对象知道如何前向计算函数,也知道如何在反向传播步骤中计算其导数。反向传播函数的引用存储在张量的 grad_fn
属性中。您可以在 Function 文档 中找到更多信息。
print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")
>> 结果
Gradient function for z = <AddBackward0 object at 0x7f083264cd60>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f083264eec0>
5.2 计算梯度
为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即我们需要在固定的 x
和 y
值下计算 和 。为了计算这些导数,我们调用 loss.backward()
,然后从 w.grad
和 b.grad
中获取值:
loss.backward()
print(w.grad)
print(b.grad)
>> 结果
tensor([[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530]])
tensor([0.3313, 0.0626, 0.2530])
注意:
我们只能从计算图中设置了
requires_grad=True
的叶节点获取梯度。如果需要在同一计算图上多次调用
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)
>> 结果
True
False
另一种实现相同效果的方法是使用张量的 detach() 方法:
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
>> 结果
False
希望禁用梯度跟踪的原因:
• 将神经网络中的某些参数标记为冻结参数。
• 在仅进行前向传递时加速计算,因为不跟踪梯度的张量计算会更高效。
5.4 更多关于计算图的知识
从概念上讲,autograd 在一个由 Function 对象组成的有向无环图(DAG)中保存数据(张量)和所有执行的操作(以及生成的新张量)的记录。在这个 DAG 中,叶节点是输入张量(x,w,b),根节点是输出张量(loss)。通过从根节点到叶节点追踪这个图,您可以使用链式法则自动计算梯度。
在前向传递中,autograd 同时执行以下操作:
-
运行请求的操作以计算结果张量。
-
在 DAG 中维护操作的梯度函数。
当在 DAG 根节点上调用 .backward()
时,反向传递开始。autograd
然后:
-
从每个
.grad_fn
计算梯度。 -
将它们累积在相应张量的
.grad
属性中。 -
使用链式法则一直传播到叶张量。
注意:
在 PyTorch 中,DAG 是动态的。需要注意的是,图是从头开始重新创建的;每次调用.backward()
后,autograd 都会填充一个新的图。这正是允许您在模型中使用控制流语句的原因。如果需要,您可以在每次迭代中更改形状、大小和操作。
5.5 张量梯度与雅可比积
在许多情况下,我们有一个标量损失函数,并且需要计算相对于某些参数的梯度。然而,在某些情况下,输出函数是任意张量。在这种情况下,PyTorch 允许您计算所谓的雅可比积,而不是实际的梯度。
对于向量函数,其中且, 相对于 的梯度由雅可比矩阵给出:
PyTorch 允许您对于给定的输入向量 计算雅可比积 ,而不是计算雅可比矩阵本身。这是通过将 作为参数传递给 backward
来实现的。 的大小应与原始张量的大小相同,我们要计算其乘积:
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nThird call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")
>> 结果
First call
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.]])
Second call
tensor([[8., 4., 4., 4., 4.],
[4., 8., 4., 4., 4.],
[4., 4., 8., 4., 4.],
[4., 4., 4., 8., 4.]])
Third call
tensor([[12., 6., 6., 6., 6.],
[ 6., 12., 6., 6., 6.],
[ 6., 6., 12., 6., 6.],
[ 6., 6., 6., 12., 6.]])
Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.]])
请注意,当我们使用相同的参数第二次调用 backward
时,梯度的值是不同的。这是因为在进行 backward
传播时,PyTorch 累积梯度,即计算的梯度值被添加到计算图的所有叶节点的 grad
属性中。如果要计算正确的梯度,需要先将 grad
属性清零。在实际训练中,优化器帮助我们做到这一点。
注意:
之前我们没有带参数调用backward()
函数。这本质上等同于调用backward(torch.tensor(1.0))
,这是在标量值函数(例如神经网络训练中的损失)情况下计算梯度的一种有用方式。
六、优化模型参数
现在我们有了模型和数据,是时候通过优化数据上的参数来训练、验证和测试我们的模型了。训练模型是一个迭代的过程;在每次迭代中,模型会对输出进行猜测,计算猜测的误差(损失),收集误差相对于其参数的导数(正如五中看到的那样),并使用梯度下降法优化这些参数。有关此过程的更详细演练,请观看反向传播视频。
之前已完成关于数据集和数据加载器以及构建模型的代码如下:
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
training_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 logits
model = NeuralNetwork()
6.1 超参数
超参数是可调节的参数,允许你控制模型优化过程。不同的超参数值会影响模型训练和收敛速度(关于超参数调优的内容)。
我们定义了以下用于训练的超参数:
-
学习率:在每个batch / epoch 中更新模型参数的程度。较小的值会导致学习速度较慢,而较大的值可能会导致训练过程中出现不可预测的行为。
-
batch size:在更新参数之前通过网络传播的数据样本数量。
-
epoch 数:迭代数据集的次数。
learning_rate = 1e-3
batch_size = 64
epochs = 5
6.2 优化循环
一旦我们设置了超参数,我们就可以使用优化循环来训练和优化我们的模型。优化循环的每次迭代称为一个epoch。
每个epoch由两个主要部分组成:
-
训练循环:迭代训练数据集并尝试收敛到最佳参数。
-
验证/测试循环:迭代测试数据集以检查模型性能是否在改善。
6.5完整实现中展示了循环过程
6.3 损失函数
当提供一些训练数据时,我们未经训练的网络可能不会给出正确的答案。损失函数衡量的是获得的结果与目标值的不相似程度,这是我们希望在训练期间最小化的损失函数。为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。
常见的损失函数包括用于回归任务的nn.MSELoss
(均方误差)和用于分类的nn.NLLLoss
(负对数似然)。nn.CrossEntropyLoss
结合了nn.LogSoftmax
和nn.NLLLoss
。
我们将模型的输出 logits 传递给nn.CrossEntropyLoss
,它将对 logits 进行归一化并计算预测误差。
# 初始化损失函数
loss_fn = nn.CrossEntropyLoss()
6.4 优化器
优化是在每个训练步骤中调整模型参数以减少模型误差的过程。优化算法定义了如何执行此过程(在本例中,我们使用随机梯度下降法)。所有优化逻辑都封装在optimizer
对象中。在这里,我们使用SGD优化器;此外,PyTorch中有许多不同的优化器,如ADAM和RMSProp,它们更适用于不同类型的模型和数据。
我们通过注册需要训练的模型参数并传入学习率超参数来初始化优化器。
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
在训练循环中,优化分三个步骤进行:
-
调用
optimizer.zero_grad()
来重置模型参数的梯度。默认情况下,梯度会累加;为了防止重复计算,我们在每次迭代时显式地将它们归零。 -
通过调用
loss.backward()
来反向传播预测损失。PyTorch会存储每个参数的损失梯度。 -
一旦我们有了梯度,我们就调用
optimizer.step()
来通过反向传播中收集的梯度调整参数。
6.5 完整实现
我们定义了train_loop
来循环我们的优化代码,test_loop
来根据测试数据评估模型的性能。
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
# 计算预测和损失
pred = model(X)
loss = loss_fn(pred, y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test_loop(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
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_loop和test_loop。随意增加epoch数,以跟踪模型性能的改善。
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!")
>> 结果
Epoch 1
-------------------------------
loss: 2.298730 [ 64/60000]
loss: 2.289123 [ 6464/60000]
loss: 2.273286 [12864/60000]
loss: 2.269406 [19264/60000]
loss: 2.249603 [25664/60000]
loss: 2.229407 [32064/60000]
loss: 2.227368 [38464/60000]
loss: 2.204261 [44864/60000]
loss: 2.206193 [51264/60000]
loss: 2.166651 [57664/60000]
Test Error:
Accuracy: 50.9%, Avg loss: 2.166725
Epoch 2
-------------------------------
loss: 2.176750 [ 64/60000]
loss: 2.169595 [ 6464/60000]
loss: 2.117500 [12864/60000]
loss: 2.129272 [19264/60000]
loss: 2.079674 [25664/60000]
loss: 2.032928 [32064/60000]
loss: 2.050115 [38464/60000]
loss: 1.985236 [44864/60000]
loss: 1.987887 [51264/60000]
loss: 1.907162 [57664/60000]
Test Error:
Accuracy: 55.9%, Avg loss: 1.915486
Epoch 3
-------------------------------
loss: 1.951612 [ 64/60000]
loss: 1.928685 [ 6464/60000]
loss: 1.815709 [12864/60000]
loss: 1.841552 [19264/60000]
loss: 1.732467 [25664/60000]
loss: 1.692914 [32064/60000]
loss: 1.701714 [38464/60000]
loss: 1.610632 [44864/60000]
loss: 1.632870 [51264/60000]
loss: 1.514263 [57664/60000]
Test Error:
Accuracy: 58.8%, Avg loss: 1.541525
Epoch 4
-------------------------------
loss: 1.616448 [ 64/60000]
loss: 1.582892 [ 6464/60000]
loss: 1.427595 [12864/60000]
loss: 1.487950 [19264/60000]
loss: 1.359332 [25664/60000]
loss: 1.364817 [32064/60000]
loss: 1.371491 [38464/60000]
loss: 1.298706 [44864/60000]
loss: 1.336201 [51264/60000]
loss: 1.232145 [57664/60000]
Test Error:
Accuracy: 62.2%, Avg loss: 1.260237
Epoch 5
-------------------------------
loss: 1.345538 [ 64/60000]
loss: 1.327798 [ 6464/60000]
loss: 1.153802 [12864/60000]
loss: 1.254829 [19264/60000]
loss: 1.117322 [25664/60000]
loss: 1.153248 [32064/60000]
loss: 1.171765 [38464/60000]
loss: 1.110263 [44864/60000]
loss: 1.154467 [51264/60000]
loss: 1.070921 [57664/60000]
Test Error:
Accuracy: 64.1%, Avg loss: 1.089831
Epoch 6
-------------------------------
loss: 1.166889 [ 64/60000]
loss: 1.170514 [ 6464/60000]
loss: 0.979435 [12864/60000]
loss: 1.113774 [19264/60000]
loss: 0.973411 [25664/60000]
loss: 1.015192 [32064/60000]
loss: 1.051113 [38464/60000]
loss: 0.993591 [44864/60000]
loss: 1.039709 [51264/60000]
loss: 0.971077 [57664/60000]
Test Error:
Accuracy: 65.8%, Avg loss: 0.982440
Epoch 7
-------------------------------
loss: 1.045165 [ 64/60000]
loss: 1.070583 [ 6464/60000]
loss: 0.862304 [12864/60000]
loss: 1.022265 [19264/60000]
loss: 0.885213 [25664/60000]
loss: 0.919528 [32064/60000]
loss: 0.972762 [38464/60000]
loss: 0.918728 [44864/60000]
loss: 0.961629 [51264/60000]
loss: 0.904379 [57664/60000]
Test Error:
Accuracy: 66.9%, Avg loss: 0.910167
Epoch 8
-------------------------------
loss: 0.956964 [ 64/60000]
loss: 1.002171 [ 6464/60000]
loss: 0.779057 [12864/60000]
loss: 0.958409 [19264/60000]
loss: 0.827240 [25664/60000]
loss: 0.850262 [32064/60000]
loss: 0.917320 [38464/60000]
loss: 0.868384 [44864/60000]
loss: 0.905506 [51264/60000]
loss: 0.856353 [57664/60000]
Test Error:
Accuracy: 68.3%, Avg loss: 0.858248
Epoch 9
-------------------------------
loss: 0.889765 [ 64/60000]
loss: 0.951220 [ 6464/60000]
loss: 0.717035 [12864/60000]
loss: 0.911042 [19264/60000]
loss: 0.786085 [25664/60000]
loss: 0.798370 [32064/60000]
loss: 0.874939 [38464/60000]
loss: 0.832796 [44864/60000]
loss: 0.863254 [51264/60000]
loss: 0.819742 [57664/60000]
Test Error:
Accuracy: 69.5%, Avg loss: 0.818780
Epoch 10
-------------------------------
loss: 0.836395 [ 64/60000]
loss: 0.910220 [ 6464/60000]
loss: 0.668506 [12864/60000]
loss: 0.874338 [19264/60000]
loss: 0.754805 [25664/60000]
loss: 0.758453 [32064/60000]
loss: 0.840451 [38464/60000]
loss: 0.806153 [44864/60000]
loss: 0.830360 [51264/60000]
loss: 0.790281 [57664/60000]
Test Error:
Accuracy: 71.0%, Avg loss: 0.787271
Done!
6.6 拓展阅读--损失函数,torch.optim,热启动训练
损失函数
torch.optim
热启动训练
七、保存和加载模型
在本节中,我们将了解如何通过保存、加载和运行模型预测来保持模型状态。
import torch
import torchvision.models as models
7.1 保存和加载模型权重
PyTorch模型将学习到的参数存储在一个内部状态字典中,称为state_dict
。这些参数可以通过torch.save
方法持久化保存:
model = models.vgg16(pretrained=True)
torch.save(model.state_dict(), 'model_weights.pth')
要加载模型权重,你需要先创建一个相同模型的实例,然后使用load_state_dict()
方法加载参数。
model = models.vgg16() # 我们不指定pretrained=True,即不加载默认权重
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()
注意:
在运行推理之前,请务必调用model.eval()
方法,以将 dropout 和 batch normalization 层设置为评估模式。如果不这样做,将会导致推理结果不一致。
7.2 保存和加载带有形状的模型
在加载模型权重时,我们需要先实例化模型类,因为该类定义了网络的结构。我们可能希望将此类的结构与模型一起保存,在这种情况下,我们可以将model
(而不是model.state_dict()
)传递给保存函数:
torch.save(model, 'model.pth')
然后我们可以像这样加载模型:
model = torch.load('model.pth')
注意:
这种方法在序列化模型时使用 Python 的 pickle 模块,因此在加载模型时依赖于实际的类定义。
(上述7.1和7.2的更详细相关教程)
在 PyTorch 中保存和加载模型:查看更详细的关于保存和加载模型的指南,包括跨设备的保存和加载。
7.3 运行模型预测
加载模型后,我们可以使用它来运行预测。以下是一个简单的示例,展示了如何加载模型并对一张图片进行预测。
from torchvision import transforms
from PIL import Image
# 加载图片并预处理
input_image = Image.open('dog.jpg')
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # 创建一个 mini-batch
# 如果有 GPU,将数据移动到 GPU 上
if torch.cuda.is_available():
input_batch = input_batch.to('cuda')
model.to('cuda')
# 运行预测
with torch.no_grad():
output = model(input_batch)
# Tensor of shape 1000, with confidence scores over Imagenet's 1000 classes
print(output[0])
# The output is unnormalized. To get probabilities, you can run a softmax on it.
probabilities = torch.nn.functional.softmax(output[0], dim=0)
print(probabilities)
在这个示例中,我们首先加载并预处理了一张图片,然后将其传递给模型进行预测。模型的输出是一个包含 1000 个类别的置信度分数的张量,我们可以通过 softmax 函数将其转换为概率。
7.4 拓展阅读--检查点方面相关技巧
从检查点加载 nn.module 的技巧
在 PyTorch 中保存和加载通用检査点