暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PyTorch-24h 07_实验跟踪

一只大鸽子 2023-02-24
227

07. PyTorch Experiment Tracking

在之前,我们训练模型时通过打印变量来查看模型训练情况。如果您想一次运行十几个(或更多)不同的模型怎么办? 实验跟踪。

What is experiment tracking?

机器学习和深度学习是非常实验性的。往往需要通过很多实验来验证不同模型和算法的有效性。如果您正在运行大量不同的实验,实验跟踪可帮助您确定哪些有效,哪些无效

Why track experiments?

如果您只运行少数模型(就像我们到目前为止所做的那样),那么只需在打印输出和一些字典中跟踪它们的结果就可以了。但是,随着您运行的实验数量开始增加,这种幼稚的跟踪方式可能会失控。 

在构建了一些模型并跟踪其结果后,您会开始注意到它会以多快的速度失控。

实验跟踪的一些方法

MethodSetupProsConsCost
Python字典, CSV, print简单使用,直接在原始的Python中使用难以追踪大量的实验Free
TensorBoard安装 tensorboard
对PyTorch的扩展,广泛认可和使用,易于扩展。用户体验不如其他选项好。Free
Weights & Biases Experiment Tracking安装 wandb
, 注册
令人难以置信的用户体验,公开实验,跟踪几乎所有内容。需要 PyTorch 之外的外部资源。Free for personal use
MLFlow安装 mlflow
 并开始追踪
完全开源的 MLOps 生命周期管理,许多集成。设置远程跟踪服务器比其他服务更难。Free

您可以使用各种地点和技术来跟踪您的机器学习实验。 注意: 还有其他各种类似于权重和偏差的选项以及类似于 MLflow 的开源选项,但为简洁起见,我将它们排除在外。您可以通过搜索“机器学习实验跟踪”找到更多信息。

What we're going to cover

我们将使用不同级别的数据、模型大小和训练时间运行几个不同的建模实验,以尝试和改进 FoodVision Mini。由于TensorBoard
与 PyTorch 的紧密集成和广泛使用,我们使用 TensorBoard
 来跟踪实验。

TopicContents
0. Getting setup我们在过去的几节中编写了一些有用的代码,让我们下载它并确保我们可以再次使用它。
1. Get data让我们获取我们一直用来尝试改进 FoodVision Mini 模型结果的比萨饼、牛排和寿司图像分类数据集。
2. Create Datasets and DataLoaders使用05中写的 data_setup.py
 创建DataLoader
3. Get and customise a pretrained model类似06,下载 torchvision.models
 的预训练模型并做修改
4. Train model amd track results让我们看看使用 TensorBoard 训练和跟踪单个模型的训练结果是什么感觉。
5. View our model's results in TensorBoard之前我们使用辅助函数可视化模型的损失曲线,现在让我们看看它们在 TensorBoard 中的样子。
6. Creating a helper function to track experiments如果我们要坚持机器学习从业者的格言实验、实验、实验!,我们最好创建一个函数来帮助我们保存我们的建模实验结果。
7. Setting up a series of modelling experiments与其一个接一个地运行实验,不如我们编写一些代码来一次运行多个实验,使用不同的模型、不同的数据量和不同的训练时间。
8. View modelling experiments in TensorBoard在这个阶段,我们将一次性运行八个建模实验,需要跟踪一些,让我们看看它们在 TensorBoard 中的结果。
9. Load in the best model and make predictions with it实验跟踪的重点是找出哪个模型表现最好,让我们加载表现最好的模型并用它做出一些预测以可视化、可视化、可视化!
  1. 为了避免过长,本文略过了数据准备部分。

  2. 1. Getting setup (略)

  3. 2. Get data (略)

  4. 3. Create Datasets and DataLoaders(略)

  5. 4. Getting a pretrained model, freezing the base layers and changing the classifier head(略)

4. Train model and track results

让我们准备好通过创建损失函数和优化器来训练它。由于我们正在处理多个类,我们将使用 torch.nn.CrossEntropyLoss()
 作为loss。我们将坚持使用 torch.optim.Adam()
,优化器的学习率为 0.001

# Define loss and optimizer
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

Adjust train()
 function to track results with SummaryWriter()

以前,我们使用多个 Python 字典(每个模型一个)来跟踪我们的建模实验。但是你可以想象,如果我们只进行几个实验,这可能会失控。不用担心,还有更好的选择!我们可以使用 PyTorch 的 torch.utils.tensorboard.SummaryWriter()
 类将模型训练进度的各个部分保存到文件中。默认情况下,SummaryWriter()
 类将有关我们模型的各种信息保存到由 log_dir
 参数设置的文件中。log_dir
 的默认位置在 runs/CURRENT_DATETIME_HOSTNAME
 下,其中 HOSTNAME
 是您计算机的名称。您可以更改log_dir
保存信息到其它位置。 SummaryWriter()
 的输出以 TensorBoard 格式 保存。TensorBoard 是 TensorFlow 深度学习库的一部分,是可视化模型不同部分的绝佳方式。

为了开始跟踪我们的建模实验,让我们创建一个默认的SummaryWriter()
实例。

from torch.utils.tensorboard import SummaryWriter

# Create a writer with all default settings
writer = SummaryWriter()

现在要使用 writer,我们可以编写一个新的训练循环,或者调整我们在 05. PyTorch Going Modular 第 4 节

让我们采取后一种选择。我们将从 engine.py
 获取 train()
 函数并进行调整它使用writer
。具体来说,我们将为train()
函数添加记录模型的训练和测试损失和准确度值的能力。可以使用 writer.add_scalars(main_tag, tag_scalar_dict)
 来做到这一点,其中:

  • • main_tag
     (string) - 被跟踪的标量的名称(例如“Accuracy”)

  • • tag_scalar_dict
     (dict) - 被跟踪值的字典(例如{"train_loss": 0.3454}

  • 注意: 该方法被称为 add_scalars()
     因为我们的损失和准确率值通常是标量

一旦我们完成跟踪值,我们将调用 writer.close()
 来告诉 writer
 停止寻找要跟踪的值。

要开始修改 train()
,我们还将从 engine.py
 导入 train_step()
 和 test_step()
 。

注意: 您几乎可以在代码中的任何位置跟踪有关模型的信息。但是经常会在模型训练时(在训练/测试循环内)跟踪实验。

torch.utils.tensorboard.SummaryWriter()
 类也有许多不同的方法来跟踪关于你的模型/数据的不同事物,例如 add_graph()
跟踪模型的计算图。更多选项,查看 SummaryWriter()
 文档

from typing import DictList
from tqdm.auto import tqdm

from going_modular.going_modular.engine import train_step, test_step

# Import train() function from: 
# https://github.com/mrdbourke/pytorch-deep-learning/blob/main/going_modular/going_modular/engine.py
def train(model: torch.nn.Module, 
          train_dataloader: torch.utils.data.DataLoader, 
          test_dataloader: torch.utils.data.DataLoader, 
          optimizer: torch.optim.Optimizer,
          loss_fn: torch.nn.Module,
          epochs: int,
          device: torch.device) -> Dict[strList]:
    
    # Create empty results dictionary
    results = {"train_loss": [],
               "train_acc": [],
               "test_loss": [],
               "test_acc": []
    }

    # Loop through training and testing steps for a number of epochs
    for epoch in tqdm(range(epochs)):
        train_loss, train_acc = train_step(model=model,
                                           dataloader=train_dataloader,
                                           loss_fn=loss_fn,
                                           optimizer=optimizer,
                                           device=device)
        test_loss, test_acc = test_step(model=model,
                                        dataloader=test_dataloader,
                                        loss_fn=loss_fn,
                                        device=device)

        # Print out what's happening
        print(
          f"Epoch: {epoch+1} | "
          f"train_loss: {train_loss:.4f} | "
          f"train_acc: {train_acc:.4f} | "
          f"test_loss: {test_loss:.4f} | "
          f"test_acc: {test_acc:.4f}"
        )

        # Update results dictionary
        results["train_loss"].append(train_loss)
        results["train_acc"].append(train_acc)
        results["test_loss"].append(test_loss)
        results["test_acc"].append(test_acc)

        ### New: Experiment tracking ###
        # Add loss results to SummaryWriter
        writer.add_scalars(main_tag="Loss"
                           tag_scalar_dict={"train_loss": train_loss,
                                            "test_loss": test_loss},
                           global_step=epoch)

        # Add accuracy results to SummaryWriter
        writer.add_scalars(main_tag="Accuracy"
                           tag_scalar_dict={"train_acc": train_acc,
                                            "test_acc": test_acc}, 
                           global_step=epoch)
        
        # Track the PyTorch model architecture
        writer.add_graph(model=model, 
                         # Pass in an example input
                         input_to_model=torch.randn(323224224).to(device))
    
    # Close the writer
    writer.close()
    
    ### End new ###

    # Return the filled results at the end of the epochs
    return results

我们的 train()
 函数现在使用 SummaryWriter()
 实例来跟踪我们模型的结果。我们尝试 5 个 epoch 看看效果。

# Train model
# Note: Not using engine.train() since the original script isn't updated to use writer
set_seeds()
results = train(model=model,
                train_dataloader=train_dataloader,
                test_dataloader=test_dataloader,
                optimizer=optimizer,
                loss_fn=loss_fn,
                epochs=5,
                device=device)

运行上面的单元格,我们得到与 06. PyTorch 迁移学习第 4 部分:训练模型类似的结果,不同之处在于 writer
 实例创建了一个 runs/
 目录存储模型的结果。

例如,保存位置可能如下所示:

runs/Jun21_00-46-03_daniels_macbook_pro

默认格式runs/CURRENT_DATETIME_HOSTNAME

在使用tensorboard之前,我们在字典中跟踪模型:

# Check out the model results
results

{'train_loss': [1.09475726634264,
  0.9005270600318909,
  0.8115105107426643,
  0.6853345558047295,
  0.7091127447783947],
 'train_acc': [0.3984375, 0.64453125, 0.75, 0.73828125, 0.73828125],
 'test_loss': [0.9034053285916647,
  0.7874306440353394,
  0.6748601198196411,
  0.6704476873079935,
  0.6768251856168112],
 'test_acc': [0.6410984848484849,
  0.8560606060606061,
  0.8759469696969697,
  0.8352272727272728,
  0.8039772727272728]}

嗯,我们可以把它格式化成一个很好的图,但你能想象一下跟踪一堆这些字典吗?一定有更好的方法...

5. View our model's results in TensorBoard

SummaryWriter()
 类默认将模型的结果以 TensorBoard 格式存储在名为 runs/
 的目录中。TensorBoard 是由 TensorFlow 团队创建的可视化程序,用于查看和检查有关模型和数据的信息。您可以通过多种方式查看 TensorBoard:

Code environmentHow to view TensorBoardResource
VS Code (notebooks or Python scripts)Press SHIFT + CMD + P
 to open the Command Palette and search for the command "Python: Launch TensorBoard".
VS Code Guide on TensorBoard and PyTorch
Jupyter and Colab NotebooksMake sure TensorBoard is installed, load it with %load_ext tensorboard
 and then view your results with %tensorboard --logdir DIR_WITH_LOGS
.
torch.utils.tensorboard
 and Get started with TensorBoard

您还可以将您的实验上传到 tensorboard.dev 与他人公开分享。

在 Google Colab 或 Jupyter Notebook
 中运行以下代码将启动交互式 TensorBoard 会话以查看 runs/
 目录中的 TensorBoard 文件.

# Example code to run in Jupyter or Google Colab Notebook (uncomment to try it out)
%load_ext tensorboard
%tensorboard --logdir runs

如果一切正常,您应该会看到如下内容: 

在 TensorBoard 中查看单个建模实验的准确度和损失结果。

注意:有关在笔记本或其他位置运行 TensorBoard 的更多信息,请参阅以下内容:

  • • TensorFlow 在笔记本中使用 TensorBoard 指南

  • • 开始使用 TensorBoard.dev(有助于将 TensorBoard 日志上传到可共享链接)

查看服务器的tensorboard的方法

  1. 1. 首先在服务器上运行tensorboard tensorboard --logdir=your_log_dir --port=8888

  2. 2. SSH端口映射: ssh -L localhost:9999:localhost:8888 -p 端口号 服务器用户名@服务器ip地址
     相当于将服务器的8888端口映射到本地的9999端口,这样就可以用本地浏览器localhost:9999
    访问了。

6. Create a helper function to build SummaryWriter()
 instances

SummaryWriter()
 类将各种信息记录到由 log_dir
 参数指定的目录中。我们如何创建一个辅助函数来为每个实验创建一个自定义目录?

本质上,每个实验都有自己的日志目录。例如,假设我们要跟踪以下内容:

  • • 实验日期/时间戳 - 实验何时进行?

  • • 实验名称 - 有什么我们想为实验命名的东西吗?

  • • model名称 - 使用什么model?

  • • 额外 - 是否应该跟踪其他任何内容?

您可以在这里跟踪几乎所有内容,并随心所欲地发挥创意,但这些应该足以开始。让我们创建一个名为 create_writer()
 的辅助函数,它生成一个 SummaryWriter()
 实例跟踪到自定义 log_dir

理想情况下,我们希望 log_dir
 类似于: runs/YYYY-MM-DD/experiment_name/model_name/extra
 其中 YYYY-MM-DD
 是实验运行的日期(如果您愿意,也可以添加时间)。

def create_writer(experiment_name: str
                  model_name: str
                  extra: str=None) -> torch.utils.tensorboard.writer.SummaryWriter():
    """Creates a torch.utils.tensorboard.writer.SummaryWriter() instance saving to a specific log_dir.

    log_dir is a combination of runs/timestamp/experiment_name/model_name/extra.

    Where timestamp is the current date in YYYY-MM-DD format.

    Args:
        experiment_name (str): Name of experiment.
        model_name (str): Name of model.
        extra (str, optional): Anything extra to add to the directory. Defaults to None.

    Returns:
        torch.utils.tensorboard.writer.SummaryWriter(): Instance of a writer saving to log_dir.

    Example usage:
        # Create a writer saving to "runs/2022-06-04/data_10_percent/effnetb2/5_epochs/"
        writer = create_writer(experiment_name="data_10_percent",
                               model_name="effnetb2",
                               extra="5_epochs")
        # The above is the same as:
        writer = SummaryWriter(log_dir="runs/2022-06-04/data_10_percent/effnetb2/5_epochs/")
    """

    from datetime import datetime
    import os

    # Get timestamp of current date (all experiments on certain day live in same folder)
    timestamp = datetime.now().strftime("%Y-%m-%d"# returns current date in YYYY-MM-DD format

    if extra:
        # Create log directory path
        log_dir = os.path.join("runs", timestamp, experiment_name, model_name, extra)
    else:
        log_dir = os.path.join("runs", timestamp, experiment_name, model_name)
        
    print(f"[INFO] Created SummaryWriter, saving to: {log_dir}...")
    return SummaryWriter(log_dir=log_dir)

让我们试一试:

# Create an example writer
example_writer = create_writer(experiment_name="data_10_percent",
                               model_name="effnetb0",
                               extra="5_epochs")

看起来不错,现在我们有了记录和追溯各种实验的方法。

6.1 Update the train()
 function to include a writer
 parameter

我们的 create_writer()
 函数效果很好。如何让我们的 train()
 函数能够接收 writer
 参数,以便我们在每次调用 train()
 时主动更新我们正在使用的 SummaryWriter()
 实例呢?

例如,假设我们正在运行一系列实验,为多个不同的模型多次调用 train()
,如果每个实验使用不同的 writer
 会更方便区分。每个实验一个 writer
 意味着每个实验一个日志目录。为了调整 train()
 函数,我们将向函数添加 writer
 参数,然后我们将添加一些代码来查看是否有 writer
,如果有,我们将在那里跟踪我们的信息。

from typing import DictList
from tqdm.auto import tqdm

# Add writer parameter to train()
def train(model: torch.nn.Module, 
          train_dataloader: torch.utils.data.DataLoader, 
          test_dataloader: torch.utils.data.DataLoader, 
          optimizer: torch.optim.Optimizer,
          loss_fn: torch.nn.Module,
          epochs: int,
          device: torch.device, 
          writer: torch.utils.tensorboard.writer.SummaryWriter # new parameter to take in a writer
          ) -> Dict[strList]:
    # Create empty results dictionary
    results = {"train_loss": [],
               "train_acc": [],
               "test_loss": [],
               "test_acc": []
    }

    # Loop through training and testing steps for a number of epochs
    for epoch in tqdm(range(epochs)):
        train_loss, train_acc = train_step(model=model,
                                          dataloader=train_dataloader,
                                          loss_fn=loss_fn,
                                          optimizer=optimizer,
                                          device=device)
        test_loss, test_acc = test_step(model=model,
          dataloader=test_dataloader,
          loss_fn=loss_fn,
          device=device)

        # Print out what's happening
        print(
          f"Epoch: {epoch+1} | "
          f"train_loss: {train_loss:.4f} | "
          f"train_acc: {train_acc:.4f} | "
          f"test_loss: {test_loss:.4f} | "
          f"test_acc: {test_acc:.4f}"
        )

        # Update results dictionary
        results["train_loss"].append(train_loss)
        results["train_acc"].append(train_acc)
        results["test_loss"].append(test_loss)
        results["test_acc"].append(test_acc)


        ### New: Use the writer parameter to track experiments ###
        # See if there's a writer, if so, log to it
        if writer:
            # Add results to SummaryWriter
            writer.add_scalars(main_tag="Loss"
                               tag_scalar_dict={"train_loss": train_loss,
                                                "test_loss": test_loss},
                               global_step=epoch)
            writer.add_scalars(main_tag="Accuracy"
                               tag_scalar_dict={"train_acc": train_acc,
                                                "test_acc": test_acc}, 
                               global_step=epoch)

            # Close the writer
            writer.close()
        else:
            pass
    ### End new ###

    # Return the filled results at the end of the epochs
    return results

7. Setting up a series of modelling experiments

以前我们一直在进行各种实验,并一一检查结果。但是,如果我们可以运行多个实验,然后一起检查结果呢?

7.1 What kind of experiments should you run?

多数时候并不能确定哪些参数对实验重要。每个超参数都代表不同实验的起点:

  • • 更改 epochs 的数量。

  • • 更改层/隐藏单元的数量。

  • • 更改数据的数量。

  • • 更改学习率

  • • 尝试不同类型的数据增强

  • • 选择不同的模型架构

通过练习和运行许多不同的实验,您将开始建立对可能对您的模型有什么帮助的直觉。总的来说,鉴于 The Bitter Lesson(我现在已经提到过两次,因为它是 AI 世界中的一篇重要文章), 通常,您的模型越大(可学习的参数越多)和拥有的数据越多(学习机会越多),性能就越好。

但是,当您第一次处理机器学习问题时:从小处着手,如果可行,请扩大规模。您的第一批实验的运行时间不应超过几秒到几分钟。实验越快,你就能越快找出不起作用的东西,反过来,你就能越快找出起作用的东西。

7.2 What experiments are we going to run?

我们的目标是改进为 FoodVision Mini 提供动力的模型,但不会变得太大。从本质上讲,我们理想的模型实现了高水平的测试集准确度(90% 以上),但训练/执行推理(做出预测)不需要太长时间。

我们有很多选择,但我们如何保持简单?让我们尝试以下组合:

  1. 1. 不同数量的数据(披萨、牛排、寿司的 10% vs. 20%)

  2. 2. 不同的模型(torchvision.models.efficientnet_b0
    torchvision. models.efficientnet_b2

  3. 3. 不同的训练时间(5 epochs vs. 10 epochs)

组合后我们可以得到8种实验(2^3)。在每次实验中,我们都会慢慢增加数据量、模型大小和训练时间。到最后,与实验 1 相比,实验 8 将使用双倍的数据、双倍的模型大小和双倍的训练时间。

注意: 我想明确一点,您可以运行的实验数量确实没有限制。我们在这里设计的只是选项的一小部分。但是,您无法测试所有内容,因此最好先尝试一些事情,然后再遵循最有效的事情。

提醒一下,我们使用的数据集是 Food101 数据集 的子集 (3 类,披萨、牛排、suhsi,而不是 101)和 10% 和 20% 的图像而不是 100%。如果我们的实验成功,我们可以开始在更多数据上运行更多(尽管这需要更长的计算时间)。您可以通过 04_custom_data_creation.ipynb
 笔记本
查看数据集是如何创建的。

7.3 Download different datasets(略)

7.4 Transform Datasets and create DataLoaders(略)

7.5 Create feature extractor models(略)

是时候开始构建我们的模型了。我们将创建两个特征提取器模型:

  1. 1. torchvision.models.efficientnet_b0()
     预训练主干+自定义分类器头(简称EffNetB0)。

  2. 2. torchvision.models.efficientnet_b2()
     预训练主干+自定义分类器头(简称EffNetB2)。

7.6 Create experiments and set up training code

我们已经准备好数据并准备好模型,是时候进行一些实验了!我们将从创建两个列表和一个字典开始:

  1. 1. 我们要测试的 epoch 数量列表([5, 10]

  2. 2. 我们要测试的模型列表 (["effnetb0", "effnetb2"]
    )

  3. 3. 不同训练数据加载器的字典

# 1. Create epochs list
num_epochs = [510]

# 2. Create models list (need to create a new model for each experiment)
models = ["effnetb0""effnetb2"]

# 3. Create dataloaders dictionary for various dataloaders
train_dataloaders = {"data_10_percent": train_dataloader_10_percent,
                     "data_20_percent": train_dataloader_20_percent}

现在我们可以编写代码来遍历每个不同的选项并尝试每个不同的组合。我们还将在每个实验结束时保存模型,以便稍后我们可以加载回最佳模型并使用它进行预测。

具体来说,让我们通过以下步骤:

  1. 1. 设置随机种子(因此我们的实验结果是可重复的,在实践中,您可以在大约 3 个不同的种子上运行相同的实验并对结果进行平均)。

  2. 2. 跟踪不同的实验编号(这主要是为了漂亮的打印输出)。

  3. 3. 循环遍历每个不同训练 DataLoader 的 train_dataloaders
     字典项。

  4. 4. 循环遍历纪元数列表。

  5. 5. 循环浏览不同型号名称的列表。

  6. 6. 为当前运行的实验创建信息打印输出(这样我们就知道发生了什么)。

  7. 7. 检查哪个模型是目标模型并创建一个新的 EffNetB0 或 EffNetB2 实例(我们每次实验都创建一个新的模型实例,因此所有模型都从相同的角度开始)。

  8. 8. 为每个新实验创建一个新的损失函数 (torch.nn.CrossEntropyLoss()
    ) 和优化器 (torch.optim.Adam(params=model.parameters(), lr=0.001)
    )。

  9. 9. 使用修改后的 train()
     函数训练模型,将适当的细节传递给 writer
     参数。

  10. 10. 使用适当的文件名将训练好的模型保存到来自 utils.py
    .

我们还可以使用 %%time
 魔法来查看我们所有的实验在单个 Jupyter/Google Colab 单元中总共需要多长时间。

我们开始做吧!

%%time
from going_modular.going_modular.utils import save_model

# 1. Set the random seeds
set_seeds(seed=42)

# 2. Keep track of experiment numbers
experiment_number = 0

# 3. Loop through each DataLoader
for dataloader_name, train_dataloader in train_dataloaders.items():

    # 4. Loop through each number of epochs
    for epochs in num_epochs: 

        # 5. Loop through each model name and create a new model based on the name
        for model_name in models:

            # 6. Create information print outs
            experiment_number += 1
            print(f"[INFO] Experiment number: {experiment_number}")
            print(f"[INFO] Model: {model_name}")
            print(f"[INFO] DataLoader: {dataloader_name}")
            print(f"[INFO] Number of epochs: {epochs}")  

            # 7. Select the model
            if model_name == "effnetb0":
                model = create_effnetb0() # creates a new model each time (important because we want each experiment to start from scratch)
            else:
                model = create_effnetb2() # creates a new model each time (important because we want each experiment to start from scratch)
            
            # 8. Create a new loss and optimizer for every model
            loss_fn = nn.CrossEntropyLoss()
            optimizer = torch.optim.Adam(params=model.parameters(), lr=0.001)

            # 9. Train target model with target dataloaders and track experiments
            train(model=model,
                  train_dataloader=train_dataloader,
                  test_dataloader=test_dataloader, 
                  optimizer=optimizer,
                  loss_fn=loss_fn,
                  epochs=epochs,
                  device=device,
                  writer=create_writer(experiment_name=dataloader_name,
                                       model_name=model_name,
                                       extra=f"{epochs}_epochs"))
            
            # 10. Save the model to file so we can get back the best model
            save_filepath = f"07_{model_name}_{dataloader_name}_{epochs}_epochs.pth"
            save_model(model=model,
                       target_dir="models",
                       model_name=save_filepath)
            print("-"*50 + "\n")

8. View experiments in TensorBoard

在TensorBoard 中查看结果如何?

# Viewing TensorBoard in Jupyter and Google Colab Notebooks (uncomment to view full TensorBoard instance)
%reload_ext tensorboard
%tensorboard --logdir runs

Reusing TensorBoard on port 6006 (pid 15764), started 1:49:35 ago. (Use '!kill 15764' to kill it.)

运行上面的单元格,我们应该得到类似于以下的输出。

在 TensorBoard 中可视化不同建模实验的测试损失值,可以看到 EffNetB2 模型训练了 10 个 epoch 并使用 20% 的数据实现了最低损失。这符合实验的总体趋势:更多的数据、更大的模型和更长的训练时间通常更好。

您还可以将您的 TensorBoard 实验结果上传到 tensorboard.dev 以免费公开托管它们。

例如,运行类似于以下的代码:

# # Upload the results to TensorBoard.dev (uncomment to try it out)
# !tensorboard dev upload --logdir runs \
#     --name "07. PyTorch Experiment Tracking: FoodVision Mini model results" \
#     --description "Comparing results of different model size, training data amount and training time."

注意: 请注意,您上传到 tensorboard.dev 的任何内容都是公开的,任何人都可以查看。因此,如果您确实上传了您的实验,请注意它们不包含敏感信息。

9. Load in the best model and make predictions with it

查看我们八个实验的 TensorBoard 日志,似乎第 8 个实验取得了最好的整体结果(最高的测试准确度,第二低的测试损失)。

这是使用的实验:

  • • EffNetB2(EffNetB0参数的两倍)

  • • 20% 比萨、牛排、寿司训练数据(原始训练数据的两倍)

  • • 10 epochs(原始训练时间的两倍)

本质上,我们最大的模型取得了最好的结果。尽管这些结果并不比其他模型好得多。相同数据上的相同模型在一半的训练时间内取得了相似的结果(实验编号 6)。这表明我们实验中最有影响力的部分可能是参数的数量和数据量。进一步检查结果似乎通常具有更多参数(EffNetB2)和更多数据(20% 比萨、牛排、寿司训练数据)的模型表现更好(更低的测试损失和更高的测试准确度)。

可以做更多的实验来进一步测试,但是现在,让我们从实验 8 中导入我们表现最好的模型(保存到:models/07_effnetb2_data_20_percent_10_epochs.pth
,你可以从课程 GitHub 下载这个模型) 并执行一些定性评估。换句话说,让我们可视化,可视化,可视化! 我们可以通过使用create_effnetb2()
 函数创建一个新的EffNetB2 实例来导入最佳保存模型,然后使用torch.load()
 加载保存的state_dict()

# Setup the best model filepath
best_model_path = "models/07_effnetb2_data_20_percent_10_epochs.pth"

# Instantiate a new instance of EffNetB2 (to load the saved state_dict() to)
best_model = create_effnetb2()

# Load the saved best model state_dict()
best_model.load_state_dict(torch.load(best_model_path))

最佳模型加载!当我们在这里时,让我们检查它的文件大小。这是稍后部署模型(将其合并到应用程序中)时的一个重要考虑因素。如果模型太大,则可能难以部署。

# Check the model file size
from pathlib import Path

# Get the model size in bytes then convert to megabytes
effnetb2_model_size = Path(best_model_path).stat().st_size // (1024*1024)
print(f"EfficientNetB2 feature extractor model size: {effnetb2_model_size} MB")

EfficientNetB2 feature extractor model size: 29 MB

看起来我们迄今为止最好的模型大小为 29 MB。如果我们想稍后部署它,我们会记住这一点。是时候做出一些预测并将其可视化了。我们创建了一个 pred_and_plot_image()
 函数,使用经过训练的模型对 06. PyTorch 迁移学习第 6 节

我们可以通过从 going_modular.going_modular.predictions.py
 导入这个函数来重用它 (我将 pred_and_plot_image()
 函数放在脚本中,以便我们可以重用它)。

因此,为了对模型以前从未见过的各种图像进行预测,我们将首先从 20% 的比萨、牛排、寿司测试数据集中获取所有图像文件路径的列表,然后我们将随机选择这些文件路径的一个子集 传递给我们的 pred_and_plot_image()
 函数。

from going_modular.going_modular.predictions import pred_and_plot_image

# Get a random list of 3 images from 20% test set
import random
num_images_to_plot = 3
test_image_path_list = list(Path(data_20_percent_path / "test").glob("*/*.jpg")) # get all test image paths from 20% dataset
test_image_path_sample = random.sample(population=test_image_path_list,
                                       k=num_images_to_plot) # randomly select k number of images

# Iterate through random test image paths, make predictions on them and plot them
for image_path in test_image_path_sample:
    pred_and_plot_image(model=best_model,
                        image_path=image_path,
                        class_names=class_names,
                        image_size=(224224))


好的!将上面的单元格运行几次,我们可以看到我们的模型表现得非常好,并且通常比我们之前构建的模型具有更高的预测概率。这表明该模型对其所做的决策更有信心。

Main takeaways

我们现在已经对 01. PyTorch Workflow Fundamentals,我们已经准备好数据,我们已经构建并选择了一个预训练模型,我们已经使用我们的各种辅助函数来训练和评估模型 在这个笔记本中,我们通过运行和跟踪一系列实验改进了我们的 FoodVision Mini 模型。 

您应该从这个项目中获得的主要想法是:

  • • 机器学习从业者的座右铭:实验、实验、实验!

  • • 在开始时,请保持小规模的实验,以便您可以快速工作。你做的实验越多,你就能越快找出不起作用的东西。

  • • 当你找到有用的东西时扩大规模。例如我们发现了性能很好的模型,也许您现在想看看当您将其扩展到整个 Food101 数据集(来自torchvision.datasets
    )时会发生什么。

  • • 跟踪您的实验需要进行一些设置,但从长远来看这是值得的,这样您就可以弄清楚哪些有效,哪些无效。


文章转载自一只大鸽子,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论