YOLO入门教程(番外):一文搞定PyTorch

whdahanh 发布于 2025-09-29 344 次阅读


在进行下一步之前,我们需要Python和Pytorch的基础。于是在网上找了一篇很完整的博文,内容系统且丰富。

我基于此做了一次注释,咱们应该依靠该大佬的优秀文章简单入个门。至于后面的咱们可以后面用的时候边用边补充。

Python部分就不展开了,花一个小时熟悉一下,应该就能使用了解了。

  • 推荐资料:https://liaoxuefeng.com/books/python/introduction/index.html

  • 文章标题:Pytorch 最全入门介绍,Pytorch入门看这一篇就够了
  • 学习教案:https://www.cnblogs.com/xfuture/p/17593872.html
  • 作者姓名:techlead_krischang

1. Pytorch简介

在这一部分,我们将会对Pytorch做一个简单的介绍,包括它的历史、优点以及使用场景等。

1.1 Pytorch的历史

PyTorch是一个由Facebook的人工智能研究团队开发的开源深度学习框架。在2016年发布后,PyTorch很快就因其易用性、灵活性和强大的功能而在科研社区中广受欢迎。下面我们将详细介绍PyTorch的发展历程。

在2016年,Facebook的AI研究团队(FAIR)公开了PyTorch,其旨在提供一个快速,灵活且动态的深度学习框架。PyTorch的设计哲学与Python的设计哲学非常相似:易读性和简洁性优于隐式的复杂性。PyTorch用Python语言编写,是Python的一种扩展,这使得其更易于学习和使用。

PyTorch在设计上取了一些大胆的决定,其中最重要的一项就是选择动态计算图(Dynamic Computation Graph)作为其核心。动态计算图与其他框架(例如TensorFlow和Theano)中的静态计算图有着本质的区别,它允许我们在运行时改变计算图。这使得PyTorch在处理复杂模型时更具灵活性,并且对于研究人员来说,更易于理解和调试。

在发布后的几年里,PyTorch迅速在科研社区中取得了广泛的认可。在2019年,PyTorch发布了1.0版本,引入了一些重要的新功能,包括支持ONNX、一个新的分布式包以及对C++的前端支持等。这些功能使得PyTorch在工业界的应用更加广泛,同时也保持了其在科研领域的强劲势头。

到了近两年,PyTorch已经成为全球最流行的深度学习框架之一。其在GitHub上的星标数量超过了50k,被用在了各种各样的项目中,从最新的研究论文到大规模的工业应用。

综上,PyTorch的发展历程是一部充满创新和挑战的历史,它从一个科研项目发展成为了全球最流行的深度学习框架之一。在未来,我们有理由相信,PyTorch将会在深度学习领域继续发挥重要的作用。

1.2 Pytorch的优点

PyTorch不仅是最受欢迎的深度学习框架之一,而且也是最强大的深度学习框架之一。它有许多独特的优点,使其在学术界和工业界都受到广泛的关注和使用。接下来我们就来详细地探讨一下PyTorch的优点。

1. 动态计算图

PyTorch最突出的优点之一就是它使用了动态计算图(Dynamic Computation Graphs,DCGs),与TensorFlow和其他框架使用的静态计算图不同。动态计算图允许你在运行时更改图的行为。这使得PyTorch非常灵活,在处理不确定性或复杂性时具有优势,因此非常适合研究和原型设计。


白话一下

PyTorch 中的动态计算图(Dynamic Computation Graph)是其核心特性,它让PyTorch用起来特别灵活和直观。别被这个名字吓到,其实它的概念很直接。

🔍 动态计算图:PyTorch的“即兴流程图”

可以把动态计算图想象成一份即时记录的“菜谱” :

  • 假设你在做一道新菜,不需要严格按照预先写好的固定菜谱,而是一边做一边记录步骤。先切菜、然后开火、再根据锅里情况决定下一步加什么调料。
  • 这里的每一个步骤(切菜、翻炒、调味)就像是计算图中的一个节点,步骤之间的顺序和依赖关系就是连接这些节点的
  • 在PyTorch中,你的每一次计算(比如张量的加法、乘法)都会在这个“菜谱”上实时添加一个步骤,计算图就在代码运行时动态构建起来 。

这与TensorFlow 1.x等框架采用的静态计算图不同。静态图需要你提前定义好整个计算流程(就像必须严格按照一份固定不变的菜谱做菜),然后才执行 。

⚙️ 动态计算图如何工作

PyTorch通过 torch.autograd 模块实现自动微分和动态计算图。当你进行前向计算时,系统会动态跟踪所有操作;而在反向传播时,则依据这个动态生成的计算图进行梯度计算。其关键组件和流程可以概括为下图:

🔑 关键组件与流程

  1. 张量 (Tensor) 与 requires_grad=True:当你创建一个张量并设置 requires_grad=True 时,PyTorch 就会开始跟踪所有施加于该张量之上的操作。例如:x = torch.tensor([2.0], requires_grad=True)
  2. 运算 (Operations) 与 Function 节点:每一个对张量的数学运算(如加法、乘法)都会在计算图中创建一个 Function 节点(也称为梯度函数 grad_fn)。这个节点记录了进行的具体操作,并在反向传播时知道如何计算该操作的导数。
  3. 前向传播 (Forward Pass):前向传播过程就是即时执行这些运算的过程。图也随之构建起来。例如:y = x ** 2 # PyTorch 会记录这个幂运算操作,并创建对应的计算图节点
  4. 反向传播 (Backward Pass):当你对最终的输出(通常是一个损失值)调用 .backward() 方法时,PyTorch 会从该输出开始,沿着动态构建的计算图反向回溯,利用链式法则自动计算所有 requires_grad=True 的张量的梯度。这些梯度会被累积到各个张量的 .grad 属性中。

来看一个简单的代码示例,直观感受一下:

import torch

# 1. 创建需要计算梯度的张量
x = torch.tensor(3.0, requires_grad=True)

# 2. 执行一些计算(动态图在此过程中构建)
y = x * 2   # 这个乘法操作成为一个计算图节点
z = y ** 2# 这个平方操作成为另一个节点

# 3. 计算梯度
z.backward() 

# 4. 查看梯度
print(x.grad) # 输出:tensor(24.) 
              # 因为 dz/dx = dz/dy * dy/dx = 2*y * 2 = 2*(x*2)*2 = 4*x, 当x=3时是12

✨ 主要优势:灵活、直观、易调试

动态计算图给PyTorch带来了许多显著优势:

  • 极高的灵活性:因为图是运行时动态生成的,所以你可以轻松地使用标准的Python控制流语句(如 if-elsefor 循环、while 循环)来改变模型的行为。这对于处理变长序列(如自然语言处理中不同长度的句子)或模型结构不确定的场景非常有用。# 示例:动态改变计算路径
    def dynamic_flow(x):
        if x.sum() > 0:
            return x * 2
        else:
            return x / 2
  • 直观易用,调试方便:由于计算图的构建与你编写代码的顺序一致,调试起来非常直观。你可以像调试普通Python程序一样使用 print 语句或调试器(如PDB)在任何地方检查中间变量的值,快速定位问题。
  • 更适合研究实验:这种灵活性和易调试性使得研究者能够快速尝试各种新奇的想法和模型结构,无需受限于固定的图结构,从而加速迭代和实验过程。

⚠️ 使用注意:生命周期与梯度管理

使用动态计算图时,有几点需要了解:

  • 计算图的生命周期:默认情况下,每次调用 .backward() 后,为节省内存,动态计算图会自动释放销毁。这意味着通常你不能对同一个计算图再次调用 .backward()(除非在第一次调用时设置了 retain_graph=True 参数)。
  • 叶子节点 (Leaf Tensors) :由用户直接创建、requires_grad=True 的张量称为叶子节点(如模型参数 w 和 b)。通过计算得到的张量称为非叶子节点(如中间特征 y 和损失值 loss)。默认情况下,只有叶子节点的梯度会被保留在它们的 .grad 属性中。
  • 梯度累积:调用 .backward() 时,计算出的梯度会累加到叶子节点的 .grad 属性中(而不是覆盖)。这意味着在每次反向传播前,通常需要手动将梯度清零(使用 optimizer.zero_grad()),否则梯度会不断累加,导致错误的更新。

📊 动态图 vs. 静态图

为了更清晰地理解动态计算图的特点,下表对比了其主要特性与传统静态计算图的区别:

特性动态计算图 (PyTorch)静态计算图 (TensorFlow 1.x)
构建方式运行时动态创建先预先定义完整计算图,再执行
灵活性⭐⭐⭐⭐⭐ 非常高,可随时改变结构,支持原生控制流⭐⭐ 较低,图结构固定后难以修改
调试难度⭐⭐⭐⭐⭐ 非常容易,可使用标准调试工具⭐⭐ 较难,图预先定义,调试中间结果不便
性能表现⭐⭐⭐⭐ 通常不错,但每次运行需重建图⭐⭐⭐⭐⭐ 优化后执行效率可能更高,尤其适合部署
典型场景研究、原型设计、需要灵活性的任务大规模生产环境部署、对性能有极致要求的固定模型

💎 总结

总而言之,PyTorch的动态计算图是一种在运行时即时构建的计算图。它就像一份即时记录的“菜谱”,跟踪你的每一步计算操作,并在需要计算梯度时,沿着这份“菜谱”反向自动求导。

灵活性直观性使其成为研究和原型设计的强大工具。理解动态计算图是理解PyTorch自动微分如何工作的基础。随着你更深入地使用PyTorch,你会越来越体会到这种“定义-by-run”模式带来的便利。

2. 易用性

PyTorch被设计成易于理解和使用。其API设计的直观性使得学习和使用PyTorch成为一件非常愉快的事情。此外,由于PyTorch与Python的深度集成,它在Python程序员中非常流行。

3. 易于调试

由于PyTorch的动态性和Python性质,调试PyTorch程序变得相当直接。你可以使用Python的标准调试工具,如PDB或PyCharm,直接查看每个操作的结果和中间变量的状态。

4. 强大的社区支持

PyTorch的社区非常活跃和支持。官方论坛、GitHub、Stack Overflow等平台上有大量的PyTorch用户和开发者,你可以从中找到大量的资源和帮助。

5. 广泛的预训练模型

PyTorch提供了大量的预训练模型,包括但不限于ResNet,VGG,Inception,SqueezeNet,EfficientNet等等。这些预训练模型可以帮助你快速开始新的项目。

6. 高效的GPU利用

PyTorch可以非常高效地利用NVIDIA的CUDA库来进行GPU计算。同时,它还支持分布式计算,让你可以在多个GPU或服务器上训练模型。

综上所述,PyTorch因其易用性、灵活性、丰富的功能以及强大的社区支持,在深度学习领域中备受欢迎。

1.3 Pytorch的使用场景

PyTorch的强大功能和灵活性使其在许多深度学习应用场景中都能够发挥重要作用。以下是PyTorch在各种应用中的一些典型用例:

  1. 计算机视觉

在计算机视觉方面,PyTorch提供了许多预训练模型(如ResNet,VGG,Inception等)和工具(如TorchVision),可以用于图像分类、物体检测、语义分割和图像生成等任务。这些预训练模型和工具大大简化了开发计算机视觉应用的过程。

  1. 自然语言处理

在自然语言处理(NLP)领域,PyTorch的动态计算图特性使得其非常适合处理变长输入,这对于许多NLP任务来说是非常重要的。同时,PyTorch也提供了一系列的NLP工具和预训练模型(如Transformer,BERT等),可以帮助我们处理文本分类、情感分析、命名实体识别、机器翻译和问答系统等任务。

  1. 生成对抗网络

生成对抗网络(GANs)是一种强大的深度学习模型,被广泛应用于图像生成、图像到图像的转换、样式迁移和数据增强等任务。PyTorch的灵活性使得其非常适合开发和训练GAN模型。

  1. 强化学习

强化学习是一种学习方法,其中智能体通过与环境的交互来学习如何执行任务。PyTorch的动态计算图和易于使用的API使得其在实现强化学习算法时表现出极高的效率。

  1. 时序数据分析

在处理时序数据的任务中,如语音识别、时间序列预测等,PyTorch的动态计算图为处理可变长度的序列数据提供了便利。同时,PyTorch提供了包括RNN、LSTM、GRU在内的各种循环神经网络模型。

总的来说,PyTorch凭借其强大的功能和极高的灵活性,在许多深度学习的应用场景中都能够发挥重要作用。无论你是在研究新的深度学习模型,还是在开发实际的深度学习应用,PyTorch都能够提供强大的支持。

2. Pytorch基础

在我们开始深入使用PyTorch之前,让我们先了解一些基础概念和操作。这一部分将涵盖PyTorch的基础,包括tensor操作、GPU加速以及自动求导机制。

2.1 Tensor操作

Tensor是PyTorch中最基本的数据结构,你可以将其视为多维数组或者矩阵。PyTorch tensor和NumPy array非常相似,但是tensor还可以在GPU上运算,而NumPy array则只能在CPU上运算。下面,我们将介绍一些基本的tensor操作。

首先,我们需要导入PyTorch库:

import torch

然后,我们可以创建一个新的tensor。以下是一些创建tensor的方法:

# 创建一个未初始化的5x3矩阵
x = torch.empty(5, 3)
print(x)

# 创建一个随机初始化的5x3矩阵
x = torch.rand(5, 3)
print(x)

# 创建一个5x3的零矩阵,类型为long
x = torch.zeros(5, 3, dtype=torch.long)
print(x)

# 直接从数据创建tensor
x = torch.tensor([5.5, 3])
print(x)
我们还可以对已有的tensor进行操作。以下是一些基本操作:

# 创建一个tensor,并设置requires_grad=True以跟踪计算历史
x = torch.ones(2, 2, requires_grad=True)
print(x)

# 对tensor进行操作
y = x + 2
print(y)

# y是操作的结果,所以它有grad_fn属性
print(y.grad_fn)

# 对y进行更多操作
z = y * y * 3
out = z.mean()

print(z, out)

上述操作的结果如下:

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x7f36c0a7f1d0>
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

在PyTorch中,我们可以使用.backward()方法来计算梯度。例如:

# 因为out包含一个标量,out.backward()等价于out.backward(torch.tensor(1.))
out.backward()

# 打印梯度 d(out)/dx
print(x.grad)

以上是PyTorch tensor的基本操作,我们可以看到PyTorch tensor操作非常简单和直观。在后续的学习中,我们将会使用到更多的tensor操作,例如索引、切片、数学运算、线性代数、随机数等等。

2.2 GPU加速

在深度学习训练中,GPU(图形处理器)加速是非常重要的一部分。GPU的并行计算能力使得其比CPU在大规模矩阵运算上更具优势。PyTorch提供了简单易用的API,让我们可以很容易地在CPU和GPU之间切换计算。

首先,我们需要检查系统中是否存在可用的GPU。在PyTorch中,我们可以使用torch.cuda.is_available()来检查:

import torch

# 检查是否有可用的GPU
if torch.cuda.is_available():
    print("There is a GPU available.")
else:
    print("There is no GPU available.")

如果存在可用的GPU,我们可以使用.to()方法将tensor移动到GPU上:

# 创建一个tensor
x = torch.tensor([1.0, 2.0])

# 移动tensor到GPU上
if torch.cuda.is_available():
    x = x.to('cuda')

我们也可以直接在创建tensor的时候就指定其设备:

# 直接在GPU上创建tensor
if torch.cuda.is_available():
    x = torch.tensor([1.0, 2.0], device='cuda')

在进行模型训练时,我们通常会将模型和数据都移动到GPU上:

# 创建一个简单的模型
model = torch.nn.Linear(10, 1)

# 创建一些数据
data = torch.randn(100, 10)

# 移动模型和数据到GPU
if torch.cuda.is_available():
    model = model.to('cuda')
    data = data.to('cuda')

以上就是在PyTorch中进行GPU加速的基本操作。使用GPU加速可以显著提高深度学习模型的训练速度。但需要注意的是,数据在CPU和GPU之间的传输会消耗一定的时间,因此我们应该尽量减少数据的传输次数。

2.3 自动求导

在深度学习中,我们经常需要进行梯度下降优化。这就需要我们计算梯度,也就是函数的导数。在PyTorch中,我们可以使用自动求导机制(autograd)来自动计算梯度。

在PyTorch中,我们可以设置tensor.requires_grad=True来追踪其上的所有操作。完成计算后,我们可以调用.backward()方法,PyTorch会自动计算和存储梯度。这个梯度可以通过.grad属性进行访问。

下面是一个简单的示例:

import torch

# 创建一个tensor并设置requires_grad=True来追踪其计算历史
x = torch.ones(2, 2, requires_grad=True)

# 对这个tensor做一次运算:
y = x + 2

# y是计算的结果,所以它有grad_fn属性
print(y.grad_fn)

# 对y进行更多的操作
z = y * y * 3
out = z.mean()

print(z, out)

# 使用.backward()来进行反向传播,计算梯度
out.backward()

# 输出梯度d(out)/dx
print(x.grad)

以上示例中,out.backward()等同于out.backward(torch.tensor(1.))。如果out不是一个标量,因为tensor是矩阵,那么在调用.backward()时需要传入一个与out同形的权重向量进行相乘。

例如:

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

以上就是PyTorch中自动求导的基本使用方法。自动求导是PyTorch的重要特性之一,它为深度学习模型的训练提供了极大的便利。

3. PyTorch 神经网络

图片在掌握了PyTorch的基本使用方法之后,我们将探索一些更为高级的特性和用法。这些高级特性包括神经网络构建、数据加载以及模型保存和加载等等。

3.1 构建神经网络

PyTorch提供了torch.nn库,它是用于构建神经网络的工具库。torch.nn库依赖于autograd库来定义和计算梯度。nn.Module包含了神经网络的层以及返回输出的forward(input)方法。

以下是一个简单的神经网络的构建示例:

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        # 输入图像channel:1,输出channel:6,5x5卷积核
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)

        # 全连接层
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 使用2x2窗口进行最大池化
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 如果窗口是方的,只需要指定一个维度
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)

        x = x.view(-1, self.num_flat_features(x))

        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)

        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # 获取除了batch维度之外的其他维度
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

net = Net()
print(net)

以上就是一个简单的神经网络的构建方法。我们首先定义了一个Net类,这个类继承自nn.Module。然后在__init__方法中定义了网络的结构,在forward方法中定义了数据的流向。在网络的构建过程中,我们可以使用任何tensor操作。

需要注意的是,backward函数(用于计算梯度)会被autograd自动创建和实现。你只需要在nn.Module的子类中定义forward函数。

在创建好神经网络后,我们可以使用net.parameters()方法来返回网络的可学习参数。

刚开始肯定很难一次性看懂代码,来个详细的解释

这段代码是一个使用 PyTorch 构建的经典卷积神经网络 (CNN),常用于图像分类任务(比如手写数字识别 MNIST)。

这段代码定义了一个名为 Net 的卷积神经网络 (CNN),它包含两个卷积层和三个全连接层,用于处理图像分类任务(很可能是识别 10 类的手写数字)。

网络通过卷积操作提取图像特征,并通过全连接层进行分类。

🔍 逐部分解析

  1. 引入必要的工具包
import torch
import torch.nn as nn
import torch.nn.functional as F
  • torch: PyTorch 的核心。
  • torch.nn (通常导入为 nn): 包含构建神经网络需要的模块和类,比如各种网络层(卷积层、全连接层)。
  • torch.nn.functional (通常导入为 F): 包含许多函数形式的操作,比如激活函数 (ReLU)、池化 (pooling) 等。它们通常不包含需要学习的参数,按需使用。
  1. 定义网络模型 (class Net(nn.Module))

这是代码的核心部分。在 PyTorch 中,我们通过定义一个继承自 nn.Module 的类来创建自定义神经网络。

__init__ 方法:定义网络结构组件

def __init__(self):
    super(Net, self).__init__()  # 调用父类的初始化方法
    
    # 卷积层
    self.conv1 = nn.Conv2d(1, 6, 5)   # 第一个卷积层
    self.conv2 = nn.Conv2d(6, 16, 5)  # 第二个卷积层
    
    # 全连接层
    self.fc1 = nn.Linear(16 * 5 * 5, 120) # 第一个全连接层
    self.fc2 = nn.Linear(120, 84)         # 第二个全连接层
    self.fc3 = nn.Linear(84, 10)          # 第三个全连接层(输出层)

在 __init__ 中,我们定义网络需要的各种,它们通常是包含可学习参数的模块:

  • nn.Conv2d (二维卷积层): 用于从图像中提取特征。
    • nn.Conv2d(1, 6, 5): 创建一个卷积层,其输入通道数为 1(例如黑白图像),输出通道数为 6(即使用 6 个不同的卷积核来提取 6 种特征),卷积核大小为 5x5。
    • nn.Conv2d(6, 16, 5): 输入通道数为 6(接收上一层的 6 个特征图),输出通道数为 16(提取 16 种更高级的特征),卷积核大小仍为 5x5。
  • nn.Linear (全连接层): 用于最终的分类决策。
    • nn.Linear(16 * 5 * 5, 120): 输入特征数为 16 * 5 * 5(这是经过前面卷积和池化后特征图展平的大小),输出特征数为 120。
    • nn.Linear(120, 84): 输入 120 维特征,输出 84 维特征。
    • nn.Linear(84, 10): 输入 84 维特征,输出 10 维。这 10 个输出值通常对应 10 个类别的分数(如手写数字 0-9),后续再通过 softmax 函数转换为概率。

forward 方法:定义数据流动路径

def forward(self, x):
    # 第一组:卷积 -> ReLU激活 -> 池化
    x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
    # 第二组:卷积 -> ReLU激活 -> 池化
    x = F.max_pool2d(F.relu(self.conv2(x)), 2)
    
    # 将多维特征图“展平”成一维向量,为全连接层准备输入
    x = x.view(-1, self.num_flat_features(x))
    
    # 全连接层部分,中间使用ReLU激活函数
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)  # 输出层通常不立即接激活函数,计算损失时会处理
    
    return x

forward 方法定义了数据从输入到输出的完整传播路径(前向传播):

  1. 卷积和池化:
    • F.relu(self.conv1(x)): 数据先通过第一卷积层 conv1,然后经过 ReLU 激活函数(引入非线性,使网络能学习更复杂的模式)。
    • F.max_pool2d(..., (2, 2)): 接着进行 2x2 的最大池化操作。池化用于降低数据维度,减少计算量,同时保留主要特征,并增强模型对小位移的鲁棒性。
    • 第二组卷积、ReLU 和池化过程类似。
  2. 展平操作 (x.view): 卷积层输出的特征是二维的特征图(多个通道),而全连接层需要一维的输入向量。x.view(-1, self.num_flat_features(x)) 将 batch 中每个样本的特征图展平成一个一维向量。-1 表示让 PyTorch 自动计算 batch size,self.num_flat_features(x) 计算出一个样本的所有特征数量(这里是 16 * 5 * 5 = 400)。
  3. 全连接层和分类:
    • 数据依次通过三个全连接层 fc1fc2fc3
    • 在 fc1 和 fc2 之后使用了 ReLU 激活函数。
    • fc3 作为输出层,其输出是未经过 softmax 处理的原始分数(logits)。这是因为在训练时,损失函数(如交叉熵损失)内部通常会集成 softmax 计算并更数值稳定。

num_flat_features 方法:计算展平后的特征数

def num_flat_features(self, x):
    size = x.size()[1:]  # 获取除了batch维度之外的其他维度
    num_features = 1
    for s in size:
        num_features *= s
    return num_features

这个辅助方法用于计算一个张量(除开 batch 维度外)总共包含多少个特征元素。 例如,如果某个层的输出张量尺寸是 [batch_size, 16, 5, 5](代表一个 batch 的数据,每个数据有 16 个通道,每个通道是 5x5 的特征图),那么该方法计算 16 * 5 * 5 = 400,并返回 400。这个值用于告诉 view 函数需要将每个样本展平成多长的一维向量。

  1. 实例化网络与打印
net = Net() # 创建Net类的实例,即我们的神经网络模型
print(net)   # 打印网络的结构信息
  • net = Net(): 创建我们定义好的神经网络的一个实例(对象)。此时,PyTorch 会自动管理网络中的所有可学习参数(如卷积核的权重、全连接层的权重和偏置)。
  • print(net): 打印网络结构,可以帮助我们确认层的设置是否正确,以及参数数量等信息。

📊 网络结构概览

下表总结了该网络各层的功能与数据变化:

层序层类型与操作关键参数与说明输出尺寸 (示例)
输入-单通道图像 (如 MNIST)[batch, 1, 32, 32]
1卷积层 Conv1 + ReLU + MaxPool2din_ch=1, out_ch=6, kernel=5
pool_size=2x2
[batch, 6, 14, 14]
2卷积层 Conv2 + ReLU + MaxPool2din_ch=6, out_ch=16, kernel=5
pool_size=2x2
[batch, 16, 5, 5]
3展平 (Flatten)view(-1, 400)[batch, 400]
4全连接层 FC1 + ReLUin=400, out=120[batch, 120]
5全连接层 FC2 + ReLUin=120, out=84[batch, 84]
6全连接层 FC3 (输出层)in=84, out=10[batch, 10]

💡 关键要点与学习提示

  1. nn.Module 是基石: 在 PyTorch 中构建任何网络,都要继承 nn.Module,并在 __init__ 中定义层,在 forward 中定义数据流。
  2. 卷积层用于特征提取: 理解 nn.Conv2d(in_channels, out_channels, kernel_size) 的参数意义。
  3. 激活函数引入非线性: 如 F.relu,这是神经网络能够拟合复杂函数的关键。
  4. 池化层用于降维和抗扰动: 如 F.max_pool2d
  5. 全连接层用于分类: 注意卷积层到全连接层之间需要用 view 或 flatten 操作将多维特征图转换成一维向量。
  6. 输出层: 通常输出 raw logits(未归一化的分数),配合 nn.CrossEntropyLoss 等损失函数使用。
  7. 这只是模型定义: 这段代码只定义了模型的结构。要使用这个模型,你还需要:
    • 定义损失函数 (如 nn.CrossEntropyLoss)
    • 定义优化器 (如 torch.optim.SGD(net.parameters(), lr=0.01))
    • 准备数据加载器 (DataLoader)
    • 编写训练循环 (前向传播 -> 计算损失 -> 反向传播 loss.backward() -> 优化器更新参数 optimizer.step())
    • 模型评估

我这个小笨笨对这个 batch的概念也比较好奇

Batch(批次)是深度学习中的一个重要概念。简单来说,Batch 指的是模型训练时,每次迭代(iteration)中同时输入到模型中进行处理的一组样本数据。这组样本的数量就称为 Batch Size(批次大小)

为了更直观地理解训练过程中 Epoch、Batch 和 Batch Size 的关系,以及不同 Batch Size 的影响,可以参考下面的图示和表格:

flowchart TD
    A[整个训练数据集] --> B[划分为多个 Batch]
    B --> C[遍历所有 Batch<br>完成一个 Epoch]
    C --> D{是否达到<br>预定 Epoch 数?}
    D -- 是 --> F[训练结束]
    D -- 否 --> B

    subgraph B_details[一个 Batch 的处理]
        direction LR
        G[单个 Batch] --> H[模型计算<br>(前向传播、反向传播)]
        H --> I[参数更新]
    end
Batch Size 大小训练速度内存消耗梯度稳定性泛化能力常见场景
 (e.g., 16, 32)较慢较低较低(震荡)较好资源有限、追求泛化
 (e.g., 64, 128)中等中等中等中等常用起点、平衡选择
 (e.g., 256, 512)较快较高较高(平滑)较差大数据集、稳定梯度

🧠 几个相关概念

理解 Batch 时,通常会同时提到几个相关概念:

  • Epoch(时期/轮次):使用整个训练数据集完整地通过模型训练一次的过程称为一个 Epoch。一个 Epoch 通常包含多个 Batch。
  • Iteration(迭代):完成一个 Batch 的训练和参数更新,称为一次 Iteration。例如,若训练集有 8000 个样本,Batch Size 设为 200,则完成一个 Epoch 需要 8000/200 = 40 次 Iteration。

⚖️ 如何选择 Batch Size

选择 Batch Size 时,需要在内存消耗训练速度梯度稳定性模型泛化能力之间进行权衡。

  • 尝试常见的 2 的幂次方:如 16, 32, 64, 128, 256 等。这是因为内存操作和硬件优化时,2 的幂次方通常效率更高。
  • 从大到小尝试:可以先将 Batch Size 设置得大一些(如 64 或 128),观察训练损失(Loss)的收敛情况。如果收敛不佳(如波动很大),再尝试调小 Batch Size
  • 关注硬件限制:确保选择的 Batch Size 不会导致 GPU 或 CPU 内存溢出(Out Of Memory, OOM)。

💎 记住要点

  • Batch Size 是重要的超参数:需要根据具体任务、数据集和硬件条件进行调整和实验。
  • 没有绝对最优值:不同的模型和数据集,其最优的 Batch Size 可能不同。
  • 与其他超参数联动:调整 Batch Size 时,有时也需要联动调整学习率(Learning Rate) 等其他超参数。

3.2 数据加载和处理

在深度学习项目中,除了模型设计之外,数据的加载和处理也是非常重要的一部分。PyTorch提供了torch.utils.data.DataLoader类,可以帮助我们方便地进行数据的加载和处理。

3.2.1 DataLoader介绍

DataLoader类提供了对数据集的并行加载,可以有效地加载大量数据,并提供了多种数据采样方式。常用的参数有:

  • dataset:加载的数据集(Dataset对象)
  • batch_size:batch大小
  • shuffle:是否每个epoch时都打乱数据
  • num_workers:使用多进程加载的进程数,0表示不使用多进程 以下是一个简单的使用示例:
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# 数据转换
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 下载并加载训练集
trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

# 下载并加载测试集
testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

来来继续读代码

这段代码是使用PyTorch准备CIFAR-10数据集并进行数据加载的常见做法。

📦 PyTorch 数据加载代码解析

简单来说,这段代码就像是为模型训练准备一个自动化的“食材处理流水线”,它负责:

  1. 下载数据(就像获取原始食材)
  2. 转换数据(就像清洗、切割、腌制食材)
  3. 分批加载数据(就像将处理好的食材按份装盘,方便厨师取用)

下面是这段代码的逐行解释:

🔧 1. 导入必要的工具包

from torch.utils.data import DataLoader
from torchvision import datasets, transforms
  • DataLoader: PyTorch 中用于批量加载数据的核心工具,它支持自动批处理、数据打乱、多进程加载等功能 。
  • datasets: 提供常用数据集(如 CIFAR-10、MNIST)的快速下载和加载方式 。
  • transforms: 提供了一系列数据预处理和数据增强的方法 。

🌀 2. 定义数据转换(预处理)流程

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

这里定义了一个数据预处理流水线,使用 transforms.Compose 将多个步骤组合在一起 :https://wxa.wxs.qq.com/tmpl/oc/base_tmpl.html

  • transforms.ToTensor(): 将 PIL 图像或 NumPy 数组转换为 PyTorch 张量(Tensor)。同时会将图像数据从原始的 [0, 255] 范围或 [0, 1] 的 PIL 图像格式转换为 [0.0, 1.0] 的浮点数范围,并调整维度顺序为 PyTorch 预期的 (C, H, W)(通道,高,宽)格式 。
  • transforms.Normalize(mean, std): 对张量进行标准化(归一化),这里使用的是均值 mean=(0.5, 0.5, 0.5) 和标准差 std=(0.5, 0.5, 0.5)。计算公式为:output = (input - mean) / std 。
    • 因为原始数据经过 ToTensor 后范围是 [0.0, 1.0],经过这里的归一化后((0.0 - 0.5)/0.5 = -1.0(1.0 - 0.5)/0.5 = 1.0),每个通道的数据范围就变成了 [-1.0, 1.0]。归一化可以使模型训练更稳定、收敛更快。

📥 3. 下载并加载训练集

trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
  • 这行代码使用 torchvision.datasets.CIFAR10 来加载 CIFAR-10 数据集 。
    • 飞机(airplane)汽车(automobile)鸟(bird)猫(cat)鹿(deer)
    • 狗(dog)青蛙(frog)马(horse)船(ship)卡车(truck)
    • CIFAR-10 数据集包含 10 个类别的 6 万张 32x32 彩色图像:
    • 其中 5 万张用于训练,1 万张用于测试 。
  • 参数说明 :
    • root='./data': 指定数据集下载和存储的根目录。
    • train=True: 指定加载的是训练集(如果为 False 则加载测试集)。
    • download=True: 如果指定路径下没有数据,则自动从互联网下载。
    • transform=transform: 将之前定义的数据预处理流水线应用于加载的每一张图像。

🚚 4. 创建训练集的数据加载器 (DataLoader)

trainloader = DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
  • 这行代码创建了一个 DataLoader 对象,它负责从数据集中按批次抽取数据,非常适合在训练循环中迭代使用 。
  • 参数说明 :
    • dataset=trainset: 指定要加载的数据集。
    • batch_size=4: 指定每个批次包含的样本数量。这里设置为 4,意味着每次迭代会返回 4 张图像和对应的 4 个标签。批量处理可以提高内存利用率和计算效率 。
    • shuffle=True: 表示在每个 epoch(训练轮次)开始时打乱数据顺序 。这非常重要,可以防止模型学习到数据出现的顺序偏差,有助于提高模型的泛化能力 。
    • num_workers=2: 指定使用多个子进程来并行加载数据 。这可以显著加速数据加载过程,避免数据加载成为训练速度的瓶颈。通常设置为 CPU 核心数的 2-4 倍 。

📊 5. 下载并加载测试集

testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
  • 这两行代码的作用与加载训练集类似,但有一些关键区别:
    • train=False: 指定加载的是测试集(验证集)。
    • shuffle=False: 测试时通常不需要打乱数据顺序 。

为了更直观地理解数据在整个流程中的变化,可以参考下面的图示:

💡 关键要点与使用场景

  • DataLoader 的优势 :
    • 批处理 (Batching): 提高内存利用率和计算效率(GPU 擅长并行计算)。
    • 打乱 (Shuffling): 避免模型学习到无关的顺序特征,提升泛化能力。
    • 并行加载 (Multiprocessing): 使用 num_workers 加速数据加载,避免 I/O 瓶颈。
  • 训练集与测试集的不同处理:
    • 训练集通常需要 shuffle=True,而测试集则设为 False
    • 训练和测试通常使用相同的 transform,以确保数据分布一致。但有时测试会使用不同的变换(例如,不包含数据增强)。
  • 后续使用:
    • 在训练循环中,你会这样使用它:for epoch in range(num_epochs): # 遍历所有数据多轮
          for images, labels in trainloader: # 迭代批次
              # 1. 将 images 输入模型,得到预测输出
              # 2. 计算损失 (loss)
              # 3. 反向传播,更新模型参数
              ...
    • 在评估模型时,则使用 testloader

数据加载是模型训练的第一步,也是非常重要的一步。PyTorch 的 DataLoader 和 torchvision.datasets 让这个过程变得非常简洁和高效。

3.2.2 自定义数据集

除了使用内置的数据集,我们也可以自定义数据集。自定义数据集需要继承Dataset类,并实现__len__和__getitem__两个方法。

以下是一个自定义数据集的简单示例:

from torch.utils.data import Dataset, DataLoader

class MyDataset(Dataset):
    def __init__(self, x_tensor, y_tensor):
        self.x = x_tensor
        self.y = y_tensor

    def __getitem__(self, index):
        return (self.x[index], self.y[index])

    def __len__(self):
        return len(self.x)

x = torch.arange(10)
y = torch.arange(10) + 1

my_dataset = MyDataset(x, y)
loader = DataLoader(my_dataset, batch_size=4, shuffle=True, num_workers=0)

for x, y in loader:
    print("x:", x, "y:", y)

这个例子中,我们创建了一个简单的数据集,包含10个数据。然后我们使用DataLoader加载数据,并设置了batch大小和shuffle参数。

以上就是PyTorch中数据加载和处理的主要方法,通过这些方法,我们可以方便地对数据进行加载和处理。

继续读代码

这段代码展示了如何在 PyTorch 中自定义数据集 (Dataset) 并使用 数据加载器 (DataLoader) 进行批量加载。

📦 PyTorch 自定义数据集与数据加载器解析

这段代码的核心是教你如何将自己的数据包装成 PyTorch 能够识别和批量处理的格式。整个过程可以概括为以下几个步骤:

🔍 代码逐行解读

  1. 导入必要的模块
from torch.utils.data import Dataset, DataLoader
  • Dataset: PyTorch 中所有数据集的抽象基类。你要创建自己的数据集,就必须继承这个类并实现其特定方法 。
  • DataLoader: 一个包装器 (Wrapper),它接收一个 Dataset 对象作为输入,并提供批量加载数据打乱多进程读取等功能,非常便于在训练循环中迭代使用 。
  1. 定义自定义数据集类 MyDataset
class MyDataset(Dataset):

这里定义了一个名为 MyDataset 的类,它继承自 torch.utils.data.Dataset。继承 Dataset 类后,必须实现 __init____getitem__ 和 __len__ 这三个方法 。

__init__ 方法:初始化

    def __init__(self, x_tensor, y_tensor):
        self.x = x_tensor
        self.y = y_tensor
  • 这是类的构造函数,在创建数据集对象时自动调用。
  • 它接收两个参数 x_tensor 和 y_tensor(通常是特征数据和标签数据),并将它们存储为类的属性(self.x 和 self.y),以便类中的其他方法可以使用 。

__getitem__ 方法:按索引获取样本

    def __getitem__(self, index):
        return (self.x[index], self.y[index])
  • 这是 Dataset 类的核心方法之一。当你使用 dataset[index] 这样的索引操作时,Python 会自动调用这个方法。
  • 它根据传入的 index(索引),从之前存储的 self.x 和 self.y 中取出对应的元素,并返回一个元组 (特征, 标签)这正好是模型训练时最需要的数据形式 。

__len__ 方法:获取数据集大小

    def __len__(self):
        return len(self.x)
  • 这是另一个核心方法。当你使用 Python 的内置函数 len(dataset) 时,会自动调用这个方法。
  • 它返回数据集中的总样本数,通常是特征数据 self.x 的长度 。
  1. 创建示例数据
x = torch.arange(10)      # 创建一个值为 [0, 1, 2, ..., 9] 的张量
y = torch.arange(10) + 1  # 创建一个值为 [1, 2, 3, ..., 10] 的张量
  • 这里创建了两个简单的 PyTorch 张量作为示例数据。x 可以看作是特征,y 是对应的标签(这里只是简单演示,实际数据可能是图像、文本等)。
  • x[i] 和 y[i] 是一一对应的。
  1. 实例化自定义数据集
my_dataset = MyDataset(x, y)
  • 使用我们定义好的 MyDataset 类以及数据 x 和 y,创建一个数据集对象 my_dataset
  • 现在,my_dataset 就具备了 PyTorch Dataset 的所有行为:
    • 你可以用 len(my_dataset) 获取它的长度(返回 10)。
    • 你可以用 my_dataset[i] 获取第 i 个样本(返回 (x[i], y[i]))。
  1. 创建数据加载器 (DataLoader)
loader = DataLoader(my_dataset, batch_size=4, shuffle=True, num_workers=0)
  • 这是关键一步。我们创建了一个 DataLoader 对象,它将负责从 my_dataset 中按批次抽取数据 。
  • 参数解析 :
    • 0 表示只在主进程中加载数据(默认值)。
    • 如果设置为大于 0 的数(如 2、4),则会开启多个子进程来并行加载数据,这通常可以显著加速数据加载过程,避免数据加载成为训练速度的瓶颈。但在某些环境(如 Windows 下的 Jupyter Notebook)下,多进程设置有时会出错,所以示例中先用 0。
    • dataset=my_dataset: 指定要加载的数据集对象。
    • batch_size=4批次大小。这是最重要的参数之一,它告诉 DataLoader 每次迭代返回 4 个样本(而不是一个一个地返回)。批量处理能极大提高训练效率。
    • shuffle=True是否打乱数据。在训练模型时,通常要设置为 True,这样每个 epoch(遍历所有数据的一次循环)开始时,数据顺序都会被随机打乱。这有助于防止模型学习到数据顺序的偏差,提升泛化能力。对于测试集或验证集,通常设置为 False
    • num_workers=0用于数据加载的子进程数 。
  1. 遍历数据加载器
for x_batch, y_batch in loader:
    print("x:", x_batch, "y:", y_batch)
  • 这里演示了如何在训练循环中使用 DataLoader直接迭代 loader 对象即可
  • 在每次循环中:
    • x_batch 是一个包含 4 个特征 的张量(因为 batch_size=4)。
    • y_batch 是对应的 4 个标签 的张量。
  • 由于设置了 shuffle=True每次运行代码时,打印出的批次顺序和批次内的样本组合很可能都不一样。例如,一次运行可能输出:x: tensor([0, 1, 2, 3]) y: tensor([1, 2, 3, 4])
    x: tensor([4, 5, 6, 7]) y: tensor([5, 6, 7, 8])
    x: tensor([8, 9]) y: tensor([9, 10]) # 最后一个批次不足4个样本
    另一次运行可能输出:x: tensor([5, 7, 3, 9]) y: tensor([6, 8, 4, 10])
    x: tensor([4, 0, 1, 8]) y: tensor([5, 1, 2, 9])
    x: tensor([6, 2]) y: tensor([7, 3])

💡 关键要点与注意事项

  1. Dataset 是“菜”,DataLoader 是“打饭的师傅”
    • Dataset 负责定义数据如何存储以及如何通过索引获取单个样本 (x_i, y_i)
    • DataLoader 负责从 Dataset 中取数据,并按你设定的规则(批量、打乱、并行)组成批次(x_batch, y_batch) 送给你。
  2. shuffle=True 的重要性:在训练阶段务必设置 shuffle=True,防止模型过拟合于数据的顺序。在验证/测试阶段则应设置为 False,以确保结果的可重复性和正确评估。
  3. num_workers 的设定:在本地环境调试成功后,可以尝试增加 num_workers(如设置为 2、4)来加速数据加载。但要注意,并非 workers 越多越好,一般设置为 CPU 核心数或其两倍左右即可。
  4. 处理最后一个不完整的批次:当数据集大小不能被 batch_size 整除时,最后一个批次会小于设定的批次大小。如果你希望丢弃最后一个不完整的批次(确保每个批次大小严格一致),可以在 DataLoader 中设置参数 drop_last=True 。
  5. 真实世界的应用:这个例子中的数据是简单的张量。在实际项目中,你的 __getitem__ 方法里可能会包含:
    • 从文件中加载图像或音频。
    • 复杂的数据预处理和数据增强(如随机裁剪、翻转等)。
    • 文本的分词和编码。

3.3 模型的保存和加载

在深度学习模型的训练过程中,我们经常需要保存模型的参数以便于将来重新加载。这对于中断的训练过程的恢复,或者用于模型的分享和部署都是非常有用的。

PyTorch提供了简单的API来保存和加载模型。最常见的方法是使用torch.save来保存模型的参数,然后通过torch.load来加载模型的参数。

3.3.1 保存和加载模型参数

以下是一个简单的示例:

# 保存
torch.save(model.state_dict(), PATH)

# 加载
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()

在保存模型参数时,我们通常使用.state_dict()方法来获取模型的参数。.state_dict()是一个从参数名字映射到参数值的字典对象。

在加载模型参数时,我们首先需要实例化一个和原模型结构相同的模型,然后使用.load_state_dict()方法加载参数。

请注意,load_state_dict()函数接受一个字典对象,而不是保存对象的路径。这意味着在你传入load_state_dict()函数之前,你必须反序列化你的保存的state_dict。

在加载模型后,我们通常调用.eval()方法将dropout和batch normalization层设置为评估模式。否则,它们会在评估模式下保持训练模式。

3.3.2 保存和加载整个模型

除了保存模型的参数,我们也可以保存整个模型。

# 保存
torch.save(model, PATH)

# 加载
model = torch.load(PATH)
model.eval()

保存整个模型会将模型的结构和参数一起保存。这意味着在加载模型时,我们不再需要手动创建模型实例。但是,这种方式需要更多的磁盘空间,并且可能在某些情况下导致代码的混乱,所以并不总是推荐的。

以上就是PyTorch中模型的保存和加载的基本方法。适当的保存和加载模型可以帮助我们更好地进行模型的训练和评估。

这里写的蛮详细的,但还是展开看看


💾 PyTorch 模型保存与加载解析

简单来说,这段代码完成了两件事:

  1. 保存:将训练好的模型“参数”(学到的知识)打包存到一个文件里。
  2. 加载:从文件中读取“参数”,并塞回一个结构完全相同的“空模型”中,让它恢复“智慧”。

下面是逐行解释:

💡 核心概念:state_dict

在深入代码之前,理解 state_dict 很重要。你可以把它想象成模型的**“身份证”或“检查点”**,它是一个 Python 字典对象,记录了模型所有可学习参数(如权重和偏置)的当前状态。例如:

  • conv1.weight
  • conv2.bias
  • fc1.weight 等等。

只保存 state_dict 而不是整个模型,使得保存的文件更小,也更灵活,是 PyTorch 官方推荐的方式。

📝 代码逐行解读

  1. 保存模型参数 (torch.save)
torch.save(model.state_dict(), PATH)
  • model.state_dict(): 获取模型当前的参数状态字典(就是上面说的“身份证”)。
  • torch.save(..., PATH): 使用 PyTorch 的 save 函数将这个字典保存到指定的文件路径 PATH(通常文件扩展名为 .pth 或 .pt)。

这就好比你把训练好的模型的所有“知识要点”(参数)记在了一个笔记本上,然后把笔记本存进了保险箱。

  1. 加载模型参数
model = TheModelClass(*args, **kwargs)  # 1. 创建一个空模型
model.load_state_dict(torch.load(PATH)) # 2. 加载参数
model.eval()                           # 3. 设置为评估模式

加载过程分为三步:

  1. 重建模型结构model = TheModelClass(*args, **kwargs)
    • 你必须先创建一个与之前保存参数时完全相同的模型结构TheModelClass)。*args, **kwargs 是创建这个模型时需要的参数(例如输入特征数、层数等)。
    • 这一步至关重要! 你相当于按照原来的“图纸”(类定义),重新造了一个空的“大脑壳”。如果结构对不上,参数是装不进去的。
  2. 加载参数model.load_state_dict(torch.load(PATH))
    • torch.load(PATH): 从指定路径 PATH 的文件中加载之前保存的参数字典。
    • model.load_state_dict(...): 将加载出来的参数加载到你刚刚创建的空模型里。
    • 这就像你从保险箱里拿出笔记本,然后把“知识要点”一字不差地写进这个新的大脑壳里。
  3. 切换为评估模式model.eval()
    • 这是一个非常重要的步骤,尤其是在你准备用模型进行推理(预测、评估) 而不是继续训练的时候。
    • model.eval() 会将模型设置为评估模式。在这个模式下,那些在训练和预测时行为不同的层(如 Dropout 和 Batch Normalization)会被调整到预测状态(例如,Dropout 会失效,BatchNorm 会使用训练阶段统计好的全局均值和方差,而不是当前批次的统计量)。
    • 忘记这一步可能会导致模型输出不一致和不可预测的结果。

⚠️ 重要注意事项

  1. 模型结构必须匹配:加载参数时,你必须能够重新实例化一个结构完全相同的模型 (TheModelClass)。这意味着你需要能够访问到该模型类的原始定义。
  2. 设备问题(CPU/GPU)
    • 如果模型是在 GPU 上训练的并保存,但你想在 CPU 上加载运行,加载时需要指定 map_locationmodel.load_state_dict(torch.load(PATH, map_location=torch.device('cpu')))
    • 反之,如果是在 CPU 上训练保存,想加载到 GPU 上:model.load_state_dict(torch.load(PATH, map_location=torch.device('cuda')))
      # 然后再将模型放到GPU上
      model.to('cuda')
  3. eval() 模式与 train() 模式
    • 进行推理时,务必使用 model.eval()
    • 如果加载模型是为了继续训练,那么在加载参数后,应该调用 model.train() 来确保模型处于训练模式(Dropout 和 BatchNorm 会恢复其训练行为),并且还需要加载优化器的状态等。

🆚 另一种方法:保存整个模型

你可能会看到另一种方式:torch.save(model, PATH) 和 model = torch.load(PATH)。这种方法会将整个模型对象(包括结构和参数)一起保存

  • 优点:加载时不需要事先知道模型结构,直接加载即可。
  • 缺点
    • 保存的文件更大。
    • 灵活性差,序列化依赖于具体的类定义,甚至 PyTorch 版本,更容易出现兼容性问题。
    • 一般不推荐在生产环境中使用。

因此,提供的这种只保存和加载 state_dict 的方法是更优、更推荐的做法。

4. PyTorch GPT加速

掌握了PyTorch的基础和高级用法之后,我们现在要探讨一些PyTorch的进阶技巧,帮助我们更好地理解和使用这个强大的深度学习框架。

4.1 使用GPU加速

PyTorch支持使用GPU进行计算,这可以大大提高训练和推理的速度。使用GPU进行计算的核心就是将Tensor和模型转移到GPU上。

4.1.1 判断是否支持GPU

首先,我们需要判断当前的环境是否支持GPU。这可以通过torch.cuda.is_available()来实现:

print(torch.cuda.is_available())  # 输出:True 或 False

4.1.2 Tensor在CPU和GPU之间转移

如果支持GPU,我们可以使用.to(device)或.cuda()方法将Tensor转移到GPU上。同样,我们也可以使用.cpu()方法将Tensor转移到CPU上:

# 判断是否支持CUDA
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 创建一个Tensor
x = torch.rand(3, 3)

# 将Tensor转移到GPU上
x_gpu = x.to(device)

# 或者
x_gpu = x.cuda()

# 将Tensor转移到CPU上
x_cpu = x_gpu.cpu()

4.1.3 将模型转移到GPU上

类似的,我们也可以将模型转移到GPU上:

model = Model()
model.to(device)

当模型在GPU上时,我们需要确保输入的Tensor也在GPU上,否则会报错。

注意,将模型转移到GPU上后,模型的所有参数和缓冲区都会转移到GPU上。

以上就是使用GPU进行计算的基本方法。通过合理的使用GPU,我们可以大大提高模型的训练和推理速度。

4.2 使用torchvision进行图像操作

torchvision是一个独立于PyTorch的包,提供了大量的图像数据集,图像处理工具和预训练模型等。

4.2.1 torchvision.datasets

torchvision.datasets模块提供了各种公共数据集,如CIFAR10、MNIST、ImageNet等,我们可以非常方便地下载和使用这些数据集。例如,下面的代码展示了如何下载和加载CIFAR10数据集:

from torchvision import datasets, transforms

# 数据转换
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 下载并加载训练集
trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

# 下载并加载测试集
testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

瞅瞅代码

这段代码是使用PyTorch加载和预处理CIFAR-10数据集的常见做法。

📦 PyTorch CIFAR-10 数据加载与预处理解析

简单来说,这段代码就像是为模型训练准备一个自动化的“食材处理流水线”,它负责:

  1. 下载数据(就像获取原始食材)
  2. 转换数据(就像清洗、切割、腌制食材)
  3. 分批加载数据(就像将处理好的食材按份装盘,方便厨师取用)

下面是这段代码的逐行解释:

🔧 1. 导入必要的工具包

from torchvision import datasets, transforms
  • torchvision: PyTorch 的一个官方库,专门用于计算机视觉任务。
  • datasets: 提供常用数据集(如 CIFAR-10、MNIST)的快速下载和加载方式。
  • transforms: 提供了一系列数据预处理和数据增强的方法。

🌀 2. 定义数据转换(预处理)流程

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

这里定义了一个数据预处理流水线,使用 transforms.Compose 将多个步骤组合在一起:

  • transforms.ToTensor()将 PIL 图像或 NumPy 数组转换为 PyTorch 张量 (Tensor)。这是非常重要的一步,因为 PyTorch 的模型只处理张量。同时,这个操作会自动将图像数据从原始的 [0, 255] 范围或 [0, 1] 的 PIL 图像格式转换为 [0.0, 1.0] 的浮点数范围,并调整维度顺序为 PyTorch 预期的 (C, H, W)(通道,高,宽)格式。
  • transforms.Normalize(mean, std)对张量进行标准化(归一化)。这里使用的是均值 mean=(0.5, 0.5, 0.5) 和标准差 std=(0.5, 0.5, 0.5)。计算公式为:output = (input - mean) / std
    • 因为原始数据经过 ToTensor 后范围是 [0.0, 1.0],经过这里的归一化后((0.0 - 0.5)/0.5 = -1.0(1.0 - 0.5)/0.5 = 1.0),每个通道的数据范围就变成了 [-1.0, 1.0]归一化可以使模型训练更稳定、收敛更快

📥 3. 下载并加载训练集

trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
  • 这行代码使用 torchvision.datasets.CIFAR10 来加载 CIFAR-10 数据集
    • CIFAR-10 数据集包含 10 个类别的 6 万张 32x32 彩色图像,每类 6000 张,分为 50,000 张训练集和 10,000 张测试集,类别包括飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船、卡车。
  • 参数说明
    • root='./data': 指定数据集下载和存储的根目录。
    • train=True: 指定加载的是训练集(如果为 False 则加载测试集)。
    • download=True: 如果指定路径下没有数据,则自动从互联网下载。
    • transform=transform: 将之前定义的数据预处理流水线应用于加载的每一张图像。

🚚 4. 创建训练集的数据加载器 (DataLoader)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
  • 这行代码创建了一个 DataLoader 对象,它负责从数据集中按批次抽取数据,非常适合在训练循环中迭代使用。
  • 参数说明
    • dataset=trainset: 指定要加载的数据集。
    • batch_size=4: 指定每个批次包含的样本数量。这里设置为 4,意味着每次迭代会返回 4 张图像和对应的 4 个标签。批量处理可以提高内存利用率和计算效率(GPU 擅长并行计算)。
    • shuffle=True: 表示在每个 epoch(训练轮次)开始时打乱数据顺序这非常重要,可以防止模型学习到数据出现的顺序偏差(例如前一半都是某一类,后一半是另一类),有助于提高模型的泛化能力。
    • num_workers=2: 指定使用多个子进程来并行加载数据。这可以显著加速数据加载过程,避免数据加载成为训练速度的瓶颈。通常设置为 CPU 核心数的 2-4 倍。

📊 5. 下载并加载测试集

testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
  • 这两行代码的作用与加载训练集类似,但有一些关键区别:
    • train=False: 指定加载的是测试集(验证集)。
    • shuffle=False测试时通常不需要打乱数据顺序。因为测试的目的是评估模型在固定数据上的性能,打乱顺序不会带来好处,反而可能使评估结果不稳定或难以解释。

为了更直观地理解数据在整个流程中的变化,可以参考下面的图示:

💡 关键要点与使用场景

  • DataLoader 的优势:
    • 批处理 (Batching): 提高内存利用率和计算效率(GPU 擅长并行计算)。
    • 打乱 (Shuffling): 避免模型学习到无关的顺序特征,提升泛化能力。
    • 并行加载 (Multiprocessing): 使用 num_workers 加速数据加载,避免 I/O 瓶颈。
  • 训练集与测试集的不同处理:
    • 训练集通常需要 shuffle=True,而测试集则设为 False
    • 训练和测试通常使用相同的 transform,以确保数据分布一致。但有时测试会使用不同的变换(例如,不包含数据增强)。
  • 归一化的作用: 归一化能够加速模型收敛,并提高训练稳定性,是深度学习预处理中非常关键的一步。
  • 后续使用: 在训练循环中,你会这样使用它:for epoch in range(num_epochs): # 遍历所有数据多轮
        for images, labels in trainloader: # 迭代批次
            # 1. 将 images 输入模型,得到预测输出
            # 2. 计算损失 (loss)
            # 3. 反向传播,更新模型参数
            ...
    在评估模型时,则使用 testloader

⚙️ 关键参数总结与常见设置

参数作用常见设置注意事项
batch_size指定每个批次包含的样本数16, 32, 64, 128, 256根据GPU内存调整,越大训练越快但可能泛化性稍差
shuffle是否在每个epoch开始时打乱数据顺序训练集: True
测试集: False
训练集打乱防止模型记忆顺序
num_workers用于数据加载的子进程数2, 4, 8 (通常为CPU核心数的2-4倍)设置太高可能耗尽内存,Windows下有时有问题
transform定义数据的预处理/增强流程包含ToTensorNormalize训练和测试集的变换通常一致

数据加载是模型训练的第一步,也是非常重要的一步。PyTorch 的 DataLoader 和 torchvision.datasets 让这个过程变得非常简洁和高效。

4.2.2 torchvision.transforms

torchvision.transforms模块提供了各种图像转换的工具,我们可以使用这些工具进行图像预处理和数据增强。

例如,上面的代码中,我们使用了Compose函数来组合了两个图像处理操作:ToTensor(将图像转换为Tensor)和Normalize(标准化图像)。

4.2.3 torchvision.models

torchvision.models模块提供了预训练的模型,如ResNet、VGG、AlexNet等。我们可以非常方便地加载这些模型,并使用这些模型进行迁移学习。

import torchvision.models as models

# 加载预训练的resnet18模型
resnet18 = models.resnet18(pretrained=True)

以上就是torchvision的基本使用,它为我们提供了非常丰富的工具,可以大大提升我们处理图像数据的效率。

再啰嗦一下!

torchvision.models 是 PyTorch 生态中一个非常强大的工具库。下面详细解释一下你提到的代码,并补充一些重要概念和常见用法。

🎯 Torchvision Models 与迁移学习解析

torchvision.models 模块是 PyTorch 针对计算机视觉任务提供的一个预训练模型宝库。它极大地简化了利用前沿模型进行开发和研究的过程。

  1. 加载预训练模型

你提供的代码是加载预训练 ResNet-18 模型的经典方式:

import torchvision.models as models
resnet18 = models.resnet18(pretrained=True) # 加载预训练的resnet18模型
  • models.resnet18(): 这会创建一个 ResNet-18 模型的结构
  • pretrained=True: 这是关键参数。当设置为 True 时,PyTorch 会自动下载在 ImageNet 数据集(一个包含1000个类别的大规模图像数据集)上预训练好的模型权重,并加载到该结构中。这样你就得到了一个已经具备强大图像特征提取能力的模型。

⚠️ 新旧版本差异

需要注意的是,在较新版本的 PyTorch Torchvision 中,官方推荐使用更显式的方式来指定权重,而不是 pretrained=True(该参数在一些版本中已被弃用)。推荐写法如下:

import torchvision.models as models
from torchvision.models import ResNet18_Weights # 显式导入权重

# 新版本推荐写法:使用 'weights' 参数
resnet18 = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
# 或者等价于
# resnet18 = models.resnet18(weights='IMAGENET1K_V1')

这种方式明确指定了所使用的权重版本,代码可读性和可维护性更好。

  1. 为什么使用预训练模型:迁移学习

你提到了“迁移学习”,这正是使用预训练模型的核心目的。其核心思想是利用在大规模数据集(如 ImageNet)上预训练的模型作为起点,将其知识(学习到的特征表示)迁移到新的、通常数据量较小的特定任务中。

🎯 迁移学习的优势

  • 节省资源和时间:无需从头训练(可能需数天甚至数周),只需微调,大大减少计算成本和时间。
  • 提升性能:预训练模型已学习到通用特征(如边缘、纹理、形状),即使在小数据集上微调,也常能获得比从零训练更好的性能,尤其适用于数据量有限的任务。
  • 降低过拟合风险:预训练权重提供了良好的起点和正则化效果,在小数据集上训练时更不容易过拟合。
  1. 迁移学习的常见策略

根据你的新任务和数据量,通常有两种微调策略:

策略操作适用场景
特征提取 (Feature Extraction)冻结预训练模型的所有层(设置 requires_grad=False),仅训练新增的或替换的分类层数据集很小,与预训练模型原始任务(如 ImageNet)相似度较高。
微调 (Fine-Tuning)解冻预训练模型的部分或全部层(设置 requires_grad=True),同时训练这些层和新分类层数据集较大,或新任务与原始任务差异较大。

策略选择示例


  1. 迁移学习实战步骤

以下是一个更完整的迁移学习示例,展示了如何修改模型并进行训练:

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.models as models
from torchvision.models import ResNet18_Weights

# 1. 加载预训练模型
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)

# 2. 冻结模型底层参数(根据策略选择)
#    特征提取时常用:冻结所有层,只训练全连接层
for param in model.parameters():
    param.requires_grad = False

# 3. 替换最后的全连接层(fc)以适应新任务
#    假设新任务有 10 个类别
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10) # 替换为输出10个神经元的新层

# 4. 定义损失函数和优化器
#    优化器通常只优化最后添加的层(如果前面被冻结了)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=0.001) # 只优化全连接层参数

# 5. 训练循环(此处为示意,需接入你的DataLoader)
# ... 在训练数据上循环,计算损失,反向传播,更新参数 ...
# for inputs, labels in your_dataloader:
#     optimizer.zero_grad()
#     outputs = model(inputs)
#     loss = criterion(outputs, labels)
#     loss.backward()
#     optimizer.step()
  1. Torchvision 提供的模型家族

torchvision.models 提供了丰富的预训练模型,远超 ResNet:

模型类别代表性模型特点
经典CNNAlexNet, VGG, GoogLeNet早期突破,结构相对简单
深度残差网络ResNet (18, 34, 50, 101, 152), Wide ResNet通过残差连接解决深度网络梯度消失问题
高效移动端模型MobileNetV2, MobileNetV3, MnasNet为移动和嵌入式设备设计,轻量且高效
现代架构EfficientNet, ConvNeXt, Vision Transformer (ViT)追求更好的精度-效率平衡,或引入Transformer结构
其他任务Faster R-CNN, Mask R-CNN, FCN, DeepLabV3用于目标检测、实例分割、语义分割等

你可以根据任务需求(精度要求、速度要求、计算资源等)选择合适的模型。例如,resnet18 是速度和精度的一个良好平衡点,非常适合入门和快速原型开发。

💎 重要提醒

  1. 输入预处理:使用预训练模型时,输入图像通常需要标准化(Normalize)。不同模型要求不同的均值和标准差(常见的是 mean=[0.485, 0.456, 0.406]std=[0.229, 0.224, 0.225])。务必使用与模型训练时相同的预处理,否则性能会下降。
  2. 模型模式:进行训练时,调用 model.train();进行推理/评估时,调用 model.eval()。这会影响某些层(如 Dropout 和 BatchNorm)的行为。
  3. 设备放置:使用 model.to(device)(如 model.to('cuda'))将模型移动到 GPU 上加速训练和推理。

torchvision.models 极大地降低了计算机视觉应用的门槛,是 PyTorch 生态中不可或缺的一部分。

4.3 使用TensorBoard进行可视化

TensorBoard 是一个可视化工具,它可以帮助我们更好地理解,优化,和调试深度学习模型。PyTorch 提供了对 TensorBoard 的支持,我们可以非常方便地使用 TensorBoard 来监控模型的训练过程,比较不同模型的性能,可视化模型结构,等等。

4.3.1 启动 TensorBoard

要启动 TensorBoard,我们需要在命令行中运行 tensorboard --logdir=runs 命令,其中 runs 是保存 TensorBoard 数据的目录。

4.3.2 记录数据

我们可以使用 torch.utils.tensorboard 模块来记录数据。首先,我们需要创建一个 SummaryWriter 对象,然后通过这个对象的方法来记录数据。

from torch.utils.tensorboard import SummaryWriter

# 创建一个 SummaryWriter 对象
writer = SummaryWriter('runs/experiment1')

# 使用 writer 来记录数据
for n_iter in range(100):
    writer.add_scalar('Loss/train', np.random.random(), n_iter)
    writer.add_scalar('Loss/test', np.random.random(), n_iter)
    writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
    writer.add_scalar('Accuracy/test', np.random.random(), n_iter)

# 关闭 writer
writer.close()

4.3.3 可视化模型结构

我们也可以使用 TensorBoard 来可视化模型结构。

# 添加模型
writer.add_graph(model, images)

4.3.4 可视化高维数据 我们还可以使用 TensorBoard 的嵌入功能来可视化高维数据,如图像特征、词嵌入等。

# 添加嵌入
writer.add_embedding(features, metadata=class_labels, label_img=images)

以上就是 TensorBoard 的基本使用方法。通过使用 TensorBoard,我们可以更好地理解和优化我们的模型。


继续白话


作为深度学习的重要工具,TensorBoard 能直观地监控和调试模型。

📊 使用 TensorBoard 可视化模型训练

TensorBoard 是一个功能强大的可视化工具包,它 originally 由 TensorFlow 团队开发,但现在已完美集成到 PyTorch 中。它允许你跟踪和可视化实验中的各种指标,如损失和准确率、检查模型结构、分析权重分布等,从而更好地理解、优化和调试你的深度学习模型。

  1. 安装与启动 TensorBoard

安装 在使用 TensorBoard 之前,你需要先安装它。通常,如果你已经安装了 PyTorch,tensorboard 可能已经作为依赖之一被安装了。如果没有,你可以使用 pip 单独安装:

pip install tensorboard

启动 TensorBoard TensorBoard 通过读取日志目录中的文件来工作。启动 TensorBoard 服务器后,你可以在浏览器中查看可视化界面。

  1. 命令行终端中,导航到你的项目目录。
  2. 运行以下命令,将 --logdir 参数指向你的日志文件所在的目录(例如 runs):tensorboard --logdir=runs
  3. 命令行会输出一个 URL(通常是 http://localhost:6006),在浏览器中打开它即可访问 TensorBoard 界面。

提示:为了更好的组织性,通常会将不同实验的日志保存在 runs 目录下的不同子文件夹中(例如 runs/experiment_1runs/experiment_2)。TensorBoard 会自动聚合所有子目录中的日志,你可以在界面顶部选择查看哪个或哪些实验的数据。

  1. 在 PyTorch 中记录数据:SummaryWriter

PyTorch 通过 torch.utils.tensorboard.SummaryWriter 类来向日志目录写入数据,这是与 TensorBoard 交互的核心。

初始化 SummaryWriter

from torch.utils.tensorboard import SummaryWriter

# 创建一个 SummaryWriter 对象,指定日志保存的目录。
# 如果目录不存在,会自动创建。通常建议使用具有描述性的目录名,以便区分不同实验。
writer = SummaryWriter('runs/experiment1')

记录标量数据(Scalars) 这是最常用的功能,用于跟踪随时间变化的指标,如损失和准确率。

for epoch in range(100):
    # ... 你的训练代码 ...
    # 假设在每个 epoch 结束后计算出了训练损失和测试准确率
    train_loss = ...
    test_accuracy = ...

    # 使用 add_scalar 记录标量值
    # 'Loss/train' 是标签名,TensorBoard 会根据斜杠进行分组显示
    # train_loss 是要记录的值
    # epoch 是全局步长(x轴的值)
    writer.add_scalar('Loss/train', train_loss, epoch)
    writer.add_scalar('Accuracy/test', test_accuracy, epoch)

在 TensorBoard 的 Scalars 标签页,你可以看到这些指标随时间变化的曲线,这对于监控训练进程和检测过拟合非常有用。

可视化模型结构(Graphs) 你可以将模型的计算图可视化,这有助于理解模型的层次结构和数据流向。

# 假设你已经定义了一个模型
model = YourModel()
# 创建一个示例输入(通常是一个与真实输入尺寸相同的虚拟张量)
dummy_input = torch.randn(1, 3, 32, 32)  # 示例:batch_size=1, 3通道, 32x32图像

# 将模型和示例输入添加到 TensorBoard
writer.add_graph(model, dummy_input)

在 TensorBoard 的 Graphs 标签页,你可以交互式地查看模型的层次结构。

记录图像数据(Images) 这对于可视化模型的输入、输出或中间特征图非常有用。

# 假设 'images' 是一个批次中的图像张量
# 使用 torchvision.utils.make_grid 可以创建一个图像的网格图
img_grid = torchvision.utils.make_grid(images)

# 将图像网格添加到 TensorBoard
writer.add_image('example_images', img_grid, epoch)  # 可以指定步长,如当前 epoch

在 TensorBoard 的 Images 标签页,你可以查看这些图像。

记录直方图(Histograms) 直方图可以显示张量值随时间的分布变化,常用于监控权重和梯度的分布,帮助诊断梯度消失或爆炸等问题。

# 定期记录模型参数的分布
if epoch % 10 == 0:  # 每10个epoch记录一次
    for name, param in model.named_parameters():
        writer.add_histogram(f'weights/{name}', param, epoch)
        # 还可以记录梯度
        # writer.add_histogram(f'grads/{name}', param.grad, epoch)

在 TensorBoard 的 Histograms 和 Distributions 标签页可以查看这些分布。

记录嵌入(Embeddings)add_embedding 方法可以帮助你将高维数据(如图像特征、词向量)投影到低维空间(如使用PCA、t-SNE),从而可视化数据点之间的关系和聚类情况。

# 假设 features 是从模型某一层提取的特征向量,metadata 是对应的标签(如类别)
# label_img 是可选的,是与特征对应的原始图像,用于在可视化中点开查看
writer.add_embedding(features,
                    metadata=class_labels,
                    label_img=images,
                    global_step=epoch)

在 TensorBoard 的 Projector 标签页可以交互式地探索这些嵌入。

关闭 SummaryWriter 完成后,务必关闭 SummaryWriter 以确保所有数据都已写入磁盘。

writer.close()
  1. 核心功能与使用场景总结

为了让 TensorBoard 的众多功能更一目了然,我为你总结了一个核心方法表格:

方法作用TensorBoard 标签页主要应用场景
add_scalar()记录标量指标SCALARS跟踪损失、准确率、学习率等数值随时间的变化。
add_graph()记录模型计算图GRAPHS可视化模型结构,理解数据流向和层间连接。
add_image() / add_images()记录图像数据IMAGES查看输入/输出图像、中间特征图、错误预测样本。
add_histogram()记录张量分布直方图HISTOGRAMS, DISTRIBUTIONS监控权重、梯度的分布,诊断梯度问题。
add_embedding()记录高维数据嵌入PROJECTOR可视化特征空间,观察数据聚类和分布。
add_hparams()记录超参数组合HPARAMS对比不同超参数设置对模型性能的影响。
  1. 最佳实践与技巧
  1. 有条理地组织实验:为不同的实验运行(例如,不同的超参数配置、不同的模型架构)使用不同的日志子目录(如 runs/exp_lr0.01runs/exp_lr0.001)。TensorBoard 可以同时显示和对比多个运行的数据。
  2. 控制记录频率:对于标量数据,可以每个 epoch 记录一次。对于直方图和图像,记录频率不宜过高(例如每几个 epoch 一次),因为它们会生成较大的日志文件。
  3. 使用有意义的标签名称:使用类似 'Loss/train' 和 'Accuracy/val' 的命名方式,TensorBoard 会自动根据斜杠进行分组,使界面更加清晰。
  4. 充分利用对比功能:在 TensorBoard 的 Scalars 面板中,你可以平滑曲线、调整比例,并并排比较多个实验的运行结果,这对于超参数调优至关重要。
  5. 定期清理旧日志:日志文件可能会占用大量磁盘空间,记得定期清理不再需要的旧日志文件。
  1. 简单示例:训练循环中的集成

这是一个更完整的在训练循环中使用 TensorBoard 的片段:

from torch.utils.tensorboard import SummaryWriter
import torchvision

writer = SummaryWriter('runs/my_experiment')

for epoch in range(num_epochs):
    # ... 训练一个 epoch ...
    train_loss = ...
    train_acc = ...
    val_loss = ...
    val_acc = ...

    # 记录标量
    writer.add_scalar('Loss/train', train_loss, epoch)
    writer.add_scalar('Accuracy/train', train_acc, epoch)
    writer.add_scalar('Loss/validation', val_loss, epoch)
    writer.add_scalar('Accuracy/validation', val_acc, epoch)

    # 记录一些权重直方图
    if epoch % 5 == 0:
        for name, param in model.named_parameters():
            writer.add_histogram(name, param, epoch)

    # 记录一批验证图像和预测(示例)
    if epoch == 0or epoch % 10 == 0:
        images, labels = next(iter(val_loader))
        with torch.no_grad():
            outputs = model(images)
            # ... 处理并记录图像 ...

writer.close()

通过使用 TensorBoard,可以将训练过程从“黑盒”转变为可观察、可分析的过程,极大地提升深度学习项目的开发效率。

5. PyTorch实战案例

在这一部分中,我们将通过一个实战案例来详细介绍如何使用PyTorch进行深度学习模型的开发。我们将使用CIFAR10数据集来训练一个卷积神经网络(Convolutional Neural Network,CNN)。

5.1 数据加载和预处理

首先,我们需要加载数据并进行预处理。我们将使用torchvision包来下载CIFAR10数据集,并使用transforms模块来对数据进行预处理。

import torch
from torchvision import datasets, transforms

# 定义数据预处理操作
transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),  # 数据增强:随机翻转图片
    transforms.RandomCrop(32, padding=4),  # 数据增强:随机裁剪图片
    transforms.ToTensor(),  # 将PIL.Image或者numpy.ndarray数据类型转化为torch.FloadTensor,并归一化到[0.0, 1.0]
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))  # 标准化(这里的均值和标准差是CIFAR10数据集的)
])

# 下载并加载训练数据集
trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)

# 下载并加载测试数据集
testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2)

在这段代码中,我们首先定义了一系列的数据预处理操作,然后使用datasets.CIFAR10来下载CIFAR10数据集并进行预处理,最后使用torch.utils.data.DataLoader来创建数据加载器,它可以帮助我们在训练过程中按照批次获取数据。

5.2 定义网络模型

接下来,我们定义我们的卷积神经网络模型。在这个案例中,我们将使用两个卷积层和两个全连接层。

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)  # 输入通道数3,输出通道数6,卷积核大小5
        self.pool = nn.MaxPool2d(2, 2)  # 最大池化,核大小2,步长2
        self.conv2 = nn.Conv2d(6, 16, 5)  # 输入通道数6,输出通道数16,卷积核大小5
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 全连接层,输入维度16*5*5,输出维度120
        self.fc2 = nn.Linear(120, 84)  # 全连接层,输入维度120,输出维度84
        self.fc3 = nn.Linear(84, 10)  # 全连接层,输入维度84,输出维度10(CIFAR10有10类)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))  # 第一层卷积+ReLU激活函数+池化
        x = self.pool(F.relu(self.conv2(x)))  # 第二层卷积+ReLU激活函数+池化
        x = x.view(-1, 16 * 5 * 5)  # 将特征图展平
        x = F.relu(self.fc1(x))  # 第一层全连接+ReLU激活函数
        x = F.relu(self.fc2(x))  # 第二层全连接+ReLU激活函数
        x = self.fc3(x)  # 第三层全连接
        return x

# 创建网络
net = Net()

在这个网络模型中,我们使用nn.Module来定义我们的网络模型,然后在__init__方法中定义网络的层,最后在forward方法中定义网络的前向传播过程。

5.3 定义损失函数和优化器

现在我们已经有了数据和模型,下一步我们需要定义损失函数和优化器。损失函数用于衡量模型的预测与真实标签的差距,优化器则用于优化模型的参数以减少损失。

在这个案例中,我们将使用交叉熵损失函数(Cross Entropy Loss)和随机梯度下降优化器(Stochastic Gradient Descent,SGD)。

import torch.optim as optim

# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 定义优化器
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

在这段代码中,我们首先使用nn.CrossEntropyLoss来定义损失函数,然后使用optim.SGD来定义优化器。我们需要将网络的参数传递给优化器,然后设置学习率和动量。

5.4 训练网络

一切准备就绪后,我们开始训练网络。在训练过程中,我们首先通过网络进行前向传播得到输出,然后计算输出与真实标签的损失,接着通过后向传播计算梯度,最后使用优化器更新模型参数。

for epoch in range(2):  # 在数据集上训练两遍

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入数据
        inputs, labels = data

        # 梯度清零
        optimizer.zero_grad()

        # 前向传播
        outputs = net(inputs)

        # 计算损失
        loss = criterion(outputs, labels)

        # 反向传播
        loss.backward()

        # 更新参数
        optimizer.step()

        # 打印统计信息
        running_loss += loss.item()
        if i % 2000 == 1999:  # 每2000个批次打印一次
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

在这段代码中,我们首先对数据集进行两轮训练。在每轮训练中,我们遍历数据加载器,获取一批数据,然后通过网络进行前向传播得到输出,计算损失,进行反向传播,最后更新参数。我们还在每2000个批次后打印一次损失信息,以便我们了解训练过程。

5.5 测试网络

训练完成后,我们需要在测试集上测试网络的性能。这可以让我们了解模型在未见过的数据上的表现如何,以评估其泛化能力。

# 加载一些测试图片
dataiter = iter(testloader)
images, labels = dataiter.next()

# 打印图片
imshow(torchvision.utils.make_grid(images))

# 显示真实的标签
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

# 让网络做出预测
outputs = net(images)

# 预测的标签是最大输出的标签
_, predicted = torch.max(outputs, 1)

# 显示预测的标签
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

# 在整个测试集上测试网络
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

在这段代码中,我们首先加载一些测试图片,并打印出真实的标签。然后我们让网络对这些图片做出预测,并打印出预测的标签。最后,我们在整个测试集上测试网络,并打印出网络在测试集上的准确率。

5.6 保存和加载模型

在训练完网络并且对其进行了测试后,我们可能希望保存训练好的模型,以便于将来使用,或者继续训练。

# 保存模型
torch.save(net.state_dict(), './cifar_net.pth')
在这段代码中,我们使用torch.save函数,将训练好的模型参数(通过net.state_dict()获得)保存到文件中。

当我们需要加载模型时,首先需要创建一个新的模型实例,然后使用load_state_dict方法将参数加载到模型中。

# 加载模型
net = Net()  # 创建新的网络实例
net.load_state_dict(torch.load('./cifar_net.pth'))  # 加载模型参数

需要注意的是,load_state_dict方法加载的是模型的参数,而不是模型本身。因此,在加载模型参数之前,你需要先创建一个模型实例,这个模型需要与保存的模型具有相同的结构。


这个是一个完整的例子,很不错!看完后我们再一起梳理一下脉络,毕竟 我是小白。重复!!

梳理使用PyTorch进行CIFAR-10图像分类的完整流程,并用流程图和表格来直观展示关键步骤和决策点。

🧠 PyTorch CIFAR-10图像分类实战全面解析

🔍 1. 项目概述与核心目标

CIFAR-10数据集包含10个类别60,000张32x32彩色图像,其中50,000张用于训练,10,000张用于测试。

这是一个经典的多类别图像分类任务,目标是训练一个模型,能够准确识别图像中的物体属于飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船或卡车中的哪一类。

📊 2. 完整工作流程

下图概括了使用PyTorch解决图像分类任务的核心步骤和关键决策点,帮助您从宏观上理解整个流程:

⚙️ 3. 数据准备与预处理详解

数据预处理是模型成功的基石,以下是关键步骤和代码实现:

3.1 数据转换与增强

import torch
import torchvision
import torchvision.transforms as transforms

# 定义训练和测试的数据预处理流程
transform_train = transforms.Compose([
    transforms.RandomHorizontalFlip(),  # 随机水平翻转
    transforms.RandomCrop(32, padding=4),  # 随机裁剪
    transforms.ToTensor(),  # 转换为Tensor并归一化到[0,1]
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))  # 标准化
])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])

# 加载数据集
trainset = torchvision.datasets.CIFAR10(
    root='./data', train=True, download=True, transform=transform_train
)
testset = torchvision.datasets.CIFAR10(
    root='./data', train=False, download=True, transform=transform_test
)

# 创建数据加载器
trainloader = torch.utils.data.DataLoader(
    trainset, batch_size=128, shuffle=True, num_workers=2
)
testloader = torch.utils.data.DataLoader(
    testset, batch_size=100, shuffle=False, num_workers=2
)

# 类别名称
classes = ('plane', 'car', 'bird', 'cat', 'deer', 
           'dog', 'frog', 'horse', 'ship', 'truck')

3.2 数据预处理的重要性

  • 数据增强:通过随机翻转、裁剪等操作增加数据多样性,提高模型泛化能力,防止过拟合。
  • 标准化:使输入数据均值为0,标准差为1,加速模型收敛,提高训练稳定性。
  • 批处理:使用DataLoader进行批处理,提高内存利用率和训练效率,充分利用GPU并行计算能力。

🧩 4. 模型构建策略与选择

在模型构建阶段,您有两种主要策略可选:

4.1 自定义CNN模型(适合学习理解)

import torch.nn as nn
import torch.nn.functional as F

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
        self.bn1 = nn.BatchNorm2d(32)
        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
        self.bn2 = nn.BatchNorm2d(64)
        self.pool = nn.MaxPool2d(2, 2)
        self.dropout = nn.Dropout(0.5)
        self.fc1 = nn.Linear(64 * 8 * 8, 512)
        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.bn1(self.conv1(x))))
        x = self.pool(F.relu(self.bn2(self.conv2(x))))
        x = x.view(-1, 64 * 8 * 8)
        x = self.dropout(x)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

4.2 使用预训练模型(适合实战应用)

import torchvision.models as models

# 加载预训练的ResNet18并调整最后一层
model = models.resnet18(pretrained=True)
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10)  # CIFAR-10有10个类别

4.3 模型选择建议

模型类型适用场景训练时间预期准确率优点缺点
自定义CNN学习、实验中等75-85%灵活可控,易于理解需要自行设计架构
预训练模型生产应用较长85-95%+性能优异,节省时间黑盒性,计算需求高

🎯 5. 训练配置与优化策略

5.1 损失函数与优化器选择

import torch.optim as optim

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)

# 学习率调度器
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

5.2 训练循环实现

def train_model(model, trainloader, testloader, criterion, optimizer, scheduler, num_epochs=50):
    device = torch.device("cuda"if torch.cuda.is_available() else"cpu")
    model.to(device)
    
    history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}
    
    for epoch in range(num_epochs):
        # 训练阶段
        model.train()
        running_loss, correct, total = 0.0, 0, 0
        
        for inputs, labels in trainloader:
            inputs, labels = inputs.to(device), labels.to(device)
            
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            
            running_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
        
        # 验证阶段
        model.eval()
        # ... 验证代码类似 ...
        
        # 学习率调整
        scheduler.step()
        
    return model, history

📈 6. 模型评估与性能分析

6.1 综合评估指标 训练完成后,需要从多个角度评估模型性能:

  • 测试准确率:模型在未见过的测试集上的整体分类准确率
  • 混淆矩阵:详细分析每个类别的分类情况,识别模型容易混淆的类别
  • 损失曲线:监控训练和验证损失,识别过拟合或欠拟合问题
  • 类别指标:计算精确率、召回率和F1分数,特别是对于不平衡数据集

6.2 性能优化策略 如果模型性能不理想,可以考虑以下优化方向:

问题现象可能原因解决方案
训练准确率高,测试准确率低过拟合增加Dropout比例、添加数据增强、使用权重衰减、早停法
训练和测试准确率都低欠拟合增加模型复杂度、延长训练时间、减少正则化
训练过程不稳定学习率不当调整学习率、使用学习率调度器
某些类别准确率低类别不平衡使用类别权重、数据重采样

💾 7. 模型保存与部署

7.1 保存训练好的模型

# 保存整个模型
torch.save(model, 'cifar10_model.pth')

# 只保存模型参数(推荐方式)
torch.save(model.state_dict(), 'cifar10_model_state_dict.pth')

7.2 加载模型进行预测

# 加载模型进行预测
model = CNN()  # 需要先创建模型实例
model.load_state_dict(torch.load('cifar10_model_state_dict.pth'))
model.eval()  # 设置为评估模式

# 进行预测
with torch.no_grad():
    outputs = model(inputs)
    _, predicted = torch.max(outputs, 1)

💡 8. 进阶技巧与最佳实践

  1. 使用TensorBoard可视化:监控训练过程,可视化损失和准确率曲线,分析模型性能
  2. 混合精度训练:使用torch.cuda.amp进行混合精度训练,减少显存占用,加快训练速度
  3. 模型量化:训练完成后对模型进行量化,减少模型大小,加快推理速度,便于部署
  4. 超参数优化:使用网格搜索、随机搜索或贝叶斯优化等方法寻找最佳超参数组合

6. 总结

这篇文章通过详细且实践性的方式介绍了 PyTorch 的使用,包括环境安装、基础知识、张量操作、自动求导机制、神经网络创建、数据处理、模型训练、测试以及模型的保存和加载。

我们利用 PyTorch 从头到尾完成了一个完整的神经网络训练流程,并在 CIFAR10 数据集上测试了网络的性能。在这个过程中,我们深入了解了 PyTorch 提供的各种功能和工具。

希望这篇文章能对你学习 PyTorch 提供帮助,对于想要更深入了解 PyTorch 的读者,我建议参考 PyTorch 的官方文档以及各种开源教程。实践是最好的学习方法,只有通过大量的练习和实践,才能真正掌握 PyTorch 和深度学习。

谢谢你的阅读,希望你在深度学习的道路上越走越远!



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2025-09-29