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

27
我从Keras转到PyTorch,其中一个令人惊讶的发现是我需要实现自己的训练循环。
在Keras中,有一个事实上的fit()函数:(1)运行梯度下降,(2)收集损失和准确性的历史指标,包括训练集和验证集。
在PyTorch中,程序员似乎需要实现训练循环。由于我刚接触PyTorch,不知道我的训练循环实现是否正确。我只想比较与Keras中看到的损失和准确性指标相同。 我已经阅读过:
  1. 官方PyTorch 60分钟入门, 其中提供了一个样本训练循环

  2. 官方PyTorch示例代码, 我发现训练循环嵌入其他代码中

  3. O'Reilly图书Programming PyTorch for Deep Learning自带训练循环

  4. 斯坦福CS230 示例代码

  5. 各种博客文章(例如这里这里)。

我在想: 是否有一种明确的、通用的训练循环实现,可以做到与Keras的fit()函数相同,并报告相同的数字?
我感到沮丧的几点是:
1.从数据加载器中提取数据在图像数据和NLP数据之间不一致。
2.正确计算损失和准确性在任何示例代码中都不一致。
3.一些代码示例使用Variable,而其他代码则不使用。
4.过于详细:将数据移动到/从GPU;知道何时调用zero_grad()。
就目前而言,这是我的当前实现。有什么明显的错误吗?
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

你可能可以使用 HuggingfaceTrainer[示例]。不确定它是否仅用于微调预训练的转换器,还是用于一般用途。 - Alaa M.
4个回答

5
简短回答:PT和TF.keras没有等效的训练循环,也永远不会有。
首先,训练循环是一种被设计用来使人们生活更加轻松的句法糖。从我的角度来看,“让生活更轻松”是TF.keras框架的口号,这也是它拥有训练循环的主要原因。训练循环无法被规范化为一个明确定义的实践,它可能会因任务、数据集、流程、指标或其他因素的不同而大相径庭,并且为了匹配两个框架的所有选项需要付出大量的努力。此外,在Pytorch中创建定义训练循环的接口对于许多实际使用该框架的用户可能过于限制性。
匹配网络输出需要匹配两个框架内每个操作的行为,这是不可能的。首先,这些框架不一定提供相同的操作集合。操作可以按不同的抽象级别进行分组。此外,一些常见函数(如sigmoid或BatchNorm)在数学上看起来很好定义,但在现实中却有许多实现特定的细节。此外,当对操作进行改进时,社区将决定将这些更新集成到主要框架分发中,还是忽略它们。不用说,两个框架的开发人员独立做出这些决策,并且很可能背后的动机不同。
总之,匹配两个框架的高级细节需要巨大的努力,并且可能会对现有用户产生非常大的影响。

4

4
我想指出现在有一个fit(...)的等效方法。PyTorch Lightning是PyTorch的包装器,可以使用清晰的面向对象方法在PyTorch中创建ML模型。它提供了一个使用他们的Trainer类的fit(...)循环。我建议查看官方网站以获取更详细的答案。

4

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

从MNIST示例笔记本中可以看到:

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)

虽然使用需要一些阅读,但相似性是存在的。
祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接