Is this a proper way to update an optimizer for 3 models?

Miguel_Campos: <p>Hello all.</p>
<p>I have three <code>nn.Module</code> joined by using a <code>nn.ModuleList</code></p>
<blockquote>
<p>decoder = nn.ModuleList([create_model() for _ in range(3)])</p>
</blockquote>
<p>I have one optimizer with the trainable parameters of the 3 decoder (remind is a list of 3 different models)</p>
<blockquote>
<p>optimizer = Adam(decoder.parameters(), lr=1e-3, weight_decay=1e-5)</p>
</blockquote>
<p>Each <strong>decoder</strong> ( decoder[0], decoder[1] and decoder[2]  ) has different inputs and  I need update their weights separately.</p>
<p>This is a brief version of how my loop looks.</p>
<pre><code class="lang-auto">for epoch in range(100):
    for features in dataloader: # features is a list of 3 inputs.
        for idx, input in enumerate(features): 
            
            # Forward pass
            loss = decoder[idx](input) # input[0] into decoder[0], input[1] into decoder[1] and input[2] into decoder[2]

            # Backward pass
            optimizer.zero_grad()
            loss.backward()
           
            # Adam step
            optimizer.step()
</code></pre>
<p>Am I updating the weight of each model with their corresponding error correctly?</p>
<p>If you prefer. here is a more complex version of my training loop:</p>
<pre><code class="lang-auto">for e in range(100):
    for inputs in dataloader:
        with torch.no_grad():
            features = feature_extractor_resnet50(inputs) # output of layers [1, 2, 3]
    
        for idx, input in enumerate(features):

            # Forward pass
            loss = decoder[idx](input)

            # Backward pass
            optimizer.zero_grad()
            loss.backward()

            # Adam step
            optimizer.step()
</code></pre>

InnovArul: <aside class="quote no-group" data-username="Miguel_Campos" data-post="1" data-topic="149039">
<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/miguel_campos/48/14538_2.png" class="avatar"> Miguel_Campos:</div>
<blockquote>
<pre><code class="lang-auto">        for idx, input in enumerate(features):

            # Forward pass
            loss = decoder[idx](input)

            # Backward pass
            optimizer.zero_grad()
            loss.backward()

            # Adam step
            optimizer.step()
</code></pre>
</blockquote>
</aside>
<p>I think, you should have <code>optimizer.zero_grad()</code>, <code>optimizer.step()</code> as below to update the parameters altogether after 3 forward passes to 3 decoders.</p>
<pre><code class="lang-auto">for e in range(100):
    for inputs in dataloader:
        with torch.no_grad():
            features = feature_extractor_resnet50(inputs) # output of layers [1, 2, 3]

        # zero gradients
        optimizer.zero_grad()

        for idx, input in enumerate(features):

            # Forward pass
            loss = decoder[idx](input)

            # Backward pass
            loss.backward()
    
        # Adam step  (only once for every input)
        optimizer.step()
</code></pre>

Miguel_Campos: <aside class="quote no-group" data-username="InnovArul" data-post="2" data-topic="149039">
<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/innovarul/48/5282_2.png" class="avatar"> InnovArul:</div>
<blockquote>
<p><code> # Backward pass</code></p>
</blockquote>
</aside>
<p>What you said makes sense, although I’m still testing it, I can say that training time was reduced from 40 to 30 minutes which is a great news.</p>

Miguel_Campos: <p>It seems that the performance keeps similar with your methodology. I think it’s ok.</p>

Miguel_Campos: <p>Sorry but it doesn’t work as well as I though. After 3 test using the same seed, it seems that your proposal is a bit behind that mine. This is 91% of performance (your) vs 93% performance (mine). Why could be this. For me, your implementation make sense…maybe this normal but is very rare after 3 tests.</p>
<p>I open the complete details of the training loop.</p>
<ol>
<li>Firstly, I extract the output of resnet50.layers1, resnet50.layer2 and resnet50.layer3 using certain data.</li>
<li>I have 3 decoders, one for each output of the resnet. This is to say, decoder1 use resnet50.layer1, decoder2 use resnet50.layer2 and decoder3 use resnet50.layer3.</li>
<li>Then, weights of each decoder need be updated.</li>
</ol>
<pre><code class="lang-auto">model.decoder.train()
    train_loss = list()
    for inputs in dataloader:
        inputs = inputs.to(device)
        with torch.no_grad():
            features = model.feature_extractor_resnet50(inputs) # output of layers [1, 2, 3] of the resnet

        for idx, feat in enumerate(features): # features is a list of 3 items
            # Forward
            loss = model.decoder[idx](feat) # this is, decoder[0] use resnet.output_layer[0], decoder[1] use resnet.output_layer[1] and so son
            
            # Backward
            optimizer.zero_grad()
            loss.backward()

            # Update weights
            torch.nn.utils.clip_grad_norm_(model.decoder[idx].parameters(), 1e0)
            optimizer.step()

            train_loss.append(loss.item())
</code></pre>
<p>Thanks for the help <img src="https://discuss.pytorch.org/images/emoji/apple/slight_smile.png?v=12" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>

InnovArul: <p>I couldn’t think of a concrete reason for this behavior.<br>
I will think more and come back if I find some valid explanations.</p>

Miguel_Campos: <p>Yes, tomorrow I will test it again. Maybe I did a mistake. I let you know. Thanks</p>

ksmdanl: <p>You might want to try to just sum up the losses, zero the gradients, and pass them to the backward function as opposed to looping over three modules. This approach would draw more or less the same computation graph.</p>
<p>I assume you want to train a multitask problem, it’s worth to try to multiply each loss with certain weight.</p>

Miguel_Campos: <p>Hello. I will check to sum the losses. It could work.<br>
About pondering the losses its a good catch.</p>

Miguel_Campos: <p>Hello all.</p>
<p>Well after a long time I would like to re-open this thread because after trying extensive tests there is someting I can not understand.</p>
<p>I will refresh what I am doing:</p>
<ul>
<li>I have to train three decoders</li>
<li>I have one optimizer for the three decoders</li>
<li>One epoch is considered when the three decoders are used</li>
</ul>
<p>Here is the code that creates the decoders and optimizer function</p>
<pre><code class="lang-auto">decoder_list = nn.ModuleList([decoder1(), decoder2(), decoder3()])
..
optimizer = Adam(decoder_list.parameters(), lr=1e-3)
</code></pre>
<p>Here I show a simplex version of my training loop</p>
<pre><code class="lang-auto">for epoch in range(100):
    for features in dataloader: #  list of 3 inputs, one for each decoder.
        for id, input in enumerate(list_features): 
            # Forward pass
            loss = decoder_list[id](input)
            # Backward pass
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(decoder_list[id].parameters(), 1.)
            # Adam step
            optimizer.step()
</code></pre>
<p>Following the guidelines of <a class="mention" href="/u/innovarul">@InnovArul</a> I tried this version of the training loop but the performance is a bit worse</p>
<pre><code class="lang-auto">for epoch in range(100):
    for features in dataloader: #  List of three elements, one per decoder.
        # Zero gradient per each data
        optimizer.zero_grad()
        for id, input in enumerate(list_features): 
            # Forward pass per each decoder
            loss = decoder_list[id](input)
            # Backward pass per each decoder
            loss.backward()

      # After the full epoch, clip gradients of the 3 decoders
      torch.nn.utils.clip_grad_norm_(decoder_list.parameters(), 1.)
      # Adam step
      optimizer.step()
</code></pre>
<p>Then I followed the guidelines of <a class="mention" href="/u/ksmdanl">@ksmdanl</a> through accumulating the losses but same as before, performance is a bit worse</p>
<pre><code class="lang-auto">for epoch in range(100):
    for features in dataloader: #  List of three elements, one per decoder.
        # Zero gradient per each data
        optimizer.zero_grad()
        loss = 0.0 # would be the accumulation of the three decoders
        for id, input in enumerate(list_features): 
            # Forward pass per each decoder
            loss += decoder_list[id](input)

      # Backward after the full epoch
      loss.backward()
      torch.nn.utils.clip_grad_norm_(decoder_list.parameters(), 1.)
      # Adam step
      optimizer.step()
</code></pre>
<p>Maybe I am making the mistake in <code>clip_grad_norm_</code> ? Why just is it working the first method? for me, the other ones make sense.</p>
<p>Thanks</p>