Simple way to inverse transform ? Normalization

tsterin: <p>Hi all!<br>
I’m using  torchvision.transforms to normalize my images before sending them to a pre trained vgg19.<br>
Therefore I have the following:</p>
<pre><code>normalize = transforms.Normalize(mean = [ 0.485, 0.456, 0.406 ],
                      std = [ 0.229, 0.224, 0.225 ])
</code></pre>
<p>My process is generative and I get an image back from it but, in order to visualize, I’d like to “un-normalize” it.<br>
Is there a simple way, in the API, to inverse the normalize transform ?<br>
Or should it be coded by hand ?</p>
<p>Also I’m a bit surprise that the process works really fine without any normalization step.<br>
The whole thing is about style transfer, from this paper: <a href="https://arxiv.org/abs/1508.06576" rel="nofollow noopener">https://arxiv.org/abs/1508.06576</a>, and there’s a nice pytorch implementation outhere (not mine) here: <a href="https://github.com/alexis-jacq/Pytorch-Tutorials" rel="nofollow noopener">https://github.com/alexis-jacq/Pytorch-Tutorials</a>.</p>
<p>That implementation doesn’t normalize anything before feeding images to vgg19 and the results are OK.<br>
Basically vgg19 is used to extract features from feeded images.<br>
Your thoughts on why it stills works ?</p>

peter_fox: <p>did you find any solution for your problem yet? I also normalize before training, to get better loss values, but the generated images look very dark and in terms of colors strange.</p>

joels: <p>Hey<br>
A way to reverse the normalization does not seem to exist. However it is pretty straightforward to create a simple class that does so.</p>
<pre><code>class UnNormalize(object):
    def __init__(self, mean, std):
        self.mean = mean
        self.std = std

    def __call__(self, tensor):
        """
        Args:
            tensor (Tensor): Tensor image of size (C, H, W) to be normalized.
        Returns:
            Tensor: Normalized image.
        """
        for t, m, s in zip(tensor, self.mean, self.std):
            t.mul_(s).add_(m)
            # The normalize code -&gt; t.sub_(m).div_(s)
        return tensor
</code></pre>
<p>You instantiate it with the same arguments used for the normalize. and then use it the same way</p>
<pre><code>unorm = UnNormalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
unorm(tensor)</code></pre>

svd3: <p>Most easiest way would be:</p>
<pre><code class="lang-auto">invTrans = transforms.Compose([ transforms.Normalize(mean = [ 0., 0., 0. ],
                                                     std = [ 1/0.229, 1/0.224, 1/0.225 ]),
                                transforms.Normalize(mean = [ -0.485, -0.456, -0.406 ],
                                                     std = [ 1., 1., 1. ]),
                               ])

inv_tensor = invTrans(inp_tensor)
</code></pre>

mjust.lkc: <p>A more concise approach based on Saurabh’s answer:</p>
<pre><code class="lang-auto">inv_normalize = transforms.Normalize(
    mean=[-0.485/0.229, -0.456/0.224, -0.406/0.255],
    std=[1/0.229, 1/0.224, 1/0.255]
)
inv_tensor = inv_normalize(tensor)
</code></pre>

svd3: <p>You’ve adjusted the mean wrongly<br>
the mean should be<br>
<code>mean=[-0.485 * 0.229, -0.456 * 0.224, -0.406 * 0.255]</code></p>
<p>because the renormalization needs the answer to be (X/sigma - mu) = (X - mu*sigma)/sigma</p>

mjust.lkc: <p>The inverse normalization should be<br>
x = z*sigma + mean<br>
= (z + mean/sigma) * sigma<br>
= (z - (-mean/sigma)) / (1/sigma),<br>
since the normalization process is actually z = (x - mean) / sigma if you look carefully at the documentation of <code>transforms.Normalize</code>. I tested my original code and it worked fine.</p>

dizcza: <p>Keep in mind, <code>torchvision.transforms.Normalize</code>'s operations are <em>in-place</em>. If you want immutable implementation,</p>
<pre><code class="lang-auto">class NormalizeInverse(torchvision.transforms.Normalize):
    """
    Undoes the normalization and returns the reconstructed images in the input domain.
    """

    def __init__(self, mean, std):
        mean = torch.as_tensor(mean)
        std = torch.as_tensor(std)
        std_inv = 1 / (std + 1e-7)
        mean_inv = -mean * std_inv
        super().__init__(mean=mean_inv, std=std_inv)

    def __call__(self, tensor):
        return super().__call__(tensor.clone())
</code></pre>

hongsukchoi: <p>Thank you.<br>
But there is a typo in the third element of mean.<br>
It’s 0.225 not 0.255</p>
<pre><code class="lang-auto">inv_normalize = transforms.Normalize(
    mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
    std=[1/0.229, 1/0.224, 1/0.255]
)
inv_tensor = inv_normalize(tensor)
</code></pre>

nofreewill: <p>There is still a typo in the std <img src="https://discuss.pytorch.org/images/emoji/apple/smiley.png?v=9" title=":smiley:" class="emoji" alt=":smiley:"></p>
<pre><code class="lang-auto">inv_normalize = transforms.Normalize(
   mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
   std=[1/0.229, 1/0.224, 1/0.225]
)
inv_tensor = inv_normalize(tensor)
</code></pre>

edgarriba: <p>you can also use <code>kornia.denormalise</code><br>
<a href="https://kornia.readthedocs.io/en/latest/color.html#kornia.color.Denormalize" class="onebox" target="_blank" rel="nofollow noopener">https://kornia.readthedocs.io/en/latest/color.html#kornia.color.Denormalize</a></p>

Ksalomon: <aside class="quote no-group" data-username="tsterin" data-post="1" data-topic="4821">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/t/c6cbf5/48.png" class="avatar"> tsterin:</div>
<blockquote>
<p>normalize = transforms.Normalize(mean = [ 0.485, 0.456, 0.406 ], std = [ 0.229, 0.224, 0.225 ])</p>
</blockquote>
</aside>
<p>A slightly modified version of the above code</p>
<p>This suppose a defined mean and std.</p>
<pre><code class="lang-auto">inv_normalize = transforms.Normalize(
   mean= [-m/s for m, s in zip(mean, std)],
   std= [1/s for s in std]
)

inv_tensor = inv_normalize(tensor)
</code></pre>

shwinshaker: <p>Sorry to bother, but this doesn’t seem to work for me. As <code>inv_normalize(tensor)</code> will raise a <code>TypeError</code> complaining  <code>tensor is not a torch image.</code></p>
<p>Is this a version issue? Thanks in advance!</p>

ptrblck: <p>Could you check the shape and type of the input you are passing to this method?<br>
I guess the number of dimensions of type might be unexpected and thus <code>Normalize</code> might raise this issue.</p>

yptheangel: <p>following <a class="mention" href="/u/joels">@joels</a>  method, it worked for me.</p>
<pre><code class="lang-auto">def inverse_normalize(tensor, mean, std):
    for t, m, s in zip(tensor, mean, std):
        t.mul_(s).add_(m)
    return tensor

input_tensor= inverse_normalize(tensor=input_tensor, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
</code></pre>

yptheangel: <p>I found out that even after performing the inverse normalization, the <strong>inverse normalized opencv imag is not equivalent to an original opencv via <code>cv2.imread</code></strong>. I am sticking to getting a mask from pytorch model output and multiplying it with original frame via <code>cv2.imread</code></p>

hustzwen: <p>like this,</p>
<pre><code class="lang-auto">def inverse_normalize(tensor, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)):
    mean = torch.as_tensor(mean, dtype=tensor.dtype, device=tensor.device)
    std = torch.as_tensor(std, dtype=tensor.dtype, device=tensor.device)
    if mean.ndim == 1:
        mean = mean.view(-1, 1, 1)
    if std.ndim == 1:
        std = std.view(-1, 1, 1)
    tensor.mul_(std).add_(mean)
    return tensor
</code></pre>

Apogentus: <p>Works like charm. Normalization+unnormalization give original image.</p>

Giorgio: <p>this code seems simpler</p>
<p>If you want to reverse the normalization, all you need to do is to use a new normalization, with slight modifications:</p>
<pre><code class="lang-auto">mean = torch.tensor([0.4915, 0.4823, 0.4468])
std = torch.tensor([0.2470, 0.2435, 0.2616])

normalize = transforms.Normalize(mean.tolist(), std.tolist()) 

unnormalize = transforms.Normalize((-mean / std).tolist(), (1.0 / std).tolist())
img_unn = unnormalize(img)

plt.imshow(img_unn.permute(1, 2, 0))
plt.show()

</code></pre>
<p>Anyway I hate the fact that we don’t have un-normalize (de-normalize), it would be quite helpful for regression neural networks too.</p>
<aside class="onebox githubissue" data-onebox-src="https://github.com/pytorch/vision/issues/528">
  <header class="source">

      <a href="https://github.com/pytorch/vision/issues/528" target="_blank" rel="noopener nofollow ugc">github.com/pytorch/vision</a>
  </header>

  <article class="onebox-body">
    <div class="github-row">
  <div class="github-icon-container" title="Issue">
	  <svg width="60" height="60" class="github-icon" viewbox="0 0 14 16" aria-hidden="true"><path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>
  </div>

  <div class="github-info-container">
    <h4>
      <a href="https://github.com/pytorch/vision/issues/528" target="_blank" rel="noopener nofollow ugc">[Feature Request] Un-Normalize Image Tensor</a>
    </h4>

    <div class="github-info">
      <div class="date">
        opened <span class="discourse-local-date" data-format="ll" data-date="2018-06-05" data-time="13:37:51" data-timezone="UTC">01:37PM - 05 Jun 18 UTC</span>
      </div>

        <div class="date">
          closed <span class="discourse-local-date" data-format="ll" data-date="2018-06-06" data-time="16:36:25" data-timezone="UTC">04:36PM - 06 Jun 18 UTC</span>
        </div>

      <div class="user">
        <a href="https://github.com/varagrawal" target="_blank" rel="noopener nofollow ugc">
          <img alt="varagrawal" src="https://avatars.githubusercontent.com/u/39499035?v=4" class="onebox-avatar-inline" width="20" height="20">
          varagrawal
        </a>
      </div>
    </div>

    <div class="labels">
    </div>
  </div>
</div>

  <div class="github-row">
    <p class="github-body-container">Basically the inverse of `transforms.Normalize` as this will allow us to visuali<span class="show-more-container"><a href="" rel="noopener" class="show-more">…</a></span><span class="excerpt hidden">ze tensors during training more easily.</span></p>
  </div>

  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>


guillermogotre: <p>Here is some working code as this is still one of the first Google results in inverse normalization. It follows torchvision.transforms convention so you can compose it.</p>
<p><a href="https://gist.github.com/guillermogotre/844024ac37d35c8a00a6133887cbd18b" rel="noopener nofollow ugc">Code Gist</a></p>
<pre><code class="lang-auto">class UnNormalize(torchvision.transforms.Normalize):
    def __init__(self,mean,std,*args,**kwargs):
        new_mean = [-m/s for m,s in zip(mean,std)]
        new_std = [1/s for s in std]
        super().__init__(new_mean, new_std, *args, **kwargs)
</code></pre>