批量大小和训练时间

Batch size and Training time

感谢@P运行e 对我的问题的批评意见。

我正在尝试使用 MNIST 数据集找出批量大小和训练时间之间的关系。

通过阅读 Whosebug 中的大量问题,例如这个问题: How does batch size impact time execution in neural networks? 人们说当我使用小批量时训练时间会减少。

然而,通过尝试这两个,我发现批量大小 == 1 的训练比批量大小 == 60,000 花费更多的时间。我将纪元设置为 10.

我将我的 MMIST 数据集分成 60k 用于训练,10k 用于测试。

下面是我的代码和结果。

mnist_trainset = torchvision.datasets.MNIST(root=root_dir, train=True, 
                                download=True, 
                                transform=transforms.Compose([transforms.ToTensor()]))

mnist_testset  = torchvision.datasets.MNIST(root=root_dir, 
                                train=False, 
                                download=True, 
                                transform=transforms.Compose([transforms.ToTensor()]))

train_dataloader = torch.utils.data.DataLoader(mnist_trainset, 
                                               batch_size=1, 
                                               shuffle=True)

test_dataloader  = torch.utils.data.DataLoader(mnist_testset, 
                                               batch_size=50, 
                                               shuffle=False)
# Define the model 
class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.linear_1 = torch.nn.Linear(784, 256)
        self.linear_2 = torch.nn.Linear(256, 10)
        self.sigmoid  = torch.nn.Sigmoid()

    def forward(self, x):
        x = x.reshape(x.size(0), -1)
        x = self.linear_1(x)
        x = self.sigmoid(x)
        pred = self.linear_2(x)

        return pred
# trainer 
no_epochs = 10

def my_trainer(optimizer, model):

    criterion = torch.nn.CrossEntropyLoss()

    train_loss = list()
    test_loss  = list()
    test_acc   = list()
    best_test_loss = 1

    for epoch in range(no_epochs):
        
        # timer starts 
        start = timer()

        total_train_loss = 0
        total_test_loss = 0

        # training
        # set up training mode 
        model.train()
    
        for itr, (image, label) in enumerate(train_dataloader):

            optimizer.zero_grad()

            pred = model(image)

            loss = criterion(pred, label)
            total_train_loss += loss.item()

            loss.backward()
            optimizer.step()

        total_train_loss = total_train_loss / (itr + 1)
        train_loss.append(total_train_loss)

        # testing 
        # change to evaluation mode 
        model.eval()
        total = 0
        for itr, (image, label) in enumerate(test_dataloader):

            pred = model(image)

            loss = criterion(pred, label)
            total_test_loss += loss.item()

            # we now need softmax because we are testing.
            pred = torch.nn.functional.softmax(pred, dim=1)
            for i, p in enumerate(pred):
                if label[i] == torch.max(p.data, 0)[1]:
                    total = total + 1

        # caculate accuracy 
        accuracy = total / len(mnist_testset)

        # append accuracy here
        test_acc.append(accuracy)

        # append test loss here 
        total_test_loss = total_test_loss / (itr + 1)
        test_loss.append(total_test_loss)

        print('\nEpoch: {}/{}, Train Loss: {:.8f}, Test Loss: {:.8f}, Test Accuracy: {:.8f}'.format(epoch + 1, no_epochs, total_train_loss, total_test_loss, accuracy))

        if total_test_loss < best_test_loss:
            best_test_loss = total_test_loss
            print("Saving the model state dictionary for Epoch: {} with Test loss: {:.8f}".format(epoch + 1, total_test_loss))
            torch.save(model.state_dict(), "model.dth")

        # timer finishes 
        end = timer()
        print(end - start)

    return no_epochs, test_acc, test_loss
model_sgd = Model()
optimizer_SGD = torch.optim.SGD(model_sgd.parameters(), lr=0.1)
sgd_no_epochs,      sgd_test_acc, sgd_test_loss           = my_trainer(optimizer=optimizer_SGD, model=model_sgd)

我计算了每个 epoch 花费了多少时间。

下面是结果。

Epoch: 1/10, Train Loss: 0.23193890, Test Loss: 0.12670580, Test Accuracy: 0.96230000
63.98903721500005 seconds

Epoch: 2/10, Train Loss: 0.10275097, Test Loss: 0.10111042, Test Accuracy: 0.96730000
63.97179028100004 seconds

Epoch: 3/10, Train Loss: 0.07269370, Test Loss: 0.09668248, Test Accuracy: 0.97150000
63.969843954 seconds

Epoch: 4/10, Train Loss: 0.05658571, Test Loss: 0.09841745, Test Accuracy: 0.97070000
64.24135530400008 seconds

Epoch: 5/10, Train Loss: 0.04183391, Test Loss: 0.09828428, Test Accuracy: 0.97230000
64.19695308500013 seconds

Epoch: 6/10, Train Loss: 0.03393899, Test Loss: 0.08982467, Test Accuracy: 0.97530000
63.96944059600014 seconds

Epoch: 7/10, Train Loss: 0.02808819, Test Loss: 0.08597597, Test Accuracy: 0.97700000
63.59837343000004 seconds

Epoch: 8/10, Train Loss: 0.01859330, Test Loss: 0.07529452, Test Accuracy: 0.97950000
63.591578820999985 seconds

Epoch: 9/10, Train Loss: 0.01383720, Test Loss: 0.08568452, Test Accuracy: 0.97820000
63.66664020100029

Epoch: 10/10, Train Loss: 0.00911216, Test Loss: 0.07377760, Test Accuracy: 0.98060000
63.92636473799985 seconds

在此之后我将批量大小更改为 60000 并再次 运行 相同的程序。

train_dataloader = torch.utils.data.DataLoader(mnist_trainset, 
                                               batch_size=60000, 
                                               shuffle=True)

test_dataloader  = torch.utils.data.DataLoader(mnist_testset, 
                                               batch_size=50, 
                                               shuffle=False)

print("\n===== Entering SGD optimizer =====\n")
model_sgd = Model()
optimizer_SGD = torch.optim.SGD(model_sgd.parameters(), lr=0.1)
sgd_no_epochs,      sgd_test_acc, sgd_test_loss           = my_trainer(optimizer=optimizer_SGD, model=model_sgd)

我得到了批量大小 == 60000 的结果

Epoch: 1/10, Train Loss: 2.32325006, Test Loss: 2.30074144, Test Accuracy: 0.11740000
6.54154992299982 seconds

Epoch: 2/10, Train Loss: 2.30010080, Test Loss: 2.29524792, Test Accuracy: 0.11790000
6.341824101999919 seconds

Epoch: 3/10, Train Loss: 2.29514933, Test Loss: 2.29183527, Test Accuracy: 0.11410000
6.161918789000083 seconds

Epoch: 4/10, Train Loss: 2.29196787, Test Loss: 2.28874513, Test Accuracy: 0.11450000
6.180891567999879 seconds

Epoch: 5/10, Train Loss: 2.28899717, Test Loss: 2.28571669, Test Accuracy: 0.11570000
6.1449509030003355 seconds

Epoch: 6/10, Train Loss: 2.28604794, Test Loss: 2.28270152, Test Accuracy: 0.11780000
6.311743144000047 seconds

Epoch: 7/10, Train Loss: 2.28307867, Test Loss: 2.27968731, Test Accuracy: 0.12250000
6.060618773999977 seconds

Epoch: 8/10, Train Loss: 2.28014660, Test Loss: 2.27666961, Test Accuracy: 0.12890000
6.171511712999745 seconds

Epoch: 9/10, Train Loss: 2.27718973, Test Loss: 2.27364607, Test Accuracy: 0.13930000
6.164125173999764 seconds

Epoch: 10/10, Train Loss: 2.27423453, Test Loss: 2.27061504, Test Accuracy: 0.15350000
6.077817454000069 seconds

正如您所看到的,很明显当 batch_size == 1 时每个 epoch 花费了更多时间,这与我所看到的不同。

也许我对每个时期的训练时间与收敛前的训练时间感到困惑?通过查看此网页,我的直觉似乎是正确的:https://medium.com/deep-learning-experiments/effect-of-batch-size-on-neural-net-training-c5ae8516e57

有人可以解释一下发生了什么吗?

这是一个边缘问题;你应该仍然能够从基础文献中提取这种理解......最终。

您的见解完全正确:您测量的是每个时期的执行时间,而不是总训练时间 (TTT)。您还采纳了通用的“小批量”建议荒谬:批量大小为 1 几乎肯定是次优的。

宏观层面的机制非常简单。

批量大小为 60k(整个训练集)时,您 运行 通过模型对所有 60k 图像进行平均,然后对该平均结果进行一次反向传播。这往往会失去从关注鲜为人知的功能中获得的知识。

批量大小为 1 时,您 运行 每个图像单独通过模型,对一个结果求平均(一个非常简单的操作 :-)),然后进行反向传播。这往往会过分强调个别效果,尤其是保留每个图像的迷信效果。它还对前几张图像的初始假设给予了过多的权重。

小批量最明显的影响是你正在做 60k back-props 而不是 1,所以每个 epoch 需要更长的时间。


这两种方法中的任何一种都是极端情况,在应用中通常是荒谬的。

您需要通过试验找到“最佳点”,让您以最快的速度收敛到可接受的(接近最佳的)准确度。在选择实验设计时有几个注意事项:

  • 内存大小:您希望能够一次将整个批次摄取到内存中。这允许您的模型流水线读取和处理。如果超出可用内存,您将浪费大量时间进行交换。如果内存使用不足,则会留下一些未开发的潜在性能。
  • 处理器:如果您使用的是多处理器芯片,您希望让它们都处于忙碌状态。如果您希望通过 OS 控件分配处理器,您还需要考虑分配给模型计算的处理器数量,以及分配给 I/O 和系统使用的处理器数量。例如,在我做的一个项目中,我们小组发现我们的 32 个内核最适合使用,其中 28 个分配给计算,4 个保留用于 I/O 和其他系统功能。
  • 缩放:某些特征在 2 的幂中效果最好。您可能会发现批处理大小为 2^n 或 3 * 2^n 对于某些 n,效果最好,仅仅是因为块大小和其他系统分配。

多年来对我最有效的实验设计是从 2 的幂开始,它大致是训练集大小的平方根。对您来说,有一个明显的起始猜测值 256。因此,您可能 运行 在 64、128、256、512 和 1024 处进行实验。看看哪些给您带来最快的收敛。

然后使用因子 3 进行一步优化。例如,如果您发现 128 时性能最佳,也可以尝试 96 和 192。

您可能会发现您的“最佳点”与相邻批次大小之间的差别很小;这是大多数复杂信息系统的本质。