PyTorch:是不是有类似于 Keras 的 fit() 的明确训练循环?

Posted

技术标签:

【中文标题】PyTorch:是不是有类似于 Keras 的 fit() 的明确训练循环?【英文标题】:PyTorch: is there a definitive training loop similar to Keras' fit()?PyTorch:是否有类似于 Keras 的 fit() 的明确训练循环? 【发布时间】:2020-04-22 08:45:39 【问题描述】:

我从 Keras 转到 PyTorch,我发现的一件令人惊讶的事情是我应该实现自己的训练循环。

在 Keras 中,有一个事实上的 fit() 函数:(1) 运行梯度下降,(2) 收集训练集和验证集的损失和准确度指标的历史记录。

在 PyTorch 中,程序员似乎需要实现训练循环。由于我是 PyTorch 的新手,我不知道我的训练循环实现是否正确。 我只是想将苹果对苹果的损失和准确性指标与我在 Keras 中看到的进行比较。

我已经通读了:

    官方PyTorch 60-minute blitz,他们提供a sample training loop。

    官方PyTorch example code,在这里我找到了the training loop placed in-line和其他代码。

    O'Reilly 的书 Programming PyTorch for Deep Learning 和 its own training loop。

    斯坦福 CS230 sample code.

    各种博文(例如here 和here)。

所以我想知道:是否有一个明确的、通用的训练循环实现与 Keras fit() 函数执行相同的操作并报告相同的数字?

我的挫败点:

    从数据加载器中拉取数据在图像数据和 NLP 数据之间不一致。

    在我见过的任何示例代码中,正确计算损失和准确率都不一致。

    一些代码示例使用Variable,而另一些则没有。

    不必要的详细信息:将数据移入/移出 GPU;知道什么时候打电话给zero_grad()

对于它的价值,这是我当前的实现。有没有明显的bug?

import time

def train(model, optimizer, loss_fn, train_dl, val_dl, epochs=20, device='cuda'):
    '''
    Runs training loop for classification problems. Returns Keras-style
    per-epoch history of loss and accuracy over training and validation data.

    Parameters
    ----------
    model : nn.Module
        Neural network model
    optimizer : torch.optim.Optimizer
        Search space optimizer (e.g. Adam)
    loss_fn :
        Loss function (e.g. nn.CrossEntropyLoss())
    train_dl : 
        Iterable dataloader for training data.
    val_dl :
        Iterable dataloader for validation data.
    epochs : int
        Number of epochs to run
    device : string
        Specifies 'cuda' or 'cpu'

    Returns
    -------
    Dictionary
        Similar to Keras' fit(), the output dictionary contains per-epoch
        history of training loss, training accuracy, validation loss, and
        validation accuracy.
    '''

    print('train() called: model=%s, opt=%s(lr=%f), epochs=%d, device=%s\n' % \
          (type(model).__name__, type(optimizer).__name__,
           optimizer.param_groups[0]['lr'], epochs, device))

    history =  # Collects per-epoch loss and acc like Keras' fit().
    history['loss'] = []
    history['val_loss'] = []
    history['acc'] = []
    history['val_acc'] = []

    start_time_sec = time.time()

    for epoch in range(epochs):

        # --- TRAIN AND EVALUATE ON TRAINING SET -----------------------------
        model.train()
        train_loss         = 0.0
        num_train_correct  = 0
        num_train_examples = 0

        for batch in train_dl:

            optimizer.zero_grad()

            x    = batch[0].to(device)
            y    = batch[1].to(device)
            yhat = model(x)
            loss = loss_fn(yhat, y)

            loss.backward()
            optimizer.step()

            train_loss         += loss.data.item() * x.size(0)
            num_train_correct  += (torch.max(yhat, 1)[1] == y).sum().item()
            num_train_examples += x.shape[0]

        train_acc   = num_train_correct / num_train_examples
        train_loss  = train_loss / len(train_dl.dataset)


        # --- EVALUATE ON VALIDATION SET -------------------------------------
        model.eval()
        val_loss       = 0.0
        num_val_correct  = 0
        num_val_examples = 0

        for batch in val_dl:

            x    = batch[0].to(device)
            y    = batch[1].to(device)
            yhat = model(x)
            loss = loss_fn(yhat, y)

            val_loss         += loss.data.item() * x.size(0)
            num_val_correct  += (torch.max(yhat, 1)[1] == y).sum().item()
            num_val_examples += y.shape[0]

        val_acc  = num_val_correct / num_val_examples
        val_loss = val_loss / len(val_dl.dataset)


        print('Epoch %3d/%3d, train loss: %5.2f, train acc: %5.2f, val loss: %5.2f, val acc: %5.2f' % \
              (epoch+1, epochs, train_loss, train_acc, val_loss, val_acc))

        history['loss'].append(train_loss)
        history['val_loss'].append(val_loss)
        history['acc'].append(train_acc)
        history['val_acc'].append(val_acc)

    # END OF TRAINING LOOP


    end_time_sec       = time.time()
    total_time_sec     = end_time_sec - start_time_sec
    time_per_epoch_sec = total_time_sec / epochs
    print()
    print('Time total:     %5.2f sec' % (total_time_sec))
    print('Time per epoch: %5.2f sec' % (time_per_epoch_sec))

    return history

【问题讨论】:

【参考方案1】:

简短回答:对于 PT 和 TF.keras 没有等效的训练循环,而且永远不会有。

首先,训练循环是一种语法糖,旨在让人们的生活更轻松。在我看来,“让生活更轻松”是 TF.keras 框架的一个主题,这是它拥有它的主要原因。训练循环不能被形式化为定义良好的实践,它可能会根据任务/数据集/过程/度量/you_name_it 的不同而有很大差异,并且需要付出很多努力才能匹配 2 个框架的所有选项。此外,在 Pytorch 中为训练循环创建定义接口对于框架的许多实际用户来说可能过于严格。

匹配网络的输出需要匹配两个框架内每个操作的行为,这是不可能的。首先,框架不一定提供相同的操作集。操作可以不同地分组到更高级别的摘要中。此外,一些常见的函数,如 sigmoid 或 BatchNorm,在纸面上看起来可能在数学上定义得很好,但实际上有几十个实现特定的细节。此外,当对操作进行改进时,由社区将这些更新集成到主要框架发行版中或忽略它们。不用说,两个框架的开发人员独立做出这些决定,并且可能有不同的动机。

总而言之,匹配两个框架的高级细节需要付出巨大的努力,并且可能会对现有用户造成很大的破坏。

【讨论】:

【参考方案2】:

确实,Pytorch Module 类 (source code) 没有 fit() 方法,所以你必须根据自己的需要实现自己的方法。 然而,也有一些模仿 Keras 训练 API 的实现,例如这个:

https://github.com/ncullen93/torchsample

或更简单的:

https://github.com/henryre/pytorch-fitmodule

【讨论】:

【参考方案3】:

在 Pytorch 中与 Keras model.fit 很接近的是名为 Torchbearer 的 Pytorch 扩展。

来自 MNIST example notebook:

trial = Trial(model, optimizer, loss, metrics=['acc', 'loss'], callbacks=callbacks).to(device)
trial.with_generators(train_generator=traingen, val_generator=valgen, test_generator=testgen)
history = trial.run(epochs=5, verbose=1)

相似之处是存在的,尽管用法需要一些阅读。 祝你好运!

【讨论】:

以上是关于PyTorch:是不是有类似于 Keras 的 fit() 的明确训练循环?的主要内容,如果未能解决你的问题,请参考以下文章

深度学习之 Keras vs Tensorflow vs Pytorch 三种深度学习框架

深度学习之 Keras vs Tensorflow vs Pytorch 三种深度学习框架的差异

Keras vs PyTorch LSTM 不同的结果

深度学习框架入门选择,Keras还是PyTorch?

使用 Keras 获取模型输出 w.r.t 权重的梯度

Keras 的功能类似于 Torch 中的视图