Torchvision model with task-specific BN layers

SofiaCP: <p>Hi,</p>
<p>Is it possible to create task-specific BN layers starting from a torchvision model? Let’s say I have two tasks for the same data and want them to share all the convolutional layers of a torchvision model, but have separate BN parameters for each task. How can that be implemented?</p>
<p>Thanks!</p>

Karthik_Ganesan: <p>Can you provide some more info? What do you mean by ‘task’? Are you going to train the same model using two sets of hyperparameters?</p>

SofiaCP: <p>Hi <a class="mention" href="/u/karthik_ganesan">@Karthik_Ganesan</a>,</p>
<p>Thank you for your answer!</p>
<p>By “task” I mean a multi-task learning scenario, where typically there is a shared backbone consisting of convolutional blocks shared across all tasks, followed by task-specific fully-connected heads (one head per task).</p>
<p>What I want to do, inspired by this paper <a href="https://openaccess.thecvf.com/content_CVPR_2019/papers/Chang_Domain-Specific_Batch_Normalization_for_Unsupervised_Domain_Adaptation_CVPR_2019_paper.pdf" rel="noopener nofollow ugc">https://openaccess.thecvf.com/content_CVPR_2019/papers/Chang_Domain-Specific_Batch_Normalization_for_Unsupervised_Domain_Adaptation_CVPR_2019_paper.pdf</a>, is to not only have task specific FC heads but also make the batch norm layers task specific, while sharing all the remaining layer across all tasks.</p>
<p>I hope it’s clearer now. Thanks!</p>

Karthik_Ganesan: <p>Hi <a class="mention" href="/u/sofiacp">@SofiaCP</a> , Thank you for clarifying. I took a quick look at the paper and it seems like building the model should be straightforward. My understanding is they pass one mini-batch from each domain at a time when training.</p>
<p>The easiest option then would be to write your own <code>domain specific BN</code>(DSBN) layer which stores two sets of BN parameters. You would also have an additional input to this layer which indicates which task the current mini-batch is for. Based on this you can set the task for the DSBN layers in your network.</p>
<p>I’m not familiar with unsupervised domain adaption but I believe that the backward pass will work the same; you just set the layer’s parameters based on the task and AutoGrad will generate the necessary gradients.</p>

SofiaCP: <p>Dear <a class="mention" href="/u/karthik_ganesan">@Karthik_Ganesan</a>,</p>
<p>Thank you for replying. Based on your answer, I am guessing that it is not possible to simply import a torchvision model (e.g. <code>densenet121</code>) and incorporate the <code>DSBN</code> by replacing the model’s BN layers. Is that so?</p>
<p>Could you possibly provide some more details on how to achieve this?</p>
<p>Thanks!<br>
Sofia</p>

Karthik_Ganesan: <p>Yes, basically that is what you need to do. But in PyTorch its a bit hard to change the layers of a model once it is already built. So you can import a model like <code>densenet121</code> but then you will have to iterate over all the layers to replace the BN layers with DSBN layers.</p>
<p>Another way would be to just create the model directly by specifying all the layers. And then in that file, replace all the BN layers with the custom DSBN layers. Since the network will have to be trained with the DSBN layers anyway, this shouldn’t be much more difficult than directly importing a model.</p>

SofiaCP: <p>I’ve managed to iterate over the layers of the model and replace the BN layers with DSBN layers, which I have defined the following way:</p>
<pre><code class="lang-auto">
class DSBN(nn.Module):
    def __init__(self, num_channels):
        super(DSBN, self).__init__()
        
        self.norm1 = nn.BatchNorm2d(num_channels)
        self.norm2 = nn.BatchNorm2d(num_channels)
        self.norm3 = nn.BatchNorm2d(num_channels)
    
    def forward(self, x, group="1"):
        assert (group in ["1","2","3"])
        if group=="1":
            print("using group 1 BN")
            x = self.norm1(x)
        elif group=="2":
            print("using group 2 BN")
            x = self.norm2(x)
        elif group=="3":
            print("using group 3 BN")
            x = self.norm3(x)
        return x
</code></pre>
<p>How can I insert a switch to decide which <code>bn</code> layer of <code>DSBN</code> to use depending on the input? My goal is to pass the group as an argument to the model:</p>
<pre><code class="lang-auto">model= model()
out_domain1= model(x,group=1)
out_domain2=model(x,group=2)
out_domain3=mode(x,group=3)
</code></pre>
<p>Thanks!</p>

Karthik_Ganesan: <p>You should be able to just add the group to the DSBN class. Since this isn’t a trainable parameter, you can just add it like this:</p>
<pre><code class="lang-auto">class DSBN(nn.Module):
    def __init__(self, num_channels):
        super(DSBN, self).__init__()
        
        self.group = None
        self.norm1 = nn.BatchNorm2d(num_channels)
        self.norm2 = nn.BatchNorm2d(num_channels)
        self.norm3 = nn.BatchNorm2d(num_channels)
    
    def forward(self, x):
        assert (self.group in ["1","2","3"])
        if self.group=="1":
            print("using group 1 BN")
            x = self.norm1(x)
        elif self.group=="2":
            print("using group 2 BN")
            x = self.norm2(x)
        elif self.group=="3":
            print("using group 3 BN")
            x = self.norm3(x)
        return x
</code></pre>
<p>Then, when you run each task, go through and set the <code>group</code> value for each <code>DSBN</code> layer to be the group you want. That should be very similar to how you accessed all the layers and replaced <code>BatchNorm</code> with <code>DSBN</code>.</p>

SofiaCP: <p>That is exactly what I ended up doing. Initially, I was trying to set the <code>group</code> argument in the forward of the <code>DSBN</code> module but ended up going with this because it is easier to implement.</p>
<p>Thank you!</p>