라플라시안이란? “이웃과 얼마나 다르게 튀는지”를 수치화하는 연산자
그래프에서는 시간축처럼 “앞/뒤”가 고정되어 있지 않다. 대신 엣지(연결)가 “누가 이웃인지”를 결정한다. 그래서 GFT는 아래와 같이 정의한다.
- 이웃과 비슷하게 변하는 패턴 = 저주파(부드러움)
- 이웃과 크게 달라지는 패턴 = 고주파(거침/경계)
이 “부드러움/거침”을 수식으로 잡아주는 게 라플라시안 (L)이다.
시작점: 두 개의 데이터프레임
(1) 노드 신호: signal_df (노드에 붙은 값)
| node | x |
|---|---|
| 0 | 10 |
| 1 | 12 |
| 2 | 13 |
| 3 | 9 |
| 4 | 8 |
- 여기서 \(x_i\) 는 “노드 i의 값(신호)”이다.
(2) 그래프 구조: edge_df (연결 + 가중치)
| src | dst | w |
|---|---|---|
| 0 | 1 | 0.9 |
| 0 | 2 | 0.2 |
| 1 | 2 | 0.7 |
| 2 | 3 | 0.8 |
| 3 | 4 | 0.6 |
- \(w_{ij}\) 는 “i와 j가 얼마나 강하게 연결되어 있는가(가중치)”다.
- (보통 GFT 기본 설명은 무방향 그래프를 가정 → 이 경우 \(w_{ij}=w_{ji}\))
- 무방향 그래프: 방향이 없는 그래프(‘주는/받는’이 아니라, 두 노드 사이의 관계 강도만 표현)
인접행렬 A: “연결 지도”
edge_df를 행렬로 바꾸면 인접행렬 (A) 이다.
정의:
$$
A_{ij} =
\begin{cases}
w_{ij} & \text{(i와 j가 연결되어 있으면)}\\
0 & \text{(연결 없으면)}
\end{cases}
$$
예시(노드 0~4): 아래는 “함수처럼” 가중치를 정의한 뒤, 무방향이면 반대 방향도 동일하게 적용한다.
- \(w(0,1)=0.9\), \(w(0,2)=0.2\), \(w(1,2)=0.7\), \(w(2,3)=0.8\), \(w(3,4)=0.6\)
- 무방향이면 \(w(i,j)=w(j,i)\) 이므로 \(w(1,0)=0.9\), \(w(2,0)=0.2\), \(w(2,1)=0.7\), \(w(3,2)=0.8\), \(w(4,3)=0.6\) 도 동일하게 성립한다.
- 그리고 인접행렬 값은 “엣지가 있으면” \(A_{ij}=w(i,j)\), 없으면 \(A_{ij}=0\) 으로 채운다.
차수행렬 D: “각 노드가 가진 연결 강도의 합”
노드 i의 가중 차수(degree):
$$
d_i = \sum_j A_{ij}
$$
그리고 이를 대각행렬로 만든 게 차수행렬 (D):
$$
D_{ii}=d_i,\quad D_{ij}=0\ (i\neq j)
$$
예시 (무방향 가정):
- \(d_0 = 0.9+0.2 = 1.1\)
- \(d_1 = 0.9+0.7 = 1.6\)
- \(d_2 = 0.2+0.7+0.8 = 1.7\)
- \(d_3 = 0.8+0.6 = 1.4\)
- \(d_4 = 0.6 = 0.6\)
라플라시안 L: 그래프에서의 “변화량 측정기”
5-1) 비정규화 라플라시안 (가장 기본)
$$
L = D – A
$$
이 한 줄이 GFT의 핵심이다.
5-2) 라플라시안이 “거칠기”를 만든다는 직관
$$
x^\top L x = \frac{1}{2}\sum_{i}\sum_{j} A_{ij}(x_i-x_j)^2
$$
의미:
- 연결된 두 노드 \((i,j)\)가 값이 비슷하면 \((x_i-x_j)^2\)가 작아서 전체가 작아짐 → 부드러움(저주파)
- 값이 크게 다르면 전체가 커짐 → 거침(고주파)
즉 \(x^\top L x\)는 “이 그래프에서 신호 x가 얼마나 이웃 대비 튀는지”를 재는 점수다.
한 번 더 직관: \((Lx)_i\)는 “이웃과의 차이의 가중합”
$$
(Lx)_i = d_i x_i – \sum_j A_{ij}x_j
= \sum_j A_{ij}(x_i – x_j)
$$
- 노드 i에서 볼 때, 연결된 이웃들과의 차이를 가중치로 합친 값이다.
- 값이 이웃과 비슷하면 \((Lx)_i\)가 작고,
- 경계처럼 혼자 튀면 크게 나온다.
라플라시안으로 “그래프 주파수 축” 만들기: 고유값/고유벡터
GFT는 DFT처럼 사인/코사인 기저를 쓰지 않는다. 대신 라플라시안의 고유분해를 쓴다.
$$
L = U\Lambda U^\top
$$
- \(U\): 고유벡터들을 모아둔 행렬(= 그래프의 “기준 파형들”)
- \(\Lambda\): 고유값 대각행렬(= 그래프 “주파수 크기”)
해석:
- \(\lambda\)가 작을수록 이웃끼리 비슷한 변화(부드러운 모드) → 저 그래프-주파수
- \(\lambda\)가 클수록 이웃 대비 급격한 변화(거친 모드) → 고 그래프-주파수
GFT 정의: “라플라시안 고유벡터 축으로 좌표변환”
그래프 신호 \(x\)를 고유벡터 기저로 투영하면 GFT다.
$$
\hat{x} = U^\top x
$$
- \(\hat{x}\)는 “그래프 주파수 성분”
- \(\hat{x}_m\)은 m번째 모드(주파수)에 얼마나 실려 있는지
역변환:
$$
x = U\hat{x}
$$
정규화 라플라시안: 노드 차수 편차를 줄이고 싶을 때
노드마다 연결 수(차수)가 크게 다르면, 비정규화 \(L\)이 특정 노드에 치우칠 수 있다. 그때 정규화 버전을 쓴다.
대칭 정규화 라플라시안
$$
L_{\text{sym}} = I – D^{-1/2}AD^{-1/2}
$$
9-2) 랜덤워크 라플라시안
$$
L_{\text{rw}} = I – D^{-1}A
$$
실무에서 한 줄 가이드:
- 그래프가 비교적 균일하면 \(L=D-A\)도 충분
- 차수 편차가 크면 \(L_{\text{sym}}\)부터 고려
DF로 “라플라시안 만들기” 흐름 요약
입력 DF:
edge_df(src,dst,w)→ \(A\)signal_df(node,x)→ \(x\)
만드는 것:
- \(A\) 만들기(없으면 0, 연결이면 w)
- \(d_i = \sum_j A_{ij}\)
- \(D\) 만들기(\(D_{ii}=d_i\))
- \(L=D-A\) (또는 \(L_{\text{sym}}\))
- \(L = U\Lambda U^\top\)
- \(\hat{x}=U^\top x\) 가 GFT
라플라시안은 “이웃과의 차이”를 수식으로 고정해 주고, 그 고유벡터가 GFT의 기저(푸리에 축)를 만든다.
그래서 GFT에서 라플라시안은 “반영”이 아니라, 정의 그 자체라고 할 수 있다.
import numpy as np
import pandas as pd
# 예시 DF
signal_df = pd.DataFrame({"node":[0,1,2,3,4], "x":[10,12,13,9,8]})
edge_df = pd.DataFrame({
"src":[0,0,1,2,3],
"dst":[1,2,2,3,4],
"w":[0.9,0.2,0.7,0.8,0.6]
})
n = signal_df["node"].max() + 1
# 1) A 만들기 (무방향)
A = np.zeros((n,n), dtype=float)
for s,d,w in edge_df.itertuples(index=False):
A[s,d] = w
A[d,s] = w
# 2) D, L
deg = A.sum(axis=1)
D = np.diag(deg)
L = D - A
# 3) 고유분해 (대칭행렬이므로 eigh)
lam, U = np.linalg.eigh(L)
# 4) GFT: xhat = U^T x
x = signal_df.sort_values("node")["x"].to_numpy()
xhat = U.T @ x
print("eigenvalues (lambda):", lam.round(4))
print("xhat:", xhat.round(4))