MLflow 中的 PyTorch

在本指南中,我们将引导您如何在 MLflow 中使用 PyTorch。我们将演示如何跟踪您的 PyTorch 实验并将您的 PyTorch 模型记录到 MLflow 中。

将 PyTorch 实验记录到 MLflow

自动记录 PyTorch 实验

与其他深度学习框架不同,MLflow 没有与 PyTorch 的自动记录集成,因为原生 PyTorch 需要编写自定义训练循环。如果你想在 PyTorch 中使用自动记录功能,请使用 Lightning 来训练你的模型。当使用 Lightning 时,你可以通过调用 mlflow.tensorflow.autolog()mlflow.autolog() 来开启自动记录。更多详情,请参阅 MLflow Lightning 开发者指南。

手动记录 PyTorch 实验

要记录你的 PyTorch 实验,你可以在 PyTorch 训练循环中插入 MLflow 日志记录,这依赖于以下 API:

将 PyTorch 日志记录到 MLflow 的最佳实践

虽然记录 PyTorch 实验与其他类型的手动日志记录相同,但我们建议您遵循一些最佳实践:

  • 在训练循环开始时,通过 mlflow.log_params() 记录你的模型和训练参数,例如学习率、批量大小等。mlflow.log_params()mlflow.log_param() 的批量记录版本,比后者更高效。

  • 在训练开始时通过 mlflow.log_artifact() 记录你的模型架构。你可以使用 torchinfo 包来获取模型摘要。

  • 在训练循环中通过 mlflow.log_metric() 记录训练和验证指标,例如分类任务中的损失和准确性。如果你在每个记录步骤有多个指标,可以使用 mlflow.log_metrics() 将它们一起记录。

  • 在训练结束时,通过 mlflow.pytorch.log_model() 将你训练/微调的模型记录到 MLflow 中。

  • [可选] 如果你希望保留中间训练状态,你也可以在训练过程中通过 mlflow.log_artifact() 将你的模型检查点记录到 MLflow。

以下是一个端到端的示例,展示了如何将您的 PyTorch 实验记录到 MLflow 中:

import mlflow
import torch

from torch import nn
from torch.utils.data import DataLoader
from torchinfo import summary
from torchmetrics import Accuracy
from torchvision import datasets
from torchvision.transforms import ToTensor

# Download training data from open datasets.
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
)

# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=64)

# Get cpu or gpu for training.
device = "cuda" if torch.cuda.is_available() else "cpu"


# Define the model.
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


def train(dataloader, model, loss_fn, metrics_fn, optimizer):
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        pred = model(X)
        loss = loss_fn(pred, y)
        accuracy = metrics_fn(pred, y)

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

        if batch % 100 == 0:
            loss, current = loss.item(), batch
            mlflow.log_metric("loss", f"{loss:3f}", step=(batch // 100))
            mlflow.log_metric("accuracy", f"{accuracy:3f}", step=(batch // 100))
            print(
                f"loss: {loss:3f} accuracy: {accuracy:3f} [{current} / {len(dataloader)}]"
            )


epochs = 3
loss_fn = nn.CrossEntropyLoss()
metric_fn = Accuracy(task="multiclass", num_classes=10).to(device)
model = NeuralNetwork().to(device)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

with mlflow.start_run():
    params = {
        "epochs": epochs,
        "learning_rate": 1e-3,
        "batch_size": 64,
        "loss_function": loss_fn.__class__.__name__,
        "metric_function": metric_fn.__class__.__name__,
        "optimizer": "SGD",
    }
    # Log training parameters.
    mlflow.log_params(params)

    # Log model summary.
    with open("model_summary.txt", "w") as f:
        f.write(str(summary(model)))
    mlflow.log_artifact("model_summary.txt")

    for t in range(epochs):
        print(f"Epoch {t+1}\n-------------------------------")
        train(train_dataloader, model, loss_fn, metric_fn, optimizer)

    # Save the trained model to MLflow.
    mlflow.pytorch.log_model(model, "model")

如果你运行上述代码并将日志记录到你的本地 MLflow 服务器(关于如何使用本地 MLflow 服务器,请阅读 跟踪服务器概述),你将在 MLflow UI 上看到类似于以下截图的结果:

使用 MLflow 的 PyTorch 基本示例

将您的 PyTorch 模型保存到 MLflow

如我们在上一节中所述,您可以通过 mlflow.pytorch.log_model() 将您的 PyTorch 模型保存到 MLflow。默认情况下,MLflow 会以 .pth 后缀保存您的模型。以下是保存和加载 PyTorch 模型的示例代码:

import mlflow
import numpy as np

from torch import nn


# Define model
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


model = NeuralNetwork()

with mlflow.start_run() as run:
    mlflow.pytorch.log_model(model, "model")

logged_model = f"runs:/{run.info.run_id}/model"
loaded_model = mlflow.pyfunc.load_model(logged_model)
loaded_model.predict(np.random.uniform(size=[1, 28, 28]).astype(np.float32))

您可以在 MLflow UI 上查看保存的文件,该文件将类似于以下内容:

基本的 PyTorch 保存

mlflow.pytorch.log_model()torch.jit.script() 兼容,如果你有一个jit编译的模型,MLflow将保存编译后的图。

模型签名

模型签名是对模型输入和输出的描述。加载模型时不需要模型签名,只要你知道输入格式,仍然可以加载模型并进行推理。然而,包含签名是一个好的做法,有助于更好地理解模型。要为PyTorch模型添加模型签名,你可以使用 mlflow.models.infer_signature() API 或手动设置签名。

mlflow.models.infer_signature() 接受你的输入数据和模型输出,以自动推断模型签名:

input = np.random.uniform(size=[1, 28, 28])
signature = mlflow.models.infer_signature(
    input,
    model(input).detach().numpy(),
)

注意

截至 MLflow 2.9.1,有一个注意事项,即输入和输出到 mlflow.models.infer_signature() 不能是 torch.Tensor,请在传递给 mlflow.models.infer_signature() 之前将其转换为 numpy.ndarray

你也可以手动设置签名:

import numpy as np
from mlflow.types import Schema, TensorSpec

input_schema = Schema([TensorSpec(np.dtype(np.float32), (-1, 28, 28))])
output_schema = Schema([TensorSpec(np.dtype(np.float32), (-1, 10))])
signature = ModelSignature(inputs=input_schema, outputs=output_schema)

设置签名后,您可以在调用 mlflow.pytorch.log_model() 时包含它:

import mlflow
import numpy as np

from torch import nn
from mlflow.types import Schema, TensorSpec
from mlflow.models import ModelSignature


# Define model
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


model = NeuralNetwork()
input_schema = Schema([TensorSpec(np.dtype(np.float32), (-1, 28, 28))])
output_schema = Schema([TensorSpec(np.dtype(np.float32), (-1, 10))])
signature = ModelSignature(inputs=input_schema, outputs=output_schema)

with mlflow.start_run() as run:
    mlflow.pytorch.log_model(model, "model", signature=signature)

logged_model = f"runs:/{run.info.run_id}/model"
loaded_model = mlflow.pyfunc.load_model(logged_model)
loaded_model.predict(np.random.uniform(size=[1, 28, 28]).astype(np.float32))

在你的 MLflow UI 中,你应该能够看到你的模型签名,如下图所示:

PyTorch 模型签名