---
# System prepended metadata

title: '[GLIP](https://github.com/microsoft/GLIP/tree/main)  程式碼'
tags: [Transformers, Machine Learning]

---

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


## Detection Model
![image](https://hackmd.io/_uploads/HJ7tI7LRgx.png)

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)


![image](https://hackmd.io/_uploads/BylhapSCeg.png)

## [BiMultiHeadAttention](https://github.com/microsoft/GLIP/blob/main/maskrcnn_benchmark/utils/fuse_helper.py#L171)
![image](https://hackmd.io/_uploads/rJrCp6HCxe.png)


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），所以比較安全。

    
    
