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



我正在尝试实现一个近似逻辑异或函数的神经网络,然而,该网络仅在使用批处理大小为1时收敛。

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

无论学习率如何,这个问题都会出现,并且我在另一个问题(更复杂)中遇到了同样的问题。

我加入我的代码供参考:

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)

[EDIT]改进可读性并更新代码

结果:

[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的minibatch的累积梯度和大小为N的minibatch的梯度,计算出的梯度基本上是相同的,只有很小(但明显)的差异可能是由于近似误差造成的,所以我的实现乍一看很好。我仍然不明白大小为1的小批量的强收敛性是从哪里来的。

问题在于您在

中定义labels/计算损失的方式
loss = (outputs-labels).pow(2).mean()

我们有labels.shape = [4]但没有outputs.shape =[4, 1]。这是由于广播,差异

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

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

解决这个问题的快速方法是在这里添加一个虚拟维度:

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

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

的方式定义您的标签
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)

否则,模型输出和标签之间的不匹配会混淆minibatch示例中的损失。

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

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

修复后,将有一个浮点下流问题,以及。梯度累加法是先对梯度进行求和,而小批量法是先对梯度进行求和,再对梯度进行除法。在数学上它们是相同的,但在实践中,它们之间会有长期的漂移。

检查这个例子:

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

相关内容

最新更新