개념
그래프(Graph): 노드(Node)와 엣지(Edge)
그래프 G = (V, E)는 꼭짓점(vertex), 즉 노드(node)의 집합 V와 엣지(edge)의 집합 E로 이루어집니다. 각 엣지는 두 노드를 연결합니다.
방향 그래프(directed graph)와 무방향 그래프(undirected graph). 무방향 그래프에서 엣지 (u, v)는 u가 v에 연결되어 있고 v도 u에 연결되어 있다는 뜻입니다. 방향 그래프, 곧 다이그래프(digraph)에서 엣지 (u, v)는 u가 v를 가리킨다는 의미이며, 그 반대 방향이 반드시 존재한다고 보장되지는 않습니다.
가중 그래프(weighted graph)와 비가중 그래프(unweighted graph). 비가중 그래프에서는 엣지가 있거나 없거나 둘 중 하나입니다. 가중 그래프에서는 각 엣지에 수치 가중치(numerical weight)가 붙습니다. 거리, 비용, 결합 강도 같은 값이 가중치가 될 수 있습니다.
| 그래프 유형(Graph type) | 예시(Example) |
|---|
| 무방향, 비가중(undirected, unweighted) | 페이스북 친구 관계 네트워크(Facebook friendship network) |
| 방향, 비가중(directed, unweighted) | 트위터 팔로우 네트워크(Twitter follow network) |
| 무방향, 가중(undirected, weighted) | 도로 지도(road map, 거리 기반) |
| 방향, 가중(directed, weighted) | 웹 페이지 링크(web page links, PageRank 점수) |
인접 행렬(Adjacency Matrix)
인접 행렬 A는 그래프의 핵심 표현입니다. 노드가 n개인 그래프에서 다음과 같이 정의합니다.
A[i][j] = 1 if there is an edge from node i to node j
A[i][j] = 0 otherwise
무방향 그래프에서는 A가 대칭(symmetric) 행렬입니다. 즉 A[i][j] = A[j][i]가 성립합니다. 가중 그래프에서는 A[i][j]가 엣지 (i, j)의 가중치 값이 됩니다.
예시: 삼각형(triangle).
Nodes: 0, 1, 2
Edges: (0,1), (1,2), (0,2)
A = [[0, 1, 1],
[1, 0, 1],
[1, 1, 0]]
인접 행렬은 모든 GNN의 입력(input)입니다. A에 가하는 행렬 연산은 그래프 위에서의 연산과 그대로 대응됩니다.
차수(Degree)
노드의 차수(degree)는 그 노드에 닿아 있는 엣지의 개수입니다. 방향 그래프에서는 들어오는 엣지 개수인 진입 차수(in-degree)와 나가는 엣지 개수인 진출 차수(out-degree)를 구분합니다.
차수 행렬(degree matrix) D는 대각 행렬(diagonal matrix)입니다.
D[i][i] = degree of node i
D[i][j] = 0 for i != j
앞의 삼각형 예시에서는 모든 노드가 다른 두 노드와 연결되어 있으므로 D = diag(2, 2, 2)가 됩니다.
차수는 노드의 중요도(importance)에 대한 정보를 줍니다. 차수가 큰 노드는 허브 노드(hub node)에 해당합니다. 네트워크의 차수 분포(degree distribution)는 그 구조를 드러냅니다. 소셜 네트워크는 거듭제곱 법칙(power law)을 따르는 경향이 있어, 허브는 적고 잎 노드(leaf node)는 많습니다. 무작위 그래프(random graph)는 푸아송 분포(Poisson-distributed) 차수를 갖습니다.
BFS와 DFS
그래프 순회(traversal)의 가장 기본이 되는 두 알고리즘입니다. 둘 다 익혀 두어야 합니다.
너비 우선 탐색(Breadth-First Search; BFS). 먼저 모든 이웃 노드(neighbor)를 살펴본 뒤, 그다음으로 이웃의 이웃을 살펴봅니다. 큐(queue, 선입선출/FIFO)를 사용합니다.
BFS from node 0:
Visit 0
Queue: [1, 2] (neighbors of 0)
Visit 1
Queue: [2, 3] (add neighbors of 1)
Visit 2
Queue: [3] (neighbors of 2 already visited)
Visit 3
Queue: [] (done)
BFS는 비가중 그래프에서 최단 경로(shortest path)를 찾아 줍니다. 시작 노드(start)에서 임의의 노드까지의 거리는 그 노드가 처음 발견(discover)되는 BFS 레벨과 같습니다. 그래서 소셜 네트워크에서 홉 수(hop-count) 거리를 구할 때 BFS가 자주 쓰입니다.
깊이 우선 탐색(Depth-First Search; DFS). 가능하면 깊이 내려간 뒤에야 되돌아오는(backtracking) 방식입니다. 스택(stack, 후입선출/LIFO) 또는 재귀 호출(recursion)을 사용합니다.
DFS from node 0:
Visit 0
Stack: [1, 2] (neighbors of 0)
Visit 2 (pop from stack)
Stack: [1, 3] (add neighbors of 2)
Visit 3 (pop from stack)
Stack: [1]
Visit 1 (pop from stack)
Stack: [] (done)
DFS는 다음 작업에 유용합니다.
- 연결 성분(connected component) 찾기. 아직 방문하지 않은 노드에서 DFS를 새로 시작합니다.
- 순환 탐지(cycle detection). DFS 트리의 후행 엣지(back edge)를 확인합니다.
- 위상 정렬(topological sorting). DFS 종료 순서를 뒤집어서 얻습니다.
| 알고리즘(Algorithm) | 자료구조(Data structure) | 찾는 대상(Finds) | 활용 사례(Use case) |
|---|
| BFS | 큐(queue) | 최단 경로(shortest paths) | 소셜 네트워크 거리, 지식 그래프 순회 |
| DFS | 스택(stack) | 연결 성분, 순환(components, cycles) | 연결성 판단, 위상 정렬 |
그래프 라플라시안(Graph Laplacian)
L = D - A로 정의됩니다. 스펙트럼 그래프 이론에서 가장 중요한 행렬입니다.
앞의 삼각형 예시에서는 다음과 같이 계산됩니다.
D = [[2, 0, 0], A = [[0, 1, 1], L = [[2, -1, -1],
[0, 2, 0], [1, 0, 1], [-1, 2, -1],
[0, 0, 2]] [1, 1, 0]] [-1, -1, 2]]
라플라시안은 다음과 같은 중요한 성질을 갖습니다.
- 양의 준정부호(positive semi-definite) 행렬입니다. 모든 고유값이
>= 0입니다.
- 영(zero) 고유값의 개수는 연결 성분의 개수와 같습니다. 연결된 그래프는 영 고유값을 정확히 하나 갖습니다. 서로 분리된 성분(disconnected component)이 3개라면 영 고유값도 3개입니다.
- 가장 작은 0이 아닌 고유값, 즉 피들러 값(Fiedler value)은 연결성(connectivity)을 측정합니다. 피들러 값이 크면 그래프가 단단하게 연결된 상태이고, 작으면 병목(bottleneck) 같은 약한 지점이 있다는 신호입니다.
- 피들러 값에 대응하는 고유벡터(Fiedler vector)는 좋은 분할(split)을 드러냅니다. 값이 양수인 노드를 한 그룹으로, 음수인 노드를 다른 그룹으로 묶으면 자연스러운 분할이 됩니다. 이것이 곧 스펙트럼 클러스터링입니다.
graph TD
subgraph "Graph to Matrices"
G["Graph G"] --> A["Adjacency Matrix A"]
G --> D["Degree Matrix D"]
A --> L["Laplacian L = D - A"]
D --> L
end
subgraph "Spectral Analysis"
L --> E["Eigenvalues of L"]
L --> V["Eigenvectors of L"]
E --> C["Connected components (zeros)"]
E --> F["Connectivity (Fiedler value)"]
V --> S["Spectral clustering"]
end
스펙트럼 성질(Spectral Properties)
인접 행렬과 라플라시안의 고유값은 그래프를 직접 순회하지 않고도 구조적 성질(structural property)을 드러냅니다.
스펙트럼 클러스터링은 다음과 같이 동작합니다.
- 라플라시안
L을 계산합니다.
L의 가장 작은 고유값에 대응하는 고유벡터 k개를 구합니다. 연결된 그래프에서는 첫 번째 모두-1 고유벡터(all-ones eigenvector)는 제외합니다.
- 이 고유벡터들을 각 노드의 새 좌표로 사용합니다.
- 그 좌표에 k-평균(k-means)을 적용합니다.
왜 잘 작동할까요? L의 고유벡터는 그래프 위에서 가장 "매끄러운"(smooth) 함수를 부호화(encode)합니다. 서로 잘 연결된 노드는 비슷한 고유벡터 값을, 병목으로 분리된 노드는 서로 다른 값을 갖게 됩니다. 결과적으로 고유벡터가 자연스럽게 군집(cluster)을 나누어 줍니다.
무작위 행보(random walk)와의 연결. 정규화 라플라시안(normalized Laplacian)은 그래프 위의 무작위 행보와 직접 관련됩니다. 무작위 행보의 정상 분포(stationary distribution)는 노드 차수에 비례합니다. 행보가 얼마나 빠르게 수렴(converge)하는지를 뜻하는 혼합 시간(mixing time)은 스펙트럼 간극(spectral gap)에 따라 결정됩니다.
메시지 패싱(Message Passing)
그래프 신경망(GNN)의 핵심 연산입니다. 각 노드는 이웃 노드로부터 메시지를 모으고, 집계(aggregate)한 뒤 자신의 상태(state)를 갱신(update)합니다.
h_v^(k+1) = UPDATE(h_v^(k), AGGREGATE({h_u^(k) : u in neighbors(v)}))
가장 단순한 형태에서는 AGGREGATE가 평균(mean), UPDATE가 선형 변환과 활성화 함수의 조합이 됩니다.
h_v^(k+1) = sigma(W * mean({h_u^(k) : u in neighbors(v)}))
사실 이 연산은 행렬 곱셈으로 다시 쓸 수 있습니다. 모든 노드 특성을 모아 놓은 행렬을 H, 인접 행렬을 A라 하면 다음과 같이 표현됩니다.
H^(k+1) = sigma(A_norm * H^(k) * W)
여기서 A_norm은 정규화된 인접 행렬로, 각 행(row)의 합이 1이 되도록 맞춘 것입니다.
메시지 패싱을 한 번 수행하면 각 노드는 바로 옆 이웃의 정보를 "보게" 됩니다. 두 번이면 이웃의 이웃까지, K번이면 K-홉(hop) 이웃 범위의 정보가 노드에 전달됩니다.
graph LR
subgraph "Round 0"
A0["Node A: [1,0]"]
B0["Node B: [0,1]"]
C0["Node C: [1,1]"]
end
subgraph "Round 1 (aggregate neighbors)"
A1["Node A: avg(B,C) = [0.5, 1.0]"]
B1["Node B: avg(A,C) = [1.0, 0.5]"]
C1["Node C: avg(A,B) = [0.5, 0.5]"]
end
A0 --> A1
B0 --> A1
C0 --> A1
A0 --> B1
C0 --> B1
A0 --> C1
B0 --> C1
개념(Concept)과 머신러닝 적용(ML Application)
| 개념(Concept) | 머신러닝 적용(ML Application) |
|---|
| 인접 행렬(adjacency matrix) | GNN의 입력 표현 |
| 그래프 라플라시안(graph Laplacian) | 스펙트럼 클러스터링, 커뮤니티 탐지(community detection) |
| BFS/DFS | 지식 그래프 순회, 경로 탐색(path finding) |
| 차수 분포(degree distribution) | 노드 중요도, 특성 공학(feature engineering) |
| 메시지 패싱(message passing) | GNN 계층(layer, 예: GCN, GAT, GraphSAGE) |
라플라시안 L의 고유값 | 커뮤니티 탐지, 그래프 분할(graph partitioning) |
| 스펙트럼 클러스터링 | 비지도 노드 그룹화(unsupervised node grouping) |
| 페이지랭크(PageRank) | 노드 중요도 산정, 웹 검색 |
만들어 보기
1단계: 그래프 클래스(graph class) 직접 구현하기
class Graph:
def __init__(self, n_nodes, directed=False):
self.n = n_nodes
self.directed = directed
self.adj = {i: {} for i in range(n_nodes)}
def add_edge(self, u, v, weight=1.0):
self.adj[u][v] = weight
if not self.directed:
self.adj[v][u] = weight
def neighbors(self, node):
return list(self.adj[node].keys())
def degree(self, node):
return len(self.adj[node])
def adjacency_matrix(self):
import numpy as np
A = np.zeros((self.n, self.n))
for u in range(self.n):
for v, w in self.adj[u].items():
A[u][v] = w
return A
def degree_matrix(self):
import numpy as np
D = np.zeros((self.n, self.n))
for i in range(self.n):
D[i][i] = self.degree(i)
return D
def laplacian(self):
return self.degree_matrix() - self.adjacency_matrix()
인접 리스트(self.adj)는 이웃 노드를 효율적으로 저장합니다. 스펙트럼 연산에는 행렬 형태가 필요하므로, 인접 행렬로의 변환 단계에서 numpy를 사용합니다.
2단계: BFS와 DFS
from collections import deque
def bfs(graph, start):
visited = set()
order = []
distances = {}
queue = deque([(start, 0)])
visited.add(start)
while queue:
node, dist = queue.popleft()
order.append(node)
distances[node] = dist
for neighbor in graph.neighbors(node):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, dist + 1))
return order, distances
def dfs(graph, start):
visited = set()
order = []
stack = [start]
while stack:
node = stack.pop()
if node in visited:
continue
visited.add(node)
order.append(node)
for neighbor in reversed(graph.neighbors(node)):
if neighbor not in visited:
stack.append(neighbor)
return order
BFS는 양방향 큐(double-ended queue)인 deque를 사용해 O(1) 시간에 popleft를 수행합니다. DFS는 리스트(list)를 스택처럼 활용합니다. 두 알고리즘 모두 모든 노드를 정확히 한 번씩 방문하므로 전체 시간 복잡도는 O(V + E)입니다.
3단계: 연결 성분(connected component)과 라플라시안 고유값
def connected_components(graph):
visited = set()
components = []
for node in range(graph.n):
if node not in visited:
order, _ = bfs(graph, node)
visited.update(order)
components.append(order)
return components
def laplacian_eigenvalues(graph):
import numpy as np
L = graph.laplacian()
eigenvalues = np.linalg.eigvalsh(L)
return eigenvalues
eigvalsh는 대칭 행렬용 함수입니다. 무방향 그래프의 라플라시안은 항상 대칭이므로 그대로 사용할 수 있습니다. 이 함수는 고유값을 오름차순(ascending order)으로 반환하므로, 0의 개수를 세면 연결 성분의 개수를 바로 알 수 있습니다.
4단계: 스펙트럼 클러스터링(spectral clustering)
def spectral_clustering(graph, k=2):
import numpy as np
L = graph.laplacian()
eigenvalues, eigenvectors = np.linalg.eigh(L)
features = eigenvectors[:, 1:k+1]
labels = np.zeros(graph.n, dtype=int)
for i in range(graph.n):
if features[i, 0] >= 0:
labels[i] = 0
else:
labels[i] = 1
return labels
k=2인 경우에는 피들러 벡터의 부호(sign)만으로 그래프를 두 군집으로 나눌 수 있습니다. k > 2인 경우에는 자명한 모두-1 고유벡터를 제외한 앞쪽 k개의 고유벡터에 k-평균을 적용합니다.
5단계: 메시지 패싱(message passing)
def message_passing(graph, features, weight_matrix):
import numpy as np
A = graph.adjacency_matrix()
row_sums = A.sum(axis=1, keepdims=True)
row_sums[row_sums == 0] = 1
A_norm = A / row_sums
aggregated = A_norm @ features
output = aggregated @ weight_matrix
return output
이 함수는 GNN 메시지 패싱을 한 번 수행한 것입니다. 각 노드의 새 특성은 이웃 노드 특성의 가중 평균(weighted average)에 가중치 행렬(weight matrix)을 곱해 변환한 결과입니다. 여러 단계를 쌓으면 정보가 더 멀리 전파(propagate)됩니다.
사용하기
NetworkX와 numpy를 사용하면 위와 같은 연산을 한 줄로 처리할 수 있습니다.
import networkx as nx
import numpy as np
G = nx.karate_club_graph()
A = nx.adjacency_matrix(G).toarray()
L = nx.laplacian_matrix(G).toarray()
eigenvalues = np.linalg.eigvalsh(L.astype(float))
print(f"가장 작은 고유값: {eigenvalues[:5]}")
print(f"연결 성분 수: {nx.number_connected_components(G)}")
communities = nx.community.greedy_modularity_communities(G)
print(f"찾은 커뮤니티 수: {len(communities)}")
pr = nx.pagerank(G)
top_nodes = sorted(pr.items(), key=lambda x: x[1], reverse=True)[:5]
print(f"상위 5개 PageRank 노드: {top_nodes}")
NetworkX는 최적화된 C 백엔드(backend)를 활용해 다양한 규모의 그래프를 처리합니다. 실서비스(production)에서는 NetworkX를 활용하고, 처음부터 직접 구현한 코드는 내부에서 무슨 일이 일어나는지를 이해하는 학습용으로 쓰면 됩니다.
numpy를 활용한 스펙트럼 분석(spectral analysis)
import numpy as np
A = np.array([
[0, 1, 1, 0, 0],
[1, 0, 1, 0, 0],
[1, 1, 0, 1, 0],
[0, 0, 1, 0, 1],
[0, 0, 0, 1, 0]
])
D = np.diag(A.sum(axis=1))
L = D - A
eigenvalues, eigenvectors = np.linalg.eigh(L)
print(f"고유값: {np.round(eigenvalues, 4)}")
print(f"피들러 값(Fiedler value): {eigenvalues[1]:.4f}")
print(f"피들러 벡터(Fiedler vector): {np.round(eigenvectors[:, 1], 4)}")
fiedler = eigenvectors[:, 1]
group_a = np.where(fiedler >= 0)[0]
group_b = np.where(fiedler < 0)[0]
print(f"군집 A: {group_a}")
print(f"군집 B: {group_b}")
핵심 역할은 피들러 벡터가 맡습니다. 양수 항(positive entry)은 한 군집, 음수 항(negative entry)은 다른 군집으로 갈립니다. 반복적인 최적화(iterative optimization)는 필요 없고, 고유값 분해(eigendecomposition)를 한 번만 수행하면 됩니다.
산출물 만들기
이 강의의 최종 산출물은 outputs/skill-graph-analysis.md입니다. 그래프 형태의 데이터(graph-structured data)를 분석할 때, 어떤 표현을 쓰고 어떤 알고리즘을 적용할지, GNN을 도입할지 판단하는 참조 자료(reference)로 사용합니다.
연결
| 개념(Concept) | 등장하는 곳(Where it shows up) |
|---|
| 인접 행렬(adjacency matrix) | GCN, GAT, GraphSAGE의 입력 |
| 라플라시안(Laplacian) | 스펙트럼 클러스터링, ChebNet 필터 |
| BFS | 지식 그래프 순회, 최단 경로 질의(shortest path query) |
| 메시지 패싱(message passing) | 모든 GNN 계층, 신경 메시지 패싱(neural message passing) |
| 스펙트럼 간극(spectral gap) | 그래프 연결성, 무작위 행보의 혼합 시간(mixing time) |
| 차수 분포(degree distribution) | 거듭제곱 법칙 네트워크(power-law network), 노드 특성 공학 |
| 연결 성분(connected component) | 전처리(preprocessing), 분리된 그래프 처리 |
| 페이지랭크(PageRank) | 노드 중요도 순위, 어텐션(attention) 초기값 |
GNN은 별도로 짚어 둘 만합니다. GCN(Kipf & Welling, 2017)의 그래프 합성곱(graph convolution) 연산은 자기 루프(self-loop)를 더한 인접 행렬 A_hat = A + I를 사용합니다.
H^(l+1) = sigma(D_hat^(-1/2) * A_hat * D_hat^(-1/2) * H^(l) * W^(l))
여기서 A_hat = A + I이고, D_hat은 A_hat의 차수 행렬입니다. 자기 루프 덕분에 집계 단계에서 각 노드가 자기 자신의 특성도 포함하게 됩니다. 이 식은 대칭 정규화(symmetric normalization)를 적용한 메시지 패싱과 같으며, D_hat^(-1/2) * A_hat * D_hat^(-1/2)가 정규화된 인접 행렬에 해당합니다. 라플라시안이 등장하는 이유는 이 정규화가 L_sym = I - D^(-1/2) * A * D^(-1/2)와 직접 연결되기 때문입니다. 라플라시안을 이해하면 GCN이 왜 잘 작동하는지를 함께 이해할 수 있습니다.
연습문제
-
(쉬움) PageRank를 처음부터 구현하기. 모든 노드를 균일한 점수에서 시작합니다. 각 단계마다 score(v) = (1-d)/n + d * sum(score(u)/out_degree(u))를 계산합니다. 여기서 u는 v를 가리키는 모든 노드입니다. d=0.85를 사용하고, 점수 변화량이 1e-6보다 작아질 때까지 반복하여 작은 웹 그래프(web graph)에서 검증합니다.
-
(중간) 스펙트럼 클러스터링으로 커뮤니티 찾기. 두 개의 완전 부분 그래프(clique)가 단 하나의 엣지로 이어진 그래프처럼, 두 군집이 뚜렷이 갈리는 그래프를 만듭니다. 스펙트럼 클러스터링을 실행해 올바른 분할을 찾는지 확인합니다. 군집 간(cross-cluster) 엣지를 점점 더 추가하면 결과가 어떻게 변하나요?
-
(중간) 가중 그래프의 최단 경로를 위한 다익스트라 알고리즘(Dijkstra's algorithm) 구현. 모든 엣지의 가중치가 같은 그래프에서 BFS 결과와 비교합니다.
-
(어려움) 2계층 메시지 패싱 신경망 만들기. 서로 다른 가중치 행렬을 두 번 적용해 메시지 패싱을 두 단계 수행합니다. 두 단계 후에는 각 노드가 2-홉 이웃의 정보를 가지게 됨을 확인합니다.
-
(어려움) 실제 그래프 분석. 가라테 클럽 그래프(Karate Club graph; 34 노드, 78 엣지)를 사용합니다. 차수 분포, 라플라시안 고유값, 스펙트럼 클러스터링을 계산하고, 스펙트럼 클러스터링 결과를 알려진 정답 분할(ground truth split)과 비교합니다.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 그래프(graph) | "노드와 엣지" | 쌍별 관계(pairwise relationship)를 부호화한 수학적 구조 G=(V,E)이다. |
| 인접 행렬(adjacency matrix) | "연결 관계 표" | 노드 i와 j가 연결되어 있으면 A[i][j] = 1이 되는 n x n 행렬이다. |
| 차수(degree) | "노드가 얼마나 많이 연결되었는가" | 해당 노드에 닿아 있는 엣지의 개수이다. |
| 라플라시안(Laplacian) | "D 빼기 A" | L = D - A로 정의되며, 고유값이 그래프 구조를 드러내는 행렬이다. |
| 피들러 값(Fiedler value) | "대수적 연결도(algebraic connectivity)" | L의 가장 작은 0이 아닌 고유값으로, 그래프가 얼마나 잘 연결되어 있는지를 측정한다. |
| 너비 우선 탐색(BFS) | "한 단계씩 넓혀 가는 탐색" | 더 깊이 들어가기 전에 모든 이웃을 먼저 방문하며 최단 경로를 찾는 순회이다. |
| 깊이 우선 탐색(DFS) | "일단 깊이부터" | 되돌아오기 전에 한 경로를 끝까지 따라가는 순회이다. |
| 메시지 패싱(message passing) | "노드끼리 이웃과 대화한다" | 각 노드가 이웃의 정보를 집계하는 과정으로, GNN의 핵심이다. |
| 스펙트럼 클러스터링(spectral clustering) | "고유벡터로 군집화한다" | 라플라시안의 고유벡터를 사용해 그래프를 분할한다. |
| 연결 성분(connected component) | "따로 떨어진 덩어리" | 모든 노드가 서로 도달 가능한 최대 부분 그래프(maximal subgraph)이다. |
더 읽을거리
- Kipf & Welling (2017), Semi-Supervised Classification with Graph Convolutional Networks: 현대적인 GNN을 대중화한 논문입니다. 스펙트럼 그래프 합성곱이 메시지 패싱 형태로 단순화되는 과정을 보여 줍니다.
- Spielman, Spectral Graph Theory lecture notes: 라플라시안, 스펙트럼 간극, 그래프 분할을 다룬 대표적인 강의 노트입니다.
- Hamilton, Graph Representation Learning: GNN의 토대부터 응용까지 폭넓게 다루는 책입니다.
- Bronstein et al. (2021), Geometric Deep Learning: 격자(grid), 군(group), 그래프(graph), 측지선(geodesic), 게이지(gauge)를 하나의 틀(framework)로 묶는 논문입니다.
- Velickovic et al. (2018), Graph Attention Networks: 메시지 패싱에 어텐션 메커니즘(attention mechanism)을 결합해 확장한 논문입니다.