首页 > 其他分享 >Pytorch基础-汇总

Pytorch基础-汇总

时间:2024-03-16 10:32:05浏览次数:14  
标签:loss tensor nn 汇总 torch 基础 Pytorch 60000 print

本教程翻译自微软教程:https://learn.microsoft.com/en-us/training/paths/pytorch-fundamentals/

初次编辑:2024/3/1;最后编辑:2024/3/4


本教程包含以下内容:

  1. 介绍pytorch基础和张量操作
  2. 介绍数据集
  3. 介绍归一化
  4. 介绍构建模型层的基本操作
  5. 介绍自动微分相关知识
  6. 介绍优化循环(optimization loop)相关知识
  7. 介绍加载与运行模型预测的相关知识
  8. 总结

另外本人还有pytorch CV相关的教程,见专题:

https://blog.csdn.net/qq_33345365/category_12578430.html


介绍

大多数机器学习工作流程都涉及处理数据、创建模型、使用超参数优化模型、保存和推断已训练的模型。本模块介绍在 PyTorch 中实现的完整机器学习(ML)工作流程,PyTorch 是一种流行的 Python ML 框架。

本教程使用 FashionMNIST 数据集来训练一个神经网络模型,该模型可以识别图像,如 T 恤/上衣、裤子、套衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包包或短靴。

在构建模型之前,会展示构建神经网络模型的关键概念。

学习目标:

  1. 学习如何在 CPU 和 GPU 上使用张量(Tensors)
  2. 理解如何管理、扩展和规范化数据集
  3. 使用神经网络构建图像识别模型
  4. 学习如何优化模型
  5. 学习如何提高模型推理性能

先修要求:

基本的 Python 知识


1.什么是张量 Tensor

张量

张量是一种专门的数据结构,非常类似于数组和矩阵。PyTorch使用张量来编码模型的输入和输出,以及模型的参数。张量类似于NumPy数组和ndarrays,但是张量可以在GPU或其他硬件加速器上运行。事实上,张量和NumPy数组通常可以共享相同的底层内存地址,具有称为bridge-to-np-label的功能,这消除了复制数据的需要。张量还针对自动微分进行了优化。

设置基本环境:

import torch
import numpy as np

初始化一个张量

张量可通过多种方式初始化,见如下例子:

1. 直接通过数据初始化

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

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

2. 从一个NumPy数组

张量可以从NumPy数组创建,反之亦然。由于numpy格式的np_array和张量格式x_np在这里共享相同的内存位置,改变其中一个的值将会改变另一个的值。

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

print(f"Numpy np_array value: \n {np_array} \n")
print(f"Tensor x_np value: \n {x_np} \n")

np.multiply(np_array, 2, out=np_array)

print(f"Numpy np_array after * 2 operation: \n {np_array} \n")
print(f"Tensor x_np value after modifying numpy array: \n {x_np} \n")

输出是:

Numpy np_array value: 
 [[1 2]
 [3 4]] 

Tensor x_np value: 
 tensor([[1, 2],
        [3, 4]], dtype=torch.int32) 

Numpy np_array after * 2 operation: 
 [[2 4]
 [6 8]] 

Tensor x_np value after modifying numpy array: 
 tensor([[2, 4],
        [6, 8]], dtype=torch.int32) 

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.7907, 0.9041],
        [0.4805, 0.0954]]) 

4. 使用随机数或常量

shape由张量维度的元组定义,它设置了张量的行数和列数。在下面的函数中,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.5893, 0.4485, 0.6525],
        [0.9083, 0.2913, 0.0752]]) 

Ones Tensor: 
 tensor([[1., 1., 1.],
        [1., 1., 1.]]) 

Zeros Tensor: 
 tensor([[0., 0., 0.],
        [0., 0., 0.]])

张量的属性 Attributes of a 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

有100多个张量操作,包括算术运算、线性代数、矩阵操作(如转置、索引和切片)。可以在找到全面的描述。

这些操作中的每一个都可以在GPU上运行(通常比在CPU上速度更快)。

  • CPU的核心数少于100个。核心是执行实际计算的单元。每个核心按顺序处理任务(一次处理一个任务)。

  • GPU有数千乃至上万个核心。GPU核心以并行处理的方式处理计算,任务被分割并在不同核心上处理。这就是为什么在大多数情况下GPU比CPU更快的原因。GPU在处理大数据时表现比小数据更好,因为更有利于并行加速。GPU通常用于图形或神经网络的高强度计算。

  • PyTorch可以使用Nvidia CUDA库来利用其GPU卡。

GPU并不总是优于CPU,因为CPU的单核性能强,因此在处理小数据时可能更占优势。

默认情况下,张量在CPU上创建。张量也可以移动到GPU;为此,需要使用.to方法将它们移动(在检查GPU可用性后)。当然,由于CPU和GPU之间的传输跨设备,这意味着传输上的巨大开销。

# 如果GPU可用则将tensor移动到GPU上,GPU可用需要保证1. CUDA安装;2. 安装对应CUDA版本的pytorch,见https://pytorch.org/,选择合适的pytorch安装方式
if torch.cuda.is_available():
  tensor = tensor.to('cuda')

类似numpy的标准索引和切片 Standard numpy-like indexing and slicing

tensor = torch.ones(4, 4)
print('First row: ',tensor[0])
print('First column: ', tensor[:, 0])
print('Last column:', tensor[..., -1])
tensor[:,1] = 0
print(tensor)

输出是:

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.]])

拼接张量

可以使用 torch.cat 函数沿着指定维度连接一个张量序列。torch.stack是一个相关的张量拼接方法,它沿着新的维度将一个张量序列堆叠起来。以下例子,维度有四个可选值:

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

dim=1;输出:

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.]])

dim=0,输出:

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.]])

dim=-1,输出:

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.]])

dim=-2,输出:

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.]])

数学操作:

# 这计算了两个张量之间的矩阵乘法。Y1 y2 y3的值是一样的
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)

y3 = torch.rand_like(tensor)
torch.matmul(tensor, tensor.T, out=y3)


# 它计算元素级乘积,Z1 z2 z3的值是一样的
z1 = tensor * tensor
z2 = tensor.mul(tensor)

z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)

print(y3)
print(z3)

输出:

tensor([[3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.],
        [3., 3., 3., 3.]])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

单元素张量

如果您有一个单元素张量,例如,通过将张量的所有值聚合为一个值,您可以使用item()将其转换为Python数值:

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

输出:

12.0 <class 'float'>

原地操作 in-place operations

将结果存储到操作数(operand)中的操作称为原地操作。它们通常用下划线_作为后缀。例如:x.copy_(y),x.t_(),会改变x的值。

注意: 就地操作可以节省一些内存,但在计算导数时可能会出现问题,因为它们会立即丢失历史记录。因此,不建议使用它们。

print(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.]])

与Numpy桥接 Bridge with Numpy

NumPy数组和CPU上的张量可以共享它们的底层内存位置,改变其中一个将改变另一个。

张量到NumPy数组的转换

t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")

输出:

t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]

NumPy数组到张量的转换

n = np.ones(5)
t = torch.from_numpy(n)
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.]

代码汇总

import torch
import numpy as np

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

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

print(f"Numpy np_array value: \n {np_array} \n")
print(f"Tensor x_np value: \n {x_np} \n")

np.multiply(np_array, 2, out=np_array)

print(f"Numpy np_array after * 2 operation: \n {np_array} \n")
print(f"Tensor x_np value after modifying numpy array: \n {x_np} \n")

# 从其他张量
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")

# random and constant
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}")

# attributes
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}")

if torch.cuda.is_available():
    tensor = tensor.to('cuda')

tensor = torch.ones(4, 4)
print('First row: ', tensor[0])
print('First column: ', tensor[:, 0])
print('Last column:', tensor[..., -1])
tensor[:, 1] = 0
print(tensor)

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

# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)

y3 = torch.rand_like(tensor)
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)

print(y3)
print(z3)

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

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

t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")

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

2 数据集与数据加载器 Datasets and Dataloaders

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

PyTorch库提供了许多预加载的样本数据集(例如FashionMNIST),它们是torch.utils.data.Dataset的子类,并实现了特定于特殊数据的功能。用于模型原型设计(prototyping)和基准测试(benchmark)的样本包括:

  1. 图像数据集
  2. 文本数据集
  3. 音频数据集

加载数据集

此处使用TorchVision来加载Fashion-MNIST数据集。Fashion-MNIST是Zalando的服装图像数据集,包含60,000个训练样本和10,000个测试样本。每个样本包括一个28×28的灰度图像和一个来自10个类别中的关联标签。

  • 每个图像高度为28个像素,宽度为28个像素,总共有784个像素。
  • 这10个类别告诉图像是什么类型,例如:T恤/上衣、裤子、套头衫、连衣裙、包、短靴等。
  • 灰度像素的值介于0到255之间,用于衡量黑白图像的强度。强度值从白色到黑色递增。例如:白色的颜色值为0,而黑色的颜色值为255。

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

  • root 是存储训练/测试数据的路径。
  • train 指定训练或测试数据集。
  • download=True 如果数据在root中不可用,则从互联网下载数据。
  • transformtarget_transform 指定特征和标签的转换。

加载训练和测试数据集的代码如下所示:

import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
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()
)

数据集的迭代与可视化

可以像列表一样手动索引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()

得到如下图片:

在这里插入图片描述

使用数据加载器(DataLoaders)准备自定义数据

Dataset 逐个样本检索数据集的特征和标签。在训练模型时,通常希望以“minibatch”的形式传递样本,在每个周期重混洗(reshuffle)数据以减少模型过拟合,并使用Python的多进程加速数据检索。

在机器学习中,需要指定数据集中的特征和标签。**特征(Feature)**是输入,**标签(label)**是输出。我们训练特征,然后训练模型以预测标签。

  • 特征是图像像素中的模式。
  • 标签是10个类别类型:T恤,凉鞋,连衣裙等。

DataLoader是一个可迭代对象,用简单的API抽象了这种复杂性。为了使用DataLoader,我们需要设置以下参数:

  • data 用于训练模型的训练数据,以及用于评估模型的测试数据。
  • batch size 每个批次要处理的记录数。
  • shuffle 按索引随机抽取数据样本。

使用DataLoader处理两个数据集的代码如下所示:

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)

使用DataLoader进行迭代

我们已将数据集加载到 DataLoader 中,现在可以根据需要迭代数据集。 下面的每次迭代都会返回一个批次的 train_featurestrain_labels(分别包含 batch_size=64 个特征和标签)。由于我们指定了 shuffle=True,在遍历完所有批次后,数据将被混洗(shuffle),以更精细地控制数据加载顺序。

展示图片和标签的代码如下所示:

# 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()
label_name = list(labels_map.values())[label]
print(f"Label: {label_name}")

得到图片:

在这里插入图片描述

Datasets和DataLoader的区别是:前者检索单个数据,后者批量处理数据

3 归一化

标准化是一种常见的数据预处理技术,用于对数据进行缩放或转换,以确保每个特征都有相等的学习贡献。例如,灰度图像中的每个像素的值都介于0和255之间,这些是特征。如果一个像素值为17,另一个像素为197,则像素重要性的分布将不均匀,因为较高的像素值会偏离学习。标准化改变了数据的范围,而不会扭曲其特征之间的区别。这种预处理是为了避免:

  • 减少预测精度
  • 模型学习困难
  • 特征数据范围的不利分布

Transforms

数据并不总是以最终处理过的形式呈现,这种形式适合训练机器学习算法。可以使用transforms来操作数据,使其适合训练。

所有 TorchVision 数据集都有两个参数(transform用于修改特征,target_transform用于修改标签),这些参数接受包含转换逻辑的可调用对象。torchvision.transforms 模块提供了几种常用的转换。

FashionMNIST 的特征是 PIL 图像格式,标签是整数。 对于训练,需要将特征转换为归一化的张量,将标签转换为 one-hot 编码的张量。 为了进行这些转换,将使用 ToTensorLambda

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))
)

解释上述代码:

ToTensor()

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

Lambda transforms

Lambda transforms 应用任何用户定义的lambda函数。在这里,我们定义了一个函数来将整数转换为一个one-hot编码的张量。它首先创建一个大小为10的零张量(数据集中标签的数量),然后调用scatter,根据标签y的索引为其分配值为1。也可以使用torch.nn.functional.one_hot的方式来实现这一点。

构建模型层


4 什么是神经网络

神经网络是由神经元(neurons)通过层连接而成的集合。每个神经元是一个小型计算单元,执行简单的计算以共同解决问题。神经元分布在三种类型的层中:输入层、隐藏层和输出层。隐藏层和输出层包含多个神经元。神经网络模仿人类大脑处理信息的方式。

一个层包含多个神经元,一个神经网络包含多层,

神经网络的组件

  • **激活函数(activation function)**决定神经元是否应该被激活。神经网络中的计算包括应用激活函数。如果一个神经元被激活,那么意味着输入是重要的。有不同类型的激活函数,选择使用哪种激活函数取决于您想要的输出是什么。激活函数的另一个重要作用是为模型添加非线性。

    • Binary:输出节点被设置为1(如果函数结果为正)或0(如果函数结果为零或负)。 f ( x ) = { 0 , if  x < 0 1 , if  x ≥ 0 f(x)= {\small \begin{cases} 0, & \text{if } x < 0\\ 1, & \text{if } x\geq 0\\ \end{cases}} f(x)={0,1,​if x<0if x≥0​
    • Sigmoid:用于预测输出节点的概率在0到1之间。 f ( x ) = 1 1 + e − x f(x) = {\large \frac{1}{1+e^{-x}}} f(x)=1+e−x1​
    • Tanh用于预测输出节点是否在1和-1之间,适用于分类用例。 f ( x ) = e x − e − x e x + e − x f(x) = {\large \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}} f(x)=ex+e−xex−e−x​
    • ReLU(修正线性激活函数)用于将输出节点设置为0(如果函数结果为负)并保持结果值(如果结果为正)。 f ( x ) = { 0 , if  x < 0 x , if  x ≥ 0 f(x)= {\small \begin{cases} 0, & \text{if } x < 0\\ x, & \text{if } x\geq 0\\ \end{cases}} f(x)={0,x,​if x<0if x≥0​
  • **权重(Weight)**影响网络输出与预期输出值之间的接近程度。当输入进入神经元时,它会乘以一个权重值,得到的输出结果要么被观察到,要么传递到神经网络中的下一层。

  • 一个层中所有神经元的权重被组织成一个张量。

  • 偏差构成了激活函数输出与其预期输出之间的差异。低偏差表明网络对输出形式做出了更多假设,而高偏差值则表示对输出形式做出了较少的假设。

可以说,具有权重 W W W和偏差 b b b的神经网络层的输出 y y y是输入乘以权重加上偏差的总和。$x = \sum{(weights * inputs) + bias} ,其中 ,其中 ,其中f(x)$是激活函数。

构建神经网络

神经网络由层和模块组成,这些模块对数据执行操作。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

取得训练的硬件设备

我们希望能够在类似GPU这样的硬件加速器上训练我们的模型,如果有的话。让我们检查一下torch.cuda是否可用;如果不可用,我们将继续使用CPU。

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('Using {} device'.format(device))

定义类

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

我们的神经网络由以下部分组成:

  • 输入层具有 28x28 或 784 个特征/像素。
  • 第一个线性模块接受 784 个特征的输入,并将其转换为具有 512 个特征的隐藏层。
  • 在转换中应用 ReLU 激活函数。
  • 第二个线性模块接受来自第一个隐藏层的 512 个特征作为输入,并将其转换为具有 512 个特征的下一个隐藏层。
  • 在转换中应用 ReLU 激活函数。
  • 第三个线性模块接受来自第二个隐藏层的 512 个特征作为输入,并将这些特征转换为具有 10 个特征的输出层,这是类别的数量。
  • 在转换中应用 ReLU 激活函数。
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),
            nn.ReLU()
        )

    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()
  (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)
    (5): ReLU()
  )
)

要使用模型,需要将输入数据传递给它。这将执行模型的forward,以及一些背景操作(background operation)。但是,请不要直接调用model.forward()!在输入上调用模型会返回一个具有每个类别的原始预测值的10维张量。

模型的背景操作是指在执行前向传播(forward pass)时发生的内部计算或处理步骤。这些操作可能包括参数初始化、梯度计算、损失函数的计算、优化器的更新等。这些操作在模型的forward方法内部进行,通常是在将输入数据传递给模型时自动执行的,而不需要用户显式调用。这些操作的目的是在训练过程中优化模型参数,使其能够更好地拟合数据并提高性能。

我们通过将其传递给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([2], device='cuda:0')

权重与偏置

nn.Linear模块随机初始化每一层的weightbias并在内部将值存储在张量中。

print(f"First Linear weights: {model.linear_relu_stack[0].weight} \n")
print(f"First Linear biases: {model.linear_relu_stack[0].bias} \n")

模型层

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

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

输出是:

torch.Size([3, 28, 28])

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])

nn.Linear

线性层是一个模块,它使用其存储的权重和偏置对输入进行线性变换。输入层中每个像素的灰度值将连接到隐藏层中的神经元进行计算。用于变换的计算是 w e i g h t ∗ i n p u t + b i a s {{weight * input + bias}} weight∗input+bias。

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

输出:

torch.Size([3, 20])

nn.ReLU

非线性激活函数是在模型的输入和输出之间创建复杂映射的关键。它们被应用在线性变换之后,引入非线性(nonlinearity),帮助神经网络学习各种现象。在这个模型中,我们在线性层之间使用nn.ReLU,但还有其他激活函数可以引入非线性到模型中。

ReLU激活函数接收线性层计算的输出,并用零替换负值。

线性输出: ${ x = {weight * input + bias}} $.
ReLU:

f ( x ) = { 0 , if  x < 0 x , if  x ≥ 0 f(x)=\begin{cases} 0, & \text{if } x < 0\\ x, & \text{if } x\geq 0\\ \end{cases} f(x)={0,x,​if x<0if x≥0​

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

输出:

Before ReLU: tensor([[ 0.2190,  0.1448, -0.5783,  0.1782, -0.4481, -0.2782, -0.5680,  0.1347,
          0.1092, -0.7941, -0.2273, -0.4437,  0.0661,  0.2095,  0.1291, -0.4690,
          0.0358,  0.3173, -0.0259, -0.4028],
        [-0.3531,  0.2385, -0.3172, -0.4717, -0.0382, -0.2066, -0.3859,  0.2607,
          0.3626, -0.4838, -0.2132, -0.7623, -0.2285,  0.2409, -0.2195, -0.4452,
         -0.0609,  0.4035, -0.4889, -0.4500],
        [-0.3651, -0.1240, -0.3222, -0.1072, -0.0112, -0.0397, -0.4105, -0.0233,
         -0.0342, -0.5680, -0.4816, -0.8085, -0.3945, -0.0472,  0.0247, -0.3605,
         -0.0347,  0.1192, -0.2763,  0.1447]], grad_fn=<AddmmBackward>)


After ReLU: tensor([[0.2190, 0.1448, 0.0000, 0.1782, 0.0000, 0.0000, 0.0000, 0.1347, 0.1092,
         0.0000, 0.0000, 0.0000, 0.0661, 0.2095, 0.1291, 0.0000, 0.0358, 0.3173,
         0.0000, 0.0000],
        [0.0000, 0.2385, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.2607, 0.3626,
         0.0000, 0.0000, 0.0000, 0.0000, 0.2409, 0.0000, 0.0000, 0.0000, 0.4035,
         0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0247, 0.0000, 0.0000, 0.1192,
         0.0000, 0.1447]], grad_fn=<ReluBackward0>)

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)

nn.Softmax

神经网络的最后一个线性层返回logits(在[-infty, infty]范围内的原始值),这些值被传递给nn.Softmax模块。Softmax激活函数用于计算神经网络输出的概率。它只用于神经网络的输出层。结果被缩放到[0,1]的值,表示模型对每个类别的预测密度。dim参数指示结果值必须在哪个维度上求和为1。具有最高概率的节点预测所需的输出。

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

模型参数

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

在这个例子中,我们遍历每个参数,并打印其大小和值的预览。

print("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()
  (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)
    (5): ReLU()
  )
) 


Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[-0.0320,  0.0326, -0.0032,  ..., -0.0236, -0.0025, -0.0175],
        [ 0.0180,  0.0271, -0.0314,  ..., -0.0094, -0.0170, -0.0257]],
       device='cuda:0', grad_fn=<SliceBackward>) 

Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0134,  0.0036], device='cuda:0', grad_fn=<SliceBackward>) 

Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[-0.0262,  0.0072, -0.0348,  ..., -0.0374,  0.0345,  0.0374],
        [ 0.0439, -0.0101,  0.0218,  ..., -0.0419,  0.0212, -0.0081]],
       device='cuda:0', grad_fn=<SliceBackward>) 

Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0131, -0.0289], device='cuda:0', grad_fn=<SliceBackward>) 

Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0376, -0.0359, -0.0329,  ..., -0.0057,  0.0040,  0.0307],
        [-0.0196, -0.0440,  0.0250,  ...,  0.0335,  0.0024, -0.0207]],
       device='cuda:0', grad_fn=<SliceBackward>) 

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0287,  0.0321], device='cuda:0', grad_fn=<SliceBackward>) 

代码汇总

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

device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('Using {} device'.format(device))


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),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits


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

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}")

print(f"First Linear weights: {model.linear_relu_stack[0].weight} \n")

print(f"First Linear biases: {model.linear_relu_stack[0].bias} \n")

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

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

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

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

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

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

print("Model structure: ", model, "\n\n")

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

5 自动微分


使用torch.autograd自动微分 Automaic differentiation

在训练神经网络时,最常用的算法是反向传播(back propagation)。在这个算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。损失函数(loss function)计算神经网络产生的预期输出和实际输出之间的差异。目标是使损失函数的结果尽可能接近零。该算法通过神经网络向后遍历以调整权重和偏差来重新训练模型。这就是为什么它被称为反向传播。随着时间的推移,通过反复进行这种回传和前向过程来将损失(loss)减少到0的过程称为梯度下降。

为了计算这些梯度,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  # z = x*w +b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

张量、函数与计算图(computational graphs)

在这个网络中,wb参数,他们会被损失函数优化。因此,需要能够计算损失函数相对于这些变量的梯度。为此,我们将这些张量的requires_grad属性设置为True。

**注意:**您可以在创建张量时设置requires_grad的值,也可以稍后使用x.requires_grad_(True)方法来设置。

我们将应用于张量的函数(function)用于构建计算图,这些函数是Function类的对象。这个对象知道如何在前向方向上计算函数,还知道在反向传播步骤中如何计算其导数。反向传播函数的引用存储在张量的grad_fn属性中。

print('Gradient function for z =',z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)

输出是:

Gradient function for z = <AddBackward0 object at 0x00000280CC630CA0>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward object at 0x00000280CC630310>

计算梯度

为了优化神经网络中参数的权重,需要计算损失函数相对于参数的导数,即我们需要在某些固定的xy值下计算 ∂ l o s s ∂ w \frac{\partial loss}{\partial w} ∂w∂loss​和 ∂ l o s s ∂ b \frac{\partial loss}{\partial b} ∂b∂loss​。为了计算这些导数,我们调用loss.backward(),然后从w.gradb.grad中获取值。

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

输出是:

tensor([[0.2739, 0.0490, 0.3279],
        [0.2739, 0.0490, 0.3279],
        [0.2739, 0.0490, 0.3279],
        [0.2739, 0.0490, 0.3279],
        [0.2739, 0.0490, 0.3279]])
tensor([0.2739, 0.0490, 0.3279])

注意: 只能获取计算图中设置了requires_grad属性为True的叶节点的grad属性。对于计算图中的所有其他节点,梯度将不可用。此外,出于性能原因,我们只能对给定图执行一次backward调用以进行梯度计算。如果我们需要在同一图上进行多次backward调用,我们需要在backward调用中传递retain_graph=True

禁用梯度追踪 Disabling gradient tracking

默认情况下,所有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)

有一些理由你可能想要禁用梯度跟踪:

  • 将神经网络中的某些参数标记为冻结参数(frozen parameters)。这在微调预训练网络的情况下非常常见。
  • 当你只进行前向传播时,为了加速计算,因为不跟踪梯度的张量上的计算更有效率。

计算图的更多知识

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

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

  • 运行所请求的操作以计算结果张量,并且
  • 在 DAG 中维护操作的 梯度函数(gradient function)

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

  • 从每个 .grad_fn 计算梯度,
  • 将它们累积在相应张量的 .grad 属性中,并且
  • 使用链式法则一直传播到叶子张量。

PyTorch 中的 DAG 是动态的

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

代码汇总:

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)

print('Gradient function for z =', z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)

loss.backward()
print(w.grad)
print(b.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)

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

6 优化循环 optimization loop


现在有了一个模型和数据,是时候通过优化其参数来训练、验证和测试我们的模型了。训练模型是一个迭代的过程;在每个迭代(周期)中,模型对输出进行猜测,计算其猜测的错误(损失),收集关于其参数的错误的导数(如前面教程所示),并使用梯度下降(gradient descent)优化这些参数。

之前教程的代码

这里加载之前教程的代码,包括加载数据集模型构建,如下所示:

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

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),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

设置超参数

超参数是可调参数,用于控制模型优化过程。不同的超参数值可以影响模型训练和精度水平。

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

  • Epochs数量 - 整个训练数据集通过网络传递的次数。
  • Batch Size - 模型在每个周期中看到的数据样本数量。迭代完成一个周期所需的批次数量。
  • 学习率(learning rate) - 模型在搜索产生更高模型准确度的最佳权重时所匹配的步长大小。较小的值意味着模型需要更长的时间来找到最佳权重。较大的值可能导致模型跨越并错过最佳权重,从而在训练过程中产生不可预测的行为。
learning_rate = 1e-3
batch_size = 64
epochs = 5

添加一个优化循环

一旦设置了超参数,就可以使用优化循环来训练和优化我们的模型。优化循环的每个迭代称为一个epoch

每个周期包括两个主要部分:

  • 训练循环 - 迭代训练数据集并尝试收敛到最优参数。
  • 验证/测试循环 - 迭代测试数据集以检查模型性能是否在提高。

让我们来研究一下训练循环中使用的一些概念。在前一教程给出了优化循环的完整实现。

添加损失函数

当给定一些训练数据时,未训练网络很可能不能给出正确答案。损失函数衡量了获取的结果与目标值之间的不相似程度,而在训练过程中希望最小化的就是这个损失函数。为了计算损失,可以使用给定数据样本的输入进行预测,然后将其与真实数据标签值进行比较。

常见的损失函数包括:

  • nn.MSELoss(均方误差),用于回归任务
  • nn.NLLLoss(负对数似然),用于分类任务
  • nn.CrossEntropyLoss(交叉熵损失),结合了 nn.LogSoftmaxnn.NLLLoss

将模型的输出 logits 传递给 nn.CrossEntropyLoss,它将对 logits 进行归一化并计算预测误差。

# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()

优化传入 optimization pass

优化是调整模型参数以减少每个训练步骤中模型误差的过程。优化算法定义了这个过程是如何执行的(本示例使用随机梯度下降(Stochastic Gradient Descent))。 所有优化逻辑都封装在optimizer对象中。这里使用SGD优化器;PyTorch中还有许多不同的优化器,如ADAMRMSProp,它们适用于不同类型的模型和数据。

我们通过注册需要训练的模型参数,并传入学习率超参数来初始化优化器。

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

在训练循环中,优化过程分为三个步骤:

  • 调用 optimizer.zero_grad() 来重置模型参数的梯度。梯度默认会累加;为了防止重复计数,我们在每次迭代时明确将其归零。
  • 使用 loss.backward() 进行预测损失的反向传播。PyTorch 会将损失相对于每个参数的梯度存储起来。
  • 一旦有了梯度,就调用 optimizer.step() 来根据反向传播收集到的梯度来调整参数。

完整实现

我们定义一个 train_loop 函数,该函数循环执行我们的优化代码,以及一个 test_loop 函数,该函数评估模型在测试数据上的性能。

def train_loop(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):        
        # Compute prediction and loss
        pred = model(X)
        loss = loss_fn(pred, y)
        
        # Backpropagation
        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)
    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 /= size
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

此处初始化损失函数和优化器,并将其传递给train_looptest_loop。 随意增加周期数以跟踪模型性能的改善。

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.301450  [    0/60000]
loss: 2.290315  [ 6400/60000]
loss: 2.276943  [12800/60000]
loss: 2.274104  [19200/60000]
loss: 2.265006  [25600/60000]
loss: 2.258067  [32000/60000]
loss: 2.241945  [38400/60000]
loss: 2.234483  [44800/60000]
loss: 2.235555  [51200/60000]
loss: 2.214317  [57600/60000]
Test Error: 
 Accuracy: 37.2%, Avg loss: 0.034860 

Epoch 2
-------------------------------
loss: 2.216829  [    0/60000]
loss: 2.212970  [ 6400/60000]
loss: 2.179734  [12800/60000]
loss: 2.192833  [19200/60000]
loss: 2.164904  [25600/60000]
loss: 2.167122  [32000/60000]
loss: 2.133708  [38400/60000]
loss: 2.123012  [44800/60000]
loss: 2.132578  [51200/60000]
loss: 2.087934  [57600/60000]
Test Error: 
 Accuracy: 38.6%, Avg loss: 0.033089 

Epoch 3
-------------------------------
loss: 2.102114  [    0/60000]
loss: 2.099246  [ 6400/60000]
loss: 2.033731  [12800/60000]
loss: 2.066001  [19200/60000]
loss: 2.012894  [25600/60000]
loss: 2.030210  [32000/60000]
loss: 1.971226  [38400/60000]
loss: 1.965057  [44800/60000]
loss: 1.990206  [51200/60000]
loss: 1.912654  [57600/60000]
Test Error: 
 Accuracy: 38.9%, Avg loss: 0.030683 

Epoch 4
-------------------------------
loss: 1.951076  [    0/60000]
loss: 1.954955  [ 6400/60000]
loss: 1.850126  [12800/60000]
loss: 1.891714  [19200/60000]
loss: 1.838581  [25600/60000]
loss: 1.836865  [32000/60000]
loss: 1.803531  [38400/60000]
loss: 1.791677  [44800/60000]
loss: 1.788134  [51200/60000]
loss: 1.659078  [57600/60000]
Test Error: 
 Accuracy: 44.9%, Avg loss: 0.027540 

Epoch 5
-------------------------------
loss: 1.766038  [    0/60000]
loss: 1.781725  [ 6400/60000]
loss: 1.659930  [12800/60000]
loss: 1.729604  [19200/60000]
loss: 1.598470  [25600/60000]
loss: 1.646501  [32000/60000]
loss: 1.615664  [38400/60000]
loss: 1.610186  [44800/60000]
loss: 1.593459  [51200/60000]
loss: 1.492508  [57600/60000]
Test Error: 
 Accuracy: 50.8%, Avg loss: 0.025097 

Epoch 6
-------------------------------
loss: 1.593179  [    0/60000]
loss: 1.627773  [ 6400/60000]
loss: 1.514483  [12800/60000]
loss: 1.623364  [19200/60000]
loss: 1.433579  [25600/60000]
loss: 1.532538  [32000/60000]
loss: 1.498388  [38400/60000]
loss: 1.499874  [44800/60000]
loss: 1.474976  [51200/60000]
loss: 1.393706  [57600/60000]
Test Error: 
 Accuracy: 53.0%, Avg loss: 0.023498 

Epoch 7
-------------------------------
loss: 1.475844  [    0/60000]
loss: 1.525665  [ 6400/60000]
loss: 1.406005  [12800/60000]
loss: 1.548480  [19200/60000]
loss: 1.324354  [25600/60000]
loss: 1.450784  [32000/60000]
loss: 1.411867  [38400/60000]
loss: 1.415346  [44800/60000]
loss: 1.389313  [51200/60000]
loss: 1.321387  [57600/60000]
Test Error: 
 Accuracy: 54.2%, Avg loss: 0.022255 

Epoch 8
-------------------------------
loss: 1.383708  [    0/60000]
loss: 1.444837  [ 6400/60000]
loss: 1.316864  [12800/60000]
loss: 1.489333  [19200/60000]
loss: 1.245268  [25600/60000]
loss: 1.384567  [32000/60000]
loss: 1.343524  [38400/60000]
loss: 1.346866  [44800/60000]
loss: 1.321173  [51200/60000]
loss: 1.261818  [57600/60000]
Test Error: 
 Accuracy: 55.5%, Avg loss: 0.021229 

Epoch 9
-------------------------------
loss: 1.308065  [    0/60000]
loss: 1.376466  [ 6400/60000]
loss: 1.239852  [12800/60000]
loss: 1.438008  [19200/60000]
loss: 1.186274  [25600/60000]
loss: 1.328856  [32000/60000]
loss: 1.289778  [38400/60000]
loss: 1.292222  [44800/60000]
loss: 1.265508  [51200/60000]
loss: 1.211885  [57600/60000]
Test Error: 
 Accuracy: 56.5%, Avg loss: 0.020382 

Epoch 10
-------------------------------
loss: 1.244786  [    0/60000]
loss: 1.317880  [ 6400/60000]
loss: 1.174213  [12800/60000]
loss: 1.393624  [19200/60000]
loss: 1.140713  [25600/60000]
loss: 1.282823  [32000/60000]
loss: 1.248486  [38400/60000]
loss: 1.249433  [44800/60000]
loss: 1.220410  [51200/60000]
loss: 1.170350  [57600/60000]
Test Error: 
 Accuracy: 57.4%, Avg loss: 0.019699 

Done!
Saved PyTorch Model State to model1.pth

你可能已经注意到,模型最初的表现并不是很好(这没关系!)。尝试增加更多的epochs或调整learning_rate到更大的数字。也可能是我们选择的模型配置对这种问题来说不是最佳的(确实不是)。

保存模型

当你对模型的性能满意时,你可以使用torch.save来保存它。PyTorch模型将学到的参数存储在一个内部状态字典中,称为state_dict。这些可以使用torch.save方法持久化保存:

torch.save(model.state_dict(), "data/model.pth")

print("Saved PyTorch Model State to model.pth")

代码汇总:

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

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),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits


model = NeuralNetwork()

learning_rate = 1e-3
batch_size = 64
epochs = 5

# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()

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


def train_loop(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        # Compute prediction and loss
        pred = model(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        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)
    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 /= size
    correct /= size
    print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")


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!")

torch.save(model.state_dict(), "data/model1.pth")

print("Saved PyTorch Model State to model1.pth")

7 加载和运行模型预测


加载模型

本单元将介绍如何加载一个带有持久化参数状态和推断模型预测的模型。下面是本教程使用的类:

import torch
import onnxruntime
from torch import nn
import torch.onnx as onnx
import torchvision.models as models
from torchvision import datasets
from torchvision.transforms import ToTensor

为了加载模型,需要定义模型类,其中包含用于训练模型的神经网络的状态和参数。

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),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

在加载模型权重时,需要首先实例化模型类,因为类定义了网络的结构。接下来,使用load_state_dict()方法加载参数。

model = NeuralNetwork()
model.load_state_dict(torch.load('data/model.pth'))
model.eval()

**注意:**在进行推理之前,请确保调用 model.eval() 方法,将dropout和批量归一化层(batch normalization layers)设置为评估模式(evaluation mode)。否则,您将看到不一致的推理结果。

模型推理

将神经网络模型优化的在各种平台和编程语言上运行是很困难的。在不同的框架和硬件组合中最大化性能非常耗时。**ONNX(Open Neural Network Exchange)**运行时为您提供了一种解决方案,您可以在任何硬件、云或边缘设备上训练一次并加速推理。

ONNX是一种通用格式,受多个供应商支持,用于共享神经网络和其他机器学习模型。您可以使用ONNX格式在其他编程语言和框架上进行推理,例如Java、JavaScript、C#和ML.NET。

输出模型到ONNX格式

PyTorch也具有原生的ONNX导出支持。然而,由于PyTorch执行图的动态性质,导出过程必须遍历执行图以产生持久化的ONNX模型。因此,在导出过程中应传入一个适当大小的测试变量(在我们的情况下,我们将创建一个正确大小的零张量作为虚拟数据。您可以使用shape函数在训练数据集上获取大小,如tensor.shape):

input_image = torch.zeros((1,28,28))
onnx_model = 'data/model.onnx'
onnx.export(model, input_image, onnx_model)

此处使用测试数据集作为样本数据,从ONNX模型进行推理以进行预测。

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]
x, y = test_data[0][0], test_data[0][1]

可以使用onnxruntime.InferenceSession创建一个推理会话。要推理ONNX模型,调用run并传入想要返回的输出列表(如果想要全部输出,则留空)和输入值的映射。结果是一个输出列表。

session = onnxruntime.InferenceSession(onnx_model, None)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name

result = session.run([output_name], {input_name: x.numpy()})
predicted, actual = classes[result[0][0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')

输出是:

Predicted: "Ankle boot", Actual: "Ankle boot"

ONNX模型使您能够在不同的平台和不同的编程语言中运行推理。

代码汇总

import torch
import onnxruntime
from torch import nn
import torch.onnx as onnx
import torchvision.models as models
from torchvision import datasets
from torchvision.transforms import ToTensor


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),
            nn.ReLU()
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits


model = NeuralNetwork()
model.load_state_dict(torch.load('data/model1.pth'))
model.eval()

input_image = torch.zeros((1, 28, 28))
onnx_model = 'data/model.onnx'
onnx.export(model, input_image, onnx_model)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]
x, y = test_data[0][0], test_data[0][1]

session = onnxruntime.InferenceSession(onnx_model, None)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name

result = session.run([output_name], {input_name: x.numpy()})
predicted, actual = classes[result[0][0].argmax(0)], classes[y]
print(f'Predicted: "{predicted}", Actual: "{actual}"')

8 总结

在本专题中,我们介绍了使用神经网络构建机器学习模型的关键概念,并使用PyTorch实现了这些概念。我们构建了一个图像识别模型,可以对图像进行分类,例如:T恤/上衣、裤子、套衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包包和短靴。

您学习了以下关键领域:

  1. 如何在CPU和GPU上使用张量
  2. 如何管理、缩放和规范化数据集
  3. 如何使用神经网络构建模型
  4. 如何优化模型
  5. 如何优化模型推理

标签:loss,tensor,nn,汇总,torch,基础,Pytorch,60000,print
From: https://blog.csdn.net/qq_33345365/article/details/136732033

相关文章

  • FreeRTOS入门基础
    RTOS是为了更好地在嵌入式系统上实现多任务处理和时间敏感任务而设计的系统。它能确保任务在指定或预期的时间内得到处理。FreeRTOS是一款免费开源的RTOS,它广泛用于需要小型、预测性强、灵活系统的嵌入式设备。创建第一个任务任务函数:任务是通过函数来定义的。函数通常看起......
  • Elasticsearch 基础-1
    Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引孳,基于RESTfulweb接口。功能:分布式的搜索引擎和数据分析引擎全文检索(like),结构化检索(a=1),数据分析(count/groupby)对海量数据进行近实时的处理(秒级)特点:可拓展性:大型分布式集群(......
  • 人工智能入门之旅:从基础知识到实战应用(一)
    一、引言人工智能(ArtificialIntelligence,AI)是指利用计算机科学和技术模拟、延伸和扩展人类智能的理论、方法、技术和应用系统的学科。它的目标是使计算机系统具有类似于人类的智能,能够感知环境、学习、推理、规划、解决问题和交流。在当今社会中,人工智能具有极其重要的地......
  • pytorch使用pytorch_wavelets包错误:ValueError: step must be greater than zero 错误
    错误描述在使用pytorch_wavelets包的DWT1DInverse时,发现报错信息如下:Traceback(mostrecentcalllast):File"/work/GDN/test/test_DWT.py",line24,inx_=idwt((YL,YH))File"/opt/conda/lib/python3.6/site-packages/torch/nn/modules/module.py",line550......
  • 1分钟带你学会Python面向对象基础语法
    1.类和对象python中的面向对象主要学习类和对象类:是多个具有特殊功能的个体的集合,例如:人类/猫类/犬类对象:在一个类中,一个具有特殊功能的个体,能够帮忙解决某件特定的事情,也被称为实例两者之间的关系:类是用于描述某一类对象的共同特征,而对象是类的具体的存在在程序中......
  • C语言新手经典基础题——冒泡排序
    冒泡排序:用户输入一组数,编写程序将该组数据进行从小到大的顺序进行排列。举个例子:用户输入;1413918766这一组数据,现在要将这组数据进行从小到大的程序进行排列。我们编写程序的思路如下:现将第一个数和第二个数进行比较,即14和13,13比14小,那么就将13和14进行位置的调换,13......
  • 大规模C++程序设计 -- 基础知识
    基础知识我们先回顾C++程序语言和面向对象分析的一些重要的方面,这些知识对于大型系统设计来说是基本的。我们仔细分析多文件程序、声明与定义,以及在头文件和实现文件上下文中的内部链接和外部链接,然后研究typedef和assert的使用。多文件C++程序对于所有的(除了最小的)程序来说,将......
  • Java学习第二天——基础语法
    Java基础语法数据类型强类型语言要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用!!!Java的数据类型分类基本类型(primitivetype)1.数值类型整数类型浮点类型字符类型(只占有两个字节)2.boolean类型:占一位,其值为true或者false引用类型(referencetype)类、接......
  • Java基础知识篇02——Java基本语法
    一、数据类型定义:就是用了保存数据的一个类型,一种数据类型,只能保存该类型数据值作用:只有了解数据类型,才能选择合适的类型存放数据,才能更好的利用计算机硬件资源(内存和硬盘等)。不同的数据类型存放数据大小是不同的。数据类型的使用方式就是用来声明一个变量,装数据的。......
  • 机器学习 - PyTorch一些常用的用法
    如果我们要创建2维随机数importtorchrandom_tensor=torch.rand(size=(3,4))print(random_tensor)#输出tensor([[0.0137,0.7773,0.0150,0.2406],[0.6414,0.7830,0.7603,0.1866],[0.8157,0.8269,0.0438,0.0314]])有时候需要通过加......