그래프 튜토리얼 8화 : GraphVAE

그래프 튜토리얼 8화 : GraphVAE
  1. GAE와 Autoencoder 응용 이해
  2. Amazon Purchase 데이터로 Link Prediction 구매 예측

안녕하세요, GUG 여러분! 그래프 튜토리얼의 가이드 Erica에요 🍹

8번째 여행에서는 GAE, VGAE 모델을 다룰 거에요. GAE는 AutoEncoder의 원리를 응용해서 Node와 Edge의 정보를 활용해 그래프의 구조를 학습합니다. 그래프 복원을 통해 누락된 Edge나 미래의 Edge를 예측하는 Link Prediction task에 특히 뛰어난 모델이랍니다.

📢 가독성을 위해 본문에는 코드 전문을 임포트하지 않았어요.
튜토리얼 코드 전체를 한번에 실행해 볼 수 있는 .ipynb 파일은 아래 링크에 있습니다.

GitHub - H4Y3J1N/Graph-Travel: with GUG, Let’s explore the Graph Neural Network!
with GUG, Let’s explore the Graph Neural Network! Contribute to H4Y3J1N/Graph-Travel development by creating an account on GitHub.

Graph Travel 튜토리얼이 도움이 되고 있다면,
꾸준한 업로드의 원동력이 되도록 STAR🌟를 눌러주세요~!

Autoencoder?

Autoencoder, ML을 공부한 사람이라면 한 번쯤은 들어봤을 거에요. 이 알고리즘은 비지도 학습 방식으로, 입력 데이터를 압축된 latent representation(잠재 표현)으로 Encoding한 다음 이를 다시 Decoding해 원본 데이터를 재구성하는 방식으로 작동하죠.

GAE는 이러한 원리를 그래프 데이터 학습에 적용했어요. 다시 말해, GAE(Graph Autoencoder)는 그래프 데이터를 위한 오토인코더(Autoencoder) 모델이랍니다.

더 발전된 형태의 VGAE 모델도 존재해요. 이 모델은 Variational Autoencode 원리를 도입하여 그래프의 노드 임베딩을 생성합니다.

Encode & Decode the Graph

Autoencoder의 원리가 학습에 적용된다고 했으니, Encoder와 Decoder 함수가 Graph 학습에서 어떤 원리로 작동하는지 이해할 필요가 있겠죠.

Encoder: 그래프의 각 노드를 잠재 표현(또는 임베딩)으로 매핑하는 함수입니다. 인코더는 일반적으로 그래프 신경망(GNN)을 기반으로 합니다. 인코더는 노드와 그 이웃들의 특성을 활용하여 Node Embedding을 생성하죠.

Encoder 종류 : GNN 기반이라는 것은, GCN, GraphSAGE, GAT 등의 다양한 컨볼루션 레이어 conv를 사용할 수 있다는 뜻입니다.

Decoder: latent representation을 사용하여 그래프의 구조나 노드 간의 관계를 재구성합니다. 예를 들어, 잠재 표현을 사용하여 노드 간의 연결 확률을 예측할 수 있습니다.

Decoder의 작동 원리: GAE에서 가장 간단한 Decoder는 두 노드 임베딩의 내적을 사용하여 해당 노드 쌍 간의 연결 확률을 계산하는 것입니다. 내적 값이 크면 두 노드 간의 연결 확률이 높다고 판단합니다.

여기서 한번 더 짚어보고 가는 것이 좋겠네요. "Encoder는 그래프의 각 노드를 임베딩(또는 표현)으로 변환한다"는 것은 정확히 무슨 뜻일까요?

이것은 다시 말해 각 Node를 고정된 크기의 Dense Vector로 표현하는 것을 의미합니다. 이 임베딩은 그래프의 구조적 특성과 노드의 속성을 반영하여 생성됩니다.

임베딩을 생성하는 과정은 아래의 4단계로 정리할 수 있어요.

  • 각 노드는 초기에 그래프에서 주어진 Node Feature(예: 노드 속성, 노드 레이블 등)을 바탕으로 초기 embedding을 가집니다. 이 초기 임베딩은 간단한 feature vector일 수도 있어요.
  • 이웃 정보 집계: 그래프 합성곱 신경망(GCN) 또는 다른 그래프 신경망 방법을 사용하여 각 노드의 이웃 정보를 집계합니다. 이렇게 해서, 각 노드는 그 주변의 정보를 캡처하는 임베딩을 얻게 됩니다.
  • 계층적 집계: 여러 layer 층의 그래프 신경망을 사용하면, 각 노드는 더 넓은 범위의 이웃 정보를 포함하는 임베딩을 얻게 됩니다. 예를 들어, 2층 GCN을 사용하면 각 노드는 2-hop 이웃의 정보를 포함하는 임베딩을 얻게 됩니다.
  • 결과: 이런 과정을 거쳐서, 각 Node는 그래프 구조와 노드의 속성을 반영하는 Dense Vector 형태의 임베딩을 얻게 됩니다.

이렇게 만든 임베딩은 그래프의 구조적 패턴과 노드의 속성을 모두 포착하기 때문에, 노드 분류, 링크 예측, 그래프 분류 등 다양한 task에 사용할 수 있답니다.

학습 과정

GAE의 목표는 원본 그래프의 연결을 가능한 한 잘 재구성하는 것입니다.
그것을 위해 원래 그래프의 Edge와 Decoder에서 예측한 연결 확률 간의 차이를 최소화하는 손실 함수를 사용합니다. 일반적으로 Binary Cross Entropy Loss Function을 많이 활용해요.
그렇게 구한 loss값을 토대로, Gradient Desent 경사 하강법을 사용해 손실 함수를 최소화하며 모델의 파라미터를 업데이트합니다.

Variational GAE (VGAE):

GAE의 변형인 VGAE는, 임베딩 생성 과정에서 Variational Autoencode 아이디어를 도입합니다. Encoder가 평균과 로그 분산을 출력하고, 이를 사용하여 잠재 변수를 샘플링하는 방식이죠. VGAE의 손실 함수는 재구성 손실과 KL 발산 두 부분으로 구성됩니다.

GAE는 각 노드에 대한 단일 임베딩만 생성하는 반면, VGAE는 각 노드의 임베딩을 생성하기 위해 평균과 로그 분산을 사용해요.

VGAE가 재구성 손실과 KL 발산 두 가지 손실 구성 요소를 최소화하려고 시도하는 반면, GAE는 재구성 손실만 최소화하려고 시도합니다.

Conv Layer

앞선 설명에서 Encoder는 그래프 합성곱 신경망(GCN) 또는 다른 그래프 신경망 구조를 사용하여 그래프의 노드를 임베딩으로 변환한다고 했었죠.

Graph AutoEncoder (GAE) 또는 Variational Graph AutoEncoder (VGAE)는 특정 그래프 신경망 구조에 국한되지 않습니다. 주어진 문제나 데이터셋에 따라 다양한 그래프 신경망 구조를 Encoder로 사용할 수 있어요.

그렇다면 어떤 구조를 응용해볼 수 있을까요?

  • GCN (Graph Convolutional Network): GCN은 간단하면서도 효과적인 그래프 합성곱 방법입니다. 각 노드의 특성은 직접적인 이웃과 함께 집계됩니다.
  • GraphSAGE (Graph Sample and Aggregation): GraphSAGE는 이웃의 표본 추출과 특성 집계를 결합하는 방법입니다. 이 방법은 다양한 집계 함수(예: Mean, LSTM, Pooling)를 사용하여 이웃의 특성을 집계합니다.
  • GAT (Graph Attention Network): GAT는 주변 노드의 특성을 집계할 때 attention 매커니즘을 사용합니다. 이를 통해 다양한 이웃에 대한 다른 중요도를 학습할 수 있습니다.

그 외에도 ChebNet, ARMA, GIN 등 다양한 그래프 신경망 구조를 활용해볼 수 있답니다. GraphSAGE와 같은 다른 구조를 Encoder로 사용하려면 해당 구조를 기반으로 Encoder를 정의하면 됩니다. 이번 튜토리얼에서는 가장 기본적인 GCN conv를 사용합니다. 여러분은 성능 비교까지 진행해봐도 재미있겠네요.

GAE with RecSys?

링크 예측(link prediction) 작업은 추천 시스템과 깊은 연관이 있습니다.

사실, 추천 시스템은 사용자와 아이템 사이의 잠재적인 관계, 즉 "Link"를 예측하는 시스템으로 볼 수 있기 때문이에요.

그래프 구조적으로, 사용자와 아이템 간의 상호 작용을 그래프로 모델링할 수 있습니다. 사용자와 아이템은 노드로 표현되며, 사용자가 아이템을 구매하거나 평가한 경우 해당 사용자와 아이템 사이에 연결(에지)이 생성돼요. 따라서, 추천 시스템에서 아직 발생하지 않은 상호 작용을 예측하는 것은 결국 링크 예측 문제와 동일합니다.

또한 링크 예측 모델은 노드(사용자나 아이템)의 임베딩을 생성합니다. 이 임베딩은 사용자의 선호나 아이템의 특성을 나타내는 밀집 벡터로 볼 수 있습니다. 이러한 임베딩을 사용하여 사용자와 아이템 사이의 잠재적인 상호 작용을 예측할 수 있죠.

따라서, 링크 예측 기법을 활용하여 추천 시스템을 구축하면, 사용자와 아이템 사이의 복잡한 관계와 상호 작용을 더욱 효과적으로 모델링할 수 있습니다. 이번 튜토리얼 코드에서는 이 내용을 직접 눈으로 확인해보기 위해, Amazon에서 공개한 co-Purchase Open data을 사용할 예정입니다.

VGAE & GraphVAE

두 모델은 학습 방법론은 유사하지만, 적용되는 문제의 범위와 세부 목적이 달라요.

VGAE (Variational Graph Autoencoder)가 그래프의 인접 행렬을 사용하여 노드 정보에 집중하고 임베딩을 생성하는 반면, GraphVAE는 그래프 구조에 집중해 자체를 생성하거나 그래프 전체를 임베딩합니다.

다시 말해 VGAE는 노드 단위, 그리고 GraphVAE는 그래프 단위로 임베딩합니다.

  1. VGAE (Variational Graph Autoencoder): 노드나 엣지에 대한 정보 임베딩에 초점을 맞춥니다. VGAE는 주로 노드 분류, 엣지 예측, 클러스터링 등의 작업에 사용되며, 그래프의 로컬 구조에 대한 정보를 더 잘 포착합니다.
  2. GraphVAE (Graph Variational Autoencoder): 전체 그래프나 서브그래프에 대한 정보를 임베딩하거나 새로운 그래프를 생성하는 것에 초점을 맞춥니다. 그래프 전체의 구조적 특성을 이해하거나 새로운 그래프를 생성할 때 유용합니다.

이처럼 GNN의 모델들은 크게 두 부분으로 나눌 수 있습니다. 노드와 에지 표현을 집중적으로 학습해 임베딩을 만드는 모델과, 그래프와 서브그래프의 구조 자체를 집중적으로 학습해 임베딩을 만드는 모델로 말이죠!

  1. 노드와 에지 표현을 중점적으로 학습하는 모델: 그래프의 local information, 즉 개별 노드나 에지의 특성을 임베딩하는 것에 주목합니다. 예를 들어, Graph Convolutional Networks (GCNs), GraphSAGE, GAT (Graph Attention Networks), VGAE 등이 여기에 해당합니다. 이런 모델들은 노드 분류, 링크 예측, 클러스터링 등의 작업에 주로 사용돼요.
  2. 그래프와 서브그래프의 구조를 중점적으로 학습하는 모델: 전체 그래프나 서브그래프를 임베딩/생성하는 것에 초점을 맞춥니다. 예를 들어, GraphVAE, GraphRNN, GraphGAN 등이 여기에 해당합니다. 이런 모델들은 그래프 생성, 그래프 비교, 그래프 클러스터링 등에 사용될 수 있습니다.

GraphVAE는 전체적인 그래프 구조나 그래프 사이의 관계에 더 집중합니다. 그러니 우리는 task에 더 적합한 VGAE 모델을 사용해 구매 예측을 진행하도록 할게요.

Predict Perchase with VGAE

 class Encoder(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super(Encoder, self).__init__()
        self.conv1 = GCNConv(in_channels, 2 * out_channels, cached=True)
        self.conv_mu = GCNConv(2 * out_channels, out_channels, cached=True)
        self.conv_logvar = GCNConv(2 * out_channels, out_channels, cached=True)
        # BatchNorm
        self.bn1 = torch.nn.BatchNorm1d(2 * out_channels)
        self.bn_mu = torch.nn.BatchNorm1d(out_channels)
        self.bn_logvar = torch.nn.BatchNorm1d(out_channels)
        # Dropout
        self.dropout = torch.nn.Dropout(0.5)

    def forward(self, x, edge_index):
        x = F.relu(self.conv1(x, edge_index))
        # Apply BatchNorm and Dropout
        x = self.dropout(self.bn1(x))
        return self.bn_mu(self.conv_mu(x, edge_index)), self.bn_logvar(self.conv_logvar(x, edge_index))

input feature 를 임베딩으로 만들기 위해 사용된 첫 번째 레이어가 GCNConv임을 확인할 수 있어요. 추후 다른 신경망 레이어를 사용하기 위해서는, Encoder부분을 다르게 정의하면 됩니다.

평균과 로그 분산을 위해 두 번째 GCNConv가 사용됩니다. 중간 임베딩에서 최종 임베딩으로 전환되는 과정이죠. 이후에는 BatchNorm 레이어와 DropOut 레이어를 적용합니다.

이 Encoder는 그래프의 노드를 두 가지 Embedding (평균 & 로그 분산)으로 변환하며, 이 임베딩은 VGAE의 Decoder에서 Edge를 재구성하는 데 사용됩니다.

out_channels = 16
encoder = Encoder(dataset.num_features, out_channels)
model = VGAE(encoder)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

각 그래프의 노드가 16차원의 벡터로 표현되도록 설정했고, VGAE 클래스의 인스턴스를 설정한 다음, Optimizer를 정의합니다. 이 튜토리얼에서는 간단하게 Adam을 사용할게요.

# train 
z = model.encode(data.x, data.train_pos_edge_index)
loss = model.recon_loss(z, data.train_pos_edge_index) + (1 / data.num_nodes) * model.kl_loss()

VGAE의 Encoder 부분을 사용하여 그래프의 노드를 임베딩으로 변환하는 부분의 코드입니다. data.x는 그래프의 노드 특성을 나타내며, data.train_pos_edge_index는 학습 데이터의 긍정 Edge를 나타내요.

이후 loss에서는 재구성 손실과 KL 발산 손실을 계산하여 총 손실을 구합니다.

# test
with torch.no_grad():
    z = model.encode(data.x, data.train_pos_edge_index)
    auc, ap = model.test(z, pos_edge_index, neg_edge_index)

test 함수에서는 그라디언트 계산을 비활성화하고, 학습 데이터를 사용하여 노드 임베딩을 생성합니다.

그렇게 생성된 노드 임베딩을 사용하여 pos 및 neg Edge에 대한 예측을 생성하고, 예측 값을 하나의 텐서로 연결합니다. Loss 손실 값을 구하기 위한 작업이죠. 모델의 성능을 제대로 파악하기 위해 AUC와 AP, F1 Score 값도 함께 출력합니다.

Epoch: 200, Loss: 1.5184, AUC: 0.9210, AP: 0.9151, F1: 0.7977

AUC 값이 0.9210은 매우 높은 편으로, 모델이 Pos와 Neg Edge를 잘 구분하고 있다는 것을 알 수 있습니다. AP 값을 보면 모델의 정밀도와 재현율 사이의 균형이 매우 좋다는 걸 알 수 있고. F1 스코어 0.7977도 나름 나쁘지 않은 수치 같네요.

이 정도면 우리는 아주 간단한 GAE 모델을 통해 구매 예측 모델을 만들어냈다고 할 수 있겠습니다! 하이퍼 파라미터 튜닝과 다양한 Conv Layer 테스트를 통해 어디까지 성능을 높일 수 있을지 직접 테스트해보는 것도 재미있을 것 같습니다.

Attention에 이어 AutoEncoder 개념까지 GNN이 응용할 수 있다는 것을 알아보았어요.

VGAE 모델의 메인 아이디어는 그래프의 구조와 특성을 가장 잘 표현하는 latent representation을 도출하는 것입니다. Encoder를 통해 그래프의 노드를 latent space의 임베딩으로 변환한 다음, Decoder로 두 노드 임베딩의 내적을 통해 해당 노드들 사이에 에지가 있을 확률을 예측한 뒤 Graph를 재구성해냈죠.

🎫 Generation ?

그렇다면 이제, Graph Generation을 배워 볼 때가 되었네요! 다음 튜토리얼 여행에서는 GraphRNN의 컨셉과 원리에 대해 배워보도록 해요!

Subscribe for daily recipes. No spam, just food.