如何正确更新 PyTorch 中的权重?
How to properly update the weights in PyTorch?
我正在尝试根据此 schema 使用 PyTorch 实现梯度下降,但无法弄清楚如何正确更新权重。这只是一个玩具示例,具有 2 个线性层,隐藏层中有 2 个节点和一个输出。
Learning rate = 0.05;
target output = 1
https://hmkcode.github.io/ai/backpropagation-step-by-step/
Forward
Backward
我的代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.linear1 = nn.Linear(2, 2, bias=None)
self.linear1.weight = torch.nn.Parameter(torch.tensor([[0.11, 0.21], [0.12, 0.08]]))
self.linear2 = nn.Linear(2, 1, bias=None)
self.linear2.weight = torch.nn.Parameter(torch.tensor([[0.14, 0.15]]))
def forward(self, inputs):
out = self.linear1(inputs)
out = self.linear2(out)
return out
losses = []
loss_function = nn.L1Loss()
model = MyNet()
optimizer = optim.SGD(model.parameters(), lr=0.05)
input = torch.tensor([2.0,3.0])
print('weights before backpropagation = ', list(model.parameters()))
for epoch in range(1):
result = model(input )
loss = loss_function(result , torch.tensor([1.00],dtype=torch.float))
print('result = ', result)
print("loss = ", loss)
model.zero_grad()
loss.backward()
print('gradients =', [x.grad.data for x in model.parameters()] )
optimizer.step()
print('weights after backpropagation = ', list(model.parameters()))
结果如下:
weights before backpropagation = [Parameter containing:
tensor([[0.1100, 0.2100],
[0.1200, 0.0800]], requires_grad=True), Parameter containing:
tensor([[0.1400, 0.1500]], requires_grad=True)]
result = tensor([0.1910], grad_fn=<SqueezeBackward3>)
loss = tensor(0.8090, grad_fn=<L1LossBackward>)
gradients = [tensor([[-0.2800, -0.4200], [-0.3000, -0.4500]]),
tensor([[-0.8500, -0.4800]])]
weights after backpropagation = [Parameter containing:
tensor([[0.1240, 0.2310],
[0.1350, 0.1025]], requires_grad=True), Parameter containing:
tensor([[0.1825, 0.1740]], requires_grad=True)]
前传 值:
2x0.11 + 3*0.21=0.85 ->
2x0.12 + 3*0.08=0.48 -> 0.85x0.14 + 0.48*0.15=0.191 -> loss =0.191-1 = -0.809
Backward pass:让我们计算w5和w6(输出节点权重)
w = w - (prediction-target)x(gradient)x(output of previous node)x(learning rate)
w5= 0.14 -(0.191-1)*1*0.85*0.05= 0.14 + 0.034= 0.174
w6= 0.15 -(0.191-1)*1*0.48*0.05= 0.15 + 0.019= 0.169
在我的示例中,Torch 没有将损失乘以导数,因此我们在更新后得到了错误的权重。对于输出节点,我们得到了新的权重 w5,w6 [0.1825, 0.1740] ,而它应该是 [0.174, 0.169]
向后移动以更新我们需要计算的输出节点(w5)的第一个权重:(prediction-target)x(gradient)x(output of previous node)x(learning rate)=-0.809*1*0.85*0.05=-0.034
。更新权重 w5 = 0.14-(-0.034)=0.174
。但是 pytorch 却计算出了 new weight = 0.1825
。它忘记乘以 (prediction-target)=-0.809
。对于输出节点,我们得到梯度 -0.8500 和 -0.4800。但是我们仍然需要将它们乘以损失 0.809 和学习率 0.05 才能更新权重。
这样做的正确方法是什么?
我们是否应该将 'loss' 作为参数传递给 backward()
,如下所示:loss.backward(loss)
.
这似乎可以解决问题。但是我在文档中找不到任何关于此的示例。
您应该将 .zero_grad()
与优化器一起使用,因此 optimizer.zero_grad()
,而不是评论中建议的损失或模型(虽然模型很好,但 IMO 不清晰或可读)。
除了你的参数更新正常,所以错误不在 PyTorch 那边。
根据您提供的渐变值:
gradients = [tensor([[-0.2800, -0.4200], [-0.3000, -0.4500]]),
tensor([[-0.8500, -0.4800]])]
让我们将它们全部乘以你的学习率 (0.05):
gradients_times_lr = [tensor([[-0.014, -0.021], [-0.015, -0.0225]]),
tensor([[-0.0425, -0.024]])]
最后,让我们应用普通的 SGD(theta -= 梯度 * lr),以获得与 PyTorch 中完全相同的结果:
parameters = [tensor([[0.1240, 0.2310], [0.1350, 0.1025]]),
tensor([[0.1825, 0.1740]])]
你所做的是采用 PyTorch 计算的梯度并将它们与前一个节点的输出相乘,这不是它的工作原理!。
你做了什么:
w5= 0.14 -(0.191-1)*1*0.85*0.05= 0.14 + 0.034= 0.174
应该做什么(使用 PyTorch 的结果):
w5 = 0.14 - (-0.85*0.05) = 0.1825
没有前一个节点的乘法,它是在幕后完成的(这就是.backprop()
所做的——为所有节点计算正确梯度),不需要乘法他们比以前的。
如果你想手动计算它们,你必须从损失开始(delta 为 1)并一直反向传播(这里不要使用学习率,这是另一回事! ).
在计算完所有这些之后,您可以将每个权重乘以优化器学习率(或与此相关的任何其他公式,例如 Momentum),然后得到正确的更新。
如何计算反向传播
学习率不是反向传播的一部分,在计算所有梯度之前不要管它(它将单独的算法、优化过程和反向传播混淆在一起)。
1。总误差的导数 w.r.t。输出
嗯,我不知道你为什么要使用 平均绝对误差(而在教程中它是 均方误差) ,这就是为什么这两个结果不同的原因。但是,让我们选择您的选择。
|的导数y_true - y_pred | w.r.t。 to y_pred 是 1,所以 它与 不同。换成 MSE 得到相等的结果(这里,导数将是 (1/2 * y_pred - y_true),但我们通常将 MSE 乘以二为了删除第一个乘法)。
在MSE情况下你会乘以损失值,但这完全取决于损失函数(有点不幸的是你使用的教程没有指出这个出来)。
2。总误差的导数 w.r.t。 w5
您可能从这里开始,但是...总误差 w.r.t 到 w5 的导数是 h1 的输出(在本例中为 0.85)。我们将它乘以总误差的导数 w.r.t。输出(它是 1!)并获得 0.85,就像在 PyTorch 中所做的那样。 w6 也有同样的想法。
我认真地建议你不要将学习率与反向传播混淆,你会让你的生活变得更加艰难(在我看来,反向传播并不容易,这很违反直觉),这是两个分开的东西(怎么强调都不够)。
This 源码不错,更循序渐进,网络思路更复杂一点(包括激活),所以如果你通读一遍,你会更好地掌握。
此外,如果您真的很想(而且您似乎很想)了解更多细节,请计算其他优化器(例如,nesterov)的权重修正,这样您就知道为什么我们应该保留这些想法分开。
我正在尝试根据此 schema 使用 PyTorch 实现梯度下降,但无法弄清楚如何正确更新权重。这只是一个玩具示例,具有 2 个线性层,隐藏层中有 2 个节点和一个输出。
Learning rate = 0.05;
target output = 1
https://hmkcode.github.io/ai/backpropagation-step-by-step/
Forward
Backward
我的代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.linear1 = nn.Linear(2, 2, bias=None)
self.linear1.weight = torch.nn.Parameter(torch.tensor([[0.11, 0.21], [0.12, 0.08]]))
self.linear2 = nn.Linear(2, 1, bias=None)
self.linear2.weight = torch.nn.Parameter(torch.tensor([[0.14, 0.15]]))
def forward(self, inputs):
out = self.linear1(inputs)
out = self.linear2(out)
return out
losses = []
loss_function = nn.L1Loss()
model = MyNet()
optimizer = optim.SGD(model.parameters(), lr=0.05)
input = torch.tensor([2.0,3.0])
print('weights before backpropagation = ', list(model.parameters()))
for epoch in range(1):
result = model(input )
loss = loss_function(result , torch.tensor([1.00],dtype=torch.float))
print('result = ', result)
print("loss = ", loss)
model.zero_grad()
loss.backward()
print('gradients =', [x.grad.data for x in model.parameters()] )
optimizer.step()
print('weights after backpropagation = ', list(model.parameters()))
结果如下:
weights before backpropagation = [Parameter containing:
tensor([[0.1100, 0.2100],
[0.1200, 0.0800]], requires_grad=True), Parameter containing:
tensor([[0.1400, 0.1500]], requires_grad=True)]
result = tensor([0.1910], grad_fn=<SqueezeBackward3>)
loss = tensor(0.8090, grad_fn=<L1LossBackward>)
gradients = [tensor([[-0.2800, -0.4200], [-0.3000, -0.4500]]),
tensor([[-0.8500, -0.4800]])]
weights after backpropagation = [Parameter containing:
tensor([[0.1240, 0.2310],
[0.1350, 0.1025]], requires_grad=True), Parameter containing:
tensor([[0.1825, 0.1740]], requires_grad=True)]
前传 值:
2x0.11 + 3*0.21=0.85 ->
2x0.12 + 3*0.08=0.48 -> 0.85x0.14 + 0.48*0.15=0.191 -> loss =0.191-1 = -0.809
Backward pass:让我们计算w5和w6(输出节点权重)
w = w - (prediction-target)x(gradient)x(output of previous node)x(learning rate)
w5= 0.14 -(0.191-1)*1*0.85*0.05= 0.14 + 0.034= 0.174
w6= 0.15 -(0.191-1)*1*0.48*0.05= 0.15 + 0.019= 0.169
在我的示例中,Torch 没有将损失乘以导数,因此我们在更新后得到了错误的权重。对于输出节点,我们得到了新的权重 w5,w6 [0.1825, 0.1740] ,而它应该是 [0.174, 0.169]
向后移动以更新我们需要计算的输出节点(w5)的第一个权重:(prediction-target)x(gradient)x(output of previous node)x(learning rate)=-0.809*1*0.85*0.05=-0.034
。更新权重 w5 = 0.14-(-0.034)=0.174
。但是 pytorch 却计算出了 new weight = 0.1825
。它忘记乘以 (prediction-target)=-0.809
。对于输出节点,我们得到梯度 -0.8500 和 -0.4800。但是我们仍然需要将它们乘以损失 0.809 和学习率 0.05 才能更新权重。
这样做的正确方法是什么?
我们是否应该将 'loss' 作为参数传递给 backward()
,如下所示:loss.backward(loss)
.
这似乎可以解决问题。但是我在文档中找不到任何关于此的示例。
您应该将 .zero_grad()
与优化器一起使用,因此 optimizer.zero_grad()
,而不是评论中建议的损失或模型(虽然模型很好,但 IMO 不清晰或可读)。
除了你的参数更新正常,所以错误不在 PyTorch 那边。
根据您提供的渐变值:
gradients = [tensor([[-0.2800, -0.4200], [-0.3000, -0.4500]]),
tensor([[-0.8500, -0.4800]])]
让我们将它们全部乘以你的学习率 (0.05):
gradients_times_lr = [tensor([[-0.014, -0.021], [-0.015, -0.0225]]),
tensor([[-0.0425, -0.024]])]
最后,让我们应用普通的 SGD(theta -= 梯度 * lr),以获得与 PyTorch 中完全相同的结果:
parameters = [tensor([[0.1240, 0.2310], [0.1350, 0.1025]]),
tensor([[0.1825, 0.1740]])]
你所做的是采用 PyTorch 计算的梯度并将它们与前一个节点的输出相乘,这不是它的工作原理!。
你做了什么:
w5= 0.14 -(0.191-1)*1*0.85*0.05= 0.14 + 0.034= 0.174
应该做什么(使用 PyTorch 的结果):
w5 = 0.14 - (-0.85*0.05) = 0.1825
没有前一个节点的乘法,它是在幕后完成的(这就是.backprop()
所做的——为所有节点计算正确梯度),不需要乘法他们比以前的。
如果你想手动计算它们,你必须从损失开始(delta 为 1)并一直反向传播(这里不要使用学习率,这是另一回事! ).
在计算完所有这些之后,您可以将每个权重乘以优化器学习率(或与此相关的任何其他公式,例如 Momentum),然后得到正确的更新。
如何计算反向传播
学习率不是反向传播的一部分,在计算所有梯度之前不要管它(它将单独的算法、优化过程和反向传播混淆在一起)。
1。总误差的导数 w.r.t。输出
嗯,我不知道你为什么要使用 平均绝对误差(而在教程中它是 均方误差) ,这就是为什么这两个结果不同的原因。但是,让我们选择您的选择。
|的导数y_true - y_pred | w.r.t。 to y_pred 是 1,所以 它与 不同。换成 MSE 得到相等的结果(这里,导数将是 (1/2 * y_pred - y_true),但我们通常将 MSE 乘以二为了删除第一个乘法)。
在MSE情况下你会乘以损失值,但这完全取决于损失函数(有点不幸的是你使用的教程没有指出这个出来)。
2。总误差的导数 w.r.t。 w5
您可能从这里开始,但是...总误差 w.r.t 到 w5 的导数是 h1 的输出(在本例中为 0.85)。我们将它乘以总误差的导数 w.r.t。输出(它是 1!)并获得 0.85,就像在 PyTorch 中所做的那样。 w6 也有同样的想法。
我认真地建议你不要将学习率与反向传播混淆,你会让你的生活变得更加艰难(在我看来,反向传播并不容易,这很违反直觉),这是两个分开的东西(怎么强调都不够)。
This 源码不错,更循序渐进,网络思路更复杂一点(包括激活),所以如果你通读一遍,你会更好地掌握。
此外,如果您真的很想(而且您似乎很想)了解更多细节,请计算其他优化器(例如,nesterov)的权重修正,这样您就知道为什么我们应该保留这些想法分开。