【W&B 深度学习实验管理】与 PyTorch 的深度集成:轻松实现高效实验跟踪

本文使用到的 Jupyter Notebook 可在GitHub仓库002文件夹找到,别忘了给仓库点个小心心~~~
https://github.com/LFF8888/FF-Studio-Resources
在这里插入图片描述

在机器学习项目的开发过程中,实验跟踪和结果可视化是至关重要的环节。无论是调整超参数、优化模型架构,还是监控训练过程中的性能变化,清晰的记录和直观的可视化都能显著提升开发效率。然而,许多开发者在实际操作中往往忽视了这一点,导致实验结果难以复现,或者在项目协作中出现混乱。今天,我们将介绍如何利用 Weights & Biases 这一强大的工具,与 PyTorch 深度集成,轻松实现实验跟踪、数据版本控制和团队协作。通过本文,你将学会如何在自己的项目中快速添加这一功能,让每一次实验都清晰可溯,每一次优化都有据可依。

🔥 = W&B ➕ PyTorch

使用 Weights & Biases 进行机器学习实验跟踪、数据集版本控制和项目协作。

在这里插入图片描述

本笔记本涵盖的内容:

我们将向你展示如何将 Weights & Biases 与 PyTorch 代码集成,以便为你的管道添加实验跟踪功能。

最终生成的交互式 W&B 仪表盘将如下所示:

在这里插入图片描述

伪代码中,我们将执行以下操作:

# 导入库
import wandb

# 启动一个新实验
wandb.init(project="new-sota-model")

# 使用 config 捕获超参数字典
wandb.config = {"learning_rate": 0.001, "epochs": 100, "batch_size": 128}

# 设置模型和数据
model, dataloader = get_model(), get_data()

# 可选:跟踪梯度
wandb.watch(model)

for batch in dataloader:
  metrics = model.training_step()
  # 在训练循环中记录指标以可视化模型性能
  wandb.log(metrics)

# 可选:在最后保存模型
model.to_onnx()
wandb.save("model.onnx")

跟随 视频教程 一起学习!

注意:以 Step 开头的部分是你在现有管道中集成 W&B 所需的全部内容。其余部分只是加载数据并定义模型。

🚀 安装、导入和登录

0️⃣ 步骤 0:安装 W&B

首先,我们需要获取库。
wandb 可以通过 pip 轻松安装。

!pip install wandb onnx -Uq
import os
import random

import numpy as np
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from tqdm.auto import tqdm

# 确保确定性行为
torch.backends.cudnn.deterministic = True
random.seed(hash("setting random seeds") % 2**32 - 1)
np.random.seed(hash("improves reproducibility") % 2**32 - 1)
torch.manual_seed(hash("by removing stochasticity") % 2**32 - 1)
torch.cuda.manual_seed_all(hash("so runs are repeatable") % 2**32 - 1)

# 设备配置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 从 MNIST 镜像列表中移除慢速镜像
torchvision.datasets.MNIST.mirrors = [mirror for mirror in torchvision.datasets.MNIST.mirrors
                                      if not mirror.startswith("http://yann.lecun.com")]

1️⃣ 步骤 1:导入 W&B 并登录

为了将数据记录到我们的 Web 服务,你需要登录。
如果你是第一次使用 W&B,你需要在出现的链接中注册一个免费账户。

import wandb
wandb.login()

👩‍🔬 定义实验和管道

2️⃣ 步骤 2:使用 wandb.init 跟踪元数据和超参数

在编程中,我们首先定义实验:超参数是什么?与此运行相关的元数据是什么?
通常,我们会将这些信息存储在 config 字典(或类似对象)中,然后根据需要访问它。

在这个例子中,我们只让少数超参数变化,其余部分手动编码。但你的模型的任何部分都可以成为 config 的一部分!
我们还包括一些元数据:我们使用的是 MNIST 数据集和卷积架构。如果我们以后在同一项目中使用全连接架构处理 CIFAR,这将帮助我们区分运行。

config = dict(
    epochs=5,
    classes=10,
    kernels=[16, 32],
    batch_size=128,
    learning_rate=0.005,
    dataset="MNIST",
    architecture="CNN")

现在,让我们定义整个管道,这对于模型训练来说非常典型:

  1. 首先,我们 make 一个模型,以及相关的数据和优化器,然后
  2. 我们 train 模型,最后
  3. test 它,看看训练效果如何。
    我们将在下面实现这些函数。
def model_pipeline(hyperparameters):

    # 告诉 wandb 开始
    with wandb.init(project="pytorch-demo", config=hyperparameters):
      # 通过 wandb.config 访问所有超参数,确保日志与执行一致!
      config = wandb.config

      # 创建模型、数据和优化问题
      model, train_loader, test_loader, criterion, optimizer = make(config)
      print(model)

      # 使用它们训练模型
      train(model, train_loader, criterion, optimizer, config)

      # 测试其最终性能
      test(model, test_loader)

    return model

与标准管道的唯一区别是,所有操作都在 wandb.init 的上下文中进行。调用此函数会在你的代码和我们的服务器之间建立通信线路。
config 字典传递给 wandb.init 会立即将所有信息记录到我们这里,因此你始终知道为实验设置了哪些超参数值。
为了确保你选择并记录的值始终是模型中使用的值,我们建议使用 wandb.config 的副本。查看下面的 make 定义以了解一些示例。

注意:我们确保在不同的进程中运行代码,因此我们这边的任何问题(例如,巨型海怪攻击我们的数据中心)都不会导致你的代码崩溃。一旦问题解决(例如,海怪返回深海),你可以使用 wandb sync 记录数据。

def make(config):
    # 创建数据
    train, test = get_data(train=True), get_data(train=False)
    train_loader = make_loader(train, batch_size=config.batch_size)
    test_loader = make_loader(test, batch_size=config.batch_size)

    # 创建模型
    model = ConvNet(config.kernels, config.classes).to(device)

    # 创建损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(
        model.parameters(), lr=config.learning_rate)

    return model, train_loader, test_loader, criterion, optimizer

📡 定义数据加载和模型

现在,我们需要指定如何加载数据以及模型的外观。
这部分非常重要,但它与没有 wandb 时没有什么不同,因此我们不会过多讨论。

def get_data(slice=5, train=True):
    full_dataset = torchvision.datasets.MNIST(root=".",
                                              train=train,
                                              transform=transforms.ToTensor(),
                                              download=True)
    # 等同于使用 [::slice] 切片
    sub_dataset = torch.utils.data.Subset(
      full_dataset, indices=range(0, len(full_dataset), slice))

    return sub_dataset


def make_loader(dataset, batch_size):
    loader = torch.utils.data.DataLoader(dataset=dataset,
                                         batch_size=batch_size,
                                         shuffle=True,
                                         pin_memory=True, num_workers=2)
    return loader

定义模型通常是乐趣所在!
但使用 wandb 时没有任何变化,因此我们将坚持使用标准的 ConvNet 架构。
不要害怕尝试一些实验——你所有的结果都会记录在 wandb.ai 上!

# 传统卷积神经网络

class ConvNet(nn.Module):
    def __init__(self, kernels, classes=10):
        super(ConvNet, self).__init__()

        self.layer1 = nn.Sequential(
            nn.Conv2d(1, kernels[0], kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.layer2 = nn.Sequential(
            nn.Conv2d(16, kernels[1], kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.fc = nn.Linear(7 * 7 * kernels[-1], classes)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.reshape(out.size(0), -1)
        out = self.fc(out)
        return out

👟 定义训练逻辑

继续我们的 model_pipeline,现在是时候指定如何 train 了。
这里有两个 wandb 函数发挥作用:watchlog

3️⃣ 步骤 3. 使用 wandb.watch 跟踪梯度,使用 wandb.log 跟踪其他内容

wandb.watch 会记录模型的梯度和参数,每 log_freq 步训练一次。
你只需要在开始训练之前调用它。
其余的训练代码保持不变:我们遍历 epoch 和 batch,运行前向和后向传播,并应用我们的 optimizer

def train(model, loader, criterion, optimizer, config):
    # 告诉 wandb 监视模型的梯度、权重等!
    wandb.watch(model, criterion, log="all", log_freq=10)

    # 运行训练并使用 wandb 跟踪
    total_batches = len(loader) * config.epochs
    example_ct = 0  # 已看到的样本数
    batch_ct = 0
    for epoch in tqdm(range(config.epochs)):
        for _, (images, labels) in enumerate(loader):

            loss = train_batch(images, labels, model, optimizer, criterion)
            example_ct +=  len(images)
            batch_ct += 1

            # 每 25 个 batch 报告一次指标
            if ((batch_ct + 1) % 25) == 0:
                train_log(loss, example_ct, epoch)


def train_batch(images, labels, model, optimizer, criterion):
    images, labels = images.to(device), labels.to(device)

    # 前向传播 ➡
    outputs = model(images)
    loss = criterion(outputs, labels)

    # 后向传播 ⬅
    optimizer.zero_grad()
    loss.backward()

    # 使用优化器更新
    optimizer.step()

    return loss

唯一的区别在于日志代码:以前你可能通过打印到终端来报告指标,现在你将相同的信息传递给 wandb.log
wandb.log 期望一个以字符串为键的字典。这些字符串标识被记录的对象,它们构成了值。你还可以选择记录你处于训练的哪个 step

注意:我喜欢使用模型看到的样本数,因为这使得跨 batch 大小的比较更容易,但你可以使用原始步骤或 batch 计数。对于较长的训练运行,按 epoch 记录也是有意义的。

def train_log(loss, example_ct, epoch):
    # 魔法发生的地方
    wandb.log({"epoch": epoch, "loss": loss}, step=example_ct)
    print(f"Loss after {str(example_ct).zfill(5)} examples: {loss:.3f}")

🧪 定义测试逻辑

一旦模型完成训练,我们想要测试它:
可能是针对生产中的一些新数据运行它,或者将其应用于一些手工挑选的“困难样本”。

4️⃣ 可选步骤 4:调用 wandb.save

这也是将模型的架构和最终参数保存到磁盘的好时机。
为了最大兼容性,我们将以 Open Neural Network eXchange (ONNX) 格式 export 我们的模型。
将该文件名传递给 wandb.save 可确保模型参数保存到 W&B 的服务器:不再丢失哪个 .h5.pb 对应哪个训练运行!
有关存储、版本控制和分发模型的更高级 wandb 功能,请查看我们的 Artifacts 工具

def test(model, test_loader):
    model.eval()

    # 在测试样本上运行模型
    with torch.no_grad():
        correct, total = 0, 0
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

        print(f"Accuracy of the model on the {total} " +
              f"test images: {correct / total:%}")

        wandb.log({"test_accuracy": correct / total})

    # 以可交换的 ONNX 格式保存模型
    torch.onnx.export(model, images, "model.onnx")
    wandb.save("model.onnx")

🏃‍♀️ 运行训练并在 wandb.ai 上实时查看你的指标!

现在我们已经定义了整个管道并插入了那几行 W&B 代码,我们准备运行完全跟踪的实验。
我们会向你报告一些链接:
我们的文档、项目页面(组织项目中的所有运行)和运行页面(存储此运行的结果)。
导航到运行页面并查看这些选项卡:

  1. 图表,其中记录了模型梯度、参数值和训练期间的损失
  2. 系统,其中包含各种系统指标,包括磁盘 I/O 利用率、CPU 和 GPU 指标(观察温度飙升 🔥)等
  3. 日志,其中包含训练期间推送到标准输出的任何内容的副本
  4. 文件,在训练完成后,你可以点击 model.onnx 使用 Netron 模型查看器 查看我们的网络。

一旦运行完成(即退出 with wandb.init 块),我们还会在单元格输出中打印结果的摘要。

# 使用管道构建、训练和分析模型
model = model_pipeline(config)

🧹 使用 Sweeps 测试超参数

在这个例子中,我们只查看了一组超参数。
但大多数 ML 工作流程的一个重要部分是迭代多个超参数。
你可以使用 Weights & Biases Sweeps 自动化超参数测试,并探索可能的模型和优化策略的空间。

查看使用 W&B Sweeps 在 PyTorch 中进行超参数优化 → →

使用 Weights & Biases 运行超参数扫描非常简单。只需 3 个简单步骤:

  1. 定义扫描:我们通过创建字典或 YAML 文件 来指定要搜索的参数、搜索策略、优化指标等。
  2. 初始化扫描sweep_id = wandb.sweep(sweep_config)
  3. 运行扫描代理wandb.agent(sweep_id, function=train)

就这样!这就是运行超参数扫描的全部内容!

在这里插入图片描述

🖼️ 示例画廊

查看使用 W&B 跟踪和可视化的项目示例,请访问我们的 画廊 →

🤓 高级设置

  1. 环境变量:在环境变量中设置 API 密钥,以便你可以在托管集群上运行训练。
  2. 离线模式:使用 dryrun 模式离线训练并稍后同步结果。
  3. 本地部署:在私有云或隔离服务器中安装 W&B。我们为从学术界到企业团队的每个人提供本地安装。
  4. Sweeps:使用我们的轻量级工具快速设置超参数搜索。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值