Graphs are a general language for describing and analyzing wntities with relations / interactions





복잡한 영역은 풍부한 관계구조를 가지고 있으며, 이는 relational graph 형태로 표현될 수 있습니다.
관계를 명시적으로 모델링함으로써, 더 나은 성능을 얻을 수 있습니다.
우리는 더 나은 예측을 위해 이러한 관계 구조를 어떻게 활용할 수 있을까?



How can we develop neural networks that are much more broadly applicable?
Graphs are the new frontier of deep learning

















Many patients take multiple drugs to treat complex or co-existing diseases:












PyG의 data 객체란?
Data(edge_index=[2, 156], x=[34, 34], y=[34], train_mask=[34])
from IPython.display import Javascript # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 300})'''))
edge_index = data.edge_index
print(edge_index.t())

COO 포맷?
- 전체 adjacency matrix 저장하지 않고, 연결된 노드 쌍만 (row, column) 형태로 저장하는 방식
- 메모리 사용량이 훨씬 줄어든다.

PyG의 그래프를 networkx 형식으로 변환하면 그래프 조작뿐만 아니라 시각화를 위한 강력한 기능들을 사용할 수 있음.
from torch_geometric.utils import to_networkx
G = to_networkx(data, to_undirected=True)
visualize(G, color = data.y)

GNN의 가장 기본적인 연산자 중 하나는 GCN (Graph Convolutional Network) 레이어.
GCNConv는 입력으로
GNN의 목표는 입력 그래프
를 받아서,
각 노드 ( v_i \in V ) 에 대한 유용한 임베딩 벡터(embedding) 를 학습하는 것
각 노드는 초기 입력 피처
를 가지고 있으며,
우리가 학습하려는 함수는 다음과 같습니다:
즉,
노드와 그 피처 벡터(그리고 그래프 구조) 를 입력받아
해당 노드를 잘 표현할 수 있는 임베딩 벡터를 출력하는 함수임.
이렇게 학습된 노드 임베딩은 그래프 내의 구조적, 의미적 정보를 반영한 벡터 표현으로, 다양한 downstream task에 활용 가능함.
예를 들어,
import torch
from torch.nn import Linear # 선형 레이어 클래스
from torch_geometric.nn import GCNConv
# PyTorch Geometric에서 제공하는 그래프 합성곱(Graph Convolution) 레이어.
# 이 레이어는 인접행렬 정보를 이용해 이웃 노드의 특징을 집계하는 역할.
# 3층 GCN(Graph Convolutional Network) + 선형 분류기(classifier)
class GCN(torch.nn.Module): # GCN 신경망 클래스 정의 + torch.nn.Module을 상속받아서 PyTorch 모델처럼 작동하게 함.
def __init__(self):
super().__init__() # 부모 클래스 초기화(필수)
torch.manual_seed(1234) # 랜덤 시드 고정하기 -> 동일한 초기 가중치 얻도록함
self.conv1 = GCNConv(dataset.num_features, 4) # 노드 피처의 개수, 출력차원 = 4 -> 각 노드의 feature를 4차원 공간으로 투영
self.conv2 = GCNConv(4, 4) # 2-hop 이웃 학습하기
self.conv3 = GCNConv(4, 2) # 입력 4 -> 출력 2로 줄여서 최종 임베딩 공간으로 만듬.
self.classifier = Linear(2, dataset.num_classes) # 마지막 선형 분류기 -> 입력 : 2차원 GNN 임베딩 , 출력 : 데이터셋의 클래스 개수 num_classes (각 노드를 어떤 클래스에 속하는 지 예측하기)
def forward(self, x, edge_index): # 모델의 순전파를 정의하는 부분
h = self.conv1(x, edge_index) # x: 각 노드의 feature matrix (shape: [num_nodes, num_features]), edge_index : 그래프의 연결구조 (shape: [2, num_edges], COO 형식)
h = h.tanh() # 비선형 활성화 함수로, 값을 [-1,1] 범위로 제한
h = self.conv2(h, edge_index)
h = h.tanh()
h = self.conv3(h, edge_index)
h = h.tanh() # Final GNN embedding space.
# Apply a final (linear) classifier.
out = self.classifier(h) # GCN에서 얻은 각 노드의 임베딩을 분류기에 통과시켜 클래스별 점수 (logit)을 출력함. out의 shape : [num_nodes, num_classes]
return out, h
# 분류 결과와 마지막 임베딩을 반환
model = GCN()
print(model)
model = GCN()
_, h = model(data.x, data.edge_index)
print(f'Embedding shape: {list(h.shape)}')
visualize(h, color=data.y)
Embedding shape: [34, 2]
하지만 여기서 의문이 생길 수 있는데,
'과연 학습을 하면 더 좋아질까?'
모델 학습 과정은 일반적인 PyTorch 모델 학습과 거의 동일함.
loss = criterion(out[data.train_mask], data.y[data.train_mask])
import time
from IPython.display import Javascript # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 430})'''))
model = GCN()
criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # Define optimizer.
def train(data): # PyG의 Data 객체
optimizer.zero_grad() # Clear gradients.
out, h = model(data.x, data.edge_index) # Perform a single forward pass.
loss = criterion(out[data.train_mask], data.y[data.train_mask]) # Compute the loss solely based on the training nodes.
loss.backward() # Derive gradients.
optimizer.step() # Update parameters based on gradients.
accuracy = {}
# Calculate training accuracy on our four examples
predicted_classes = torch.argmax(out[data.train_mask], axis=1) # [0.6, 0.2, 0.7, 0.1] -> 2
target_classes = data.y[data.train_mask]
accuracy['train'] = torch.mean(
torch.where(predicted_classes == target_classes, 1, 0).float())
# Calculate validation accuracy on the whole graph
predicted_classes = torch.argmax(out, axis=1)
target_classes = data.y
accuracy['val'] = torch.mean(
torch.where(predicted_classes == target_classes, 1, 0).float())
return loss, h, accuracy
for epoch in range(500): # 500번 반복 학습 (epoch = 전체 그래프 1회 학습 단위)
loss, h, accuracy = train(data) # 매 epoch마다 train을 호출해서 손실 및 정확도 갱신
# Visualize the node embeddings every 10 epochs
if epoch % 10 == 0: # 10 epoch마다 한 번씩 노드 임베딩 시각화
visualize(h, color=data.y, epoch=epoch, loss=loss, accuracy=accuracy) # 각 노드를 2D 좌표로 그려서, 같은 레이블 data.y끼리 색을 다르게 표현
time.sleep(0.3) # 시각화 사이에 0.3초 대기 -> 애니메이션처럼 변화 과정을 볼 수 있게함
(1) 경사 초기화 및 Forward Pass
optimizer.zero_grad()
out, h = model(data.x, data.edge_index)
(2) 손실 계산 (학습용 노드만 사용)
loss = criterion(out[data.train_mask], data.y[data.train_mask])
(3) Backward Pass (기울기 계산 및 업데이트)
loss.backward() # Derive gradients.
optimizer.step() # Update parameters based on gradients.
(4) 정확도 계산 (Train & Validation)
accuracy = {}
✅ 학습 데이터 정확도
predicted_classes = torch.argmax(out[data.train_mask], axis=1)
target_classes = data.y[data.train_mask]
accuracy['train'] = torch.mean(
torch.where(predicted_classes == target_classes, 1, 0).float())
✅ 전체 그래프(Validation) 정확도
predicted_classes = torch.argmax(out, axis=1)
target_classes = data.y
accuracy['val'] = torch.mean(
torch.where(predicted_classes == target_classes, 1, 0).float())
As one can see, our 3-layer GCN model manages to separate the communities pretty well and classify most of the nodes correctly.
Furthermore, we did this all with a few lines of code, thanks to the PyTorch Geometric library which helped us out with data handling and GNN implementations.