그래프 신경망이란 무엇일까? (심화)¶
✅ 그래프 신경망 복습¶
✅ 그래프 신경망에서의 어텐션¶
1️⃣ 기본 그래프 신경망의 한계¶
📌 기본 그래프 신경망 vs 그래프 합성곱 신경망¶
- 기본 그래프 신경망에서는 이웃들의 정보를 동일한 가중치로 평균을 낸다.
- 그래프 합성곱 신경망 역시 단순히 연결성을 고려한 가중치로 평균을 낸다.
2️⃣ 그래프 어텐션 신경망¶
📌 그래프 어텐션 신경망(Graph Attention Network, GAT)에서는 가중치 자체도 학습한다.¶
- 실제 그래프에서는 이웃 별로 미치는 영향이 다를 수 있기 때문이다.
- 가중치를 학습하기 위해서 셀프-어텐션(Self-Attention)이 사용된다.
📌각 층에서 정점 $i$로부터 이웃$j$로의 가중치 $\alpha_{ij}$는 3단계를 통해 계산된다.¶
- 해당 층의 정점 $i$의 임베딩 $h_{i}$에 신경망 $W$를 곱해서 새로운 임베딩을 얻는다.
- 정점 $i$와 정점 $j$의 새로운 임베딩을 연결한 후, 어텐션 계수 a를 내적한다. 어텐션 계수 a는 모든 정점이 공유하는 학습 변수이다.
- 2번의 결과에 소프트맥스(Softmax)를 적용한다.
- 여기서 학습 변수들은 $W$와 $a$벡터이다.
📌 여러 개의 어텐션을 동시에 학습한 뒤, 결과를 연결하여 사용할 수도 있다.¶
- 멀티헤드 어텐션(Multi-head Attention)이라고 부른다.
📌 어텐션의 결과 정점 분류의 정확도(Accuracy)가 향상되는 것을 확인할 수 있다.¶
✅ 그래프 표현 학습과 그래프 풀링¶
1️⃣ 그래프 표현 학습¶
📌 그래프 표현 학습, 혹은 그래프 임베딩이란 그래프 전체를 벡터의 형태로 표현하는 것이다.¶
- 개별 정점을 벡터의 형태로 표현하는 정점 표현 학습과 구분된다.
- 그래프 임베딩은 벡터의 형태로 표현된 그래프 자체를 의미하기도 한다.
- 그래프 임베딩은 그래프 분류 등에 활용
- 그래프 형태로 표현된 화합물의 분자 구조로부터 특성을 예측하는 것이 한가지 예시이다.
2️⃣ 그래프 풀링¶
📌 그래프 풀링(Graph Pooling)이란 정점 임베딩들로부터 그래프 임베딩을 얻는 과정이다.¶
- 평균 등 단순한 방법보다 그래프의 구조를 고려한 방법을 사용할 경우 그래프 분류 등의 후속 과제에서 더 높은 성능을 얻는 것으로 알려져 있다.
- 아래 그림의 미분가능한 풀링(Differentiable Pooling, DiffPool)은 군집 구조를 활용 임베딩을 계층적으로 집계한 그림이다.
✅ 지나친 획일화 문제¶
1️⃣ 지나친 획일화 문제¶
📌 지나친 획일화(Over-smoothing) 문제란 그래프 신경망의 층의 수가 증가하면서 정점의 임베딩이 서로 유사해지는 현상을 의미한다.¶
- 지나친 획일화 문제는 작은 세상 효과와 관련이 있다.
- 적은 수의 층으로도 다수의 정점에 의해 영향을 받게 된다.
5-layer정도면 5의 거리에 있는 정점을 확인하게되는데 이는 수 많은 정점으로 부터 정보를 합산하기 때문에 마치 지역적인 정보만 보는 것이 아니라 그래프의 전반을 보게되는 효과가 되어 정점들이 비슷비슷한 임베딩을 얻게 되고 분류의 성능이 떨어진다.
📌지나친 획일화의 결과로 그래프 신경망의 층의 수를 늘렸을 때, 후속 과제에서의 정확도가 감소하는 현상이 발견되었다.¶
- 아래 그림에서 보듯이 그래프 신경망의 층이 2개 혹은 3개 일 떄 정확도가 가장 높다.
❗ 잔차항(Residual)을 넣는 것을 생각할 수 있는데, 즉 이전 층의 임베딩을 한 번 더 더해주는 것만으로는 효과가 제한적이다.
2️⃣ 지나친 획일화 문제에 대한 대응¶
📌 JK 네트워크(Jumping Knowledge Network)는 마지막 층의 임베딩 뿐 아니라, 모든 층의 임베딩을 함께 사용한다.¶
📌 APPNP는 0번째 층을 제외하고는 신경망 없이 집계 함수를 단순화하였습니다¶
APPNP의 경우, 층의 수 증가에 따른 정확도 감소 효과가 없는 것을 확인
- 후속 과제로는 정점 분류가 사용되었다.
✅ 그래프 데이터의 증강¶
1️⃣ 그래프 데이터 증강¶
📌 데이터 증강(Data Augmentation)은 다양한 기계학습 문제에서 효과적이다.¶
- 그래프에도 누락되거나 부정확한 간선이 있을 수 있고, 데이터 증강을 통해 보완할 수 있다.
- 임의 보행을 통해 정점간 유사도를 계산하고, 유사도가 높은 정점 간의 간선을 추가하는 방법이 제안되었다.
2️⃣ 그래프 데이터 증강에 따른 효과¶
📌 그래프 데이터 증강의 결과 정점 분류의 정확도가 개선되는 것을 확인할 수 있다.¶
- 아래 그림의 HEAT과 PPR은 제안된 그래프 데이터 증강 기법을 의미한다.
✅ GraphSAGE의 집계 함수 구현¶
라이브러리 로드¶
In [1]:
import numpy as np
import time
import torch
import torch.nn as nn
import torch.nn.functional as F
import dgl
from dgl.data import CoraGraphDataset
from sklearn.metrics import f1_score
import dgl.function as fn
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 구현의 첫 단계로 GraphSAGE의 집계 함수를 직접 구현해본다.¶
- AGG함수로는 평균을 사용한다.
In [3]:
class SAGEConv(nn.Module):
"""
in_feats: 인풋 feature의 사이즈
out_feats: 아웃풋 feature의 사이즈
activation: None이 아니라면, 노드 피쳐의 업데이트를 위해서 해당 activation function을 적용한다.
"""
'''
https://arxiv.org/pdf/1706.02216.pdf
https://docs.dgl.ai/en/0.4.x/_modules/dgl/nn/pytorch/conv/sageconv.html
위 두 페이지 참조
'''
def __init__(self, in_feats, out_feats, activation):
super(SAGEConv, self).__init__()
self._in_feats = in_feats
self._out_feats = out_feats
self.activation = activation
# 신경망의 입력으로 input 임베딩 차원을 x2 해준다.
# 위 수식을 보면 두개의 벡터를 concat하는 부분 때문이다.
self.W = nn.Linear(in_feats+in_feats, out_feats, bias=True)
def forward(self, graph, feature):
graph.ndata['h'] = feature
graph.update_all(fn.copy_src('h', 'm'), fn.sum('m', 'neigh'))
# Aggregate & Noramlization
degs = graph.in_degrees().to(feature)
# 평균 계산
hkNeigh = graph.ndata['neigh']/degs.unsqueeze(-1)
# concat
hk = self.W(torch.cat((graph.ndata['h'], hkNeigh), dim=-1))
if self.activation != None:
hk = self.activation(hk)
return hk
위에서 구현한 집계 함수(SAGEConv)를 이용하여 각 층을 정의¶
In [4]:
class GraphSAGE(nn.Module):
'''
graph : 학습할 그래프
inFeatDim : 데이터의 feature의 차원
numHiddenDim : 모델의 hidden 차원
numClasses : 예측할 라벨의 경우의 수
numLayers : 인풋, 아웃풋 레이어를 제외하고 중간 레이어의 갯수
activationFunction : 활성화 함수의 종류
'''
def __init__(self, graph, inFeatDim, numHiddenDim, numClasses, numLayers, activationFunction):
super(GraphSAGE, self).__init__()
self.layers = nn.ModuleList()
self.graph = graph
# 인풋 레이어
self.layers.append(SAGEConv(inFeatDim, numHiddenDim, activationFunction))
# 히든 레이어
for i in range(numLayers):
self.layers.append(SAGEConv(numHiddenDim, numHiddenDim, activationFunction))
# 출력 레이어
self.layers.append(SAGEConv(numHiddenDim, numClasses, activation=None))
# 순전파
def forward(self, features):
x = features
for layer in self.layers:
x = layer(self.graph, x)
return x
역전파를 통해 GraphSAGE를 학습¶
In [5]:
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 [6]:
# 모델 학습 결과를 평가할 함수
# def accuracy(logits, labels):
# _, indices = torch.max(logits, dim=1)
# correct = torch.sum(indices == labels)
# return correct.item() * 1.0 / len(labels)
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 [7]:
# 하이퍼파라미터 정의
learningRate = 1e-2
numEpochs = 50
numHiddenDim = 128
numLayers = 2
weightDecay = 5e-4
전체 과정 진행¶
In [8]:
# 모델 생성
model = GraphSAGE(G, inputFeatureDim, numHiddenDim, numClasses, numLayers, F.relu)
print(model)
lossFunction = torch.nn.CrossEntropyLoss()
# 옵티마이저 초기화
optimizer = torch.optim.Adam(model.parameters(), lr=learningRate, weight_decay=weightDecay)
In [9]:
train(model, lossFunction, features, labels, trainMask, optimizer, numEpochs)
In [10]:
test(model, features, labels, testMask)
'AI > 이론' 카테고리의 다른 글
Semantic segmentation (0) | 2021.03.09 |
---|---|
Image classification II (0) | 2021.03.09 |
Annotation data efficient learning (2) | 2021.03.08 |
Image classification I (0) | 2021.03.08 |
그래프 신경망이란 무엇일까? (기본) (1) | 2021.02.26 |
그래프를 추천시스템에 어떠게 활용할까? (심화) (0) | 2021.02.25 |
그래프의 정점을 어떻게 벡터로 표현할까? (0) | 2021.02.25 |
그래프를 추천시스템에 어떻게 활용할까?(기본) (0) | 2021.02.24 |