# [GLIP](https://github.com/microsoft/GLIP/tree/main) 程式碼
## Detection Model

1. language_dict_features的 mask -> one-hot
首先建立一個 和原本 mask 張量形狀一樣 的張量(tensor),但裡面的值全部都是 0。同時確保它被放在同一個 GPU/CPU 裝置上(用 .device)。
```python
new_masks = torch.zeros_like(language_dict_features['masks'],
device=language_dict_features['masks'].device)
```
將前面幾個維度(對應到類別數)全部設為 1。
```python
new_masks[:, :self.cfg.MODEL.DYHEAD.NUM_CLASSES] = 1
```
最後,將這個新建的 new_masks 回存回原本的 language_dict_features 裡面。
```python
language_dict_features['masks'] = new_masks
```
## Region Proposal Network (RPN)
## VLDyHead
1. `torch.nn.functional.normalize` : 會把輸入張量的每個向量除以它的範數(通常是 L2 norm),讓每個向量的長度變成 1。寫成數學就是
$$y=\cfrac{x}{\vert \vert x\vert \vert _p}$$
2. Contrastive Align Projection
有 對比學習(contrastive learning) 和 跨模態對齊(alignment) 的概念
```python
self.contrastive_align_projection_text = nn.Linear(channels, contrastive_hdim, bias=True)
proj_tokens = F.normalize(
self.contrastive_align_projection_text(embedding), p=2, dim=-1
)
```
- 文字 embedding (embedding)
通過一個線性層 (nn.Linear) 映射到新的向量空間,這層叫做 projection。
- F.normalize
把向量歸一化到單位長度,使得它只保留方向資訊(方便做餘弦相似度)。
- 結果 (proj_tokens)
繼續與圖像特徵的 projection 進行 contrastive loss 訓練
為什麼要「projection + alignment + contrastive」?這是多模態模型(例如 CLIP, MDETR, ALBEF)的共同做法。
核心目標是:把圖片和文字嵌入映射到同一語義空間,讓語意相近的對應樣本在這個空間裡更接近。
- projection_text:把文字 embedding 投影到對比空間
- projection_image:把圖片 embedding 投影到同一空間
然後算相似度矩陣
```python
logits = proj_image @ proj_text.T # 內積或 cosine similarity
```
計算損失
```python
loss = contrastive_loss(logits)
```
這樣模型會學會
- 圖像 A ↔ 文字 A 相似度高
- 圖像 A ↔ 文字 B 相似度低
3. 矩陣乘法 (matrix multiplication)
```python
torch.matmul(embedding, self.bias_lang)
```
如果 A 是形狀 (batch, d1),B 是形狀 (d1, d2),輸出 (batch, d2)。
$$ Y = A \times B$$
| 函數| 全名| 維度要求| 適用場景| broadcasting |
|-|-|-|-|-|
|`torch.matmul` / `@`| **matrix multiply**| 任意維度 ≥ 1 | 通用矩陣乘法(自動擴展 batch)|✅ |
| `torch.bmm`| **batch matrix multiply** | 僅支援 **3D 張量** (B, N, M) × (B, M, P) | 一批矩陣乘法(批次版 matmul)| ❌|
4. permute and flatten
```python
def permute_and_flatten(layer, N, A, C, H, W):
layer = layer.view(N, -1, C, H, W)
layer = layer.permute(0, 3, 4, 1, 2)
layer = layer.reshape(N, -1, C)
return layer
```
整體過程
```python
原始輸入 layer: [N, A*C, H, W]
↓ reshape
[N, A, C, H, W]
↓ permute
[N, H, W, A, C]
↓ flatten
[N, H*W*A, C]
```
- 第一步 : 將 [N, A*C, H, W] → [N, A, C, H, W]
原本每個位置的通道數是 A*C,這裡把它拆成:
- A: 每個位置有 A 個 anchor
- C: 每個 anchor 有 C 個輸出(例如類別或 box offsets)
- 第二步 : 調整維度順序
`layer = layer.permute(0, 3, 4, 1, 2)` 會重新排列維度
```bash
( N, A, C, H, W ) → ( N, H, W, A, C )
```
- 第三步 : 攤平成 anchor 列表
`layer = layer.reshape(N, -1, C)` 把 (H, W, A) 攤平成一個維度,也就是「所有空間位置的所有 anchor」
5. `bias = dot_product_proj_tokens_bias.unsqueeze(1).repeat(1, A, 1)`
是在 改變 bias 的張量形狀 (shape),讓它能在計算中與其他張量(例如 anchor 或 feature map)對齊進行 廣播 (broadcast)。
- `repeat(1, A, 1)` : 會沿著每個維度「重複」張量
```python
bias = bias.repeat(1, A, 1)
```
第 0 維(batch)重複 1 次 → 不變
第 1 維(anchor 數 A)重複 A 次
第 2 維(通道 C)不變
```python
dot_product_proj_tokens_bias.shape = [2, 256]
```
經過 `repeat()`
```python
bias.shape after repeat = [2, 9, 256]
```
## [VLFuse](https://github.com/microsoft/GLIP/blob/main/maskrcnn_benchmark/modeling/rpn/vldyhead.py#L350)

## [BiMultiHeadAttention](https://github.com/microsoft/GLIP/blob/main/maskrcnn_benchmark/utils/fuse_helper.py#L171)

1. 維度轉換
```pyhton
q = feat.flatten(2).transpose(1, 2)
# 從 第二維 把所有維度展開
# 把維度 1 和 2 交換
# (bs, C, H, W) -> (bs, C, H*W) -> (bs, H*W, C)
```
2. Transformer -> Multi-Head
```python
def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int):
return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous()
```
| 步驟 | 操作| 輸入形狀 | 輸出形狀| 目的|
|-|-|-|-| -|
|1|`view(bsz, seq_len, num_heads, head_dim)`| `(bsz, seq_len, embed_dim)`|`(bsz, seq_len, num_heads, head_dim)`|拆成多頭|
|2|`transpose(1, 2)`|`(bsz, seq_len, num_heads, head_dim)`|`(bsz, num_heads, seq_len, head_dim)`| 交換維度方便注意力計算 |
|3|`contiguous()`|非連續張量|連續張量|確保可安全運算|
`contiguous()` : 確保 tensor 在記憶體中的儲存是連續的(contiguous memory layout)。這樣才不會有 `RuntimeError: view size is not compatible with input tensor's size and stride` 之類的錯誤
3. Query 和 Key 的內積
```python
attn_weights = torch.bmm(query_states, key_states.transpose(1, 2))
```
也就是在計算 attention 中的 $QK^T$
$$Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V$$
`torch.bmm(A, B)` : 對一批 (batch) 的矩陣做乘法運算。舉例:
```bash
A 的 shape 是 (batch, n, m)
B 的 shape 是 (batch, m, p)
則輸出 shape 是 (batch, n, p)
```
4. Stabilization Trick: 把所有值減掉最大值
```python
attn_weights = attn_weights - attn_weights.max()
```
如果直接去計算 $softmax(\frac{QK^T}{\sqrt{d_k}})$
$$Softmax(x)_i = \cfrac{e^{x_i}}{\sum_j{e^{x_j}}}$$
中的 $e^{x_j}$ 可能會很大到接近 $\infty$ 然後爆掉,可能又會因為 $e^{x_j}$ 太小而都是 0
所以要用 數值穩定化 (Stabilization Trick) 的技巧
因為 softmax 只取決於相對大小,而不是絕對值,那麼對所有元素減去同一個常數(例如最大值):
$$Softmax(x)_i=Softmax(x-c)_i$$
結果不會變,但這樣可以避免 $e^{x_i}$ 的數值爆炸
以下舉例說明
```python
attn_weights = [1000, 1001, 999]
```
全部減去最大值
```python
attn_weights = [-1, 0, -2]
```
再去算 softmax 就會很安全
5. 防止 underflow
```python
torch.clamp(x, min=a)
```
把 x 中所有小於 a 的值都設成 a。
6. 對每個 query token 的注意力分數列 (row) 分別做穩定化
```python
attn_weights_l = (attn_weights_T - torch.max(attn_weights_T, dim=-1, keepdim=True)[0])
```
- `torch.max(..., dim=-1, keepdim=True)` 在最後一個維度(dim=-1)上找出每一行的最大值, `keepdim` 是看要不要保留被壓縮維度
以下舉例:
維度是 (2, 3)
```python
attn_weights_T = [[100, 102, 101],
[ 2, 3, 1]]
```
今天 `torch.max(attn_weights_T, dim=-1, keepdim=True)[0]` 就是在最後一維度(第 2 個維度) 中找出每組元素中最大值
```python
[[102],
[ 3]]
```
在減掉
```pyhotn
attn_weights_l = attn_weights_T - [[102], [3]]
```
最後變
```python
[[-2, 0, -1],
[-1, 0, -2]]
```
一樣可以防止 overflow
7. Stabilization Trick: 在 Softmax 前,先將 array 中的值做 softmax
```python
attn_weights_l = attn_weights_l.softmax(dim=-1)
```
8. 把結果拼回來成原本的維度
```python
attn_output_v = attn_output_v.view(bsz, self.num_heads, tgt_len, self.head_dim)
attn_output_v = attn_output_v.transpose(1, 2)
attn_output_v = attn_output_v.reshape(bsz, tgt_len, self.embed_dim)
```
為什麼不用 `view()`
- `view()` 要求 tensor 在記憶體中是連續的 (contiguous()),否則會報錯;
- `reshape()` 會自動幫你處理非連續的情況(會自動 copy 一份新的 tensor),所以比較安全。