为什么大于 1 的小批量不起作用,但更大的累积梯度起作用?

Why mini-batches larger than 1 doesn't work, but larger accumulating gradients work?

我正在尝试实现一个近似于逻辑 XOR 函数的神经网络,但是,网络仅在使用批量大小为 1 时才会收敛。

我不明白为什么:当我对多个大小为 1 的小批量使用梯度累积时,收敛非常平滑,但大小为 2 或更大的小批量根本不起作用。

无论学习率如何,都会出现这个问题,而且我对另一个比 XOR 更复杂的问题也有同样的问题。

我加入我的代码以供参考:

import numpy as np
import torch.nn as nn
import torch
import torch.optim as optim
import copy

#very simple network
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(2,3,True)
        self.fc1 = nn.Linear(3,1, True)

    def forward(self, x):
        x = torch.sigmoid(self.fc(x))
        x = self.fc1(x)
        return x

def data(n): # return n sets of random XOR inputs and output
    inputs = np.random.randint(0,2,2*n)
    inputs = np.reshape(inputs,(-1,2))
    outputs = np.logical_xor(inputs[:,0], inputs[:,1])
    return torch.tensor(inputs, dtype = torch.float32),torch.tensor(outputs, dtype = torch.float32)


N = 4
net = Net() # first network, is updated with minibatches of size N
net1 = copy.deepcopy(net) # second network, updated with N minibatches of size 1
inputs = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype = torch.float32)
labels = torch.tensor([0,1,1,0], dtype = torch.float32)
optimizer = optim.SGD(net.parameters(), lr=0.01)
optimizer1 = optim.SGD(net1.parameters(), lr=0.01)
running_loss = 0
running_loss1 = 0
for epoch in range(25000):  # loop over the dataset multiple times
    # get the inputs; data is a list of [inputs, labels]
    input, labels = data(N)

    # zero the parameter gradients
    optimizer.zero_grad()
    optimizer1.zero_grad()
    # forward + backward + optimize
    loss1_total = 0
    for i in range(N):
        outputs1 = net1(input[i])
        loss1 = (outputs1-labels[i]).pow(2)/N # I divide by N to get the effective mean
        loss1.backward()
        loss1_total += loss1.item()


    outputs = net(input)
    loss = (outputs-labels).pow(2).mean()
    loss.backward()
    
    # optimization
    optimizer.step()
    optimizer1.step()

    # print statistics
    running_loss += loss.item()
    running_loss1 += loss1_total
    if epoch % 1000 == 999:    # print every 1000 mini-batches
        print(f'[{epoch + 1},  loss: {running_loss/1000 :.3f}, loss1: {running_loss1/1000 :.3f}')
        running_loss1 = 0.0
        running_loss = 0.0
        
print('Finished Training')
 # exemples of data and outputs for reference ; network 2 always converge to the sub-optimal point(0.5,0.5)
datatest = data(4)
outputs = net(datatest[0])
outputs1 = net1(datatest[0])
inputs = datatest[0]
labels = datatest[1]
print("input",inputs)
print("target",labels)
print("net output",outputs)
print("net output",outputs1)

[编辑] 提高了可读性并更新了代码

结果:

[1000,  loss: 0.259, loss1: 0.258
[2000,  loss: 0.252, loss1: 0.251
[3000,  loss: 0.251, loss1: 0.250
[4000,  loss: 0.252, loss1: 0.250
[5000,  loss: 0.251, loss1: 0.249
[6000,  loss: 0.251, loss1: 0.247
[7000,  loss: 0.252, loss1: 0.246
[8000,  loss: 0.251, loss1: 0.244
[9000,  loss: 0.252, loss1: 0.241
[10000,  loss: 0.251, loss1: 0.236
[11000,  loss: 0.252, loss1: 0.230
[12000,  loss: 0.252, loss1: 0.221
[13000,  loss: 0.250, loss1: 0.208
[14000,  loss: 0.251, loss1: 0.193
[15000,  loss: 0.251, loss1: 0.175
[16000,  loss: 0.251, loss1: 0.152
[17000,  loss: 0.252, loss1: 0.127
[18000,  loss: 0.251, loss1: 0.099
[19000,  loss: 0.251, loss1: 0.071
[20000,  loss: 0.251, loss1: 0.048
[21000,  loss: 0.251, loss1: 0.029
[22000,  loss: 0.251, loss1: 0.016
[23000,  loss: 0.250, loss1: 0.008
[24000,  loss: 0.251, loss1: 0.004
[25000,  loss: 0.251, loss1: 0.002

Finished Training

input tensor([[1., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.]])
target tensor([1., 0., 0., 0.])
net output tensor([[0.4686],
        [0.4472],
        [0.4472],
        [0.4472]], grad_fn=<AddmmBackward0>)
net1 output tensor([[0.9665],
        [0.0193],
        [0.0193],
        [0.0193]], grad_fn=<AddmmBackward0>)

请你解释一下为什么会出现这种奇怪的现象?在网上找了半天,没有成功...

请问我的问题格式不正确,这是我第一次问关于堆栈溢出的问题。

编辑: 我发现,比较大小为 1 的小批量的累积梯度和大小为 N 的小批量的梯度,计算出的梯度大部分相同,只有很小(但明显)的差异可能是由于近似误差造成的,所以我的实现乍一看还不错。我仍然不明白大小为 1 的小批量的强收敛 属性 从何而来。

问题在于你定义labels/计算损失的方式

 loss = (outputs-labels).pow(2).mean()

我们有 labels.shape = [4]outputs.shape =[4, 1]。这个因播出不同

(outputs - labels).shape = [4, 4]

这意味着我们计算 all 输出和标签之间的成对差异(然后取它们的二次方并取平均值),这基本上意味着没有发生有意义的监督。

解决此问题的快速方法是在此处添加虚拟维度:

loss = (outputs-labels[:, None]).pow(2).mean()

但是 clean 方法将从一开始就以正确的方式进行,即以 labels.shape = [_, 1]:[=19 的方式定义标签=]

labels = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

(与您的 data() 函数类似)。

标签和输出的尺寸似乎存在一个小问题。

这个:

labels = torch.tensor([0,1,1,0], dtype = torch.float32)

需要变成这样:

labels = torch.tensor([[0],[1],[1],[0]], dtype = torch.float32)

否则,模型输出和标签之间的不匹配会弄乱小批量示例中的损失。

这可以在 data(n) 中修复,如果您向输出添加额外的维度:

outputs = np.logical_xor(inputs[:,0], inputs[:,1]).reshape((n, 1))

修复后,还会出现 floating-point underflow 问题。梯度累加法先对梯度求和,而在 minibatch 方法中先求和再除。在数学上它们是相同的,但在实践中,它们之间会存在 long 运行.

的漂移

检查这个例子:

x = np.array([0.00649802, 0.24420964, 0.05081264,])
(x/3).sum() - x.mean()
# -1.3877787807814457e-17