找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2066

积分

0

好友

294

主题
发表于 2025-12-25 01:50:05 | 查看: 36| 回复: 0

在利用 PyTorch 构建和训练机器学习模型时,开发者常会遇到一些看似基础但至关重要的问题。本文将以经典的 MNIST 手写数字分类任务为背景,深入探讨其中三个典型问题的解决方法,涵盖代码结构、设备管理与性能评估指标。

核心问题解析与实现

1. 模块导入与脚本执行:if __name__ == '__main__': 的作用

在 Python 脚本中,if __name__ == '__main__': 是一个常见的代码守卫,它用于区分模块是被导入还是被直接运行。这个机制对于编写可复用的代码至关重要,尤其是在进行 Python 项目开发和软件测试时。

  • 模块重用:当你编写一个包含函数和类的 .py 文件(模块),并希望在其他脚本中导入它们时,你不希望导入操作触发模块中的训练或测试代码。if __name__ == ‘__main__‘: 内部的代码只在直接运行该文件时执行,确保了模块的纯净性。
  • 单元测试:进行自动化测试时,测试框架会导入你的模块。如果没有这个守卫,主程序代码(如训练循环)可能会意外执行,干扰测试流程。
  • 避免副作用:某些初始化或配置代码只在作为独立程序运行时才有意义,该守卫可以有效避免在导入时产生不必要的副作用(如下载数据、分配大量资源)。

在我们的 MNIST 训练脚本中,它将所有模型定义、数据加载、训练和测试逻辑包裹起来,确保这部分代码仅在直接运行此脚本时启动。

2. 设备无关的代码:张量如何转移到 CPU?

在 PyTorch 中,无论张量(Tensor)的形状如何(无论是标量、向量还是多维矩阵),将其在 CPU 和 GPU 之间转移的方法是统一的。使用 .to() 方法是实现设备转移的标准且安全的方式。

import torch

# 创建一个形状为 [3, 3] 的张量,可能位于 GPU 上
tensor_gpu = torch.randn(3, 3, device=‘cuda:0‘)

# 使用 .to() 方法将其转移到 CPU,此方法适用于任何形状的张量
tensor_cpu = tensor_gpu.to(‘cpu‘)

print(f‘转移后的设备: {tensor_cpu.device}‘)
# 输出: transfered device: cpu

在模型的测试阶段,我们计算出的准确率 acc 是一个 Python 浮点数(由 .item() 从标量张量转换而来),它本身不涉及设备问题。但如果需要处理非标量的中间张量结果,.to(‘cpu’) 是通用的解决方案。

3. 超越准确率:精确度、召回率与 F1 分数的实现

准确率(Accuracy)是分类任务中最直观的指标,但在类别不平衡的数据集中,它可能具有误导性。为了更全面地评估 人工智能 模型性能,我们通常需要结合精确度(Precision)、召回率(Recall)和 F1 分数(F1-Score)进行综合分析。

  • 精确度:在所有被模型预测为正类的样本中,真正为正类的比例。它衡量了模型预测结果的“精确性”。
  • 召回率:在所有真实为正类的样本中,被模型正确预测为正类的比例。它衡量了模型对正类的“识别覆盖率”。
  • F1 分数:精确度和召回率的调和平均数,旨在找到一个平衡点,在两者之间取得权衡。

我们可以使用 sklearn.metrics 库方便地计算这些指标:

from sklearn.metrics import precision_score, recall_score, f1_score

# 示例:真实标签和模型预测标签
true_labels = [1, 0, 1, 1, 0, 0]
predicted_labels = [1, 1, 1, 0, 0, 1]

# 计算指标, ‘weighted‘ 平均方式考虑了类别不平衡
precision = precision_score(true_labels, predicted_labels, average=‘weighted‘)
recall = recall_score(true_labels, predicted_labels, average=‘weighted‘)
f1 = f1_score(true_labels, predicted_labels, average=‘weighted‘)

print(f‘精确度 (Precision): {precision:.4f}‘)
print(f‘召回率 (Recall): {recall:.4f}‘)
print(f‘F1 分数 (F1-Score): {f1:.4f}‘)

要将这些指标集成到 PyTorch 的测试循环中,你需要在循环中累积所有批次的预测结果和真实标签,然后在循环结束后统一计算。

完整实战示例:MNIST 分类模型

以下代码整合了上述要点,构建了一个完整的训练和测试流程。

import torch
import matplotlib.pyplot as plt
from torch.optim import SGD
from torch import nn
from torchvision import datasets
from torchvision.transforms import ToTensor
from torch.utils.data import DataLoader

# 使用 if __name__ == '__main__' 守卫主执行逻辑
if __name__ == '__main__':
    # 设备配置
    device = ‘cuda‘ if torch.cuda.is_available() else ‘cpu‘
    print(f‘使用的设备: {device}‘)

    # 加载 MNIST 数据集
    train_ds = datasets.MNIST(
        root=‘data‘,
        train=True,
        download=True,
        transform=ToTensor(),
    )
    test_ds = datasets.MNIST(
        root=‘data‘,
        train=False,
        download=True,
        transform=ToTensor(),
    )

    batch_size = 256
    train_dataloader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)
    test_dataloader = DataLoader(test_ds, batch_size=batch_size, shuffle=False, num_workers=4)

    # 定义一个简单的全连接网络
    class MyNet(nn.Module):
        def __init__(self):
            super().__init__()
            self.fc1 = nn.Linear(28 * 28, 512)
            self.fc2 = nn.Linear(512, 10)

        def forward(self, x):
            x = torch.flatten(x, start_dim=1)  # 将图像展平为向量
            x = self.fc1(x)
            out = self.fc2(x)
            return out

    # 初始化模型、损失函数和优化器
    net = MyNet().to(device)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = SGD(net.parameters(), lr=1e-3)

    losses = []  # 记录损失

    # 训练函数
    def train(dataloader, model, loss_fn, optimizer):
        model.train()
        total_batches = len(dataloader)
        for batch_idx, (X, y) in enumerate(dataloader):
            X, y = X.to(device), y.to(device)
            pred = model(X)
            loss = loss_fn(pred, y)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            losses.append(loss.item())
            if batch_idx % 100 == 0:
                print(f‘[{batch_idx+1:>4d}/{total_batches}] 损失: {loss.item():.4f}‘)

    # 测试函数(目前仅计算准确率)
    def test(dataloader, model, loss_fn):
        model.eval()
        size = len(dataloader.dataset)
        correct = 0
        with torch.no_grad():
            for X, y in dataloader:
                X, y = X.to(device), y.to(device)
                pred = model(X)
                correct += (pred.argmax(1) == y).type(torch.float).sum().item()
        acc = correct / size
        print(f‘测试准确率: {acc:.4f}\n‘)

    # 执行训练和测试
    epochs = 5
    for epoch in range(epochs):
        print(f‘Epoch {epoch+1}/{epochs}‘)
        train(train_dataloader, net, loss_fn, optimizer)
        test(test_dataloader, net, loss_fn)

    # 可选:绘制训练损失曲线
    plt.plot(losses)
    plt.xlabel(‘迭代次数‘)
    plt.ylabel(‘损失‘)
    plt.title(‘训练损失变化‘)
    plt.show()

运行上述代码,你将在完成训练后看到类似以下的输出,并生成损失曲线图:

使用的设备: cuda
Epoch 1/5
[   1/235] 损失: 2.3025
[ 101/235] 损失: 1.9233
[ 201/235] 损失: 1.1660
测试准确率: 0.8243
...

总结

通过本次实践,我们系统地解决了 PyTorch 模型开发中的三个关键环节:首先,利用 if __name__ == '__main__': 构建了符合工程规范的脚本结构,确保了代码的可复用性与可测试性;其次,掌握了使用 .to() 方法实现设备无关的编码,使模型能灵活运行于不同硬件环境;最后,在准确率之外,探讨了精确度、召回率和 F1 分数等更细致的模型评估指标及其计算方法,为全面优化模型性能打下了坚实基础。将这些知识融会贯通,能显著提升机器学习项目的开发效率和模型质量。




上一篇:Python AOT编译器性能对比:Cython与Nuitka实战选型指南
下一篇:TrueNAS与U-NAS核心技术架构与适用场景深度对比
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 20:15 , Processed in 0.282514 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表