---
# System prepended metadata

title: NARS 第二版-開發文件
tags: [國泰專案]

---

# NARS 第二版-開發文件

###### tags: `國泰專案`
![](https://i.imgur.com/3Im7895.png)

## 目標
1. **DGL** $\rightarrow$ **PyG**

## Feature Averaging 步驟
1. **Message Passing**: 會對特定一種 relation type 做**訊息傳遞**。
2. **Message Aggregation-1**: 聚合(相加)特定一種 relation type 的訊息，回傳形狀 `(num_dst_nodes, feat_dim)` 的張量。
3. **Message Aggregation-2**: 聚合(相加)不同 relation type 所聚合的特徵。
4. 計算每個 node 的 degree。
5. 將每個 node 聚合後的特徵除以對應的 degree。

<center>
  <img src=https://i.imgur.com/ffhIeTP.png width=300 height=200>
</center>

## Feature Averaging 程式概述

1. **SumConv(x, edge_index, size)**
   + **x**: `Union[Tensor, OptPairTensor]` node 特徵。
   + **edge_index**: 邊。 
   + **size**
    ```python=
    from torch_geometric.nn.conv import MessagePassing
    
    class SumConv(MessagePassing):
        def __init__(self): 
            super().__init__(aggr='add', flow="source_to_target")  # "Add" aggregation

        def forward(self, x, edge_index, size = None):
            if isinstance(x, Tensor):
                x: OptPairTensor = (x, x)
            return self.propagate(edge_index, x=x, size=size)

        def message(self, x_j):  # x_i center node, x_j neighbor node
            return x_j
    ```
    ```mermaid
    graph LR
    A["propagate"] --> B["message"] --> C["aggregation"]
    ```

2. **聚合(相加)不同 relation type 傳遞到 node 的特徵**
    ```python=
    for stype, etype, dtype in new_data.edge_types: 
        x_pair = (new_data[stype][f'hop_{hop-1}'], new_data[dtype][f'hop_{hop-1}']) 
        edges = new_data[stype, etype, dtype].edge_index
        new_feat = sum_conv(x_pair, edges)
        if dtype in ntype2feat:
            ntype2feat[dtype] += new_feat
        else:
            ntype2feat[dtype] = new_feat
    ```

3. **計算 degree**
    ```python=
    for ntype in ntypes:
        deg = 0 
        for stype, etype, dtype in new_data.edge_types:
            if ntype == dtype: 
                deg = deg + degree(new_data[stype, etype, dtype].edge_index[1, :], new_data[dtype]['hop_0'].shape[0])
        norm = 1.0 / deg.float()
        norm[torch.isinf(norm)] = 0
        new_data[ntype]["norm"] = norm.view(-1, 1).to(device)
    ```


## 使用範例
```python=
# upload nars_trainer, nars_utils, nars_model 
import nars_trainer
from nars_utils import read_relation_subsets

...
...

trainer = nars_trainer.NARS_TRAINER()
trainer.build_model(partial, sample_size, num_feats, in_feats, num_hops, num_hidden, num_classes, ff_layer)
trainer.compile(loss_fcn, optimizer, lr, weight_decay, device)
trainer.fit(g, labels, target_node_type, rel_subsets, train_nid, val_nid, test_nid, num_epochs, batch_size)
```     

## Code Review
1. fix index `i`
    ```python=
    def gen_rel_subset_feature(data, target_node_type, rel_subset, R, device):
        new_data = HeteroData()
        ntypes = set()
        sum_conv = SumConv()

        for etype in rel_subset:
            stype, _, dtype = list(filter(lambda x: x[1] == etype, data.edge_types))[0]
            # add nodes 
            new_data[stype]['hop_0'] = data[stype]['feat']
            new_data[dtype]['hop_0'] = data[dtype]['feat']
            ntypes.add(stype)
            ntypes.add(dtype)

            # add edges
            src, dst = list(data[(stype, etype, dtype)].edge_index)
            new_data[stype, etype, dtype].edge_index = torch.stack([src, dst], dim=0)
            new_data[dtype, etype + "_r", stype].edge_index = torch.stack([dst, src], dim=0)

        for ntype in ntypes:
            deg = 0 
            for stype, etype, dtype in new_data.edge_types:
                if ntype == dtype: 
                    deg = deg + degree(new_data[stype, etype, dtype].edge_index[1, :], new_data[dtype]['hop_0'].shape[0])
            norm = 1.0 / deg.float()
            norm[torch.isinf(norm)] = 0
            new_data[ntype]["norm"] = norm.view(-1, 1).to(device)

        res = []

        for hop in range(1, R + 1): 
            ntype2feat = {}   # save k-hop feature (sum)
            for stype, etype, dtype in new_data.edge_types: 
                x_pair = (new_data[stype][f'hop_{hop-1}'], new_data[dtype][f'hop_{hop-1}']) 
                edges = new_data[stype, etype, dtype].edge_index
                new_feat = sum_conv(x_pair, edges)
                if dtype in ntype2feat:
                    ntype2feat[dtype] += new_feat
                else:
                    ntype2feat[dtype] = new_feat

            for ntype in new_data.node_types:
                assert ntype in ntype2feat  # because subgraph is not directional
                feat_dict = new_data[ntype]
                old_feat = feat_dict.pop(f'hop_{hop-1}')
                if ntype == target_node_type:
                    res.append(old_feat.cpu())
                feat_dict[f"hop_{hop}"] = ntype2feat.pop(ntype).mul_(feat_dict["norm"])

        res.append(new_data[target_node_type].pop(f"hop_{R}").cpu())
        return res
    

    def recompute_selected_subsets(data, target_node_type, selected_subsets, R, feat_size, device):
        # TODO: recompute in parallel using multi-processing
        # Or we should save neighbor-averaged features to disk and load them back instead of re-computing
        start = time.time()
        num_nodes, _ = data[target_node_type]["feat"].shape
        with torch.no_grad():
            feats = [
                torch.zeros(num_nodes, len(selected_subsets), feat_size)
                for _ in range(R + 1)
            ]
            for i, subset in enumerate(selected_subsets):
                rel_feats = gen_rel_subset_feature(data, target_node_type, subset, R, device)
                for hop in range(R + 1):
                    feats[hop][:, i, :] = rel_feats[i]
        end = time.time()
        print("Recompute takes {:.4f} sec".format(end - start))
        return feats

    ```
    + `feats`: 長度為 `R+1` 的列表，每個元素的形狀為 `(num_nodes, len(selected_subsets), feat_size)`
    + `gen_rel_subset_feature`: 長度為 `R+1` 的列表，每個元素的形狀為 `(num_nodes, feat_size)` 
`




## 參考資料
1. [PyG Message Passing](https://pytorch-geometric.readthedocs.io/en/latest/notes/create_gnn.html)
2. [程式碼](https://colab.research.google.com/drive/1E1vFUhHTGgaQbKaZA6OtGS5ru43b1WHf)
3. [Message Passing 詳解](https://zqfang.github.io/2021-08-07-graph-pyg/)