0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-07-01

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. 実行してみる

コンソール結果
$ 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

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

埋め込み画像

Figure_1.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. 実行してみる

コンソール結果
$ 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

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. 実行してみる

コンソール結果
$ 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

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. 実行してみる

コンソール結果
$ 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
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?