【问题标题】:pytorch - connection between loss.backward() and optimizer.step()pytorch - loss.backward() 和 optimizer.step() 之间的连接
【发布时间】:2019-05-27 07:02:45
【问题描述】:

optimizerloss 之间的显式连接在哪里?

优化器如何知道在不调用这个optimizer.step(loss) 的情况下从哪里获取损失的梯度?

-更多上下文-

当我最小化损失时,我不必将梯度传递给优化器。

loss.backward() # Back Propagation
optimizer.step() # Gardient Descent

【问题讨论】:

    标签: machine-learning neural-network pytorch gradient-descent


    【解决方案1】:

    有些答案解释得很好,但我想举一个具体的例子来解释机制。

    假设我们有一个函数:z = 3 x^2 + y^3。
    z w.r.t x 和 y 的更新梯度公式为:

    初始值为 x=1 和 y=2。

    x = torch.tensor([1.0], requires_grad=True)
    y = torch.tensor([2.0], requires_grad=True)
    z = 3*x**2+y**3
    
    print("x.grad: ", x.grad)
    print("y.grad: ", y.grad)
    print("z.grad: ", z.grad)
    
    # print result should be:
    x.grad:  None
    y.grad:  None
    z.grad:  None
    

    然后计算当前值(x=1,y=2)中x和y的梯度

    # calculate the gradient
    z.backward()
    
    print("x.grad: ", x.grad)
    print("y.grad: ", y.grad)
    print("z.grad: ", z.grad)
    
    # print result should be:
    x.grad:  tensor([6.])
    y.grad:  tensor([12.])
    z.grad:  None
    

    最后,使用 SGD 优化器根据公式更新 x 和 y 的值:

    # create an optimizer, pass x,y as the paramaters to be update, setting the learning rate lr=0.1
    optimizer = optim.SGD([x, y], lr=0.1)
    
    # executing an update step
    optimizer.step()
    
    # print the updated values of x and y
    print("x:", x)
    print("y:", y)
    
    # print result should be:
    x: tensor([0.4000], requires_grad=True)
    y: tensor([0.8000], requires_grad=True)
    

    【讨论】:

    • 这是一个很好的解释以及代码。谢谢。
    【解决方案2】:

    无需深入研究 pytorch 的内部结构,我可以提供一个简单的答案:

    回想一下,在初始化 optimizer 时,您明确告诉它应该更新模型的哪些参数(张量)。一旦您在损失上调用backward(),梯度就会由张量本身“存储”(它们具有gradrequires_grad 属性)。在计算模型中所有张量的梯度后,调用optimizer.step() 会使优化器迭代它应该更新的所有参数(张量),并使用它们内部存储的grad 来更新它们的值。

    有关计算图的更多信息以及存储在 pytorch 张量中的其他“grad”信息可以在 this answer 中找到。

    优化器引用参数有时会导致麻烦,例如,当模型在初始化优化器后移动到 GPU 时。 确保您在构建优化器之前完成了模型的设置。详情请见this answer

    【讨论】:

    • @Aerin 这不是一个微不足道的联系......人们会期望optimizer.step 得到loss.backward() 作为参数。然而,这一切都发生在“幕后”……
    • 那么optimizer.step()是如何从loss.backward()中获取梯度值的。看来这个答案并没有回答“连接”的机制。优化器参考了模型参数。但是损失函数是完全独立的。看起来它没有引用模型或优化器。
    • @cfeng 损失函数根本不是独立的!它是单个巨大计算图中的最后一个叶子,它从模型输入开始并包含所有模型参数。该图是针对每个批次计算的,并在每个批次上产生一个标量数。当我们做loss.backward() 时,反向传播的过程从损失开始,并通过它的所有父母一直到模型输入。图中的所有节点都包含对其父节点的引用。
    • @mofury 简而言之,这个问题并没有那么简单。粗略地说,首先是损失函数类的实例,比如nn.CrossEntropyLoss 的实例,可以调用并返回Tensor。这很重要,这个 Tensor 对象有一个 grad_fn 道具,其中存储了它派生的张量。而这些张量也有这样的 props,所以backward 函数可以通过这样的 props 进行反向传播,最终得到我们想要在模型中优化的参数。你可以参考这个:pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html.
    【解决方案3】:

    也许这将澄清loss.backwardoptim.step之间的联系(尽管其他答案很重要)。

    # Our "model"
    x = torch.tensor([1., 2.], requires_grad=True)
    y = 100*x
    
    # Compute loss
    loss = y.sum()
    
    # Compute gradients of the parameters w.r.t. the loss
    print(x.grad)     # None
    loss.backward()      
    print(x.grad)     # tensor([100., 100.])
    
    # MOdify the parameters by subtracting the gradient
    optim = torch.optim.SGD([x], lr=0.001)
    print(x)        # tensor([1., 2.], requires_grad=True)
    optim.step()
    print(x)        # tensor([0.9000, 1.9000], requires_grad=True)
    

    loss.backward() 将所有张量的grad 属性设置为requires_grad=True 在计算图中损失是叶子(在这种情况下只有x)。

    优化器只是迭代它在初始化时收到的参数列表(张量)以及张量具有requires_grad=True 的任何地方,它减去存储在其.grad 属性中的梯度值(只需乘以新加坡元的情况)。它不需要知道梯度的计算损失是多少,它只想访问.grad 属性,以便它可以执行x = x - lr * x.grad

    注意,如果我们在训练循环中执行此操作,我们将调用 optim.zero_grad(),因为在每个训练步骤中,我们想要计算新的梯度 - 我们不关心前一批的梯度.不将 grads 归零会导致批次间的梯度累积。

    【讨论】:

    • 我喜欢这种“动手”的解释来理解事物。谢谢,对我来说更有意义!
    【解决方案4】:

    简答:

    loss.backward() # 对我们设置的所有参数进行渐变required_grad= True。参数可以是代码中定义的任何变量,例如h2hi2h

    optimizer.step() # 根据优化器函数(之前在我们的代码中定义),我们更新这些参数,最终得到最小的损失(错误)。

    【讨论】:

      【解决方案5】:

      假设我们定义了一个模型:model,损失函数:criterion,我们有以下步骤序列:

      pred = model(input)
      loss = criterion(pred, true_labels)
      loss.backward()
      

      pred 将具有grad_fn 属性,该属性引用创建它的函数,并将其绑定回模型。因此,loss.backward() 将获得有关它正在使用的模型的信息。

      尝试删除grad_fn 属性,例如:

      pred = pred.clone().detach()
      

      那么模型梯度将为None,因此权重不会更新。

      并且优化器与模型相关联,因为我们在创建优化器时传递了model.parameters()

      【讨论】:

      • 不应该是“那么模型 gradients 将不会更新。”,因为 loss.backward() 会更新梯度?
      • @zwithouta,谢谢,这是一个很好的观点。我更新了我的答案。
      【解决方案6】:

      当您调用loss.backward() 时,它所做的只是计算损失中所有具有requires_grad = True 的参数的损失梯度,并将它们存储在每个参数的parameter.grad 属性中。

      optimizer.step()根据parameter.grad更新所有参数

      【讨论】:

      • loss 正在计算与网络无关的两个张量之间的损失。 loss.backward() 如何知道它需要为哪个网络引用和计算 parameter.grad
      • @AzizAlfoudari 请参阅我的回答以进行澄清尝试:)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-06
      • 2021-12-01
      • 2021-12-31
      • 1970-01-01
      • 2018-07-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多