当前位置: 首页 > news >正文

Pytorch框架从入门到精通

目录

一、Tensors

1.1 初始化一个Tensor

1)赋值初始化

2)从 NumPy 数组初始化

3)从另一个张量

4)使用随机值或常量值

1.2 Tensor 的属性

1.3 对 Tensor 的操作

1.3.1 总体介绍

1.3.2 索引和切片

1.3.3 算术运算

矩阵乘法

元素乘法

 1.3.4 tensor值求和后转变为python数值

1.3.5 就地操作

1.4  使用 NumPy 桥接

1.4.1 Tensor 到 NumPy 数组

1.4.2 NumPy 数组到 Tensor

 二、数据集和数据加载器--torch.utils.data API 接口

2.1 加载数据集

2.2 迭代和可视化数据集 

2.3 创建自定义数据集

__init__ 

__len__

__getitem__

2.4 准备数据以使用 DataLoader 进行训练

2.5 遍历 DataLoader

三、变换--torchvision.transforms API 接口

3.1 ToTensor()

3.2 Lambda 转换

四、构建神经网络--torch.nn API

4.1 获取训练设备

4.2 定义类

4.3 模型层

1)nn.Flatten

2)nn.Linear

3)nn.ReLU

4)nn.Sequential

5)nn.Softmax

4.4 模型参数 

五、自动微分--Autograd Mechanics

5.1 张量、函数与计算图

5.2 计算梯度 

5.3 禁用梯度跟踪

5.4 更多关于计算图的知识

5.5 张量梯度与雅可比积

六、优化模型参数

6.1 超参数

6.2 优化循环

6.3 损失函数

6.4 优化器

6.5 完整实现

6.6 拓展阅读--损失函数,torch.optim,热启动训练

七、保存和加载模型

7.1 保存和加载模型权重

7.2 保存和加载带有形状的模型

(上述7.1和7.2的更详细相关教程)

7.3 运行模型预测

7.4 拓展阅读--检查点方面相关技巧


( 想要快速入门pytorch,请看这里哦~:PyTorch 快速入门-CSDN博客)

一、Tensors

  1. 数据结构
    Tensors 是一种与数组和矩阵类似的数据结构,用于存储和操作多维数据。

  2. 在 PyTorch 中的作用

    • 用于编码模型的输入、输出和参数。

    • 是 PyTorch 中最基本的数据单元。

  3. 与 NumPy 的相似性

    • Tensors 类似于 NumPy 的 ndarrays。

    • 两者可以共享相同的底层内存,无需复制数据。

  4. 硬件加速支持

    • Tensors 可以在 GPU 或其他硬件加速器上运行,从而加速计算。

    • 这使得 Tensors 特别适合深度学习和大规模数值计算。

  5. 自动微分优化

    • Tensors 针对自动微分进行了优化,便于在深度学习中进行梯度计算和反向传播。

  6. 灵活性

    • Tensors 支持多种数据类型和操作,适用于各种科学计算和机器学习任务。

 先导包:torch,numpy

import torch
import numpy as np

1.1 初始化一个Tensor

1)赋值初始化

data = [[1, 2],[3, 4]]
print(data)
x_data = torch.tensor(data)
print(x_data)>> 运行结果
>> [[1, 2], [3, 4]]
>> tensor([[1, 2],[3, 4]])

2)从 NumPy 数组初始化

np_array = np.array(data)
print(np_array)
x_np = torch.from_numpy(np_array)
print(x_np)>> 结果:
>> [[1 2][3 4]]
>> tensor([[1, 2],[3, 4]])

3)从另一个张量

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

x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")>> 结果:
Ones Tensor: tensor([[1, 1],[1, 1]]) Random Tensor: tensor([[0.7093, 0.8628],[0.4379, 0.9094]]) 

4)使用随机值或常量值

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

shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")>> 结果:
Random Tensor: tensor([[0.3727, 0.1424, 0.1199],[0.0873, 0.0711, 0.3417]]) Ones Tensor: tensor([[1., 1., 1.],[1., 1., 1.]]) Zeros Tensor: tensor([[0., 0., 0.],[0., 0., 0.]])

1.2 Tensor 的属性

Tensor 属性描述其形状数据类型存储它们的设备

tensor = torch.rand(3,4)print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")>> 结果
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu

1.3 对 Tensor 的操作

1.3.1 总体介绍

1)张量运算:涵盖算术运算、线性代数、矩阵操作(如转置、索引、切片)以及采样等,这些运算的详细描述可在PyTorch官方文档中查看。

2)运算设备支持
不仅可以在CPU上运行,还支持在各种加速器上运行,如CUDA、MPS、MTIA或XPU等,以提升计算效率

3)张量默认创建位置及移动
默认情况下,张量是在CPU上创建的。若需将张量移动到加速器上,需在检查加速器可用性之后,使用.to方法显式进行移动,例如:
 

# We move our tensor to the current accelerator if available
if torch.accelerator.is_available():tensor = tensor.to(torch.accelerator.current_accelerator())

1.3.2 索引和切片

:操作:表示省略某个维度

...操作:表示从该位置往前的维度都省略

形状计算步骤

假设 tensor 的形状为 (d1, d2, ..., dn),其中 d1, d2, ..., dn 分别表示每个维度的大小。
tensor[...,-1]操作后最终结果的形状为 (d1, d2, ..., dn-1),tensor[...,:2](取前两个元素)操作后最终结果的形状为 (d1, d2, ..., dn-1,2)

tensor = torch.ones(2, 3, 1)
print(tensor)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}") #等价于tensor[:,:,-1]
tensor[:,1] = 0
print(tensor)>> 结果
tensor([[[1.],[1.],[1.]],[[1.],[1.],[1.]]])
First row: tensor([[1.],[1.],[1.]])
First column: tensor([[1.],[1.]])
Last column: tensor([[1., 1., 1.],[1., 1., 1.]])
tensor([[[1.],[0.],[1.]],[[1.],[0.],[1.]]])

1.3.3 联接张量

可用于沿给定维度连接一系列张量。 

法一:torch.stack--沿新维度连接一系列张量。

torch.stack(tensors, dim=0, *, out=None)

参数: 

  • tensors:要连接的张量序列。tensors中所有tensor大小都要相同
  • dim (int, optional):要插入的维度。必须介于 0 和 维度数字(包括)。默认值:0
  • 关键字参数out (Tensor, optional) - 输出张量。

例子:

>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]])
>>> torch.stack((x, x)) # same as torch.stack((x, x), dim=0)
tensor([[[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]],[[ 0.3367,  0.1288,  0.2345],[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x)).size()
torch.Size([2, 2, 3])
>>> torch.stack((x, x), dim=1)
tensor([[[ 0.3367,  0.1288,  0.2345],[ 0.3367,  0.1288,  0.2345]],[[ 0.2303, -1.1229, -0.1863],[ 0.2303, -1.1229, -0.1863]]])
>>> torch.stack((x, x), dim=2)
tensor([[[ 0.3367,  0.3367],[ 0.1288,  0.1288],[ 0.2345,  0.2345]],[[ 0.2303,  0.2303],[-1.1229, -1.1229],[-0.1863, -0.1863]]])
>>> torch.stack((x, x), dim=-1)
tensor([[[ 0.3367,  0.3367],[ 0.1288,  0.1288],[ 0.2345,  0.2345]],[[ 0.2303,  0.2303],[-1.1229, -1.1229],[-0.1863, -0.1863]]])

stack形状计算步骤

假设 tensor 的形状为 (d1, d2, ..., dn),其中 d1, d2, ..., dn 分别表示每个维度的大小,
dim = 1。

  • 如果2个tensor拼接,则最终结果的形状为 (d1, 2, d2, ..., dn),即在维度1上增加一个大小为2的新维度;
  • 如果3个tensor拼接,则最终结果的形状为 (d1, 3, d2, ..., dn),即在维度1上增加一个大小为3的新维度;
  • 如果4个tensor拼接,则最终结果的形状为 (d1, 4, d2, ..., dn),即在维度1上增加一个大小为4的新维度;

法二:torch.cat--沿现有维度连接给定序列。

tensor = torch.ones(2, 3)
t1 = torch.cat([tensor, tensor, tensor], dim=1)
t2 = torch.stack([tensor, tensor, tensor], dim=1)
print(t1)
print(t1.shape)
print(t2)
print(t2.shape)>> 结果
tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1.],[1., 1., 1., 1., 1., 1., 1., 1., 1.]])
torch.Size([2, 9])
tensor([[[1., 1., 1.],[1., 1., 1.],[1., 1., 1.]],[[1., 1., 1.],[1., 1., 1.],[1., 1., 1.]]])
torch.Size([2, 3, 3])

cat形状计算步骤

假设 tensor 的形状为 (d1, d2, ..., dn),其中 d1, d2, ..., dn 分别表示每个维度的大小,
dim = 1。

  • 如果2个tensor拼接,则最终结果的形状为 (d1, d2*2, ..., dn),即在维度1上增加一个大小为2的新维度;
  • 如果3个tensor拼接,则最终结果的形状为 (d1, d2*3, ..., dn),即在维度1上增加一个大小为3的新维度;
  • 如果4个tensor拼接,则最终结果的形状为 (d1, d2*4, ..., dn),即在维度1上增加一个大小为4的新维度;

1.3.3 算术运算

矩阵乘法
  1. @ 运算符:张量的矩阵乘法。

  2. matmul 函数

    • y2 = tensor.matmul(tensor.T):使用 matmul 函数计算 tensor 和其转置 tensor.T 的矩阵乘法。

    • matmul 函数是 PyTorch 中用于矩阵乘法的函数,与 @ 运算符功能相同。

  3. matmul 函数的 out 参数

    • torch.matmul(tensor, tensor.T, out=y3):将 tensor 和其转置 tensor.T 的矩阵乘法结果直接写入已存在的 y3 张量中。

    • out 参数允许将运算结果直接写入指定的张量,避免额外的内存分配。

元素乘法
  1. * 运算符:张量的元素乘法,即对应位置的元素相乘。

  2. mul 函数

    • z2 = tensor.mul(tensor):使用 mul 函数计算 tensor 与其自身的元素乘法。

    • mul 函数是 PyTorch 中用于元素乘法的函数,与 * 运算符功能相同。

  3. mul 函数的 out 参数

    • torch.mul(tensor, tensor, out=z3):将 tensor 与其自身的元素乘法结果直接写入已存在的z3张量中。

    • out 参数允许将运算结果直接写入指定的张量,避免额外的内存分配。

tensor = torch.ones(2, 3)# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` returns the transpose of a tensor
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)>> 结果
tensor([[0.0132, 2.5215, 1.2758],[3.7079, 0.0481, 1.4282]])

 1.3.4 tensor值求和后转变为python数值

通过item()可以将tensor聚合后的所有值转换为一个值

agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))>> 结果
3.5523605346679688 <class 'float'>

1.3.5 就地操作

是指直接在原始张量上进行修改,而不创建新的张量。这些操作通常以 _ 结尾,例如 add_mul_fill_ 等。

print(f"{tensor} \n")
tensor.add_(5)
print(tensor)>> 结果
tensor([[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.],[1., 0., 1., 1.]])tensor([[6., 5., 6., 6.],[6., 5., 6., 6.],[6., 5., 6., 6.],[6., 5., 6., 6.]])

1.4  使用 NumPy 桥接

CPU 和 NumPy 数组上的张量可以共享其底层内存 locations 的 Locations,更改一个位置将更改另一个位置。

1.4.1 Tensor 到 NumPy 数组
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")>> 结果
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]

此时如果张量发生变化,那么numpy数组也会发生变化

t.add_(1)
print(f"t: {t}")
print(f"n: {n}")>> 结果
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
1.4.2 NumPy 数组到 Tensor
n = np.ones(5)
t = torch.from_numpy(n)

 此时如果numpy数组发生变化,那么张量也会发生变化

np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")>> 结果
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]

 二、数据集和数据加载器--torch.utils.data API 接口

处理数据样本的代码容易混乱,难以维护。所以PyTorch提供了Dataset和DataLoader这两个工具,把数据集代码和模型训练代码分开,提高可读性和模块化。

  • torch.utils.data.Dataset:抽象数据集对象,负责封装样本和标签的存储
  • torch.utils.data.DataLoader:构建在Dataset之上的可迭代包装器,实现高效数据加载

2.1 加载数据集

从 TorchVision 加载 Fashion-MNIST 数据集。

Fashion-MNIST 是 Zalando 的文章图像数据集,由 60,000 个训练示例和 10,000 个测试示例组成。 每个示例都包含一个 28×28 灰度图像和一个来自 10 个类之一的关联标签。

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

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

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

  • download=True从 Internet 下载数据(如果 .root上没有数据)

  • transform指定特征和标签转换target_transform

import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plttraining_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor()
)test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=ToTensor()
)>> 结果0%|          | 0.00/26.4M [00:00<?, ?B/s]0%|          | 65.5k/26.4M [00:00<01:12, 361kB/s]1%|          | 229k/26.4M [00:00<00:38, 680kB/s]3%|3         | 885k/26.4M [00:00<00:10, 2.53MB/s]7%|7         | 1.93M/26.4M [00:00<00:06, 4.08MB/s]25%|##4       | 6.52M/26.4M [00:00<00:01, 15.2MB/s]38%|###7      | 9.96M/26.4M [00:00<00:00, 17.1MB/s]59%|#####8    | 15.5M/26.4M [00:01<00:00, 26.5MB/s]73%|#######2  | 19.2M/26.4M [00:01<00:00, 24.5MB/s]93%|#########3| 24.6M/26.4M [00:01<00:00, 31.4MB/s]
100%|##########| 26.4M/26.4M [00:01<00:00, 19.3MB/s]0%|          | 0.00/29.5k [00:00<?, ?B/s]
100%|##########| 29.5k/29.5k [00:00<00:00, 327kB/s]0%|          | 0.00/4.42M [00:00<?, ?B/s]1%|1         | 65.5k/4.42M [00:00<00:12, 361kB/s]5%|5         | 229k/4.42M [00:00<00:06, 679kB/s]21%|##        | 918k/4.42M [00:00<00:01, 2.57MB/s]44%|####3     | 1.93M/4.42M [00:00<00:00, 4.07MB/s]
100%|##########| 4.42M/4.42M [00:00<00:00, 6.06MB/s]0%|          | 0.00/5.15k [00:00<?, ?B/s]
100%|##########| 5.15k/5.15k [00:00<00:00, 30.8MB/s]

2.2 迭代和可视化数据集 

可以像列表一样手动索引,用于可视化训练数据中的一些样本。

Datasets:training_data[index]

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

2.3 创建自定义数据集

自定义 Dataset 类必须实现三个函数:__init__、__len__ 和 __getitem__。

存储 FashionMNIST 图像 在 directory 中,并且它们的标签单独存储在 CSV file 中。

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

__init__ 

__init__ 函数在实例化 Dataset 对象时运行一次。我们初始化 包含图像、annotations 文件和两个转换的目录

labels.csv 文件如下:

tshirt1.jpg, 0
tshirt2.jpg, 0
......
ankleboot999.jpg, 9

__len__

返回数据集中的样本数

__getitem__

__getitem__函数根据给定的索引从数据集中加载并返回一个样本。基于该索引,它识别图像在磁盘上的位置,并将其转换为张量,从CSV数据中检索相应的标签,对它们调用转换函数,并以元组的形式返回张量图像和相应的标签。

2.4 准备数据以使用 DataLoader 进行训练

一次检索数据集的一个样本的特征和标签。在训练模型时,我们通常希望以“小批量”的形式传递样本,在每个epoch时重新洗牌数据以减少模型过拟合,并使用Python的多线程来加快数据检索速度。

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

2.5 遍历 DataLoader

将该数据集加载到DataLoader中,并可以根据需要遍历数据集。下面的每次迭代都会返回一个批次的feature和label(分别包含特征和标签)。由于指定了batchs,所以在遍历完所有批次后,数据会被打乱(若要更精细地控制数据加载顺序,可以查看[相关文档])。 

# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")>> 结果
Feature batch shape: torch.Size([64, 1, 28, 28])
Labels batch shape: torch.Size([64])
Label: 5

三、变换--torchvision.transforms API 接口

在训练机器学习算法时,数据并不总是以所需的最终处理形式出现。我们使用 transform 来执行一些数据操作使其适合训练。

所有 TorchVision 数据集都有两个参数

  • transform:修改特征和修改标签 ;
  • target_transform:接受包含转换逻辑的可调用对象。

torchvision.transforms 模块提供 几个开箱即用的常用转换。 FashionMNIST 功能采用 PIL 图像格式,标签为整数。 对于训练,我们需要将特征作为标准化张量,将标签作为独热编码张量。 为了进行这些转换,我们使用 ToTensor ()和 Lambda

import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambdads = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor(),target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)>> 结果0%|          | 0.00/26.4M [00:00<?, ?B/s]0%|          | 65.5k/26.4M [00:00<01:12, 363kB/s]1%|          | 197k/26.4M [00:00<00:45, 575kB/s]3%|2         | 721k/26.4M [00:00<00:12, 2.04MB/s]6%|5         | 1.57M/26.4M [00:00<00:07, 3.33MB/s]21%|##1       | 5.64M/26.4M [00:00<00:01, 13.2MB/s]35%|###5      | 9.27M/26.4M [00:00<00:01, 16.4MB/s]56%|#####5    | 14.7M/26.4M [00:01<00:00, 25.6MB/s]70%|#######   | 18.5M/26.4M [00:01<00:00, 24.4MB/s]90%|########9 | 23.7M/26.4M [00:01<00:00, 30.9MB/s]
100%|##########| 26.4M/26.4M [00:01<00:00, 19.3MB/s]0%|          | 0.00/29.5k [00:00<?, ?B/s]
100%|##########| 29.5k/29.5k [00:00<00:00, 328kB/s]0%|          | 0.00/4.42M [00:00<?, ?B/s]1%|1         | 65.5k/4.42M [00:00<00:12, 361kB/s]5%|5         | 229k/4.42M [00:00<00:06, 678kB/s]21%|##1       | 950k/4.42M [00:00<00:01, 2.18MB/s]87%|########6 | 3.83M/4.42M [00:00<00:00, 7.58MB/s]
100%|##########| 4.42M/4.42M [00:00<00:00, 6.06MB/s]0%|          | 0.00/5.15k [00:00<?, ?B/s]
100%|##########| 5.15k/5.15k [00:00<00:00, 36.4MB/s]

3.1 ToTensor()

ToTensor()是PyTorch中torchvision.transforms模块的一个常用函数,主要用于将输入数据(如图像、数组等)转换为张量的形式。

  • 数据类型转换:将PIL图像或NumPy ndarray转换为FloatTensor。

  • 像素值归一化:将图像的像素强度值从0,255范围缩放至0.,1.范围内。

  • 通道顺序调整:将形状为(H, W, C)的多维数组或PIL图像对象转换为形状为(C, H, W)的张量。

3.2 Lambda 转换

Lambda转换可以对输入数据应用任意的自定义函数。这个自定义函数可以是任何可调用对象,例如Python函数、类的实例等。通过Lambda转换,用户可以实现各种复杂的转换逻辑,而无需编写额外的转换类。

在这里,我们定义了一个匿名函数,将整数转换为one-hot编码张量。具体步骤如下:

  1. 创建一个长度为10的零张量(因为我们数据集中有10个标签)。

  2. 使用scatter_方法将指定位置的值设置为1。

(  匿名函数:

使用lambda关键字定义

lambda arguments: expression• arguments:函数的参数,可以有多个,用逗号分隔。
• expression:函数的返回值,是一个表达式。

四、构建神经网络--torch.nn API

神经网络由对数据执行操作的层/模块组成。torch.nn 命名空间提供了构建您自己的神经网络所需的所有构建块。PyTorch 中的每个模块都是 nn.Module 的子类。神经网络本身也是一个模块,由其他模块(层)组成。这种嵌套结构使得构建复杂的架构变得容易。

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

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

4.1 获取训练设备

我们希望能够在 GPU 或 MPS 等硬件加速器上训练我们的模型(如果可用)。让我们检查一下 torch.cuda 或 torch.backends.mps 是否可用,否则我们使用 CPU。

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

4.2 定义类

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

class NeuralNetwork(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 512),nn.ReLU(),nn.Linear(512, 512),nn.ReLU(),nn.Linear(512, 10),)def forward(self, x):x = self.flatten(x)logits = self.linear_relu_stack(x)return logits

我们创建一个 NeuralNetwork 的实例,并将其移动到 device 上,然后打印其结构。

model = NeuralNetwork().to(device)
print(model)>> 结果
NeuralNetwork((flatten): Flatten(start_dim=1, end_dim=-1)(linear_relu_stack): Sequential((0): Linear(in_features=784, out_features=512, bias=True)(1): ReLU()(2): Linear(in_features=512, out_features=512, bias=True)(3): ReLU()(4): Linear(in_features=512, out_features=10, bias=True))
)

要使用模型,我们将输入数据传递给它。这将执行模型的 forward 方法,以及一些后台操作。不要直接调用 model.forward()!
在输入上调用模型会返回一个 10 维张量,其中包含每个类的原始预测值。我们通过 nn.Softmax 模块的实例来获得预测概率。 

X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")>> 结果
Predicted class: tensor([9], device='cuda:0')

4.3 模型层

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

input_image = torch.rand(3, 28, 28)
print(input_image.size())>> 结果
torch.Size([3, 28, 28])

1)nn.Flatten

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

flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())>> 结果
torch.Size([3, 784])

2)nn.Linear

线性层 使用其存储的权重和偏置对输入应用线性变换。 

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())>> 结果
torch.Size([3, 20])

3)nn.ReLU

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

print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")>> 结果:负数全部变成0.0000,正数不变
Before ReLU: tensor([[ 0.0399, -0.0856,  0.1098, -0.0472, -0.1217,  0.1842, -0.3489,  0.3911,-0.2160, -0.2117,  0.3714, -0.2562,  0.0076,  0.2177, -0.1724,  0.2404,0.1411,  0.1120, -0.0715,  0.0376],[ 0.0208, -0.0104,  0.0881, -0.0955, -0.0816,  0.1917, -0.3871,  0.3878,-0.1827, -0.2110,  0.3427, -0.2707,  0.0063,  0.2005, -0.1583,  0.2273,0.1337,  0.0921, -0.0562,  0.0126],[ 0.0909, -0.1817,  0.0926, -0.0005, -0.1026,  0.2360, -0.3727,  0.4387,-0.2225, -0.2530,  0.3822, -0.2901,  0.0119,  0.2223, -0.1836,  0.2681,0.1432,  0.0975, -0.0892,  0.0478]], grad_fn=<AddmmBackward0>)After ReLU: tensor([[0.0399, 0.0000, 0.1098, 0.0000, 0.0000, 0.1842, 0.0000, 0.3911, 0.0000,0.0000, 0.3714, 0.0000, 0.0076, 0.2177, 0.0000, 0.2404, 0.1411, 0.1120,0.0000, 0.0376],[0.0208, 0.0000, 0.0881, 0.0000, 0.0000, 0.1917, 0.0000, 0.3878, 0.0000,0.0000, 0.3427, 0.0000, 0.0063, 0.2005, 0.0000, 0.2273, 0.1337, 0.0921,0.0000, 0.0126],[0.0909, 0.0000, 0.0926, 0.0000, 0.0000, 0.2360, 0.0000, 0.4387, 0.0000,0.0000, 0.3822, 0.0000, 0.0119, 0.2223, 0.0000, 0.2681, 0.1432, 0.0975,0.0000, 0.0478]], grad_fn=<ReluBackward0>)

4)nn.Sequential

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

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

5)nn.Softmax

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

softmax = nn.Softmax(dim=1) #这里指第二维列特征值和为1
pred_probab = softmax(logits)

4.4 模型参数 

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

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

print(f"Model structure: {model}\n\n")for name, param in model.named_parameters():print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")>> 结果
Model structure: NeuralNetwork((flatten): Flatten(start_dim=1, end_dim=-1)(linear_relu_stack): Sequential((0): Linear(in_features=784, out_features=512, bias=True)(1): ReLU()(2): Linear(in_features=512, out_features=512, bias=True)(3): ReLU()(4): Linear(in_features=512, out_features=10, bias=True))
)Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[-0.0075,  0.0333, -0.0135,  ...,  0.0331, -0.0281,  0.0350],[ 0.0315, -0.0007,  0.0277,  ..., -0.0087, -0.0335, -0.0144]],device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0154, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[-0.0417,  0.0111,  0.0067,  ..., -0.0365, -0.0205, -0.0344],[ 0.0071, -0.0414, -0.0253,  ..., -0.0147, -0.0413, -0.0335]],device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0481, -0.0060], device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0025, -0.0210, -0.0270,  ..., -0.0199, -0.0220, -0.0364],[ 0.0213, -0.0376, -0.0220,  ..., -0.0360, -0.0225, -0.0227]],device='cuda:0', grad_fn=<SliceBackward0>)Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0150, -0.0261], device='cuda:0', grad_fn=<SliceBackward0>)

五、自动微分--Autograd Mechanics

在训练神经网络时,最常用的算法是反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。

为了计算这些梯度,PyTorch 内置了一个名为 torch.autograd 的微分引擎。它支持任何计算图的梯度自动计算。

考虑最简单的单层神经网络,具有输入 x、参数 w 和 b,以及一些损失函数。它可以通过以下方式在 PyTorch 中定义:

import torchx = torch.ones(5)  # 输入张量
y = torch.zeros(3)  # 期望输出
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

5.1 张量、函数与计算图

上段代码定义了以下计算图:

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

我们应用于张量以构建计算图的函数实际上是 Function 类的对象。该对象知道如何前向计算函数,也知道如何在反向传播步骤中计算其导数。反向传播函数的引用存储在张量的 grad_fn 属性中。您可以在 Function 文档 中找到更多信息。 

print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")>> 结果
Gradient function for z = <AddBackward0 object at 0x7f083264cd60>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f083264eec0>

5.2 计算梯度 

为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即我们需要在固定的 x 和 y 值下计算 \frac{\partial \text{loss}}{\partial w} 和 \frac{\partial \text{loss}}{\partial 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])

注意:

  • 我们只能从计算图中设置了 requires_grad=True 的叶节点获取梯度。

  • 如果需要在同一计算图上多次调用 backward(),应传递 retain_graph=True 参数。

5.3 禁用梯度跟踪

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

z = torch.matmul(x, w)+b
print(z.requires_grad)with torch.no_grad():z = torch.matmul(x, w)+b
print(z.requires_grad)>> 结果
True
False

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

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

希望禁用梯度跟踪的原因:
• 将神经网络中的某些参数标记为冻结参数。
• 在仅进行前向传递时加速计算,因为不跟踪梯度的张量计算会更高效。

5.4 更多关于计算图的知识

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

在前向传递中,autograd 同时执行以下操作:

  • 运行请求的操作以计算结果张量。

  • 在 DAG 中维护操作的梯度函数

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

  • 从每个 .grad_fn 计算梯度。

  • 将它们累积在相应张量的 .grad 属性中。

  • 使用链式法则一直传播到叶张量。

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

5.5 张量梯度与雅可比积

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

对于向量函数\vec{y} = f(\vec{x}),其中\vec{x} = \langle x_1, \ldots, x_n \rangle\vec{y} = \langle y_1, \ldots, y_m \rangle\vec{y} 相对于 \vec{x} 的梯度由雅可比矩阵给出:

J = \begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix}

PyTorch 允许您对于给定的输入向量 v = (v_1 ... v_m) 计算雅可比积 v^T · J,而不是计算雅可比矩阵本身。这是通过将 v 作为参数传递给 backward 来实现的。v 的大小应与原始张量的大小相同,我们要计算其乘积: 

inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nThird call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")>> 结果
First call
tensor([[4., 2., 2., 2., 2.],[2., 4., 2., 2., 2.],[2., 2., 4., 2., 2.],[2., 2., 2., 4., 2.]])Second call
tensor([[8., 4., 4., 4., 4.],[4., 8., 4., 4., 4.],[4., 4., 8., 4., 4.],[4., 4., 4., 8., 4.]])Third call
tensor([[12.,  6.,  6.,  6.,  6.],[ 6., 12.,  6.,  6.,  6.],[ 6.,  6., 12.,  6.,  6.],[ 6.,  6.,  6., 12.,  6.]])Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],[2., 4., 2., 2., 2.],[2., 2., 4., 2., 2.],[2., 2., 2., 4., 2.]])

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

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

六、优化模型参数

现在我们有了模型和数据,是时候通过优化数据上的参数来训练、验证和测试我们的模型了。训练模型是一个迭代的过程;在每次迭代中,模型会对输出进行猜测,计算猜测的误差(损失),收集误差相对于其参数的导数(正如五中看到的那样),并使用梯度下降法优化这些参数。有关此过程的更详细演练,请观看反向传播视频。

之前已完成关于数据集和数据加载器以及构建模型的代码如下:

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

6.1 超参数

超参数是可调节的参数,允许你控制模型优化过程。不同的超参数值会影响模型训练和收敛速度(关于超参数调优的内容)。

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

  • 学习率:在每个batch / epoch 中更新模型参数的程度。较小的值会导致学习速度较慢,而较大的值可能会导致训练过程中出现不可预测的行为。

  • batch size:在更新参数之前通过网络传播的数据样本数量。

  • epoch 数:迭代数据集的次数。

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

6.2 优化循环

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

每个epoch由两个主要部分组成:

  • 训练循环:迭代训练数据集并尝试收敛到最佳参数。

  • 验证/测试循环:迭代测试数据集以检查模型性能是否在改善。

6.5完整实现中展示了循环过程

6.3 损失函数

当提供一些训练数据时,我们未经训练的网络可能不会给出正确的答案。损失函数衡量的是获得的结果与目标值的不相似程度,这是我们希望在训练期间最小化的损失函数。为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。

常见的损失函数包括用于回归任务的nn.MSELoss(均方误差)和用于分类的nn.NLLLoss(负对数似然)。nn.CrossEntropyLoss结合了nn.LogSoftmaxnn.NLLLoss

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

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

6.4 优化器

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

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

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

在训练循环中,优化分三个步骤进行:

  • 调用optimizer.zero_grad()来重置模型参数的梯度。默认情况下,梯度会累加;为了防止重复计算,我们在每次迭代时显式地将它们归零。

  • 通过调用loss.backward()来反向传播预测损失。PyTorch会存储每个参数的损失梯度。

  • 一旦我们有了梯度,我们就调用optimizer.step()来通过反向传播中收集的梯度调整参数。

6.5 完整实现

我们定义了train_loop来循环我们的优化代码,test_loop来根据测试数据评估模型的性能。

def train_loop(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)for batch, (X, y) in enumerate(dataloader):# 计算预测和损失pred = model(X)loss = loss_fn(pred, y)# 反向传播optimizer.zero_grad()loss.backward()optimizer.step()if batch % 100 == 0:loss, current = loss.item(), batch * len(X)print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")def test_loop(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)test_loss, correct = 0, 0with 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_batchescorrect /= sizeprint(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

我们初始化损失函数和优化器,并将其传递给train_loop和test_loop。随意增加epoch数,以跟踪模型性能的改善。

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)epochs = 10
for t in range(epochs):print(f"Epoch {t+1}\n-------------------------------")train_loop(train_dataloader, model, loss_fn, optimizer)test_loop(test_dataloader, model, loss_fn)
print("Done!")>> 结果
Epoch 1
-------------------------------
loss: 2.298730  [   64/60000]
loss: 2.289123  [ 6464/60000]
loss: 2.273286  [12864/60000]
loss: 2.269406  [19264/60000]
loss: 2.249603  [25664/60000]
loss: 2.229407  [32064/60000]
loss: 2.227368  [38464/60000]
loss: 2.204261  [44864/60000]
loss: 2.206193  [51264/60000]
loss: 2.166651  [57664/60000]
Test Error:Accuracy: 50.9%, Avg loss: 2.166725Epoch 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.915486Epoch 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.541525Epoch 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.260237Epoch 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.089831Epoch 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.982440Epoch 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.910167Epoch 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.858248Epoch 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.818780Epoch 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.787271Done!

6.6 拓展阅读--损失函数,torch.optim,热启动训练

损失函数

torch.optim

热启动训练

七、保存和加载模型

在本节中,我们将了解如何通过保存、加载和运行模型预测来保持模型状态。

import torch
import torchvision.models as models

7.1 保存和加载模型权重

PyTorch模型将学习到的参数存储在一个内部状态字典中,称为state_dict。这些参数可以通过torch.save方法持久化保存:

model = models.vgg16(pretrained=True)
torch.save(model.state_dict(), 'model_weights.pth')

要加载模型权重,你需要先创建一个相同模型的实例,然后使用load_state_dict()方法加载参数。

model = models.vgg16() # 我们不指定pretrained=True,即不加载默认权重
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()

注意:
在运行推理之前,请务必调用model.eval()方法,以将 dropout 和 batch normalization 层设置为评估模式。如果不这样做,将会导致推理结果不一致。

7.2 保存和加载带有形状的模型

在加载模型权重时,我们需要先实例化模型类,因为该类定义了网络的结构。我们可能希望将此类的结构与模型一起保存,在这种情况下,我们可以将model(而不是model.state_dict())传递给保存函数:

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

然后我们可以像这样加载模型:

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

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

(上述7.1和7.2的更详细相关教程)

在 PyTorch 中保存和加载模型:查看更详细的关于保存和加载模型的指南,包括跨设备的保存和加载。

7.3 运行模型预测

加载模型后,我们可以使用它来运行预测。以下是一个简单的示例,展示了如何加载模型并对一张图片进行预测。

from torchvision import transforms
from PIL import Image# 加载图片并预处理
input_image = Image.open('dog.jpg')
preprocess = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # 创建一个 mini-batch# 如果有 GPU,将数据移动到 GPU 上
if torch.cuda.is_available():input_batch = input_batch.to('cuda')model.to('cuda')# 运行预测
with torch.no_grad():output = model(input_batch)
# Tensor of shape 1000, with confidence scores over Imagenet's 1000 classes
print(output[0])
# The output is unnormalized. To get probabilities, you can run a softmax on it.
probabilities = torch.nn.functional.softmax(output[0], dim=0)
print(probabilities)

在这个示例中,我们首先加载并预处理了一张图片,然后将其传递给模型进行预测。模型的输出是一个包含 1000 个类别的置信度分数的张量,我们可以通过 softmax 函数将其转换为概率。

7.4 拓展阅读--检查点方面相关技巧

从检查点加载 nn.module 的技巧

在 PyTorch 中保存和加载通用检査点

相关文章:

Pytorch框架从入门到精通

目录 一、Tensors 1.1 初始化一个Tensor 1&#xff09;赋值初始化 2&#xff09;从 NumPy 数组初始化 3&#xff09;从另一个张量 4&#xff09;使用随机值或常量值 1.2 Tensor 的属性 1.3 对 Tensor 的操作 1.3.1 总体介绍 1.3.2 索引和切片 1.3.3 算术运算 矩阵乘…...

Vue.js组件开发-实现全屏图片文字缩放切换特效

使用 Vue 实现全屏图片文字缩放切换特效 步骤 创建 Vue 项目&#xff1a;使用 Vue CLI 来快速创建一个新的 Vue 项目。设计组件结构&#xff1a;创建一个包含图片和文字的组件&#xff0c;并实现缩放和切换效果。实现样式&#xff1a;使用 CSS 来实现全屏显示、缩放和切换动画…...

在 WSL2 中重启 Ubuntu 实例

在 WSL2 中重启 Ubuntu 实例&#xff0c;可以按照以下步骤操作&#xff1a; 方法 1: 使用 wsl 命令 关闭 Ubuntu 实例: 打开 PowerShell 或命令提示符&#xff0c;运行以下命令&#xff1a; wsl --shutdown这会关闭所有 WSL2 实例。 重新启动 Ubuntu: 再次打开 Ubuntu&#x…...

Flutter 新春第一弹,Dart 宏功能推进暂停,后续专注定制数据处理支持

在去年春节&#xff0c;Flutter 官方发布了宏&#xff08;Macros&#xff09;编程的原型支持&#xff0c; 同年的 5 月份在 Google I/O 发布的 Dart 3.4 宣布了宏的实验性支持&#xff0c;但是对于 Dart 内部来说&#xff0c;从启动宏编程实验开始已经过去了几年&#xff0c;但…...

Signature

打开得到加密脚本&#xff1a; import ecdsa import randomdef ecdsa_test(dA,k):sk ecdsa.SigningKey.from_secret_exponent(secexpdA,curveecdsa.SECP256k1)sig1 sk.sign(databHi., kk).hex()sig2 sk.sign(databhello., kk).hex()r1 int(sig1[:64], 16)s1 int(sig1[64:…...

UE求职Demo开发日志#18 数据表获取物品信息,添加背包模块

1 把获取物品信息改为读取数据表 先创建结构&#xff0c;暂时有这几个属性&#xff1a; USTRUCT(BlueprintType) struct ARPG_CPLUS_API FMyItemData:public FTableRowBase {GENERATED_USTRUCT_BODY()UPROPERTY(EditAnywhere, BlueprintReadWrite)int ItemId;//物品Id&#x…...

neo4j-community-5.26.0 create new database

1.edit neo4j.conf 把 # The name of the default database initial.dbms.default_databasehonglouneo4j # 写上自己的数据库名称 和 # Name of the service #5.0 server.windows_service_nameneo4j #4.0 dbms.default_databaseneo4j #dbms.default_databaseneo4jwind serve…...

项目中用的网关Gateway及SpringCloud

在现代微服务架构中&#xff0c;网关&#xff08;Gateway&#xff09;起到了至关重要的作用。它不仅负责路由请求&#xff0c;还提供了统一的认证、授权、负载均衡、限流等功能。Spring Cloud Gateway 是 Spring Cloud 生态系统中的一个重要组件&#xff0c;专门为微服务架构提…...

​《Ollama Python 库​》

Ollama Python 库 Ollama Python 库提供了将 Python 3.8 项目与 Ollama 集成的最简单方法。 先决条件 应该安装并运行 Ollama拉取一个模型以与库一起使用&#xff1a;例如ollama pull <model>ollama pull llama3.2 有关可用模型的更多信息&#xff0c;请参阅 Ollama.com。…...

大模型概述(方便不懂技术的人入门)

1 大模型的价值 LLM模型对人类的作用&#xff0c;就是一个百科全书级的助手。有多么地百科全书&#xff0c;则用参数的量来描述&#xff0c; 一般地&#xff0c;大模型的参数越多&#xff0c;则该模型越好。例如&#xff0c;GPT-3有1750亿个参数&#xff0c;GPT-4可能有超过1万…...

Ubuntu16.04编译安装Cartographer 1.0版本

说明 官方文档 由于Ubuntu16.04已经是很老的系统&#xff0c;如果直接按照Cartographer官方安装文档安装会出现代码编译失败的问题&#xff0c;本文给出了解决这些问题的办法。正常情况下执行本文给出的安装方法即可成功安装。 依赖安装 # 这里和官方一致 # Install the req…...

AI-ISP论文Learning to See in the Dark解读

论文地址&#xff1a;Learning to See in the Dark 图1. 利用卷积网络进行极微光成像。黑暗的室内环境。相机处的照度小于0.1勒克斯。索尼α7S II传感器曝光时间为1/30秒。(a) 相机在ISO 8000下拍摄的图像。(b) 相机在ISO 409600下拍摄的图像。该图像存在噪点和色彩偏差。©…...

2 MapReduce

2 MapReduce 1. MapReduce 介绍1.1 MapReduce 设计构思 2. MapReduce 编程规范3. Mapper以及Reducer抽象类介绍1.Mapper抽象类的基本介绍2.Reducer抽象类基本介绍 4. WordCount示例编写5. MapReduce程序运行模式6. MapReduce的运行机制详解6.1 MapTask 工作机制6.2 ReduceTask …...

OpenCV:SIFT关键点检测与描述子计算

目录 1. 什么是 SIFT&#xff1f; 2. SIFT 的核心步骤 2.1 尺度空间构建 2.2 关键点检测与精细化 2.3 方向分配 2.4 计算特征描述子 3. OpenCV SIFT API 介绍 3.1 cv2.SIFT_create() 3.2 sift.detect() 3.3 sift.compute() 3.4 sift.detectAndCompute() 4. SIFT 关…...

初识Cargo:Rust的强大构建工具与包管理器

初识Cargo&#xff1a;Rust的强大构建工具与包管理器 如果你刚刚开始学习Rust&#xff0c;一定会遇到一个名字&#xff1a;Cargo。Cargo是Rust的官方构建工具和包管理器&#xff0c;它让Rust项目的创建、编译、测试和依赖管理变得非常简单。本文将带你快速了解Cargo的基本用法…...

LightM-UNet(2024 CVPR)

论文标题LightM-UNet: Mamba Assists in Lightweight UNet for Medical Image Segmentation论文作者Weibin Liao, Yinghao Zhu, Xinyuan Wang, Chengwei Pan, Yasha Wang and Liantao Ma发表日期2024年01月01日GB引用> Weibin Liao, Yinghao Zhu, Xinyuan Wang, et al. Ligh…...

2025年02月01日Github流行趋势

项目名称&#xff1a;oumi 项目地址url&#xff1a;https://github.com/oumi-ai/oumi 项目语言&#xff1a;Python 历史star数&#xff1a;544 今日star数&#xff1a;103 项目维护者&#xff1a;xrdaukar, oelachqar, taenin, wizeng23, kaisopos 项目简介&#xff1a;一切你需…...

自动化测试框架搭建-封装requests-优化

目的 1、实际的使用场景&#xff0c;无法避免的需要区分GET、POST、PUT、PATCH、DELETE等不同的方式请求&#xff0c;以及不同请求的传参方式 2、python中requests中&#xff0c;session.request方法&#xff0c;GET请求&#xff0c;只支持params传递参数 session.request(me…...

什么是线性化PDF?

线性化PDF是一种特殊的PDF文件组织方式。 总体而言&#xff0c;PDF是一种极为优雅且设计精良的格式。PDF由大量PDF对象构成&#xff0c;这些对象用于创建页面。相关信息存储在一棵二叉树中&#xff0c;该二叉树同时记录文件中每个对象的位置。因此&#xff0c;打开文件时只需加…...

XML DOM 浏览器差异

DOM 解析中的浏览器差异 所有现代的浏览器都支持 W3C DOM 规范。 然而&#xff0c;浏览器之间是有差异的。一个重要的差异是&#xff1a; 处理空白和换行的方式 DOM - 空白和换行 XML 经常在节点之间包含换行或空白字符。这是在使用简单的编辑器&#xff08;比如记事本&…...

电子电气架构 --- 汽车电子拓扑架构的演进过程

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…...

01-六自由度串联机械臂(ABB)位置分析

ABB工业机器人&#xff08;IRB2600&#xff09;如下图所示&#xff08;d1444.8mm&#xff0c;a1150mm&#xff0c;a2700mm&#xff0c;a3115mm&#xff0c;d4795mm&#xff0c;d685mm&#xff09;&#xff0c;利用改进DH法建模&#xff0c;坐标系如下所示&#xff1a; 利用改进…...

04树 + 堆 + 优先队列 + 图(D1_树(D6_B树(B)))

目录 一、学习前言 二、基本介绍 三、特性 1. 从概念上说起 2. 举个例子 四、代码实现 节点准备 大体框架 实现分裂 实现新增 实现删除 五、完整源码 一、学习前言 前面我们已经讲解过了二叉树、二叉搜索树&#xff08;BST&#xff09;、平衡二叉搜索树&#xff08…...

350.两个数组的交集 ②

目录 题目过程解法 题目 给你两个整数数组 nums1 和 nums2 &#xff0c;请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数&#xff0c;应与元素在两个数组中都出现的次数一致&#xff08;如果出现次数不一致&#xff0c;则考虑取较小值&#xff09;。可以不考虑…...

C#,入门教程(09)——运算符的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(08)——基本数据类型及使用的基础知识https://blog.csdn.net/beijinghorn/article/details/123906998 一、算术运算符号 算术运算符号包括&#xff1a;四则运算 加 , 减-, 乘*, 除/与取模%。 // 加法&#xff0c;运算 int va 1 …...

Python-基于PyQt5,wordcloud,pillow,numpy,os,sys等的智能词云生成器

前言&#xff1a;日常生活中&#xff0c;我们有时后就会遇见这样的情形&#xff1a;我们需要将给定的数据进行可视化处理&#xff0c;同时保证呈现比较良好的量化效果。这时候我们可能就会用到词云图。词云图&#xff08;Word cloud&#xff09;又称文字云&#xff0c;是一种文…...

海外问卷调查之渠道查,企业经营的指南针

海外问卷调查&#xff0c;是企业调研最常用到的方法&#xff0c;有目的、有计划、有系统地收集研究对象的现实状况或历史状况的一种有效手段&#xff0c;是指导企业经营的有效手段。 海外问卷调查充分运用历史法、观察法等方法&#xff0c;同时使用谈话、问卷、个案研究、测试…...

C++:虚函数与多态性习题

题目内容&#xff1a; 构建一个车&#xff08;vehicle&#xff09;基类&#xff0c;包含Run、Stop两个纯虚函数。由此基类&#xff0c;派生出&#xff08;Car&#xff09;轿车类&#xff0c;&#xff08;truck&#xff09;卡车类&#xff0c;在这两个类中别分定义Run和Stop两个…...

单片机基础模块学习——超声波传感器

一、超声波原理 左边发射超声波信号&#xff0c;右边接收超声波信号 左边的芯片用来处理超声波发射信号&#xff0c;中间的芯片用来处理接收的超声波信号 二、超声波原理图 T——transmit 发送R——Recieve 接收 U18芯片对输入的N_A1信号进行放大&#xff0c;然后输入给超声…...

通过protoc工具生成proto的pb.go文件以及使用protoc-go-inject-tag工具注入自定义标签

1.ProtoBuf认识,安装以及用法 参考:[golang 微服务] 3. ProtoBuf认识&#xff0c;安装以及golang 中ProtoBuf使用 2. 使用protoc-go-inject-tag工具注入自定义标签 这里有一个案例: syntaxproto3; package test;option go_package ".;test";message MyMessage {int6…...

42【语言的编码架构】

不同语言采用的编码架构不一样 火山采用&#xff1a;UTF-16 易语言采用&#xff1a;GBK php采用&#xff1a;UTF-8 这个编码架构指的就是文本所代表的字节集&#xff0c;比如易语言中“你好”表示的就是{196,227,186,195} 窗口程序集名保 留 保 留备 注窗口程序集_启动窗口 …...

TOF技术原理和静噪对策

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、什么是TOF TOF 是Time of Flight的缩写&#xff0c;它是一种通过利用照射波和反射波之间的时间差来测量到物体的距离的测…...

ssh调试:fatal: Could not read from remote repository.

我遇到的原因和网上说的什么在生产密钥时没加邮箱&#xff0c;以及多个密钥的配置问题都不一样&#xff1b; 例如https://blog.csdn.net/baoyin0822/article/details/122584931 或https://blog.csdn.net/qq_55558061/article/details/124117445 我遇到的问题的原因跟他们都i不…...

win10部署本地deepseek-r1,chatbox,deepseek联网(谷歌网页插件)

win10部署本地deepseek-r1&#xff0c;chatbox&#xff0c;deepseek联网&#xff08;谷歌网页插件&#xff09; 前言一、本地部署DeepSeek-r1step1 安装ollamastep2 下载deepseek-r1step2.1 找到模型deepseek-r1step2.2 cmd里粘贴 后按回车&#xff0c;进行下载 step3 测试指令…...

SpringCloud系列教程:微服务的未来(十九)请求限流、线程隔离、Fallback、服务熔断

前言 前言 在现代微服务架构中&#xff0c;系统的高可用性和稳定性至关重要。为了解决系统在高并发请求或服务不可用时出现的性能瓶颈或故障&#xff0c;常常需要使用一些技术手段来保证服务的平稳运行。请求限流、线程隔离、Fallback 和服务熔断是微服务中常用的四种策略&…...

Hot100之子串

560和为K的子数组 题目 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列 思路解析 ps&#xff1a;我们的presum【0】就是0&#xff0c;如果没有这个0的话我们的第一个元素就无法减去上…...

SpringBoot笔记

1.创建 使用idea提供的脚手架创建springboot项目&#xff0c;选上需要的模块&#xff0c;会自动进行导包 打成jar包&#xff0c;之前直接用原生的maven打包的是一个瘦jar&#xff0c;不能直接跑&#xff0c;把服务器上部署的jar排除在外了&#xff0c;但是现在加上打包查件&am…...

一、TensorFlow的建模流程

1. 数据准备与预处理&#xff1a; 加载数据&#xff1a;使用内置数据集或自定义数据。 预处理&#xff1a;归一化、调整维度、数据增强。 划分数据集&#xff1a;训练集、验证集、测试集。 转换为Dataset对象&#xff1a;利用tf.data优化数据流水线。 import tensorflow a…...

4 Hadoop 面试真题

4 Hadoop 面试真题 1. Apache Hadoop 3.0.02. HDFS 3.x 数据存储新特性-纠删码Hadoop面试真题 1. Apache Hadoop 3.0.0 Apache Hadoop 3.0.0在以前的主要发行版本&#xff08;hadoop-2.x&#xff09;上进行了许多重大改进。 最低要求的Java版本从Java 7增加到Java 8 现在&…...

信息学奥赛一本通 ybt 1608:【 例 3】任务安排 3 | 洛谷 P5785 [SDOI2012] 任务安排

【题目链接】 ybt 1608&#xff1a;【 例 3】任务安排 3 洛谷 P5785 [SDOI2012] 任务安排 【题目考点】 1. 动态规划&#xff1a;斜率优化动规 2. 单调队列 3. 二分答案 【解题思路】 与本题题面相同但问题规模不同的题目&#xff1a; 信息学奥赛一本通 1607&#xff1a…...

实验六 项目二 简易信号发生器的设计与实现 (HEU)

声明&#xff1a;代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog&#xff08;或VHDL&#xff09;、图形描述方式、IP核&#xff0c;结合数字系统设计方法&#xff0c;在Quartus开发环境下&#xff…...

基于最近邻数据进行分类

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 完整代码&#xff1a; import torch import numpy as np from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt# 生成一个简单的数据…...

SpringSecurity:There is no PasswordEncoder mapped for the id “null“

文章目录 一、情景说明二、分析三、解决 一、情景说明 在整合SpringSecurity功能的时候 我先是去实现认证功能 也就是&#xff0c;去数据库比对用户名和密码 相关的类&#xff1a; UserDetailsServiceImpl implements UserDetailsService 用于SpringSecurity查询数据库 Logi…...

redex快速体验

第一步&#xff1a; 2.回调函数在每次state发生变化时候自动执行...

Flask框架基础入门教程_ezflaskapp

pip install flaskFlask 快速入门小应用 学东西&#xff0c;得先知道我们用这个东西&#xff0c;能做出来一个什么东西。 一个最小的基于flask 的应用可能看上去像下面这个样子&#xff1a; from flask import Flask app Flask(__name__)app.route(/) def hello_world():ret…...

Anaconda 全面解析:从入门到精通的操作教程

大家好&#xff0c;我是滔滔&#xff0c;欢迎来到我的空间。先简单介绍下anconda 一、环境管理 轻松创建独立的 Python 环境&#xff1a;可以为不同的项目创建不同的环境&#xff0c;每个环境可以有不同的 Python 版本和安装不同的包&#xff0c;避免了包冲突问题。例如&…...

3D图形学与可视化大屏:什么是材质属性,有什么作用?

一、颜色属性 漫反射颜色 漫反射颜色决定了物体表面对入射光进行漫反射后的颜色。当光线照射到物体表面时&#xff0c;一部分光被均匀地向各个方向散射&#xff0c;形成漫反射。漫反射颜色的选择会直接影响物体在光照下的外观。例如&#xff0c;一个红色的漫反射颜色会使物体在…...

HTML-新浪新闻-实现标题-排版

标题排版 图片标签&#xff1a;<img> src&#xff1a;指定图片的url&#xff08;绝对路径/相对路径&#xff09; width&#xff1a;图片的宽度&#xff08;像素/相对于父元素的百分比&#xff09; heigth&#xff1a;图片的高度&#xff08;像素/相对于父元素的百分比&a…...

.Net / C# 分析文件编码 并将 各种编码格式 转为 另一个编码格式 ( 比如: GB2312→UTF-8, UTF-8→GB2312)

相关库 .Net 8 编码识别: github.com/CharsetDetector/UTF-unknown <PackageReference Include"UTF.Unknown" Version"2.5.1" />代码 using UtfUnknown;var dir_path "D:\\Desktop\\新建文件夹2\\新建文件夹"; var dir_new_path &quo…...

本地部署 DeepSeek-R1 大模型

本地部署 DeepSeek-R1 大模型指南 1. 引言 1.1 DeepSeek-R1 模型简介 在人工智能的世界里&#xff0c;大型语言模型&#xff08;LLM&#xff09;正如一座巨大的宝库&#xff0c;里面储存着丰富的信息和无限的潜力。而DeepSeek-R1&#xff0c;就像那扇打开智慧之门的钥匙。它…...