---
# System prepended metadata

title: NARS 文檔
tags: [Travis, autoglab]

---

# NARS 文檔

###### tags: `autoglab`, `Travis`

## 概述
**NARS** 是 **GNN** 的一種，在很多分類任務上達到了 SOTA，開發模組旨在降低使用門檻，提高開發效率。 

## 應用場景
對異質圖中特定類型的節點 (以下簡稱**目標節點**) 進行類別預測。例如，**ACM** 資料集的目標節點為所有 **paper** 類型的節點，要預測其屬於哪個學術範疇。
## 環境
![](https://i.imgur.com/ZxDL0Xt.png)
![](https://i.imgur.com/0qbT9kZ.png)
![](https://i.imgur.com/KAsoWqU.png)



## NARS 原理
如下圖所示，**NARS** 流程可分為四個步驟：
![](https://i.imgur.com/Jp9Rmky.png)
1. **抽樣關連子集** (relation subsets): 這個階段會抽樣出多個關連子集，每個關連子集包含一種或多種關連。
   :::info
   **注意**: 關連子集中至少要有一種邊連接目標節點類型。
   :::
3. **生成關連子圖** (relation subgraphs): 這個階段會根據上個階段生成的關連子集生成相對應的關連子圖，且生成的關連子圖均為無向圖。
4. **特徵平均** (feature averaging): 計算每個關連子圖中所有目標節點的 L-hops 特徵 (L 為超參數)，計算邏輯如下公式：
   $$
   H_{v, l}^{i} = \sum_{u \in N_i(v)} \frac{1}{|N_i(v)|} H_{u, l-1}^{i}
   $$ 其中，$N_i(v)$ 為第 $i$ 個關連子圖 $G_i$ 中 $v$ 節點的所有鄰居節點，$H_{u,l-1}^{i}$ 為節點 $u$ 在 $G_i$ 中的 $(l-1)-hop$ 特徵。因此，特徵平均後的特徵包含下列四個維度:  
   + `hop_dim`
   + `rel_subset_dim`
   + `num_target_node`
   + `feat_dim`
5. **模型**: 將特徵放入 1-D convolution (`out_channels=1`)中進行特徵壓縮，過程中`rel_subset_dim` 的維度會被壓縮掉，這樣可以降低特徵維度，避免過擬合，公式如下:
   $$
   H_{v, l}^{agg} = \sum_{i=1}^{K} a_{i,l} \cdot H_{v, l}^{i}
   $$ 特徵接著會放入 **SIGN** 模型 (如下圖)，**SIGN** 會先將所有 hop 的特徵一一放入全連結層，再將每個 hop 的輸出結果進行合併，最後，放入全連結層輸出最後結果。
   <center>
     <img src=https://i.imgur.com/m1bMqDk.png width=300>
   </center>
   
上述方法有個嚴重的缺點，**模型需要事先生成所有目標節點的特徵**，無法像 GraphSage 一樣可以一個個批次地處理資料，若同時又有很多關連子集，這對電腦 RAM 開銷非常大，為了解決這個問題，原文引進 **部分訓練** 模式，模型訓練若干個 epoch 就會抽樣一部分(非全部，抽樣的數量 `args.sample_size` 為超參數)的關連子集，並產生對應的 L-hop 特徵，放入模型訓練，這樣就能節省 RAM 的使用。部分訓練公式如下: 
   $$
   H_{v,l}^{(t)} = \sum_{G_i \in S^{(t)} \subseteq S} b_{i, l} \cdot H_{v, l}^{i} + \alpha H_{v, l}^{(t-1)}
   $$ 其中，$S^{(t)}$ 為第 $t$ 個 stage 抽樣出的關連子圖集合，$b_{i,l}, \alpha$ 為第 $t$ 個 stage 的可訓練參數。
:::info
為應對訓練大圖的需求，步驟 1～3 都會在 CPU 中進行，避免 GPU OOM，訓練模型時也要注意 CPU 的容量是否能夠負荷。
:::



## NARS 模組參數
1. **參數設定**
   + `data`: 選擇訓練模型的資料集，目前有 `acm` & `mag` 兩個資料集可直接使用，若要訓練其他資料集，輸入 `other`，且需要改寫 `nars_data.py` 中的 `load_custom_dataset` 函數，詳見**第二點**。
     :::info
     **注意**: 因 NARS 模型有特徵平均的步驟，因此，要確保所有類型的節點特徵在同一個維度上，如果不滿足這個條件，常見做法會把所有節點類型的特徵投影到同一個維度上。另外，若訓練資料未提供部分節點類型的特徵，常見做法會使用知識圖譜的方法來產生這些節點的嵌入，在 `mag` & `acm` 的資料集中，我們使用此做法，詳見[此](https://github.com/facebookresearch/NARS/tree/main/graph_embed)。
     :::
   + `target_node_type`: 目標節點的類型，以 `acm` 資料集為例如，`args.target_node_type='paper'`。
   + `rel_path`: 儲存所有關連子集的檔案，格式如下圖所示，每一行代表一個關連子集的關連，圖中共有 8 個關連子集。
     :::info
     **注意**: 
     1. 如果 `args.rel_path` 路徑下不存在檔案，程式會自動隨機生成關連子集檔案在該路徑下，過程中，會要求使用者輸入想要生成關連子集的數量。
     2. 主程式還會在訓練模型前驗證每個關連子集是否符合符合規範下列：
        + 關連子集中的每種關連是否存在。
        + 關連子集中是否至少有一種關連會連接到目標節點。
     :::
   <center>
     <img src=https://i.imgur.com/wvlCs9n.png width=500>
   </center>


   + `R`: 使用多少個 hop 當作特徵。
   + `partial`: 是否進行部分訓練模式。
   + `sample_size`: 若 `args.partial=True` 才會有作用，代表每個 stage 抽樣關連子集的數量。
   + `resample_every`: 若 `args.partial=True` 才會有作用，代表訓練多少個 epoch 後進行關連子集的重新抽樣。
   + `num_hidden`: **SIGN** 全連結層 $\Theta_0, \Theta_1, ..., \Theta_L$ 的輸出維度。
   + `ff_layer`: **SIGN** 全連結層 $\Theta_0, \Theta_1, ..., \Theta_L$ 的層數。
   + `dropout`: **SIGN** 全連結層 $\Theta_0, \Theta_1, ..., \Theta_L$ 的 dropout rate。
   + `input_dropout`: 是否進行 dropout。
   + `lr`: 訓練時的學習率。
   + `weight_decay`: 訓練時的 $L_2$ 懲罰項超參數。
   + `num_epochs`: 訓練多少個 epoch。
   + `batch_size`: 訓練時的批次數量。

2. **客製化資料**: 使用客製化的資料來訓練模型時，必須改寫 `nars_data.py` 中的 `load_custom_dataset` 函數，且函數的回傳值需滿足一定的規範，如下範例:
   ```python
   def load_custom_dataset():
       ...
       return data, labels, n_classes, train_nid, val_nid, test_nid
   ```
   + **data**: `PyG` 中的 `HeteroData` 物件，包含異質圖的節點、節點特徵與邊，資料務必遵循下圖的格式進行編寫 (**ACM** 範例)。
     + `data` 的鍵包含所有類型的節點與邊，且邊的格式為 `(src, edge, dst)`。 
     + 使用 `.feat` 來取得特定類型節點的特徵，特徵形狀為 `(num_nodes, feat_size)` 的 `torch.Tensor`。例如：範例中 paper 的特徵為 `data['paper'].feat`。
     + 使用 `.edge_index` 來取得特定類型的邊，形狀為 `(2, num_edges)` 的 `torch.Tensor`。例如：範例中 pa 關連的邊為 `data['paper', 'pa', 'author'].edge_index`。
    
     ![](https://i.imgur.com/kLWs2RJ.png)
   + **labels**: 所有目標節點的標籤，形狀為 `(num_target_nodes,)` 的 `torch.Tensor`。
   + **n_classes**: 分類問題中的類別數量。
   + **train_nid** & **val_nid** & **test_nid**: 對目標節點進行訓練、驗證、測試分割，形狀均為 `(num_target_nodes,)` 的 `numpy.ndarray`。
## 執行程式
1. **文件配置**
   ```
   train_nars.py
   nars_trainer.py    
   nars_utils.py
   nars_model.py
   nars_data.py
   ```

2. **訓練範例**: 在 `nars_trainer.py` 的路徑下輸入下列指令。
   + **acm**(`args.partial=False`)
     ```
     python train_nars.py --data "acm" --target_node_type "paper" --rel_path "./acm/acm" --R 2 --partial "False" \
                          --num_hidden 64 --ff_layer 2 --dropout 0.5 --input_dropout "True" \
                          --lr 3e-3 --weight_decay 0 --num_epochs 1000 --batch_size 50000
     ```
   
   + **acm**(`args.partial=True`)
     ```
     python train_nars.py --data "acm" --target_node_type "paper" --rel_path "./acm/acm" --R 2 --partial "True" \
                          --sample_size 1 --num_hidden 64 --ff_layer 2 --dropout 0.5 --input_dropout "True" \
                          --lr 3e-3 --weight_decay 0 --num_epochs 1000 --batch_size 50000 --resample_every 10
     ```
   + **mag**(`args.partial=False`)
     ```
     python train_nars.py --data "mag" --target_node_type "paper" --rel_path "./mag/mag" --R 5 --partial "False" \
                      --num_hidden 512 --ff_layer 2 --dropout 0.5 --input_dropout "True" --lr 1e-3 --weight_decay 0\
                      --num_epochs 300 --batch_size 50000
     ```
   + **mag**(`args.partial=True`) 
     ```
     python train_nars.py --data "mag" --target_node_type "paper" --rel_path "./mag/mag" --R 2 --partial "True" \
                          --sample_size 3 --num_hidden 512 --ff_layer 2 --dropout 0.5 --input_dropout "True" \
                          --lr 1e-3 --weight_decay 0 --num_epochs 300 --batch_size 50000 --resample_every 10
     ```


## Code Review 問題
1. **特徵維度是否符合要求**:
   + `args.partial=False` 符合要求: `gen_rel_subset_feature` 的輸出為 `[hop0_feat, hop1_feat, ..., hopL_feat]`，`hop{k}` 的形狀為 `(num_nodes, feat_size)`。
        ```python
        def preprocess_features(g, rel_subsets, args, device):
            # pre-process heterogeneous graph g to generate neighbor-averaged features
            # for each relation subsets
            num_paper, feat_size = g.nodes["paper"].data["feat"].shape
            new_feats = [torch.zeros(num_paper, len(rel_subsets), feat_size) for _ in range(args.R + 1)]
            print("Start generating features for each sub-metagraph:")
            for subset_id, subset in enumerate(rel_subsets):
                print(subset)
                feats = gen_rel_subset_feature(g, subset, args, device)
                for i in range(args.R + 1):
                    feat = feats[i]
                    new_feats[i][:feat.shape[0], subset_id, :] = feat
                feats = None
            return new_feats
        ```
   + `args.partial=True` 不符合要求，但實驗結果較好，暫不修改 

2. **新增驗證 edge type 的 程式碼**: 已完成。
3. **mag** 資料集實驗: 使用 AWS EC2 p2.8xlarge 執行個體，訓練 300 個 epoch。
   |  hop   | Accuracy  | Time  |
   |  ----  | ----   | ----   |
   | 2      | 0.4369 | 1h 27min |
   | 3      | 0.4557 | 1h 50min |
   | 4      | 0.4613 | 2h 15min |
   | 5      | 0.4649 | 2h 41min  |
## 參考資料
1. [論文](https://arxiv.org/pdf/2011.09679.pdf)
2. [github](https://github.com/facebookresearch/NARS)