Appropriate method for weight sharing in convolutional layers?

aaroswings: <p>I’m imagining a scenario where I want to apply a learnable convolution layer to multiple Tensor inputs in a module. I would want the layer to learn from all inputs using the average of the gradients of the shared convolution filter w.r.t. each input.</p>
<p>I see this question has been <a href="https://discuss.pytorch.org/t/a-few-question-about-sharing-weights-of-convolution-kernels-and-dilation-convlution-in-pytorch/99468">asked before</a>, so let me expand on it a bit.</p>
<p>Say I have a convolution module that shares weights like this:</p>
<pre><code class="lang-auto">class SharedFilterConv2d(nn.Module):
  def __init__(self):
    super(SharedFilterConv2d, self).__init__()
    self.conv = nn.Conv2d(3, 3, 3)
    self.shared_weight = nn.Parameter(self.conv.weight)

  def forward(self, inputs):
    outputs = []
    for x in inputs:
      outputs.append(F.conv2d(x, self.shared_weight, ...))
    return torch.cat(outputs, dim=1)
</code></pre>
<p>The output of this operation is passed on through the network, and eventually <code>.backward()</code> is called on some value that depends on them. Since the weights need gradients calculated with respect to multiple inputs in one backward pass, what happens to the gradients? Are they averaged when <code>optimizer.step()</code> is called? Should they be averaged? Do the gradients accumulate for all inputs in the input list, even if the input list varies in size?<br>
I am trying to get a grasp on how autograd handles this.</p>

ptrblck: <aside class="quote no-group" data-username="aaroswings" data-post="1" data-topic="145093">
<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/aaroswings/48/32628_2.png" class="avatar"> aaroswings:</div>
<blockquote>
<p>Are they averaged when <code>optimizer.step()</code> is called?</p>
</blockquote>
</aside>
<p>No, <code>optimizer.step()</code> will just update the passed parameters using the <code>.grad</code> attribute of each parameter.<br>
The gradients will be accumulated in the <code>.grad</code> attribute if you are reusing the <code>shared_weight</code> parameter.</p>

aaroswings: <p>Thanks <a class="mention" href="/u/ptrblck">@ptrblck</a> .<br>
Since the gradients are added up, in order to effectively average the update across the different forward passes, would dividing the gradients by the number of forward passes shared weights are used in, using a backward hook, be a good way to do this?</p>
<p>I see in <a href="https://discuss.pytorch.org/t/how-to-use-shared-weights-in-different-layers-of-a-model/71263/2">this topic</a>, <a class="mention" href="/u/alband">@albanD</a> recommended sharing weights by <code>.clone()</code>ing the same weight into the <code>weight</code> attribute of different modules on the forward pass. Are there reasons that would better or more memory efficient than using <code>F.conv2d</code> and passing in the weight tensor to a functional call?</p>

ptrblck: <p>Both approaches should work and the difference might be the coding style you prefer (functional API vs. modules).</p>
<p>Yes, dividing the gradients by the number of steps (or the loss) should work, too.</p>