文章目录
- pytorch学习 基础知识
- pytorch学习(1) Tensors
- 1.1 初始化Tensor
- 1.2 Tensor 的属性
- 1.3 Tensors 的操作
- 1.4 与 NumPy 的桥梁
- 1.4.1 Tensor 到 NumPy 数组
- 1.4.2 NumPy 数组 到 Tensor
- pytorch学习(2) 数据集和数据加载器
- 2.1 加载一个数据集
- 2.2 迭代和可视化数据集
- 2.3 为你的文件创建一个自定义数据集
- 2.3.1 init
- 2.3.2 len
- 2.3.3 getitem
- 2.4 用 DataLoaders 准备你的数据进行训练
- 2.5 遍历 DataLoader
- 2.6
- pytorch学习(3) transforms
- 3.1 ToTensor()
- 3.2 Lambda Transforms
- 3.3
- pytorch学习(4) 构建神经网络模型
- 4.1 获取训练的设备
- 4.2 定义类
- 4.3 模型层
- 4.3.1 nn.Flatten
- 4.3.2 nn.Linear
- 4.3.3 nn.ReLU
- 4.3.4 nn.Sequential
- 4.3.5 nn.Softmax
- 4.4 模型参数
- 4.5 进一步阅读
- pytorch学习(5) 自动微分运算-`TORCH.AUTOGRAD`
- 5.1 tensor、函数和计算图
- 5.2 计算梯度
- 5.3 禁用梯度追踪
- 5.4 计算图的更多内容
- 5.5 选读: tensor梯度和 Jacobian (译为:雅各布)乘积
- 5.6 进一步阅读:
- pytorch学习(6) 优化模型参数
- 6.1 前提代码
- 6.2 超参数
- 6.3 优化循环
- 6.4 损失函数
- 6.5 优化器
- 6.6 完整实现
- 6.7 进一步阅读
- pytorch学习(7) 模型保存和加载
- 7.1 模型权重的保存和加载
- 7.2 保存和加载模型结构
- 7.3 关联的教程
pytorch学习 基础知识
转载地址:https://pytorch.apachecn.org/2.0/tutorials/beginner/basics/intro/
pytorch学习(1) Tensors
Tensors 是一种特殊的数据结构,与数组和矩阵非常相似。在 PyTorch 中,我们使用 tensors 对模型的输入和输出以及模型的参数进行编码。
Tensors 类似于 NumPy’s 的 ndarrays,不同的是 tensors 可以在 GPU 或其他硬件加速器上运行。事实上,tensors 和 NumPy 数组通常可以共享相同的底层内存,这样就不需要复制数据了(参见使用 NumPy 的 Bridge)。 Tensors 也针对自动微分进行了优化(我们将在后面的 Autograd 部分中看到更多细节介绍)。如果您熟悉 ndarray,那么您就会熟悉 Tensor API。如果不熟悉,那么请跟我来!
import torch
import numpy as np
1.1 初始化Tensor
Tensors 可以用不同的方式初始化。看看下面的例子:
直接从原生数据创建
Tensors 可以直接从数据中创建,数据类型可以自动推断。
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
从 NumPy 数组创建
Tensors 可以从 NumPy 数组创建(反之亦然 —— 参见 bridge-to-np-label
)。
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
从 tensor 变量创建
新 tensor 将继承参数 tensor 的属性(形状、数据类型) ,除非显式重写。
x_ones = torch.ones_like(x_data) # 继承 x_data 的属性
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # 覆盖从 x_data 继承的数据类型
print(f"Random Tensor: \n {x_rand} \n")
输出:
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.8823, 0.9150],
[0.3829, 0.9593]])
从随机数据或常量创建
shape
是 tensor 维数的元组。在下面的实例中,它决定了输出 tensor 的维数。
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.3904, 0.6009, 0.2566],
[0.7936, 0.9408, 0.1332]])
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 Tensors 的操作
tensor 有超过100种操作,包括算术、线性代数、矩阵操作(转置、索引、切片) 、抽样等,在这里有详细描述。
这些操作都可以在 GPU 上运行(通常比在 CPU 上运行的速度更快)。如果你使用 Colab,通过 Runtime > Change runtime type > GPU 来分配一个GPU。
默认情况下,tensors 是在 CPU 上创建的。我们需要使用 .to
方法显式地将 tensors 移动到 GPU 上(在检查GPU的可用性之后)。请记住,在不同的设备上复制大型的 tensors,在时间和内存上都是很昂贵的!
# We move our tensor to the GPU if available
if torch.cuda.is_available():
tensor = tensor.to("cuda")
尝试列表中的一些操作。如果您熟悉 NumPy API,您会发现使用 Tensor API 简直易如反掌。
类似 numpy 索引和分片的标准操作:
tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
输出:
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
连接 tensors
您可以使用 torch.cat
将一系列tensor沿着给定的维数连接起来。另请参见 torch.stack,它是另一个tensor连接运算符,与 torch.cat
略有不同。
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
输出:
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
算术运算
# 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([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
单个元素的 tensors
如果你有一个一维的 tensors 变量,通过将 tensors 的所有值聚合成一个值,就可以使用 item()
将它转换成 Python 数值:
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
输出:
12.0 <class 'float'>
就地操作
将修改结果存储到操作数中的操作被称为就地操作,通常它们以后缀 _
来表示。例如:x.copy_(y)
, x.t_()
, 将改变 x
。
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 上的 Tensors 和 NumPy 数组可以共享它们的底层内存存储,更改其中一个将更改另一个。
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.]
tensor 的变化反映到 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)
print(f"t: {t}")
print(f"n: {n}")
输出:
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
NumPy 数组中的更改反映到 tensor 中。
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.]
pytorch学习(2) 数据集和数据加载器
处理数据样本的代码可能会变得杂乱无章,难以维护;我们希望我们的数据集代码与我们的模型训练代码分离,以提高可读性和模块化。 PyTorch 提供了两个数据基类: torch.utils.data.DataLoader
和 torch.utils.data.Dataset
。允许你使用预加载的数据集以及你自己的数据集。 Dataset
存储样本和它们相应的标签,DataLoader
在 Dataset
基础上添加了一个迭代器,迭代器可以迭代数据集,以便能够轻松地访问 Dataset
中的样本。
PyTorch 领域库提供了一些预加载的数据集(如FashionMNIST),这些数据集是 torch.utils.data.Dataset
的子类,并实现特定数据的功能。它们可以被用来为你的模型制作原型和基准。你可以在这里找到它们:Image Datasets、Text Datasets、Audio Datasets
2.1 加载一个数据集
下面是一个如何从 TorchVision 加载 Fashion-MNIST 数据集的例子。 Fashion-MNIST是一个由 60,000 个训练实例和 10,000 个测试实例组成的 Zalando 的文章图像数据集。 每个例子包括一个28×28的灰度图像和10个类别中的一个相关标签。
我们加载 FashionMNIST Dataset 参数如下:
root
是存储训练/测试数据的路径,train
指定训练或测试数据集,download=True
如果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()
)
2.2 迭代和可视化数据集
我们可以像列表一样手动索引 Datasets
: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 为你的文件创建一个自定义数据集
一个自定义的数据集类必须实现三个函数: __init__
, __len__
, 和 __getitem__
。 以 FashionMNIST 数据集为例,它的图片存储在 img_dir
参数指定的目录中,标签存储在 annotations_file
参数指定的CSV文件中。
在接下来的章节中,我们将分别介绍这些函数方法的作用
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
2.3.1 init
在实例化数据集对象时,__init__
函数会运行一次,用于初始化图像目录、标签文件和图像转换属性(下一节将详细介绍)。
标签 csv 文件看起来像:
tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9
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
2.3.2 len
函数 __len__
返回我们数据集中的样本数。
Example:
def __len__(self):
return len(self.img_labels)
2.3.3 getitem
函数 __getitem__
从数据集中给定的索引 idx
处加载并返回一个样本。根据索引可以确定图像在硬盘上的位置,用 read_image
将其转换为tensor,从 self.img_labels
的csv数据中获取相应的标签,再对它们调用 transform 函数(如果适用),并返回tensor图像和相应的标签的元组。
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
2.4 用 DataLoaders 准备你的数据进行训练
Dataset
每次加载一组我们数据集的特征和标签样本。在训练一个模型时,我们通常希望以 “小批量” 的方式传递样本,在每个训练周期重新打乱数据以减少模型的过拟合,并使用 Python 的 multiprocessing
来加快数据的加载速度。
DataLoader
是一个可迭代的对象,它用一个简单的API为我们抽象出这种复杂性。
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
中,并可以根据需要迭代该数据集。每次迭代都会返回一批 train_features
和 train_labels
(分别包含 batch_size=64
个特征和标签)。因为我们指定了 shuffle=True
,在我们遍历所有批次后,数据会被打乱(为了更精细地控制数据加载顺序,请看Samplers)。
# 显示图像和标签。
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
2.6
- torch.utils.data API
pytorch学习(3) transforms
数据并不总是以训练机器学习算法所需的最终处理形式出现。我们使用变换来对数据进行一些处理,使其适合训练。
所有的 TorchVision 数据集都有两个参数: transform
用于修改特征和 target_transform
用于修改标签,它们接受包含转换逻辑的 callables。torchvision.transforms 模块提供了几个常用的转换算法,开箱即用。
FashionMNIST 的特征是 PIL 图像格式,而标签是整数。对于训练,我们需要将特征作为归一化的tensor,将标签作为独特编码的tensor。 为了进行这些转换,我们使用 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))
)
输出:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/26421880 [00:00<?, ?it/s]
0%| | 65536/26421880 [00:00<01:12, 361690.02it/s]
1%| | 229376/26421880 [00:00<00:38, 679756.53it/s]
2%|2 | 655360/26421880 [00:00<00:14, 1775435.30it/s]
7%|6 | 1736704/26421880 [00:00<00:06, 3785228.35it/s]
15%|#4 | 3833856/26421880 [00:00<00:02, 8223694.86it/s]
21%|##1 | 5570560/26421880 [00:00<00:02, 9088903.43it/s]
32%|###1 | 8454144/26421880 [00:01<00:01, 13772389.09it/s]
39%|###9 | 10420224/26421880 [00:01<00:01, 13068367.31it/s]
50%|##### | 13238272/26421880 [00:01<00:00, 16440554.97it/s]
58%|#####7 | 15269888/26421880 [00:01<00:00, 14938744.03it/s]
68%|######8 | 18055168/26421880 [00:01<00:00, 17703674.30it/s]
76%|#######6 | 20119552/26421880 [00:01<00:00, 15854480.37it/s]
87%|########6 | 22904832/26421880 [00:01<00:00, 18366169.37it/s]
95%|#########4| 25034752/26421880 [00:01<00:00, 16404116.31it/s]
100%|##########| 26421880/26421880 [00:02<00:00, 13106029.06it/s]
Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/29515 [00:00<?, ?it/s]
100%|##########| 29515/29515 [00:00<00:00, 326257.67it/s]
Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
0%| | 0/4422102 [00:00<?, ?it/s]
1%|1 | 65536/4422102 [00:00<00:12, 362747.74it/s]
5%|5 | 229376/4422102 [00:00<00:06, 681864.40it/s]
15%|#4 | 655360/4422102 [00:00<00:02, 1798436.42it/s]
40%|#### | 1769472/4422102 [00:00<00:00, 3872995.18it/s]
79%|#######9 | 3506176/4422102 [00:00<00:00, 7404355.18it/s]
100%|##########| 4422102/4422102 [00:00<00:00, 5422111.79it/s]
Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
0%| | 0/5148 [00:00<?, ?it/s]
100%|##########| 5148/5148 [00:00<00:00, 35867569.75it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
3.1 ToTensor()
ToTensor 将 PIL 图像或 NumPy 的 ndarray
转换为 FloatTensor
。图像的像素强度值在 [0., 1.] 范围内缩放。
3.2 Lambda Transforms
Lambda transforms 应用任何用户定义的 lambda 函数。在这里,我们定义了一个函数来把整数变成一个独热(one-hot)编码的tensor。 它首先创建一个大小为10(我们数据集中的标签数量)的零tensor,然后传递参数 value=1
在标签 y
所给的索引上调用 scatter_ 。
target_transform = Lambda(lambda y: torch.zeros(
10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))
3.3
- torchvision.transforms API
pytorch学习(4) 构建神经网络模型
神经网络由在数据上进行操作的层/模块构成。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.backend.mps
是否可用,否则我们使用 CPU。
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
print(f"Using {device} device")
输出:
Using cuda 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()
!
将数据传递给模型并调用后返回一个 2 维tensor(第0维对应一组 10 个代表每种类型的原始预测值,第1维对应该类型对应的原始预测值)。我们将它传递给一个 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([7], device='cuda:0')
4.3 模型层
让我们分析这个 FashionMNIST 模型的各层。为了说明,我们会取一个由 3 张 28x28 的图片数据组成的样例数据,并看看当我们将它传递给模型后会发生什么。
input_image = torch.rand(3,28,28)
print(input_image.size())
输出:
torch.Size([3, 28, 28])
4.3.1 nn.Flatten
我们初始化 nn.Flatten
(展平层) 层来将每个 2 维的 28x28 图像转换成一个包含 784 像素值的连续数组(微批数据的维度(第0维)保留了).
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
输出:
torch.Size([3, 784])
4.3.2 nn.Linear
nn.Linear(线性层)是一个对输入值使用自己存储的权重 (w) 和偏差 (b) 来做线性转换的模块。
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
输出:
torch.Size([3, 20])
4.3.3 nn.ReLU
非线性的激活函在模型的输入和输出之间数创造了复杂的映射关系。它们在线性转换之后引入非线性,帮助神经网络学习更广阔范围的现象。
在这个模型中,我们在线性层之间使用 nn.ReLU
,不过还有其他的激活函数可以用在你的模型中引入非线性。
(译者注:ReLU 即 Rectified Linear Unit,译为线性整流函数或者修正线性单元)
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
输出:
Before ReLU: tensor([[ 0.4158, -0.0130, -0.1144, 0.3960, 0.1476, -0.0690, -0.0269, 0.2690,
0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, -0.1692, 0.4504,
0.2476, -0.1787, -0.2754, 0.2462],
[ 0.2326, 0.0623, -0.2984, 0.2878, 0.2767, -0.5434, -0.5051, 0.4339,
0.0302, 0.1634, 0.5649, -0.0055, 0.2025, 0.4473, -0.2333, 0.6611,
0.1883, -0.1250, 0.0820, 0.2778],
[ 0.3325, 0.2654, 0.1091, 0.0651, 0.3425, -0.3880, -0.0152, 0.2298,
0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, -0.1897, 0.4030,
0.1189, -0.3237, 0.2048, 0.4343]], grad_fn=<AddmmBackward0>)
After ReLU: tensor([[0.4158, 0.0000, 0.0000, 0.3960, 0.1476, 0.0000, 0.0000, 0.2690, 0.1353,
0.1975, 0.4484, 0.0753, 0.4455, 0.5321, 0.0000, 0.4504, 0.2476, 0.0000,
0.0000, 0.2462],
[0.2326, 0.0623, 0.0000, 0.2878, 0.2767, 0.0000, 0.0000, 0.4339, 0.0302,
0.1634, 0.5649, 0.0000, 0.2025, 0.4473, 0.0000, 0.6611, 0.1883, 0.0000,
0.0820, 0.2778],
[0.3325, 0.2654, 0.1091, 0.0651, 0.3425, 0.0000, 0.0000, 0.2298, 0.3872,
0.0342, 0.8503, 0.0937, 0.1796, 0.5007, 0.0000, 0.4030, 0.1189, 0.0000,
0.2048, 0.4343]], grad_fn=<ReluBackward0>)
4.3.4 nn.Sequential
nn.Sequential
是一个模块的有序容器。数据会沿着模块定义的顺序流动。你可以使用 sequential container(译者注:有序容器,也有的书称之为线性容器)来组成一个快速网络,比如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.3.5 nn.Softmax
模型的最后一层返回 logits(介于[负无穷,正无穷]之间的原始值),然后被传递给 nn.Softmax
模块。这些 logits 值被缩放到 [0,1],代表模型对每种类型的预测概率, dim
参数代表沿着该维度数值应该加总为 1.
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
4.4 模型参数
神经网络内的许多层都是参数化的,比如可以在训练中与层相关的可优化权重和偏置。 nn.Module
的子类会自动追踪所有你定义在模型对象中的字段,并通过你模型的 parameters()
或者 named_parameters()
方法访问所有参数。
在这个例子中,我们在每个参数上遍历,然后打印出它的大小(size)与数值。
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.0273, 0.0296, -0.0084, ..., -0.0142, 0.0093, 0.0135],
[-0.0188, -0.0354, 0.0187, ..., -0.0106, -0.0001, 0.0115]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0155, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 0.0116, 0.0293, -0.0280, ..., 0.0334, -0.0078, 0.0298],
[ 0.0095, 0.0038, 0.0009, ..., -0.0365, -0.0011, -0.0221]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0148, -0.0256], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[-0.0147, -0.0229, 0.0180, ..., -0.0013, 0.0177, 0.0070],
[-0.0202, -0.0417, -0.0279, ..., -0.0441, 0.0185, -0.0268]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([ 0.0070, -0.0411], device='cuda:0', grad_fn=<SliceBackward0>)
4.5 进一步阅读
torch.nn API
pytorch学习(5) 自动微分运算-TORCH.AUTOGRAD
在训练神经网络的时候,最常用的算法就是反向传播算法。在这个算法中,模型参数根据相对于每个给定参数的损失函数的梯度来调整。
为了计算这些梯度,PyTorch 有一个内置的微分运算引擎叫 torch.autograd
。它支持对任何计算图自动计算梯度。
考虑一个最简单的单层神经网络,它有输入值 x
、参数 w
和 b
、和一些损失函数。它可以在 PyTorch 中这么定义:
import torch
x = 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 tensor、函数和计算图
这个代码会定义下面的计算图:
在这个网络中,w
和 b
都是我们需要优化的参数。因此,我们需要能够对这些变量分别计算损失函数的梯度。为了这么做,我们设置这些tensor的 requires_grad
属性。
注意: 你可以在创建tensor的时候就设置
requires_grad
的值、或者在创建之后用x.requires_grad_(True)
方法来设置。
我们对tensor应用来创建计算图的函数事实上是一个 Function
类的对象。这个对象知道如何前向地计算函数,以及如何在向后传播的步骤中计算导数。反向传播函数的一个引用保存在tensor的 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 0x7f9615a14580>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f9615a14bb0>
5.2 计算梯度
为了优化神经网络中的参数,我们需要对参数计算损失函数的导数,也就是,我们需要在给定 x
和 y
下的
∂
l
o
s
s
∂
w
\frac{\partial loss}{\partial w}
∂w∂loss ∂loss∂w 和
∂
l
o
s
s
∂
b
\frac{\partial loss}{\partial b}
∂b∂loss ∂loss∂b 。要计算这些导数,我们调用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])
注意:
- 我们只能从计算图中将
require_grad
设置为True
的叶子结点获取grad
属性。对于计算图中的其他节点,梯度不可获取。- 在给定的计算图中,出于性能原因我们只能用
backward
进行一次梯度计算。如果我们想要对同一张计算图做几次backward
调用,我们需要在backward
调用时传递retain_graph=True
参数。
5.3 禁用梯度追踪
默认情况下,所有设置 requires_grad=True
的tensor会追踪它的计算历史并支持梯度计算。但是也有我们并不需要这么做的场景,比如,当我们已经训练了模型且只想对一些输入数据应用的时候,比如我们只想做沿着网络的前向计算。我们可以通过用 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
另一种取得同样效果的方法是在tensor上使用 detach()
方法。
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
输出:
False
你想要禁用梯度追踪的原因可能是:
- 为了把你神经网络中的某些参数标记为冻结参数(frozen parameters)
- 为了在你只做前向传递的时候加快计算速度,因为在不追踪梯度的tensor上进行的运算会更加高效。
5.4 计算图的更多内容
从概念上来说,autograd 在一个由函数(Function)对象构成的有向无环图中保持一份数据(tensor)以及全部执行的操作(以及产生的新tensor)的记录。在这个有向无环图(DAG)中,叶子节点是输入tensor,根节点是输出tensor。通过从根节点到叶子节点地追踪这个图,你可以用链式法则自动计算梯度。
在前向传递中,autograd 同时做两件事:
- 运行指定的操作来计算、生成一个tensor
- 维持这次运算在有向无环图中的梯度函数
当对有向无环图的根节点调用 .backward()
方法时,反向传递就开始了。然后 autograd
会:
- 从每个
.grad_fn
中计算梯度 - 在对应tensor的
.grad
属性中累计它们 - 应用链式法则,一路传播到叶子tensor。
注意: PyTorch 中的有向无环图是动态的: 一个重要的观察是这个图是从零重建的;每次
.backward()
调用之后,autograd 都会开始构建一张新图。这一点允许你在模型中使用流控制语句;如果需要的话,你可以在每次迭代中改变结构、大小和和运算。
5.5 选读: tensor梯度和 Jacobian (译为:雅各布)乘积
在许多场景中,我们有一个标量损失函数,且我们需要对某些参数计算梯度。然而,也有些场景下输出函数是一个任意的tensor。在这种场景下,PyTorch 允许你计算一个 Jacobian 乘积,而不是真实的梯度。
对于一个向量函数
y
⃗
=
f
(
x
⃗
)
\vec y = f(\vec x)
y=f(x), 给定
x
⃗
=
<
x
1
,
.
.
.
,
x
n
>
\vec x = < x_1,...,x_n >
x=<x1,...,xn> 且
y
⃗
=
<
y
1
,
.
.
.
,
y
n
>
\vec y = < y_1,...,y_n >
y=<y1,...,yn>, 一个
y
⃗
\vec y
y 对
x
⃗
\vec x
x 的梯度可以用 Jacobian 矩阵表示为:
∂
y
1
∂
x
1
.
.
.
∂
y
1
∂
x
n
.
.
.
.
.
.
.
.
∂
y
m
∂
x
1
.
.
.
∂
y
m
∂
x
n
(1)
\begin{matrix} \frac{\partial y_1}{\partial x1} & ... & \frac{\partial y_1}{\partial x_n} \\ ... & ... & .. \\ \frac{\partial y_m}{\partial x1} & ... & \frac{\partial y_m}{\partial x_n} \end{matrix}\tag{1}
∂x1∂y1...∂x1∂ym.........∂xn∂y1..∂xn∂ym(1)
PyTorch 允许你对一个给定的输入向量
v
=
<
v
1
,
.
.
.
,
v
m
>
v = < v_1,...,v_m >
v=<v1,...,vm> 计算 Jacobian 乘积
v
T
⋅
J
v^T \cdot J
vT⋅J。这可以通过把 v 作为调用 backward
时的参数来实现的。v 的大小应该和我们想要计算乘积的原始tensor一致:
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}")
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.]])
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))
, 这是在标量值函数的情况下计算梯度的有效方法,比如神经网络训练中的损失。
5.6 进一步阅读:
- Autograd Mechanics (自动梯度技术)
pytorch学习(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 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()
输出:
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
0%| | 0/26421880 [00:00<?, ?it/s]
0%| | 65536/26421880 [00:00<01:11, 366175.29it/s]
1%| | 229376/26421880 [00:00<00:38, 685578.25it/s]
3%|3 | 851968/26421880 [00:00<00:10, 2413401.26it/s]
7%|7 | 1933312/26421880 [00:00<00:05, 4152542.15it/s]
17%|#7 | 4521984/26421880 [00:00<00:02, 9852648.59it/s]
25%|##5 | 6684672/26421880 [00:00<00:01, 11079775.27it/s]
35%|###4 | 9142272/26421880 [00:01<00:01, 14258039.45it/s]
44%|####3 | 11501568/26421880 [00:01<00:01, 14171893.75it/s]
53%|#####2 | 13926400/26421880 [00:01<00:00, 16469591.54it/s]
62%|######1 | 16351232/26421880 [00:01<00:00, 15727451.19it/s]
71%|####### | 18743296/26421880 [00:01<00:00, 17539641.69it/s]
80%|######## | 21200896/26421880 [00:01<00:00, 16492622.63it/s]
89%|########9 | 23592960/26421880 [00:01<00:00, 18100280.02it/s]
99%|#########8| 26116096/26421880 [00:01<00:00, 16993447.90it/s]
100%|##########| 26421880/26421880 [00:01<00:00, 13272484.55it/s]
Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
0%| | 0/29515 [00:00<?, ?it/s]
100%|##########| 29515/29515 [00:00<00:00, 328358.81it/s]
Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
0%| | 0/4422102 [00:00<?, ?it/s]
1%|1 | 65536/4422102 [00:00<00:11, 365656.04it/s]
5%|5 | 229376/4422102 [00:00<00:06, 686664.05it/s]
18%|#7 | 786432/4422102 [00:00<00:01, 2220917.26it/s]
41%|#### | 1802240/4422102 [00:00<00:00, 3882085.99it/s]
98%|#########7| 4325376/4422102 [00:00<00:00, 9553123.99it/s]
100%|##########| 4422102/4422102 [00:00<00:00, 6037898.77it/s]
Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
0%| | 0/5148 [00:00<?, ?it/s]
100%|##########| 5148/5148 [00:00<00:00, 39691685.65it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
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.LogSoftmax
和 nn.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)
# Set the model to training mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.train()
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
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_loop(dataloader, model, loss_fn):
# Set the model to evaluation mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
# Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode
# also serves to reduce unnecessary gradient computations and memory usage for tensors with 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_loop
和 test_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!")
输出:
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.7 进一步阅读
- Loss Functions 损失函数
- torch.optim torch的优化器包
- Warmstart Training a Model 给模型的训练预热
pytorch学习(7) 模型保存和加载
在这个章节我们会学习如何持久化模型状态来保存、加载和执行模型预测。
import torch
import torchvision.models as models
7.1 模型权重的保存和加载
PyTorch 将模型学习到的参数存储在一个内部状态字典中,叫 state_dict
。它们可以通过 torch.save
方法来持久化。
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')
输出:
Downloading: "https://download.pytorch.org/models/vgg16-397923af.pth" to /var/lib/jenkins/.cache/torch/hub/checkpoints/vgg16-397923af.pth
0%| | 0.00/528M [00:00<?, ?B/s]
4%|4 | 22.5M/528M [00:00<00:02, 236MB/s]
9%|8 | 46.5M/528M [00:00<00:02, 245MB/s]
13%|#3 | 70.5M/528M [00:00<00:01, 248MB/s]
18%|#7 | 94.4M/528M [00:00<00:01, 249MB/s]
22%|##2 | 118M/528M [00:00<00:01, 250MB/s]
27%|##6 | 142M/528M [00:00<00:01, 250MB/s]
31%|###1 | 166M/528M [00:00<00:01, 249MB/s]
36%|###5 | 190M/528M [00:00<00:01, 249MB/s]
40%|#### | 214M/528M [00:00<00:01, 249MB/s]
45%|####5 | 238M/528M [00:01<00:01, 250MB/s]
50%|####9 | 262M/528M [00:01<00:01, 251MB/s]
54%|#####4 | 286M/528M [00:01<00:01, 250MB/s]
59%|#####8 | 310M/528M [00:01<00:00, 249MB/s]
63%|######3 | 333M/528M [00:01<00:00, 249MB/s]
68%|######7 | 357M/528M [00:01<00:00, 247MB/s]
72%|#######2 | 381M/528M [00:01<00:00, 248MB/s]
77%|#######6 | 405M/528M [00:01<00:00, 249MB/s]
81%|########1 | 429M/528M [00:01<00:00, 250MB/s]
86%|########5 | 453M/528M [00:01<00:00, 251MB/s]
90%|######### | 477M/528M [00:02<00:00, 251MB/s]
95%|#########5| 502M/528M [00:02<00:00, 253MB/s]
100%|##########| 528M/528M [00:02<00:00, 258MB/s]
100%|##########| 528M/528M [00:02<00:00, 251MB/s]
要加载模型权重,你需要先创建一个跟要加载权重的模型结构一样的模型,然后使用 load_state_dict()
方法加载参数。
model = models.vgg16() # we do not specify ``weights``, i.e. create untrained model
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()
注意: 请确保在进行推理前调用
model.eval()
方法来将 dropout 层和 batch normalization 层设置为评估模式(evaluation模式)。如果不这么做的话会产生并不一致的推理结果。
7.2 保存和加载模型结构
在加载模型权重的时候,我们需要首先实例化一个模型类,因为模型类定义了神经网络的结构。我们也想把模型类结构和模型一起保存,那就可以通过将 model
传递给保存函数(而不是 model.state_dict()
)。
torch.save(model, 'model.pth')
然后我们可以这样载入模型:
model = torch.load('model.pth')
7.3 关联的教程
在PyTorch中保存、加载一个Checkpoint