[Python dgllge]いろんなグラフ埋め込みを試してみる

# 1. DeepWalk軽く触ってみる

グラフ情報を埋め込みできるライブラリdgllgeを試してみたので、よければ参考にしてみてください。

## 1-1. ライブラリのインストール

```
$ python -V
Python 3.11.1

$ pip install dgllge
```

```learnDeepWalk.py
import ge
import numpy as np

from torch_geometric.datasets import KarateClub
from torch_geometric.utils import to_networkx

np.random.seed(1)

# 空手クラブを使用
data = KarateClub()[0]

# グラフデータを作成
G = to_networkx(data)

# パラメータの設定
embedDim = 64
numbOfWalksPerVertex = 16
walkLength = 32
lr =0.025
windowSize = 3

# DeepWalk
deepwalk = ge.DeepWalk(
    G,
    walkLength=walkLength,
    embedDim=embedDim,
    numbOfWalksPerVertex=numbOfWalksPerVertex,
    windowSize=windowSize,
    lr = lr
)

modelSkipGram = ge.SkipGramModel(deepwalk.totalNodes, deepwalk.embedDim)

_ = deepwalk.learnNodeEmbedding(modelSkipGram)

# 現状はノードごとにしか埋め込みデータを取得することができないので、
# (ノードごとに取得するのがめんどくさいので)
# 後で「getNodeEmbedding()」を編集する。
deepwalk_emb = deepwalk.getNodeEmbedding().detach().numpy().copy()

print("サイズ:", deepwalk_emb.shape)

#埋め込みデータ保存
np.save('./embedded_data/deepwalk', deepwalk_emb)

# 埋め込みグラフ表示
ge.plot_2DEmbedding(deepwalk)
```

## 1-2. ライブラリ内の「getNodeEmbedding()」を編集

```python3.11/site-packages/ge/deepWalk.py
#def getNodeEmbedding(self, node):
#    return self.model.W1[int(self.nodeEncoder.transform([node]))].data

#####以下のように編集#####

def getNodeEmbedding(self):
    return self.model.W1
```

## 1-3. 実行してみる

```
$ python learnDeepWalk.py

サイズ: (34, 64)
```

## 1-4. 埋め込みグラフ表示

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2702119/a08ac3e6-f78f-b82a-f5d9-81fa5893efdb.png)

# 2. Node2VecとStruc2Vecを試してみる

```learn_node2vec_struc2vec.py
import ge
import numpy as np

from torch_geometric.datasets import KarateClub
from torch_geometric.utils import to_networkx

np.random.seed(1)

# 空手クラブを使用
data = KarateClub()[0]

# グラフデータを作成
G = to_networkx(data)

# パラメータの設定
embedDim = 64
numbOfWalksPerVertex = 16
walkLength = 32
lr =0.025
windowSize = 3

# Node2Vec
node2vec = ge.Node2vec(
    G,
    walkLength=walkLength,
    embedDim=embedDim,
    numbOfWalksPerVertex=numbOfWalksPerVertex,
    windowSize=windowSize,
    lr=lr,
    p = 0.5,
    q = 0.8
)

modelSkipGram = ge.SkipGramModel(node2vec.totalNodes, node2vec.embedDim)

_ = node2vec.learnNodeEmbedding(modelSkipGram)

# 同様に「getNodeEmbedding()」を編集する。
node2vec_emb = node2vec.getNodeEmbedding().detach().numpy().copy()

print("node2vec", node2vec_emb.shape)

np.save('./embedded_data/node2vec', node2vec_emb)

ge.plot_2DEmbedding(node2vec)

# Struc2Vec
struc2vec = ge.Struc2Vec(
    G,
    walkLength=walkLength,
    embedDim=embedDim,
    numbOfWalksPerVertex=numbOfWalksPerVertex,
    windowSize=windowSize,
    lr = lr
)

modelSkipGram = ge.SkipGramModel(struc2vec.totalNodes, struc2vec.embedDim)

_ = struc2vec.learnNodeEmbedding(modelSkipGram)

# 同様に「getNodeEmbedding()」を編集する。
struc2vec_emb = struc2vec.getNodeEmbedding().detach().numpy().copy()

print("struc2vec:", struc2vec_emb.shape)

np.save('./embedded_data/struc2vec', struc2vec_emb)

ge.plot_2DEmbedding(struc2vec)

```

## 2-1. node2vec.pyの「getNodeEmbedding()」を編集する。

```python3.11/site-packages/ge/node2vec.py
#    def getNodeEmbedding(self, node):
#       return self.model.W1[int(self.nodeEncoder.transform([node]))].data

#####以下のように編集#####

    def getNodeEmbedding(self):
        return self.model.W1
```

## 2-2. struc2vec.pyの「getNodeEmbedding()」を編集する。

```python3.11/site-packages/ge/struc2vec.py
#    def getNodeEmbedding(self, node):
#       return self.model.W1[int(self.nodeEncoder.transform([node]))].data

#####以下のように編集#####

    def getNodeEmbedding(self):
        return self.model.W1
```

## 2-3. 実行してみる

```
$ python learn_node2vec_struc2vec.py

node2vec: (34, 64)
struc2vec: (34, 64)
```

## 2-4. node2vec埋め込みグラフ

![node2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2702119/7ef9b7ab-9888-f241-60e2-3b0cffea3952.png)


## 2-5. struc2ve埋め込みグラフ

![stru.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2702119/576274a1-cbe5-55be-ea9b-67a29d46b09e.png)

# 3. それぞれの埋め込み方法でノード予測をしてみる(SVM)

```val_svm.py
import torch
import numpy as np

from sklearn.svm import SVC
from sklearn.model_selection import LeaveOneOut
from torch_geometric.datasets import KarateClub
from sklearn.metrics import accuracy_score

torch.manual_seed(1)

deepwalk_path = './embedded_data/deepwalk.npy'
node2vec_path = './embedded_data/node2vec.npy'
struc2vec_path = './embedded_data/struc2vec.npy'

path_list = [deepwalk_path, node2vec_path, struc2vec_path]

# それぞれの埋め込みデータで予測してみる。
for path in path_list:

    # 保存した埋め込みデータを読み込む
    x = np.load(path)

    # 空手データセットのノードラベルを取得
    y = KarateClub()[0].y.detach().numpy()

    # SVMモデル呼び出し
    model = SVC(kernel='linear')

    # 予測ラベル保存用
    pred_list = np.array([])

    # Leave One Outを用いる
    loo = LeaveOneOut()

    # ノードごとに予測する
    for train_index, test_index in loo.split(x):
        x_train, x_test = x[train_index], x[test_index]
        y_train, y_test = y[train_index], y[test_index]
        
        # 埋め込みデータを学習
        model.fit(x_train, y_train)
        
        # 予測
        pred = model.predict(x_test)
        
        pred_list = np.append(pred_list, pred.item())

    accuracy = accuracy_score(y, pred_list)
    print(path)
    print(f"正解率: {round(accuracy, 5)}")
    print()
```

## 3-1. 実行してみる

```
$ python val_svm.py

./embedded_data/deepwalk.npy
正解率: 0.94118

./embedded_data/node2vec.npy
正解率: 0.88235

./embedded_data/struc2vec.npy
正解率: 0.73529
```

# 4. それぞれの埋め込み方法でノード予測をしてみる(ニューラルネットワーク)

```val_NN.py
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

from sklearn.model_selection import LeaveOneOut
from torch_geometric.datasets import KarateClub
from sklearn.metrics import accuracy_score

class Net(nn.Module):    
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(64, 32)
        self.fc2 = nn.Linear(32, 4)
    
    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.softmax(x, dim=1)
        return x

deepwalk_path = './embedded_data/deepwalk.npy'
node2vec_path = './embedded_data/node2vec.npy'
struc2vec_path = './embedded_data/struc2vec.npy'

path_list = [deepwalk_path, node2vec_path, struc2vec_path]

# それぞれの埋め込みデータで予測してみる。
for path in path_list:

    # 保存した埋め込みデータを読み込む
    x = torch.from_numpy(np.load(path).astype(np.float32)).clone()

    # 空手データセットのノードラベルを取得
    y = KarateClub()[0].y

    # 予測ラベル保存用
    pred_list = np.array([])

    # Leave One Outを用いる
    loo = LeaveOneOut()

    # モデル呼び出し
    model = Net()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.01)

    # ノードごとに予測する
    for train_index, test_index in loo.split(x):
        
        x_train, x_test = x[train_index], x[test_index]
        y_train, y_test = y[train_index], y[test_index]
        
        # 学習
        model.train()
        for epoch in range(40):
            optimizer.zero_grad()
            out = model(x_train)
            loss = criterion(out, y_train)
            loss.backward(retain_graph=True)
            optimizer.step()
        
        # 予測
        model.eval()
        _, pred = model(x_test).max(dim=1)
        
        pred_list = np.append(pred_list, pred.item())

    accuracy = accuracy_score(y, pred_list)
    print(path)
    print(f"正解率: {round(accuracy, 5)}")
    print()
```

## 4-1. 実行してみる

```
$ python val_NN.py

./embedded_data/deepwalk.npy
正解率: 1.0

./embedded_data/node2vec.npy
正解率: 0.97059

./embedded_data/struc2vec.npy
正解率: 1.0
```