How to do backward() for each outputs seperately

nourisaj: <p>I am new to Pytorch so pardon if the question sounds stupid.<br>
I am trying to do some sort of reinforcement learning similar to “<a href="http://karpathy.github.io/2016/05/31/rl/" rel="nofollow noopener">http://karpathy.github.io/2016/05/31/rl/</a>”.<br>
An agent needs to choose either “up” or “down” based on the inputs. As mentioned in the link, uppon winning the game, I assign “1” and “0” as errors to “up” and “down”, respectively, otherwise, “0” and “-1”, and backpropagate the error.<br>
The problem is, in all pytorch c++ examples, i just see that the mean or sum of the errors (loss) is backpropagated. So my question is, how can I backpropagate for each loss defined for each output item?</p>

albanD: <p>Hi,</p>
<p>You will need to run as many backwards as gradients you want unfortunately.<br>
The nice thing with sum/avg is that since you want a single gradient, you can compute them at once.</p>
<p>That being said, if your network has some special structure, you might be able to use tricks like this one to do it: <a href="https://github.com/cybertronai/autograd-hacks">https://github.com/cybertronai/autograd-hacks</a></p>

nourisaj: <p>Can you help me to understand how repeating backwards would help? Assuming that I have the loss as either [1,0] or [0,-1], how can I use backwards() as it only works for a scalar loss? thanks</p>

albanD: <blockquote>
<p>how can I backpropagate for each loss defined for each output item?</p>
</blockquote>
<p>Simply call backward for each of them: <code>output[i].backward(your_error)</code> ?</p>

nourisaj: <p>as a minimal example, is this what you suggest?</p>
<pre><code class="lang-auto">struct Net : torch::nn::Module {
  Net() {
    fc1 = register_module("fc1", torch::nn::Linear(1, 3));
    fc2 = register_module("fc2", torch::nn::Linear(3, 4));
    fc3 = register_module("fc3", torch::nn::Linear(4, 2));
  }
    
  // Implement the Net's algorithm.
  torch::Tensor forward(torch::Tensor x) {
    x = torch::sigmoid(fc1-&gt;forward(x));
    x = torch::dropout(x, /*p=*/0.5, /*train=*/is_training());
    x = torch::sigmoid(fc2-&gt;forward(x));
    x = torch::log_softmax(fc3-&gt;forward(x), /*dim=*/1);
    return x;
  }
int main(){
      auto net = std::make_shared&lt;Net&gt;();
      torch::optim::SGD optimizer(net-&gt;parameters(), /*lr=*/0.01);
      for (int ii=0; ii&lt;100; ii++) {
      optimizer.zero_grad();
      auto inputs = generate_data(); // doesnt matter the values
      torch::Tensor prediction = net-&gt;forward(inputs);
      torch::Tensor loss =calculate_loss(prediction); // e.g. loss = [1,0]
      for (auto item:loss){
          item.backward();
      }
      optimizer.step();
}
</code></pre>

albanD: <p>If you do this, then the gradient from every backward call will be accumulated in the <code>.grad</code> field. Another way to get the same result is to sum your loss and backward the result.</p>

nourisaj: <p>as i understand, doing this:</p>
<pre><code class="lang-auto">for (auto item:loss){
          item.backward();
      }
</code></pre>
<p>or</p>
<pre><code class="lang-auto">loss.sum().backward()
</code></pre>
<p>should have more or less the same results. But neither is what i have in mind.<br>
For example, if the errors are [1,0], by doing loss.sum().backward(), i am encouraging the whole system by 1, which is not my intention. What I want to do is to backpropagate 1 from the first output and 0 from the second output</p>

albanD: <p>Sorry I’m not sure to understand what you want to do here.<br>
Could you write down mathematically what are the gradients you want to obtain?</p>

nourisaj: <p>OK. I prepared some stuff that could help.<br>
lets assume that FIG1 is my network with two outputs and corresponding errors, i.e. [loss1, loss2].<br>
I can write the gradients for each output separately as EQUATION SET#1.<br>
Basically, what is different for them is the value of the loss, i.e. loss 1 for ‘up’ and loss 2 for ‘down’.<br>
This differs from the case we go with sum (or mean) that would result in EQUATION SET#2.<br>
So my question is, how can I use Pytorch to implement the former but not latter?<br>
<div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/3X/0/8/080662aecb34806725c714f853d3d84632651f39.png" data-download-href="https://discuss.pytorch.org/uploads/default/080662aecb34806725c714f853d3d84632651f39" title="25"><img src="https://discuss.pytorch.org/uploads/default/optimized/3X/0/8/080662aecb34806725c714f853d3d84632651f39_2_389x500.png" alt="25" data-base62-sha1="18Zv2gLkbPD62eSworGwNmZeNiV" width="389" height="500" srcset="https://discuss.pytorch.org/uploads/default/optimized/3X/0/8/080662aecb34806725c714f853d3d84632651f39_2_389x500.png, https://discuss.pytorch.org/uploads/default/optimized/3X/0/8/080662aecb34806725c714f853d3d84632651f39_2_583x750.png 1.5x, https://discuss.pytorch.org/uploads/default/optimized/3X/0/8/080662aecb34806725c714f853d3d84632651f39_2_778x1000.png 2x" data-dominant-color="FAFAFA"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">25</span><span class="informations">1060×1360 78.2 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>

albanD: <p>And is <code>w_{set1}</code> used when computing L2 ? And <code>w_{set2}</code> when computing L1?<br>
From your Equation set 1 they look independent. Is that the case?</p>

nourisaj: <p>yes they are independent. “up” and “down” are the output nodes so weights coming to each of them are different</p>

albanD: <p>If they are independent, then dL1/dw_{set2} = 0. So if you use <code>L1 + L2</code>, the gradient you compute for set2 for example are: <code>d(L1+L2)/dw_{set2} = dL1/dw_{set2} + dL2/dw_{set2} = 0 + dL2/dw_{set2} = dL2/dw_{set2}</code> which is what you want. And same thing for the gradients wrt <code>w_{set1}</code></p>

nourisaj: <p>I was trying to verify what you just said that I came to something unexpected.<br>
I have a dummy example of a 1*2 network without the inner layer. I compute the loss based on the first node of the output. As a result, the gradient on the second node is zero as the code also shows correctly. However, when I check the gradients on the weights, I can see non-zero values for the second row, which I expected to be zeros. How does this happen? if the gradient on a node is zero, shouldn’t the weights coming to that node also have zero gradients?</p>
<pre><code class="lang-auto">import torch 
from torch import optim
#### network ####
class Net(torch.nn.Module):
  def __init__(self, N, M):
      super(Net, self).__init__()
      self.fc1 = torch.nn.Linear(N, M)

  def forward(self, x):
      x = torch.softmax(self.fc1(x),1)
      return x 

nn = Net(1,2)
optimizer = optim.Adam(nn.parameters(), lr=0.01)

xx = torch.ones((1,1),requires_grad = True)

ys =  nn.forward(xx)
ys.register_hook(print)
loss = abs(ys[0][0]-0.5)  
optimizer.zero_grad()
loss.backward()
print(nn.fc1.weight.grad)
optimizer.step()


</code></pre>
<p><strong>output:</strong></p>
<pre><code class="lang-auto">Gradients on the output nodes:
tensor([[-1.,  0.]])
Gradients on the weights:
tensor([[-0.1939],
        [ 0.1939]])
</code></pre>

albanD: <p>Hi,</p>
<p>I think the prints don’t match your code. In particular, I’m not sure where the <code>Gradients on the output nodes:</code> comes from.</p>
<p>But to answer your question, this is most likely the <code>softmax</code> that makes the gradients dense even though they are sparse on its output (it’s actually a very useful feature of softmax!).</p>

nourisaj: <p>Thanks. It was because of softmax.<br>
What i have learned as a newbie in pytorch is as follow:<br>
If I have L1 and L2 as loss values for two different output nodes, I can simply do <code>L = mean(L1,L2)</code> and then <code> L.backward()</code>. But the trick is, since, in reinforcement learning (RL), the target value is not calculated based on NN output nodes, first we need to associate loss/reward to output nodes. To do so, in this example, doing (L1+ys[0][0].detach() - ys[0][0]) would keep the value of error still L1 but would make it connect to “ys”, so doing backward() would indeed adjust weights.<br>
Similar approach is also taken in classic RL. Once we calculate reward R, it needs to be applied to the probability of chosen action. So, if we have output values as a tensor<code> ys = [y1, y2]</code>, and assuming that agent has chosen the first output in this episode, i.e. <code>[1,0]</code>, the loss function would be<code> [y1,y2].*[1,0].*R</code>, which connects R (which doesn’t have gradients) to ys (which has grads).<br>
Hope this is helpful</p>