AI/이론
그래프 신경망이란 무엇일까? (기본)
N-analyst
2021. 2. 26. 15:36
그래프 신경망이란 무엇일까? (기본)¶
✅ 정점 표현 학습 복습¶
📌 출력으로 임베딩 자체를 얻는 변환식 임베딩 방법은 여러 한계를 갖는다.¶
- 학습이 진행된 이후에 추가된 정점에 대해서는 임베딩을 얻을 수 없다.
- 모든 정점에 대한 임베딩을 미리 계산하여 저장해두어야 한다.
- 정점이 속성(Attribute) 정보를 가진 경우에 이를 활용할 수 없다.
📌 출력으로 인코더를 얻는 귀납식 임베딩 방법은 여러 장점을 갖는다.¶
- 학습이 진행된 이후에 추가된 정점에 대해서도 임베딩을 얻을 수 있다.
- 모든 정점에 대한 임베딩을 미리 계산하여 저장해둘 필요가 없다.
- 정점이 속성(Attribute) 정보를 가진 경우에 이를 활용할 수 있다.
✅ 그래프 신경망 기본¶
1️⃣ 그래프 신경망의 구조¶
📌 그래프 신경망은 그래프와 정점의 속성 정보를 입력으로 받는다.¶
- 그래프의 인접 행렬을 A라고 하자.
- 인접 행렬 A는 $|V|$X$|V|$의 이진 행렬이다.
- 각 정점 $u$의 속성(Attrivute) 벡터를 $X_{u}$라고 하자.
- 정점 속성 벡터 $X_{u}$는 m차원 벡터이고, m은 속성의 수를 의미한다.
정점의 속성의 예시는 다음과 같다.
- 온라인 소셜 네트워크에서 사용자의 지역, 성별, 연령, 프로필 사진 등
- 논문 인용 그래프에서 논문에 사용된 키워드에 대한 원-핫 벡터
- PageRank 등의 정점 중심성, 군집 계수(Clustering Coefficient) 등
📌 그래프 신경망은 이웃 정점들의 정보를 집계하는 과정을 반복하여 임베딩을 얻는다.¶
- 아래 예시를 보면 대상 정점의 임베딩을 얻기 위해 이웃들 그리고 이웃의 이웃들의 정보를 집계한다.
📌 각 집계 단계를 층(Layer)이라고 부르고, 각 층마다 임베딩을 얻습니다¶
- 각 층에서는 이웃들의 이전 층 임베딩을 집계하여 새로운 임베딩을 얻는다.
- 0번 층, 즉 입력 층의 임베딩으로는 정점의 속성 벡터를 사용한다.
📌 대상 정점 마다 집계되는 정보가 상이하다.¶
- 대상 정점 별 집계되는 구조를 계산 그래프(Computation Graph)라고 부른다.
서로 다른 대상 정점간에도 층 별 집계 함수는 공유한다.
📌 서로 다른 구조의 계산 그래프를 처리하기 위해서는 어떤 형태의 집계 함수가 필요할까?¶
집계 함수는 (1) 이웃들 정보의 평균을 계산하고 (2) 신경망에 적용하는 단계를 거친다.
마지막 층에서의 정점 별 임베딩이 해당 정점의 출력 임베딩이다.
2️⃣ 그래프 신경망의 학습¶
📌 그래프 신경망의 학습 변수(Trainable Parameter)는 층 별 신경망의 가중치이다.¶
📌 먼저 손실함수를 결정한다. 정점간 거리를 "보존"하는 것을 목표로 할 수 있다.¶
- 변환식 정점 임베딩에서처럼 그래프에서의 정점간 거리를 "보존"하는 것을 목표로 할 수 있다.
- 만약, 인접성을 기반으로 유사도를 정의한다면, 손실 함수는 다음과 같다.
📌후속 과제(Downstream Task)의 손실함수를 이용한 종단종(End-to-End) 학습도 가능하다.¶
🔎 정점 분류가 최종 목표인 경우를 생각해 보자.
예를 들어,
- 그래프 신경망을 이용하여 정점의 임베딩을 얻고
- 이를 분류기(Classifier)의 입력으로 사용하여
- 각 정점의 유형을 분류하려고 한다.
이 경우 분류기의 손실함수, 예를 들어 교차 엔트로피(Cross Entropy)를 전체 프로세스의 손실함수로 사용하여 종단종(End-to-End) 학습을 할 수 있다.
📌 그래프 신경망과 변환적 정점 임베딩을 이용한 정점 분류 비교¶
- 그래프 신경망의 종단종(End-to-End) 학습을 통한 분류는 변환적 정점 임베딩 이후에 별도의 분류기를 학습하는 것보다 정확도가 대체로 높다.
아래 표는 다양한 데이터에서의 정점 분류의 정확도(Accuracy)를 보여준다.
📌학습에 사용할 대상 정점을 결정하여 학습 데이터를 구성¶
- 우리는 모든 정점을 넣고 계산할 필요는 없다.
- 선택한 대상 정점들만을 가지고 계산 그래프를 구성 가능하다.
📌 마지막으로 오차역전파(Backpropagation)을 통해 손실 함수를 최소화 한다.¶
구체적으로, 오차역전파를 통해 신경망의 학습 변수들을 학습한다.
3️⃣ 그래프 신경망의 활용¶
- 또한 일부 정점들을 선택하여 계산 그래프를 계산하였고 학습된 신경망을 적용하여, 학습에 사용되지 않은 정점의 임베딩을 얻을 수 있다.
📌 마찬가지로, 학습 이후에 추가된 정점의 임베딩도 얻을 수 있다.¶
- 온라인 소셜네트워크 등 많은 실제 그래프들은 시간에 따라서 변화한다.
📌 학습된 그래프 신경망을, 새로운 그래프에 적용할 수도 있다.¶
- 예를 들어, A종의 단백질 상호 작용 그래프에서 학습한 그래프 신경망을 B종의 단백질 상호작용 그래프에 적용할 수 있다.
✅ 그래프 신경망 변형¶
1️⃣ 그래프 합성곱 신경망¶
📌 그래프 합성곱 신경망(Graph Convolutional Network, GCN)의 집계 함수이다.¶
차이를 보기 위해 기존의 집계 함수와 비교해 보자. 작은 차이지만 큰 성능의 향상으로 이어지기도 한다.
2️⃣ GraphSAGE¶
📌 GraphSAGE의 집계 함수이다.¶
- 이웃들의 임베딩을 AGG함수를 이용해 합친 후, 자신의 임베딩과 연결(Concatenation)하는 점이 독특하다.
📌 AGG 함수로는 평균, 풀링, LSTM 등이 사용될 수 있다.¶
- 여기서 $\pi$는 이웃들의 임베딩을 가지고 와서 순서를 섞은 다음에 LSTM에 넣는다는 의미로 이해하면 된다.
✅ 합성곱 신견망(CNN)과의 비교¶
1️⃣ 합성곱 신경망과 그래프 신경망의 유사성¶
📌 합성곱 신경망과 그래프 신경망은 모두 이웃의 정보를 집계하는 과정을 반복한다.¶
- 구체적으로, 합성곱 신경망은 이웃 픽셀의 정보를 집계하는 과정을 반복한다.
2️⃣ 합성곱 신경망과 그래프 신경망의 차이¶
❗❗ 합성곱 신경망에서는 이웃의 수가 균일하지만, 그래프 신경망에서는 아니다.
- 그래프 신경망에서는 정점 별로 집계하는 이웃의 수가 다르다.
❓ 그래프의 인접 행렬에 합성곱 신경망을 적용하면 효과적일까요?¶
그래프에는 합성곱 신경망이 아닌 그래프 신경망을 적용해야 한다 ❗❗
- 합성곱 신경망이 주로 쓰이는 이미지에서는 인접 픽셀이 유용한 정보를 담고 있을 가능성이 높다.
- 하지만, 그래프의 인접 행렬에서의 인접 원소는 제한된 정보를 가진다.
- 특히나, 인접 행렬의 행과 열의 순서는 임의로 결정되는 경우가 많다.
✅ DGL라이브러리와 GraphSAGE를 이용한 정점 분류¶
라이브러리 로드¶
In [1]:
import numpy as np
import time
import torch
import torch.nn as nn
import torch.nn.functional as F
# Deep Graph Library
import dgl
from dgl.data import CoraGraphDataset
from sklearn.metrics import f1_score
Cora 인용 그래프 불러오기¶
In [2]:
'''
Cora 데이터셋은 2708개의 논문(노드), 10556개의 인용관계(엣지)로 이루어졌습니다.
NumFeat은 각 노드를 나타내는 특성을 말합니다.
Cora 데이터셋은 각 노드가 1433개의 특성을 가지고, 개개의 특성은 '1'혹은 '0'으로 나타내어지며 특정 단어의 논문 등장 여부를 나타냅니다.
즉, 2708개의 논문에서 특정 단어 1433개를 뽑아서, 1433개의 단어의 등장 여부를 통해 각 노드를 표현합니다.
노드의 라벨은 총 7개가 존재하고, 각 라벨은 논문의 주제를 나타냅니다
[Case_Based, Genetic_Algorithms, Neural_Networks, Probabilistic_Methods, Reinforcement_Learning, Rule_Learning, Theory]
2708개의 노드 중, 학습에는 140개의 노드를 사용하고 모델을 테스트하는 데에는 1000개를 사용합니다.
본 실습에서는 Validation을 진행하지않습니다.
요약하자면, 앞서 학습시킬 모델은 Cora 데이터셋의
[논문 내 등장 단어들, 논문들 사이의 인용관계]를 활용하여 논문의 주제를 예측하는 모델입니다.
'''
# Cora Graph Dataset 불러오기
G = CoraGraphDataset()
numClasses = G.num_classes
G = G[0]
# 노드들의 feauture & feature의 차원
features = G.ndata['feat']
inputFeatureDim = features.shape[1]
# 각 노드들의 실제 라벨
labels = G.ndata['label']
# 학습/테스트에 사용할 노드들에 대한 표시
trainMask = G.ndata['train_mask']
testMask = G.ndata['test_mask']
GraphSAGE 정의의 첫 단계로 각 층을 정의¶
In [3]:
# 기존에 구현되어 있는 SAGEConv 모듈을 불러와서 GraphSAGE 모델을 구축한다.
from dgl.nn.pytorch.conv import SAGEConv
class GraphSAGE(nn.Module):
'''
graph : 학습할 그래프
inFeatDim : 데이터의 feature의 차원
numHiddenDim : 모델의 hidden 차원
numClasses : 예측할 라벨의 경우의 수
numLayers : 인풋, 아웃풋 레이어를 제외하고 중간 레이어의 갯수
activationFunction : 활성화 함수의 종류
dropoutProb : 드롭아웃 할 확률
aggregatorType : [mean, gcn, pool (for max), lstm]
'''
def __init__(self,graph, inFeatDim, numHiddenDim, numClasses, numLayers, activationFunction, dropoutProb, aggregatorType):
super(GraphSAGE, self).__init__()
self.layers = nn.ModuleList()
self.graph = graph
# 인풋 레이어
self.layers.append(SAGEConv(inFeatDim, numHiddenDim, aggregatorType, dropoutProb, activationFunction))
# 히든 레이어
for i in range(numLayers):
self.layers.append(SAGEConv(numHiddenDim, numHiddenDim, aggregatorType, dropoutProb, activationFunction))
# 출력 레이어 (마지막 층에는 activation함수가 없다.)
# Output 차원은 class의 개수로 맞춰준다.
self.layers.append(SAGEConv(numHiddenDim, numClasses, aggregatorType, dropoutProb, activation=None))
def forward(self, features):
x = features
for layer in self.layers:
x = layer(self.graph, x)
return x
역전파을 통해 GraphSAGE를 학습한다.¶
In [4]:
def train(model, lossFunction, features, labels, trainMask, optimizer, numEpochs):
executionTime=[]
flag=True
for epoch in range(numEpochs):
model.train()
startTime = time.time()
logits = model(features) # 포워딩
loss = lossFunction(logits[trainMask], labels[trainMask]) # 모델의 예측값과 실제 라벨을 비교하여 loss 값 계산
optimizer.zero_grad()
loss.backward()
optimizer.step()
executionTime.append(time.time() - startTime)
acc = evaluateTrain(model, features, labels, trainMask)
if epoch<5 or epoch>numEpochs-5:
print("Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f}".format(epoch, np.mean(executionTime), loss.item(), acc))
elif flag:
print('...')
flag=False
평가 및 테스트 함수¶
In [5]:
# 모델 학습 결과를 평가할 함수
def evaluateTrain(model, features, labels, mask):
model.eval()
with torch.no_grad():
logits = model(features)
logits = logits[mask]
labels = labels[mask]
_, indices = torch.max(logits, dim=1)
correct = torch.sum(indices == labels)
return correct.item() * 1.0 / len(labels)
def evaluateTest(model, features, labels, mask):
model.eval()
with torch.no_grad():
logits = model(features)
logits = logits[mask]
labels = labels[mask]
_, indices = torch.max(logits, dim=1)
macro_f1 = f1_score(labels, indices, average = 'macro')
correct = torch.sum(indices == labels)
return correct.item() * 1.0 / len(labels), macro_f1
def test(model, feautures, labels, testMask):
acc, macro_f1 = evaluateTest(model, features, labels, testMask)
print("Test Accuracy {:.4f}".format(acc))
print("Test macro-f1 {:.4f}".format(macro_f1))
파라미터 설정¶
In [6]:
# 하이퍼파라미터 초기화
dropoutProb = 0.5
learningRate = 1e-2
numEpochs = 50
numHiddenDim = 128
numLayers = 2
weightDecay = 5e-4
aggregatorType = "gcn"
이제 전체 과정 진행¶
In [7]:
# 모델 생성
model = GraphSAGE(G, inputFeatureDim, numHiddenDim, numClasses, numLayers, F.relu, dropoutProb, aggregatorType)
print(model)
# 손실함수
lossFunction = torch.nn.CrossEntropyLoss()
# 옵티마이저 초기화
optimizer = torch.optim.Adam(model.parameters(), lr=learningRate, weight_decay=weightDecay)
In [8]:
train(model, lossFunction, features, labels, trainMask, optimizer, numEpochs)
In [9]:
test(model, features, labels, testMask)