3
$\begingroup$

The VQ-VAE implimentation:https://colab.research.google.com/github/zalandoresearch/pytorch-vq-vae/blob/master/vq-vae.ipynb

quantized = inputs + (quantized - inputs).detach()

Why are we subtracting and adding input to quantized result?

$\endgroup$
2
  • $\begingroup$ Numerical stability. $\endgroup$ Commented Mar 11, 2021 at 19:02
  • $\begingroup$ @AbhishekVerma it would be really helpful if you could please explain this with a little detail....kind of a newbie here. $\endgroup$
    – Vortex
    Commented Mar 12, 2021 at 20:09

1 Answer 1

4
$\begingroup$

This is to get the gradient to "skip" the quantization part.

The trick implements the red arrow in the original paper's diagram:

VQ-VAE architecture, with red arrow signifying gradient skipping from quantized code vector to encoder output

Simplified example: Rounding

Let's simplify this a bit and imagine we want to use rounding in our architecture:

import torch
x = torch.tensor([1.1, 2.1], requires_grad=True)
y = 2*x
z = torch.round(y)
r = z.sum()

The graph (torchviz.make_dot) looks like this:

enter image description here

We can look at the output:

r 
# tensor(6., grad_fn=<SumBackward0>)

All is looking good. However, when we try to compute the gradient, we get a tensor of zeroes:

r.backward()
x.grad
# tensor([0., 0.])

This makes sense: the rounding function has derivative zero almost everywhere:

enter image description here

However, it also means we cannot train our network.

To circumvent that, we could simply tell the gradient to skip the rounding network. To do that, we use detach, a function that tells PyTorch to detach a vector from the computational graph.

x = torch.tensor([1.1, 2.1], requires_grad=True)
y = 2*x
z = torch.round(y)
z = y + (z - y).detach() # Detach everything between z and y, including z
r = z.sum()

We still get the same answer for $r$

r 
# tensor(6., grad_fn=<SumBackward0>)

But we now also get reasonable gradients

r.backward()
x.grad
# tensor([2., 2.])

Thanks to the fact that the gradient now "skips" the rounding part:

enter image description here

Back to VQ-VAE

In VQ-VAE, we are replacing the output of the encoder (the input to the vector quantization layer) with the code vector. In some sense, this similar to "rounding", only this time we're "rounding" the encoder's output to the nearest code vector.

enter image description here

We therefore run into the same problem. Here's what the authors say about it

Note that there is no real gradient defined for [the quantization], however we approximate the gradient similar to the straight-through estimator and just copy gradients from decoder input $z_q(x)$ to encoder output $z_e(x)$

which is precisely what

quantized = inputs + (quantized - inputs).detach()

does in the notebook.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .