#from networkx import karate_club_graph, to_numpy_matrix
import numpy as np
import networkx as nx
from scipy.linalg import sqrtm
import matplotlib.pyplot as plt
= nx.barbell_graph(m1=10, m2=4)
G
= np.arange(G.number_of_nodes())
order = nx.to_numpy_matrix(G, nodelist=order)
A = np.eye(G.number_of_nodes()) I
ref
GNN
Unsupervised graph representation learning using Graph ConvNet
- 스펙트럼 그래프 합성곱
-
키프와 웰링이 제안한 정규화
\[H^t=\sigma(\hat D^{-\dfrac{1}{2}} \hat A \hat D^{-\dfrac{1}{2}}XW)\]
- \(\hat D\)는 \(\hat A\)의 대각 노드 차수 행렬
-
자체 루프 추가, 대각 노드 차수 행렬 준비
7)
np.random.seed(
= A + np.eye(G.number_of_nodes()) # add self-connections # G의 노드 수만큼 단위 행렬 만들기
A_hat
= np.array(np.sum(A_hat, axis=0))[0]
D_hat = np.array(np.diag(D_hat))
D_hat = np.linalg.inv(sqrtm(D_hat))
D_hat # D_hat은 A_hat의 각 열의 합으로 이루어진 대각행렬의 역행렬
# 그래프 각 노드의 연결 강도를 정규화
= D_hat @ A_hat @ D_hat
A_hat #A_hat을 정규화된 그래프 연결 행렬로
-
2개의 레이어로 구성된 GCN(그래프합성곱신경망)만들기
- 가중치 \(W\)
def glorot_init(nin, nout): # 각 GCN의 가중치 행렬 난수로 초기화
= np.sqrt(6.0 / (nin + nout))
sd return np.random.uniform(-sd, sd, size=(nin, nout))
class GCNLayer():
def __init__(self, n_inputs, n_outputs):
self.n_inputs = n_inputs
self.n_outputs = n_outputs
self.W = glorot_init(self.n_outputs, self.n_inputs)
self.activation = np.tanh
def forward(self, A, X): #인접행렬과 피처행렬을 이용해 그래프 신경망을 순방향으로 계산. 결과 반환
self._X = (A @ X).T # (N,N)*(N,n_outputs) ==> (n_outputs,N)
= self.W @ self._X # (N, D)*(D, n_outputs) => (N, n_outputs)
H = self.activation(H)
H return H.T # (n_outputs, N)
-
네트워크 만들고 순방향 패스 계산. 네트워크를 통해 신호 전파
= GCNLayer(G.number_of_nodes(), 8)
gcn1 = GCNLayer(8, 4)
gcn2 = GCNLayer(4, 2)
gcn3
= gcn1.forward(A_hat, I) #중간 출력
H1 = gcn2.forward(A_hat, H1) #중간 출력
H2 = gcn3.forward(A_hat, H2) #최종 결과
H3
= H3 # 최종 결과 embeddings
embeddings
matrix([[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.01476752, -0.05053743],
[-0.03171266, -0.05642689],
[-0.08356075, -0.03146726],
[-0.11696591, -0.00297877],
[-0.10436451, 0.0226648 ],
[-0.06008126, 0.01901998],
[-0.00104536, -0.05409599],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606],
[ 0.01041858, -0.06209606]])
from gem.embedding.gf import GraphFactorization
def draw_graph(G, filename=None, node_size=50):
= nx.spring_layout(G)
pos_nodes =False, node_size=node_size, edge_color='gray')
nx.draw_networkx(G, pos_nodes, with_labels
= {}
pos_attrs for node, coords in pos_nodes.items():
= (coords[0], coords[1] + 0.08)
pos_attrs[node]
'off')
plt.axis(= plt.gca()
axis 1.2*x for x in axis.get_xlim()])
axis.set_xlim([1.2*y for y in axis.get_ylim()])
axis.set_ylim([
= np.array(embeddings)
embeddings draw_graph(G)
0], embeddings[:, 1])
plt.scatter(embeddings[:, 'embedding_gcn.png',dpi=300) plt.savefig(
Unsupervised GCN training using similarity graph distance
StellarGraph 사용
대상 변수 없이 비지도 방식으로 벡터 삽입하는 방법
!pip install -q stellargraph[demos]==1.2.1
pip install chardet
Collecting chardet
Downloading chardet-5.1.0-py3-none-any.whl (199 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 199.1/199.1 kB 10.0 MB/s eta 0:00:00
Installing collected packages: chardet
Successfully installed chardet-5.1.0
Note: you may need to restart the kernel to use updated packages.
import pandas as pd
import numpy as np
import networkx as nx
import os
import stellargraph as sg
from stellargraph.mapper import FullBatchNodeGenerator
from stellargraph.layer import GCN
import tensorflow as tf
from tensorflow.keras import layers, optimizers, losses, metrics, Model
from sklearn import preprocessing, model_selection
from IPython.display import display, HTML
import matplotlib.pyplot as plt
%matplotlib inline
-
데이터셋: PROTENIS
= sg.datasets.PROTEINS()
dataset
display(HTML(dataset.description))= dataset.load() graphs, graph_labels
Each graph represents a protein and graph labels represent whether they are are enzymes or non-enzymes. The dataset includes 1113 graphs with 39 nodes and 73 edges on average for each graph. Graph nodes have 4 attributes (including a one-hot encoding of their label), and each graph is labelled as belonging to 1 of 2 classes.
39개 노드, 73개 간선이 있는 1,114개 그래프로 구성
각 노드는 4개의 속성으로 설명되며 두 클래스 중 하나에 속한다.
# let's print some info to better understand the dataset
print(graphs[0].info())
graph_labels.value_counts().to_frame()
StellarGraph: Undirected multigraph
Nodes: 42, Edges: 162
Node types:
default: [42]
Features: float32 vector, length 4
Edge types: default-default->default
Edge types:
default-default->default: [162]
Weights: all 1 (default)
Features: none
label | |
---|---|
1 | 663 |
2 | 450 |
-
모델 만들기
# TODO
= sg.mapper.PaddedGraphGenerator(graphs) generator
# 64,32 사이즈의 레이어 2개를 포함한 GCN model 정의
# ReLU 활성화 함수는 레이어 간 비선형을 추가하고자 사용
= sg.layer.GCNSupervisedGraphClassification(
gc_model 64, 32], ["relu", "relu"], generator, pool_all_layers=True
[ )
# 다음 레이어에 연결할 수 있도록 GC 레이어의 입력 및 출력 텐서 확인
= gc_model.in_out_tensors()
inp1, out1 = gc_model.in_out_tensors()
inp2, out2
= tf.norm(out1 - out2, axis=1) vec_distance
# 모델 생성, 임베딩을 찾기 쉽게 반사 모델 생성
= Model(inp1 + inp2, vec_distance)
pair_model = Model(inp1, out1) embedding_model
-
훈련
입력 그래프의 각 쌍에 유사성 점수 할당
단순화를 위해 그래프의 라플라시안 스펙트럼 사이의 거리 사용
def graph_distance(graph1, graph2):
= nx.laplacian_spectrum(graph1.to_networkx(feature_attr=None))
spec1 = nx.laplacian_spectrum(graph2.to_networkx(feature_attr=None))
spec2 = min(len(spec1), len(spec2))
k return np.linalg.norm(spec1[:k] - spec2[:k])
= np.random.RandomState(0).randint(len(graphs), size=(100, 2))
graph_idx = [graph_distance(graphs[left], graphs[right]) for left, right in graph_idx]
targets = generator.flow(graph_idx, batch_size=10, targets=targets) train_gen
<class 'networkx.utils.decorators.argmap'> compilation 20:4: FutureWarning: laplacian_matrix will return a scipy.sparse array instead of a matrix in Networkx 3.0.
-
모델 학습
compile(optimizers.Adam(1e-2), loss="mse") pair_model.
= pair_model.fit(train_gen, epochs=500, verbose=0)
history sg.utils.plot_history(history)
-
시각화
# 임베딩 검색
= embedding_model.predict(generator.flow(graphs)) embeddings
1113/1113 [==============================] - 1s 531us/step
# 차원 축소를 위해 TSNE 사용
from sklearn.manifold import TSNE
= TSNE(2)
tsne = tsne.fit_transform(embeddings) two_d
- 출력이 32차원이므로 임베딩을 2차원 공간에 플로팅해 임베딩 정상적 평가
0], two_d[:, 1], c=graph_labels.cat.codes, cmap="jet", alpha=0.4)
plt.scatter(two_d[:, 'embedding_TSNE.png',dpi=300) plt.savefig(
- 빨간색=1, 파란색=0