I have read and heard this thing multiple times: Masked Image Modeling.
I had a clue of what it was about, just by the name itself. You mask parts of an image and get a model to predict what has been hidden. Ok. But I am not satisfied with this. This is just a shallow understanding of it. In order to fully grasp something, I need to get my hands dirty and derive the maths. So that’s exactly what I am proposing to you in this blog post.
For us to understand Masked Image Modeling, we need to start from an image.
Let
where is the height of our image, is its width and the number of channels. A typical natural image would have three such channels (R, G, B). But when dealing with other types of images, like hyperspectral ones, you could easily have hundreds of them. Figure 1 is here to give you a sense of what this image can look like.
In order to process such an image, it is usually common to divide it using non overlapping patches of size such that the number of patches is:
You will sometimes hear people saying that they ‘patchify’ the image. Cute. Figure 2 can help you visualize what a patch is looking like.
Each patch can therefore be written as
Why is that? Because this patch is having an height of P, a width of P and a “depth” of C channels.
So now our image becomes
Note that written as such, we have
In human words, this means that we now deal with an image, which is having N patches and each path is having elements.
From here we want to embed each patch. In order to do so we need to introduce a linear projection:
As you can see this linear projection is going to change the dimension we are working with (from we will move to ). Using this linear projection, our embedded patch is written as follows:
We can stack these embeddings such that
and therefore we have with matrix notations:
From Figure 3 you may get a sense of what we just did: we started from a patch with elements, we transformed it into one token of dimension , and we stacked those tokens together. In pratice however, all these operations happen with matrix multiplications just like you so with the previous equation.
Great. So now we have our tokens stacked together. Neat. What to do from here?
We are going to encode these tokens. In this following stage, we will be using self attention. However this mechanism does not have any notion of spatial position which is why we are going to introduce positional embeddings:
As you can see we have one positional embedding per token and if you look carefully does have the same dimension as . That’s interesting, because it means we can sum them up.
So the actual input for the encoder will be
Figure 4 is here to give you a visual clue of what we just did.
Now comes the time to mask patches. For this we need to define a masking ratio:
We then sample a set of indices such that
Our set of visible indices is
such that
We can rewrite our visible indices such that
From here we retain only their corresponding embeddings:
Figure 5 below shows one illustrative sampled mask. The dashed rows are masked, while the filled rows are the visible embeddings retained by the encoder.
Now that we have the embeddings for our visible patches, it is time to encode them. We can define a vision transformer encoder like so:
Now let
One way to understand this quantity is to say that each is a contextualized representation of the visible patch . This is due to self attention. Each token attends to the others to enrich its own context.
Now we are going to prepare the decoder input. Let’s assume the decoder works with a smaller dimension . Let’s project each encoder output using a new linear projection:
Let’s introduce a mask token
Now we are going to reconstruct a sequence of length (the original number of patches).
For every original position , we have
This means that we are ending up with
Here , at position i, either holds a contextualized embedding of a visible patch which directly comes from our encoder, either holds a masked embedding for the patches we kept hidden.
Since the decoder needs positional embeddings, let’s define
Thus we have
Figure 6 might give you a good overview of what we just did.
Now you may wonder why adding positional embedding here. Notice that for the masked positions, they are initially all having the same vector. Therefore adding its position indicates to the decoder which piece of the image it is supposed to reconstruct.
In order to decode, let’s define our decoder transformer as
We have
We now have one representation for each original image patch.
From here we want to go back to the pixel world. We need to remember that each patch contains pixel values. This means that we have to introduce the following linear projection:
Now for every patch we can write
We can stack these estimations such that
Figure 7 below summarizes the decoder and reconstruction path: each decoder representation is projected back into the pixel values of one patch, and the reconstructed patches are stacked into . Again, in practice, everything happens at the matrix multiplication level, we do not compute each element row wise and then stack, we just do matmul. I am voluntarily showing this here for educational purposes.
From here, we need a training objective. We are going to define one on the masked patches:
For this we can use a pixel-space mean square error (MSE):
However please note that even though I am showing here this example of reconstruction happening in the pixel space, you could also consider reconstructing at the latent space. Figure 8 is here to convey a visual clue for our loss.
A few words here to understand the narrative. The model is allowed to see a number of visible patch. The encoder must therefore produce a useful representation of for the decoder to be able to estimate . With the loss that we have defined, you can see that we are trying to minimize the difference between the real pixels (that we masked) and the estimations our model predicted for these.
In practice we usually set relatively high (e.g 0.75). Having a high masking ratio incentives the model to learn large scale structure rather than relying on local texture interpolation.
The complete training architecture looks like so:
Great! We made our way through Masked Image Modeling! Congrats!
From what I have understood, during pretraining the goal is mainly to learn the encoder and would be discarded. Later on can be used for classification, segmentation and so on.
I hope this blog post was useful. If you have any question, feedback or issues that need to be fixed in the post please feel free to reach out! :)