[pyg Node2Vec]グラフ情報を埋め込んで、いろんな機械学習の分類モデルを試してみる(ノード分類)

# 1. Node2Vecを試してみる

node2vecを試しに使ってみたので、よければ参考にしてください。

今回は空手データセットを使用しました。

```node2vec.py
import sys
import torch
import matplotlib.pyplot as plt

from torch_geometric.datasets import KarateClub
from torch_geometric.nn import Node2Vec

# 埋め込みデータ保存先
MODEL_PATH = f'./embedded_data/node2vec_karate.pickle'

# パラメータ設定
torch.manual_seed(1)
batch_size = 2
num_workers = 4 if sys.platform == 'linux' else 0

# データセットの取得
data = KarateClub()[0]

# テストマスクの作成
test_mask_list = []
for i in data.train_mask:
    if i.item():
        test_mask_list.append(False)
    else:
        test_mask_list.append(True)
data.test_mask = torch.tensor(test_mask_list)

# モデルの定義
model = Node2Vec(
    data.edge_index,
    embedding_dim=2,
    walk_length=5,
    walks_per_node=5,
    context_size=5,
)

# データを分ける
loader = model.loader(batch_size=batch_size, shuffle=True, num_workers=num_workers)

optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# 学習
for epoch in range(1, 51):
    model.train()
    for pos_rw, neg_rw in loader:
        optimizer.zero_grad()
        loss = model.loss(pos_rw, neg_rw)
        loss.backward()
        optimizer.step()
    print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')

# 評価
model.eval()
z = model()
acc = model.test(
    train_z=z[data.train_mask],
    train_y=data.y[data.train_mask],
    test_z=z[data.test_mask],
    test_y=data.y[data.test_mask]
)

print(f'Acc: {acc:.4f}')

# 埋め込みデータの作成
torch.save(z, MODEL_PATH)

emb2d = z.detach().numpy()

# 埋め込みデータをグラフで表示
plt.title("node embedding in 2D")
plt.scatter(emb2d[:,0],emb2d[:,1])
plt.show()
```

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

<details><summary>コンソール結果</summary><div>

```
$ python node2vec.py

Epoch: 001, Loss: 1.8213
Epoch: 002, Loss: 1.2606
Epoch: 003, Loss: 1.6105
Epoch: 004, Loss: 1.5242
Epoch: 005, Loss: 1.3069
Epoch: 006, Loss: 1.3369
Epoch: 007, Loss: 1.8641
Epoch: 008, Loss: 1.3242
Epoch: 009, Loss: 1.4022
Epoch: 010, Loss: 1.3207
Epoch: 011, Loss: 1.4372
Epoch: 012, Loss: 1.4107
Epoch: 013, Loss: 1.2831
Epoch: 014, Loss: 1.4015
Epoch: 015, Loss: 1.3993
Epoch: 016, Loss: 1.3672
Epoch: 017, Loss: 1.2989
Epoch: 018, Loss: 1.4034
Epoch: 019, Loss: 1.4309
Epoch: 020, Loss: 1.3101
Epoch: 021, Loss: 1.3130
Epoch: 022, Loss: 1.3254
Epoch: 023, Loss: 1.3217
Epoch: 024, Loss: 1.2541
Epoch: 025, Loss: 1.2302
Epoch: 026, Loss: 1.2345
Epoch: 027, Loss: 1.5912
Epoch: 028, Loss: 1.2548
Epoch: 029, Loss: 1.2034
Epoch: 030, Loss: 1.4017
Epoch: 031, Loss: 1.4904
Epoch: 032, Loss: 1.3489
Epoch: 033, Loss: 1.0623
Epoch: 034, Loss: 1.2798
Epoch: 035, Loss: 1.2487
Epoch: 036, Loss: 1.0272
Epoch: 037, Loss: 1.3455
Epoch: 038, Loss: 1.2721
Epoch: 039, Loss: 1.2123
Epoch: 040, Loss: 1.1893
Epoch: 041, Loss: 1.1719
Epoch: 042, Loss: 1.0916
Epoch: 043, Loss: 1.1160
Epoch: 044, Loss: 1.2925
Epoch: 045, Loss: 1.2810
Epoch: 046, Loss: 1.2935
Epoch: 047, Loss: 1.3090
Epoch: 048, Loss: 0.8576
Epoch: 049, Loss: 1.3404
Epoch: 050, Loss: 1.3522

Acc: 0.8000
```

</div></details>

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

埋め込み画像

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2702119/e1bcbbf8-3214-6441-9ffb-8fca36bb5738.png)

# 2. SVMでノード分類

```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

MODEL_PATH = f'./embedded_data/node2vec_karate.pickle'

# 保存した埋め込みデータを読み込む
x = torch.load(MODEL_PATH).detach().numpy()

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

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

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

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

i = 1

# ノードごとに予測する
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)
    
    print(i, "番目")
    print("予測：", pred)
    print("正解：", y_test)
    print()
    
    pred_list = np.append(pred_list, pred.item())
    i = i + 1

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

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

<details><summary>コンソール結果</summary><div>

```
$ python svm.py

1 番目
予測： [1]
正解： [1]

2 番目
予測： [1]
正解： [1]

3 番目
予測： [1]
正解： [1]

4 番目
予測： [1]
正解： [1]

5 番目
予測： [3]
正解： [3]

6 番目
予測： [3]
正解： [3]

7 番目
予測： [3]
正解： [3]

8 番目
予測： [1]
正解： [1]

9 番目
予測： [0]
正解： [0]

10 番目
予測： [0]
正解： [1]

11 番目
予測： [3]
正解： [3]

12 番目
予測： [1]
正解： [1]

13 番目
予測： [1]
正解： [1]

14 番目
予測： [1]
正解： [1]

15 番目
予測： [0]
正解： [0]

16 番目
予測： [0]
正解： [0]

17 番目
予測： [3]
正解： [3]

18 番目
予測： [1]
正解： [1]

19 番目
予測： [0]
正解： [0]

20 番目
予測： [1]
正解： [1]

21 番目
予測： [0]
正解： [0]

22 番目
予測： [1]
正解： [1]

23 番目
予測： [0]
正解： [0]

24 番目
予測： [0]
正解： [0]

25 番目
予測： [0]
正解： [2]

26 番目
予測： [0]
正解： [2]

27 番目
予測： [0]
正解： [0]

28 番目
予測： [2]
正解： [0]

29 番目
予測： [0]
正解： [2]

30 番目
予測： [0]
正解： [0]

31 番目
予測： [0]
正解： [0]

32 番目
予測： [0]
正解： [2]

33 番目
予測： [0]
正解： [0]

34 番目
予測： [0]
正解： [0]

正解率: 0.824
```

</div></details>

# 3. KNN(K近傍法)でノード分類

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

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

MODEL_PATH = f'./embedded_data/node2vec_karate.pickle'

# 保存した埋め込みデータを読み込む
x = torch.load(MODEL_PATH).detach().numpy()

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

# knnモデル呼び出し
knn = KNeighborsClassifier(n_neighbors=3)

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

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

i = 1

# ノードごとに予測する
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]
    
    # 埋め込みデータを学習
    knn.fit(x_train, y_train)
    
    # 予測
    pred = knn.predict(x_test)
    
    print(i, "番目")
    print("予測：", pred)
    print("正解：", y_test)
    print()
    
    pred_list = np.append(pred_list, pred.item())
    i = i + 1

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

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

<details><summary>コンソール結果</summary><div>

```
$ python svm.py

1 番目
予測： [1]
正解： [1]

2 番目
予測： [1]
正解： [1]

3 番目
予測： [1]
正解： [1]

4 番目
予測： [1]
正解： [1]

5 番目
予測： [3]
正解： [3]

6 番目
予測： [3]
正解： [3]

7 番目
予測： [3]
正解： [3]

8 番目
予測： [1]
正解： [1]

9 番目
予測： [0]
正解： [0]

10 番目
予測： [0]
正解： [1]

11 番目
予測： [3]
正解： [3]

12 番目
予測： [1]
正解： [1]

13 番目
予測： [1]
正解： [1]

14 番目
予測： [1]
正解： [1]

15 番目
予測： [0]
正解： [0]

16 番目
予測： [0]
正解： [0]

17 番目
予測： [3]
正解： [3]

18 番目
予測： [1]
正解： [1]

19 番目
予測： [0]
正解： [0]

20 番目
予測： [1]
正解： [1]

21 番目
予測： [0]
正解： [0]

22 番目
予測： [1]
正解： [1]

23 番目
予測： [0]
正解： [0]

24 番目
予測： [0]
正解： [0]

25 番目
予測： [2]
正解： [2]

26 番目
予測： [2]
正解： [2]

27 番目
予測： [0]
正解： [0]

28 番目
予測： [2]
正解： [0]

29 番目
予測： [0]
正解： [2]

30 番目
予測： [0]
正解： [0]

31 番目
予測： [0]
正解： [0]

32 番目
予測： [2]
正解： [2]

33 番目
予測： [0]
正解： [0]

34 番目
予測： [0]
正解： [0]

正解率: 0.912
```

</div></details>

# 4. PyTorchを用いてノード分類

```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(2, 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

MODEL_PATH = f'./embedded_data/node2vec_karate.pickle'

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

# 空手データセットのノードラベルを取得
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)

i = 1

# ノードごとに予測する
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)

    print(i, "番目")
    print("予測：", pred.item())
    print("正解：", y_test.item())
    print("loss:", round(loss.item(), 3))
    print()
    
    pred_list = np.append(pred_list, pred.item())
    i = i + 1

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

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

<details><summary>コンソール結果</summary><div>

```
$ python nn.py

1 番目
予測： 1
正解： 1
loss: 0.922

2 番目
予測： 1
正解： 1
loss: 0.896

3 番目
予測： 1
正解： 1
loss: 0.836

4 番目
予測： 1
正解： 1
loss: 0.83

5 番目
予測： 3
正解： 3
loss: 0.825

6 番目
予測： 3
正解： 3
loss: 0.819

7 番目
予測： 3
正解： 3
loss: 0.814

8 番目
予測： 1
正解： 1
loss: 0.811

9 番目
予測： 0
正解： 0
loss: 0.808

10 番目
予測： 0
正解： 1
loss: 0.777

11 番目
予測： 3
正解： 3
loss: 0.807

12 番目
予測： 1
正解： 1
loss: 0.806

13 番目
予測： 1
正解： 1
loss: 0.806

14 番目
予測： 1
正解： 1
loss: 0.806

15 番目
予測： 0
正解： 0
loss: 0.805

16 番目
予測： 0
正解： 0
loss: 0.805

17 番目
予測： 3
正解： 3
loss: 0.805

18 番目
予測： 1
正解： 1
loss: 0.805

19 番目
予測： 0
正解： 0
loss: 0.805

20 番目
予測： 1
正解： 1
loss: 0.805

21 番目
予測： 0
正解： 0
loss: 0.805

22 番目
予測： 1
正解： 1
loss: 0.805

23 番目
予測： 0
正解： 0
loss: 0.805

24 番目
予測： 0
正解： 0
loss: 0.805

25 番目
予測： 2
正解： 2
loss: 0.805

26 番目
予測： 2
正解： 2
loss: 0.805

27 番目
予測： 0
正解： 0
loss: 0.805

28 番目
予測： 2
正解： 0
loss: 0.774

29 番目
予測： 2
正解： 2
loss: 0.804

30 番目
予測： 0
正解： 0
loss: 0.805

31 番目
予測： 0
正解： 0
loss: 0.804

32 番目
予測： 2
正解： 2
loss: 0.804

33 番目
予測： 0
正解： 0
loss: 0.804

34 番目
予測： 0
正解： 0
loss: 0.804

正解率: 0.941
```

</div></details>