24-12-22 pytorch学习 基础知识 帝乡明日到,犹自梦渔樵。

文章目录

  • 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.DataLoadertorch.utils.data.Dataset。允许你使用预加载的数据集以及你自己的数据集。 Dataset 存储样本和它们相应的标签,DataLoaderDataset 基础上添加了一个迭代器,迭代器可以迭代数据集,以便能够轻松地访问 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 指定的目录没有数据,就自动从网上下载数据。
  • transformtarget_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 迭代和可视化数据集

我们可以像列表一样手动索引 Datasetstraining_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_featurestrain_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。 为了进行这些转换,我们使用 ToTensorLambda

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.cudatorch.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、参数 wb、和一些损失函数。它可以在 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、函数和计算图

这个代码会定义下面的计算图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个网络中,wb 都是我们需要优化的参数。因此,我们需要能够对这些变量分别计算损失函数的梯度。为了这么做,我们设置这些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 计算梯度

为了优化神经网络中的参数,我们需要对参数计算损失函数的导数,也就是,我们需要在给定 xy 下的 ∂ l o s s ∂ w \frac{\partial loss}{\partial w} wloss ∂loss∂w 和 ∂ l o s s ∂ b \frac{\partial loss}{\partial b} bloss ∂loss∂b 。要计算这些导数,我们调用loss.backward(),然后从 w.gradb.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} x1y1...x1ym.........xny1..xnym(1)

PyTorch 允许你对一个给定的输入向量 v = < v 1 , . . . , v m > v = < v_1,...,v_m > v=<v1,...,vm> 计算 Jacobian 乘积 v T ⋅ J v^T \cdot J vTJ。这可以通过把 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.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)
    # 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_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!")

输出:

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络功能 - 服务和客户端程序CS架构和简单web服务示例

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述准备工作扫描服务端有那些开放端口创建客户端-服务器设置启动服务器和客户端进程双向发送数据保持服务器进程处于活动状态设置最小…

M3D: 基于多模态大模型的新型3D医学影像分析框架,将3D医学图像分析从“看图片“提升到“理解空间“的层次,支持检索、报告生成、问答、定位和分割等8类任务

M3D: 基于多模态大模型的新型3D医学影像分析框架&#xff0c;将3D医学图像分析从“看图片“提升到“理解空间“的层次&#xff0c;支持检索、报告生成、问答、定位和分割等8类任务 论文大纲理解1. 确认目标2. 分析过程&#xff08;目标-手段分析&#xff09;核心问题拆解 3. 实…

【102. 二叉树的层序遍历 中等】

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]] 示例…

第四届电气工程与控制科学

重要信息 官网&#xff1a;www.ic2ecs.com 时间&#xff1a;2024年12月27-29日 简介 第四届电气工程与控制科学定于2024年12月27-29日在中国南京召开。主要围绕“电气工程“、”控制科学“、”机械工程“、”自动化”等主题展开&#xff0c;旨在为从电…

监控易在汽车制造行业信息化运维中的应用案例

引言 随着汽车制造行业的数字化转型不断深入&#xff0c;信息化类IT软硬件设备的运行状态监控、故障告警、报表报告以及网络运行状态监控等成为了企业运维管理的关键环节。监控易作为一款全面、高效的信息化运维管理工具&#xff0c;在汽车制造行业中发挥着重要作用。本文将结合…

大模型+安全实践之春天何时到来?

引子:距《在大模型实践旅途中摸了下上帝的脚指头》一文发布近一年,2024年笔者继续全情投入在大模型+安全上,深度参与了一些应用实践,包括安全大模型首次大规模应用在国家级攻防演习、部分项目的POC直到项目落地,也推动了一些场景安全大模型应用从0到3的孵化上市。这一年也…

大小端存储的问题

请你用C语言写一个简单的程序&#xff0c;判断你使用的主机是大端存储还是小端存储 #include <stdio.h> int main(){int x 0x11223344;char *p (char *)&x;if(0x44 *p){printf("小端\n");}else if(0x11 *p){printf("大端\n");}return 0; }

山景BP1048增加AT指令,实现单片机串口控制播放音乐(一)

1、设计目的 山景提供的SDK是蓝牙音箱demo&#xff0c;用户使用ADC按键或者IR遥控器&#xff0c;进行人机交互。然而现实很多场景&#xff0c;需要和单片机通信&#xff0c;不管是ADC按键或者IR接口都不适合和单片机通信。这里设计个AT指令用来和BP1048通信。AT指令如下图所示…

EMC VMAX/DMX 健康检查方法

近期连续遇到2个由于对VMAX存储系统没有做及时的健康检查&#xff0c;出现SPS电池故障没有及时处理&#xff0c;然后同一pair就是同一对的另外一个SPS电池再次出现故障&#xff0c;然后存储系统保护性宕机vault&#xff0c;然后业务系统挂掉的案例。 开始之前&#xff0c;先纠…

51c大模型~合集94

我自己的原文哦~ https://blog.51cto.com/whaosoft/12897659 #D(R,O) Grasp 重塑跨智能体灵巧手抓取&#xff0c;NUS邵林团队提出全新交互式表征&#xff0c;斩获CoRL Workshop最佳机器人论文奖 本文的作者均来自新加坡国立大学 LinS Lab。本文的共同第一作者为上海交通大…

移动魔百盒中的 OpenWrt作为旁路由 安装Tailscale并配置子网路由实现在外面通过家里的局域网ip访问内网设备

移动魔百盒中的 OpenWrt作为旁路由 安装Tailscale并配置子网路由实现在外面通过家里的局域网ip访问内网设备 一、前提条件 确保路由器硬件支持&#xff1a; OpenWrt 路由器需要足够的存储空间和 CPU 性能来运行 Tailscale。确保设备架构支持 Tailscale 二进制文件&#xff0c;例…

Webpack学习笔记(4)

1.缓存 可以通过命中缓存降低网络流量&#xff0c;是网站加站速度更快。 然而在部署新版本时&#xff0c;不更改资源的文件名&#xff0c;浏览器可能认为你没有更新&#xff0c;所以会使用缓存版本。 由于缓存存在&#xff0c;获取新的代码成为问题。 接下来将配置webpack使…

java抽奖系统(八)

9. 抽奖模块 9.1 抽奖设计 抽奖过程是抽奖系统中最重要的核⼼环节&#xff0c;它需要确保公平、透明且⾼效。以下是详细的抽奖过程设计&#xff1a; 对于前端来说&#xff0c;负责控制抽奖的流程&#xff0c;确定中奖的人员 对于后端来说&#xff1a; 接口1&#xff1a;查询完…

VulnHub靶场渗透之:Gigachad

环境搭建 VulnHub是一个丰富的实战靶场集合&#xff0c;里面有许多有趣的实战靶机。 本次靶机介绍&#xff1a;http://www.vulnhub.com/entry/gigachad-1,657/ 下载靶机ova文件&#xff0c;导入虚拟机&#xff0c;启动环境&#xff0c;便可以开始进行靶机实战。 虚拟机无法分…

解决Apache/2.4.39 (Win64) PHP/7.2.18 Server at localhost Port 80问题

配置一下apache里面的配置文件&#xff1a;httpd.conf 和 httpd.vhosts.conf httpd.conf httpd-vhosts.conf 重启服务 展示&#xff1a; 浏览器中中文乱码问题&#xff1a;

.NET重点

B/S C/S什么语言 B/S&#xff1a; 浏览器端&#xff1a;JavaScript&#xff0c;HTML&#xff0c;CSS 服务器端&#xff1a;ASP&#xff08;.NET&#xff09;PHP/JSP 优势&#xff1a;维护方便&#xff0c;易于升级和扩展 劣势&#xff1a;服务器负担沉重 C/S java/.NET/…

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门任务间的通讯-消息队列【入门四】

继续上一篇任务创建 【Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门任务间的通讯-信号量【入门三】-CSDN博客】 今天要实现消息队列进行任务的通讯 一、从上一篇信号量通讯demo拷贝一份重命名&#xff0c;还是之前的两个任务&#xff0c;重命名了。 xTaskCreatePinned…

【AI日记】24.12.22 容忍与自由 | 环境因素和个人因素

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 内容&#xff1a;看 OpenAi 这周的发布会和其他 AI 新闻&#xff0c;大佬视频时间&#xff1a;3 小时 读书 书名&#xff1a;富兰克林自传时间&#xff1a;1 小时评估&#xff1a;读完&#xff0c;总体…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>电话号码的字母组合

题目&#xff1a; 解析&#xff1a; 代码&#xff1a; private String[] hash {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};private StringBuffer p…

设计模式 -- 单例模式

设计模式 -- 单例模式 单例模式C++ 实现饿汉式单例模式懒汉式单例模式使用静态局部变量实现懒汉式单例模式(推荐)使用call_once实现懒汉式单例模式(推荐)使用静态全局部变量和指针的方式实现懒汉式单例模式(不推荐)双重检测懒汉式单例模式单例模式 单例模式:确保在整个程…