Help- Accumulate unreduced loss over several batches and do .backward()

SofiaCP: <p>Hello,</p>
<p>I have this multi-label problem where I am using a masked BCE loss. For each sample, the loss values that are masked depend on the ground truth of the sample, so the mask is dynamic according to each specific example.</p>
<p>There are <code>18 labels</code> and lets assume <code>batch_size=4</code>. Basically, after applying the mask, the loss of a batch looks something like this:</p>
<pre><code class="lang-auto">        [[0.7634, 0.4821, 0.5363, 1.0051, 0.7906, 1.2232, 0.5324, 0.6181, 0.5487,
         0.0000, 0.6234, 0.0000, 0.9262, 0.6945, 0.5609, 0.2120, 0.5893, 0.5999],
        [0.7202, 0.5397, 0.6313, 0.8765, 0.0000, 1.4007, 0.0000, 0.0000, 0.4348,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.2584, 0.0000, 0.6737],
        [0.7303, 0.4027, 1.0703, 0.8589, 0.0000, 1.0411, 0.0000, 0.0000, 0.9066,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.2357, 0.0000, 0.6243],
        [0.8076, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]
</code></pre>
<p>The first label is never masked, so for a given batch, I will always have 4 non-zero losses to average over and do a backward pass. The thing is that some labels will not contain 4 non-zero losses in a single batch, which is somewhat equivalent to reducing the batch size for those labels. Due to this, I intended to accumulate the losses of each label until I get 4 non-zero values and only then do a backward pass for that label and call <code>optimizer.step()</code>.</p>
<p>I am having trouble implementing this. I basically tried to accumulate the loss matrix of every new batch using <code>torch.cat()</code> and then do <code>accumulated_loss[:i]</code> when the number of non-zero elements for column <code>i</code> reaches 4 but I get the RuntimeError</p>
<blockquote>
<p>Trying to backward through the graph a second time (or directly access saved variables after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved variables after calling backward.</p>
</blockquote>
<p>Any help would be really appreciated.</p>

mMagmer: <p>I think you’re using backward before cat<br>
this may help</p><aside class="quote quote-modified" data-post="2" data-topic="136036">
  <div class="title">
    <div class="quote-controls"></div>
    <img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/ptrblck/48/1823_2.png" class="avatar">
    <a href="https://discuss.pytorch.org/t/runtimeerror-trying-to-backward-through-the-graph-a-second-time-when-we-store-the-variable/136036/2">RuntimeError: Trying to backward through the graph a second time, when we store the variable</a> <a class="badge-category__wrapper " href="/c/autograd/7"><span data-category-id="7" data-drop-close="true" class="badge-category " title="A category of posts relating to the autograd engine itself."><span class="badge-category__name">autograd</span></span></a>
  </div>
  <blockquote>
    Your code is unfortunately not formatted correctly, but I assume the backward() operation is performed inside the loop. The first backward pass would free the intermediate activations from the forward pass, so that you wouldn’t be able to call backward a second time (if this is needed, use retain_graph=True, but it’s usually not the case). Since you are storing the e tensor in the L list and calculating the sum afterwards, the next backward pass would then try to backpropagate through both e ten…
  </blockquote>
</aside>


SofiaCP: <p>Hello,</p>
<p>Thanks for the reply. I am not using backward before cat.<br>
I was trying something like this:</p>
<pre><code class="lang-auto">placeholder={x: torch.empty((0,1),device=device) for x in ["l1","l2","l3", (...... etc) , "l18"]}
for batch_idx, (inputs, labels,filenames) in loop:
   
    inputs = inputs.to(device)
    labels = labels.to(device)

    # zero the parameter gradients
    optimizer.zero_grad()
    
    # forward
    with torch.set_grad_enabled(phase == 'train'):
        
        outputs = model(inputs)
        outputs=torch.squeeze(outputs,1)
        loss_raw=criterion(outputs_prob.float(),labels.float())
        loss= loss_raw * mask

#for each example in batch
for k in range(loss_masked.size(0)):
    
    #add the loss of each label to its placeholder
    placeholder["l1"]=torch.cat((placeholder["l1"],torch.unsqueeze(
        loss_masked[k,1],0)),0)

    placeholder["l2"]=torch.cat((placeholder["l2"],torch.unsqueeze(
        loss_masked[k,2],0)),0)

    placeholder["l3"]=torch.cat((placeholder["l3"],torch.unsqueeze(
        loss_masked[k,3],0)),0)

   placeholder ...... (etc) ............

    for l in ["l1","l2","l3", .... (etc) .... "l18"]:
        #if the number of non-zero elements of the placeholder is =4, do a backward pass
        # and clear the placeholder
        if placeholder[l][placeholder[l].sum(1)!=0].size(0)==4:      
            print("------    backprop of branch", l, "    ------")
            placeholder[l].clone().mean().backward()
            placeholder[l]=torch.empty((0,placeholder[l].size(1)),device=device)

#the loss of label 0 is always !=0             
loss_masked[:,0].mean().backward()

optimizer.step()
</code></pre>
<p>PS- I know the unsqueezing would be unnecessary in this small working example but in reality the 18 labels are distributed by four groups which I want to cluster together.</p>
<p>Any ideas?<br>
Thanks</p>

mMagmer: <p>I don’t know much about it but,<br>
<strong><a href="https://pytorch.org/docs/stable/generated/torch.Tensor.backward.html" rel="noopener nofollow ugc">retain_graph</a></strong> (<em>bool</em> ,* <em>optional</em> ) – If <code>False</code> , the graph used to compute the grads will be freed.<br>
If you want to backward grad through the model multiple times, you should set <code>retain_graph=True</code> . Something like —&gt; Loss.backward(retain_graph=True), and then you should manually free the graph.(I don’t know how)</p>

SofiaCP: <p>I agree that <code>loss.backward(retain_graph=True)</code> + manually free the graph should work, but I also don’t know how to do that.</p>
<p>Thanks for your help!</p>

my3bikaht: <p>I think this approach is way too complicated. You have a problem with (correct me if wrong) unbalanced loss, since some labels will have more non-zero losses than others. But IMO this is similar to unbalanced datasets. Why not just do weighted loss (number of non-zero losses per label / total non-zero losses)?</p>

SofiaCP: <p>Makes total sense and it is soooo much more obvious than the complicated solution I was going for. Sometimes all one needs is a fresh pair of eyes. Will let you know if it works as expected. Cheers</p>