从零开始上手PyTorch:一段真实项目中的初探之旅

工程师的半亩地
2025-06-25 08:49
阅读 543

大家好,我是某AI创业公司的技术负责人。在最近的一次图像分类项目中,我带领团队尝试使用 PyTorch 来构建模型,并从中收获了不少经验和教训。今天想借这篇文章和大家分享一下这段真实的经历。

背景介绍

背景介绍

我们公司主要做的是工业质检方向的AI应用,具体来说就是通过视觉识别判断产品外观是否合格。去年年底,有一个客户希望我们能帮他开发一个自动筛选零部件是否符合标准的系统。

这个项目的输入是大量的零部件图像,输出是一个简单的“良品/不良品”二分类标签。数据集大小约3万张,其中大约20%是标注好的样本(包括部分噪声)。

技术选型

虽然 TensorFlow 一直是我们在内部系统的主力框架,但由于以下几点原因,我们决定用 PyTorch 来尝试:

  1. 团队中有几个新成员对 PyTorch 比较熟悉;
  2. 我们需要一些快速实验的能力,尤其在模型设计初期;
  3. 社区活跃度高,很多最新的论文代码都是基于 PyTorch 的实现;
  4. 部署端还在规划中,不急于确定最终框架。

于是,我带着两位刚加入的小伙伴开始了我们的 PyTorch 探索之旅。

遇到的问题和挑战

遇到的问题和挑战

第一个障碍:环境搭建 & 兼容性问题

说实话,刚开始的头三天,我们一直在折腾环境。一方面是 Python 包版本混乱,另一方面是因为某些 GPU 驱动版本不兼容导致训练时频繁中断。

小插曲:

有一次训练突然失败,查了半天发现是 cudnn 版本和 torch 不兼容。我们当时用的是 torch=1.7.1,后来换成了官方推荐的 1.8.0 后问题就解决了。所以建议大家安装前先看一遍官网文档,避免走弯路。

第二个问题:写法风格难以适应

作为一个习惯了 TensorFlow/Keras 写法的开发者,一开始接触 PyTorch 会觉得很别扭。特别是它那种“动态图”的写法让我有点困惑 —— 在调试的时候确实更方便,但写起来总觉得少了点约束感。

举个例子,在 Keras 中我们通常这样写模型:

model = Sequential()
model.add(Dense(64, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

而在 PyTorch 中却是这样的:

import torch.nn as nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(784, 64),
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

这种需要定义 forward() 方法的做法,在最开始真的让我有些不太习惯。

第三个难点:训练逻辑不透明

对于新手来说,PyTorch 更像是一个裸机工具箱。不像 Keras 提供了 .fit() 这样的一站式接口,你需要自己写训练循环。这在模型复杂、多阶段训练的时候很容易出错。

比如,我们在写 DataLoader 的时候一开始没处理好 shuffle 和 batch_size 的关系,结果训练效果一直上不去。

还有一个头疼的问题是梯度更新方式的选择。Adam、SGD with Momentum、RMSprop……每种优化器在不同学习率下的表现都不一样。我们也花了挺多时间调优这些参数。

解决过程与实践心得

解决过程与实践心得

数据预处理先行

我们一开始就犯了一个错误 —— 直接拿原始图片喂模型。结果训练出来准确率始终在 50% 上下徘徊。

后来我们才意识到:图像没有统一尺寸,也没有标准化!于是我们做了如下改进:

  • 使用 OpenCV 对所有图片 resize 到统一尺寸;
  • 归一化像素值(除以255);
  • 做了简单的增强,比如随机翻转、亮度变换;
  • 将数据集划分为 train / val / test 三个子集,比例为 7:2:1。

这部分我们用了 PyTorch 的 torchvision.transforms 模块来封装处理逻辑:

transform_train = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485], std=[0.229])
])

train_dataset = ImageFolder('data/train', transform=transform_train)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

模型选择与迁移学习

因为数据量不算太大,我们决定采用迁移学习策略。选择了预训练的 ResNet18 网络作为骨干网络,在最后加一个适配当前分类任务的全连接层。

具体代码如下:

import torchvision.models as models

model = models.resnet18(pretrained=True)
for param in model.parameters():
    param.requires_grad = False  # 冻结前面的层

# 替换最后一层
model.fc = nn.Linear(model.fc.in_features, 1)
model = model.to(device)

这样做的好处是训练更快,也能有效防止过拟合。

训练循环怎么写?

PyTorch 官方教程里给的例子比较简单,但在实际项目中往往需要更多的控制逻辑。这是我们写的一个简化版训练循环,供大家参考:

def train_model(model, dataloader, criterion, optimizer, device):
    model.train()
    running_loss = 0.0
    corrects = 0
    
    for inputs, labels in dataloader:
        inputs = inputs.to(device)
        labels = labels.float().to(device)

        outputs = model(inputs).squeeze()
        loss = criterion(outputs, labels)

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

        preds = (outputs > 0.5).float()
        corrects += torch.sum(preds == labels.data)

        running_loss += loss.item() * inputs.size(0)

    epoch_acc = corrects.double() / len(dataloader.dataset)
    epoch_loss = running_loss / len(dataloader.dataset)
    
    print(f'Train Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')

这里需要注意的一些细节:

  • labels.float() 是因为我们用的是 sigmoid 分类,loss 函数不能直接用整数;
  • preds 计算要用阈值 0.5 来二分;
  • 不要忘了 optimizer.zero_grad(),否则梯度会累加!

模型评估怎么做?

由于这是一个二分类任务,除了 accuracy 以外,我们还关注 precision、recall、F1 分数。为此我们专门写了评估函数:

from sklearn.metrics import classification_report

def evaluate_model(model, dataloader, device):
    model.eval()
    y_true = []
    y_pred = []

    with torch.no_grad():
        for inputs, labels in dataloader:
            inputs = inputs.to(device)
            outputs = model(inputs).squeeze()
            preds = (outputs > 0.5).cpu().numpy()
            y_true.extend(labels.numpy())
            y_pred.extend(preds)

    print(classification_report(y_true, y_pred))

这部分其实非常关键。我们之前只看 acc,结果忽略了 recall 很低的情况,后来调整了阈值并引入类别权重后才有了显著改善。

参数调优经验分享

调参真的是让人又爱又恨的部分,尤其是 learning rate 和 batch size 这两个核心参数。

我们最初的设置是 lr=0.001,batch_size=64。训练了一轮之后,acc 只有 70%,但 val loss 一直没下降。后来我们试了下面几种组合:

Learning Rate Batch Size Val Accuracy
0.001 64 70%
0.0005 64 82%
0.0001 64 85%
0.0001 128 87%

我们最终定在 lr=1e-4,batch_size=128,再配合 early stopping 和 ReduceLROnPlateau,效果提升了不少。

部署准备和 TorchScript 小试牛刀

项目后期我们尝试将模型导出成 TorchScript 格式用于部署:

model.eval()
script_model = torch.jit.script(model)
torch.jit.save(script_model, 'resnet18_jit.pth')

不过因为客户那边还没完全准备好部署环境,这部分我们暂时停留在测试阶段。后续我们会考虑整合 ONNX 导出路径,便于跨平台使用。

成果与收益

整个项目历时三周完成,最终模型在测试集上的准确率达到 88%+,比预期目标高出不少。客户也表示认可,已经开始推进正式集成。

更宝贵的是团队在过程中积累了不少实战经验:

  • 对 PyTorch 的 API 结构有了更深的理解;
  • 实现了一套可复用的数据加载和训练模板;
  • 建立起了基础的模型调参和评估流程;
  • 积累了多个实用代码片段(已提交 GitLab 存档);

给读者的一些建议

神经网络结构图-1

如果你也是刚刚开始学 PyTorch,或者正打算上手试试,我想跟你分享以下几个建议:

✅ 建议一:不要死记 API,重在理解机制

PyTorch 的模块很多,但它的核心思想是“一切皆 tensor”。只要你搞清楚 tensor 的操作和流向,就能理解大多数代码。

例如,你可以试着打印中间变量的 shape、dtype,帮助你理解数据流是如何流动的。

✅ 建议二:从经典项目入手

像 ImageNet、CIFAR 这些公开数据集非常适合入门,而且社区有大量的 demo 代码可以参考。建议先跑通一个完整的训练流程再谈深入。

✅ 建议三:合理利用文档 + Stack Overflow

PyTorch 的文档写得非常好,每个 API 都有示例说明。遇到问题第一时间去查文档 + GitHub Issues,很多坑别人已经踩过了。

✅ 建议四:重视可视化和日志记录

训练过程一定要保存 loss、acc 曲线,可以用 TensorBoard 或者 Visdom。这对后期分析模型行为至关重要。

✅ 建议五:学会调试训练中的常见问题

比如:

  • 准确率上不去 → 检查数据增强是否正确、标签是否一致;
  • loss 下降但不收敛 → 检查学习率、batch size 设置;
  • 显存爆了? → 适当减小 batch size 或者升级硬件;
  • GPU 没被调用? → 检查是否漏掉了 .to(device)

这些问题在实践中几乎都会碰到,早点了解不吃亏。

结语:从“好奇”到“信心满满”

回顾这次 PyTorch 上手过程,从一开始的手忙脚乱到现在能独立搭建起一个完整流程,我和团队都收获颇丰。

PyTorch 最大的魅力在于它的灵活性和易读性 —— 你可以像写普通 Python 那样调试你的神经网络结构。这对于研究型工作或者快速迭代的场景非常友好。

当然,它也不是万能的。如果你更看重生产级部署的稳定性,可能 TensorFlow 更合适。但在原型验证、算法创新等方面,PyTorch 确实是目前首选的框架之一。

如果你也正好在思考要不要开始学 PyTorch,我的建议只有一个字:“干”就完了。


如果觉得这篇文章对你有所帮助,欢迎点赞、收藏、转发。如果有任何问题或不同的看法,也欢迎在评论区留言交流。我们共同进步~

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝