为什么大于 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
我正在尝试实现一个近似于逻辑 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