14 손으로 푸는 선형대수 · 제4부 고유값과 특이값
대칭행렬의 스펙트럼 분해
13장에서 본 나쁜 경우 — 고유값이 복소수가 되는 회전행렬, 고유벡터가 모자라는 결손행렬 — 이 대칭행렬에서는 하나도 일어나지 않는다. 대칭이라는 조건 하나가 실수성·직교성·대각화를 전부 보장한다. 이 장은 그 정리를 직접 증명하고, 우리 G = ATA에서 고유값 16·9·1과 정수 고유벡터를 특성다항식부터 끝까지 손으로 뽑는다.
- 직관 — 대칭이면 나쁜 일이 일어나지 않는다
- 수식 읽는 법 — 스펙트럼 정리와 두 줄짜리 증명
- 손으로 풀기 — λ³ − 26λ² + 169λ − 144 = 0부터 Q까지
- 코드 — eigh 대조와 야코비 회전법 직접 구현
- 시각화 — QΛQT는 회전·늘이기·되돌리기
약어 및 기호 정의
- S = ST 대칭행렬 symmetric matrix
- 전치해도 자기 자신인 정사각행렬. 우리 G = ATA가 그렇다 — 1장에서 만들 때부터 그랬다.
- 스펙트럼 분해 spectral decomposition
- S = QΛQT. 대칭행렬을 직교행렬과 대각행렬로 쪼개는 것. 이 장의 목표.
- Λ Lambda
- 고유값을 대각에 늘어놓은 대각행렬. 이 장에서는 diag(16, 9, 1).
- Q 직교행렬 orthogonal matrix
- 열이 정규직교인 정사각행렬. QTQ = I이므로 Q−1 = QT다 — 9장의 핵심.
- 특성다항식 characteristic polynomial
- p(λ) = det(S − λI). 근이 곧 고유값이다. 13장에서 2×2로 연습했고 여기서 3×3을 한다.
- 주소행렬식 principal minor
- 같은 번호의 행과 열을 함께 남겨 만든 부분행렬식. 특성다항식의 계수가 이것들의 합이다.
- 정규직교 orthonormal
- 서로 직교하고(내적 0) 각자 길이가 1인 벡터들. 직교만으로는 부족하고 길이까지 1이어야 한다.
- 야코비 회전법 Jacobi rotation
- 비대각 성분을 한 쌍씩 0으로 만드는 회전을 반복해 대칭행렬을 대각화하는 고전 알고리즘.
직관 — 대칭이면 나쁜 일이 일어나지 않는다
13장은 고유값 이야기를 기분 좋게 끝내지 않았다. 두 가지 반례가 있었다.
그래서 13장의 결론은 "모든 행렬이 대각화되는 것은 아니다"였다. 그런데 13장은 동시에 예고를 하나 남겼다. 대칭행렬은 항상 대각화되고, 게다가 고유벡터를 서로 직교하게 잡을 수 있다. 이 장이 그 예고를 증명하고 예제로 확인한다.
왜 대칭이 그렇게 강한지 감을 먼저 잡자. 회전행렬이 말썽을 부린 이유는 비대칭이었기 때문이다. R의 (1,2) 성분은 −1인데 (2,1) 성분은 +1이다. 이 어긋남이 곧 "돌린다"는 뜻이고, 돌리는 변환에는 불변 방향이 없다. 대칭행렬에는 정의상 그 어긋남이 없다. 대칭행렬은 돌리지 않는다. 오직 축을 따라 늘이거나 줄일 뿐이다.

오른쪽 그림의 S는 G의 축소판이라 손으로 바로 확인된다. tr S = 12, det S = 20 − 9 = 11이므로 λ² − 12λ + 11 = 0, 근은 λ = 11, 1. 고유벡터는 (1, 3)과 (3, −1)이고 내적이 3 − 3 = 0, 정확히 직교한다. 길이도 둘 다 √10으로 같다. 이것이 우연이 아니라 정리라는 것이 STEP 02의 내용이다.
수식 읽는 법 — 스펙트럼 정리
정리부터 적고, 세 조항을 하나씩 증명한다.
S ∈ ℝn×n가 실대칭이면
① 고유값 λ₁, …, λₙ 이 전부 실수다.
② 고유벡터를 정규직교로 잡을 수 있다 — qi·qj = 0 (i≠j), ‖qi‖ = 1.
③ 따라서 S = QΛQT 로 반드시 분해된다. (Q = [q₁ … qₙ], Λ = diag(λ₁ … λₙ))
(14.1) "반드시"가 핵심이다. 13장의 일반 행렬에는 이런 보장이 없었다.① 고유값이 실수인 이유
실수라는 것을 보이려면 일단 복소수일 수도 있다고 가정하고 시작해야 한다. Sv = λv라 하고, λ와 v가 복소수라고 두자. 양변 왼쪽에 v̄T(켤레 전치)를 곱한다.
증명이 ST = S를 딱 한 번 쓴다는 점을 보자. 회전행렬에서는 RT = −R이라 2단계에서 부호가 뒤집히고 λ는 순허수 ±i가 된다. 정리가 성립하는 이유와 반례가 생기는 이유가 같은 줄에 들어 있다.
② 고유벡터가 직교하는 이유
이쪽이 더 짧다. 서로 다른 고유값 λ₁ ≠ λ₂에 대한 고유벡터 v₁, v₂를 잡고, (Sv₁)·v₂를 두 가지로 계산한다.
(왼쪽에 붙여 계산) (Sv₁)·v₂ = (λ₁v₁)·v₂ = λ₁ (v₁·v₂)
(오른쪽으로 넘겨 계산) (Sv₁)·v₂ = v₁TSTv₂ = v₁TSv₂ = v₁·(Sv₂) = λ₂ (v₁·v₂)
가운데 등호 ST = S 한 번이 전부다. (14.2)같은 값을 두 방식으로 쓴 것이니 둘은 같아야 한다.
이게 전부다. 서로 다른 고유값에 붙은 고유벡터는 자동으로 직교한다. 고유값이 겹칠 때는 그 고유공간 안에서 그람-슈미트(11장)를 돌려 직교 기저를 만들면 되므로, 어느 경우든 ②가 성립한다. 우리 G는 16·9·1이 전부 다르므로 겹침 걱정은 없다.
③ S = QΛQT — 그리고 랭크1 분해
②에서 정규직교 고유벡터 n개를 얻었으니 이것을 열로 세워 Q를 만든다. SQ = QΛ는 열마다 Sqi = λiqi를 적은 것에 불과하다. 여기서 Q가 직교행렬이라 Q−1 = QT, 즉 역행렬을 구할 필요가 없다(9장). 오른쪽에 QT를 곱하면 바로 나온다.
S = QΛQT QTQ = QQT = I
(14.4)13장의 일반 대각화 A = PΛP−1와 비교하면 달라진 것은 P−1이 QT로 바뀐 것뿐인데, 이 차이가 실무에서 전부다. P는 열이 기울어져 조건수가 얼마든지 커지지만, Q는 조건수가 항상 1이고 역행렬이 전치다.
그리고 식 (14.4)를 2장의 네 번째 읽기로 풀어 쓰면 이 장에서 가장 쓸모 있는 형태가 나온다. 2장에서 행렬곱을 "랭크1 행렬의 합"으로 읽는 법을 배웠다. QΛ의 열은 λiqi이고 QT의 행은 qiT이므로,
1장 코드에서 g_rank1 = Σ outer(ai, ai)로 G를 만들어 본 적이 있다. 그때는 행 네 개의 바깥곱이었고 계수가 전부 1이었다. 지금은 고유방향 세 개의 바깥곱이고 계수가 16·9·1이다. 같은 G를 서로 다른 랭크1 조각으로 쓴 것인데, 후자에서는 조각들이 서로 직교하고 계수가 크기순으로 정렬된다. 이 정렬이 16장 SVD와 18장 저랭크 근사의 전부다.
손으로 풀기 — G의 스펙트럼 분해
(가) 특성다항식을 전개한다
주인공은 1장에서 만든 G = ATA다.
⎡ 2 1 3 ⎤ ⎡ 2−λ 1 3 ⎤
G = AᵀA = ⎢ 1 14 3 ⎥ G − λI = ⎢ 1 14−λ 3 ⎥
⎣ 3 3 10 ⎦ ⎣ 3 3 10−λ ⎦
(14.6)첫 행을 따라 여인수 전개한다(6장). 부호는 + − +.
det(G − λI)를 끝까지 전개
첫째 항. 안쪽 2×2부터.
(14−λ)(10−λ) − 3·3 = 140 − 24λ + λ² − 9 = λ² − 24λ + 131
(2−λ)(λ² − 24λ + 131) = 2λ² − 48λ + 262 − λ³ + 24λ² − 131λ
= −λ³ + 26λ² − 179λ + 262
둘째 항. 부호가 −다.
− 1 · [ 1·(10−λ) − 3·3 ] = −[10 − λ − 9] = −[1 − λ] = λ − 1
셋째 항. 부호가 +다.
+ 3 · [ 1·3 − (14−λ)·3 ] = 3[3 − 42 + 3λ] = 3[3λ − 39] = 9λ − 117
합친다. λ 차수별로 모은다.
λ³: −λ³ · λ²: +26λ² · λ: −179λ + λ + 9λ = −169λ · 상수: 262 − 1 − 117 = 144
det(G − λI) = −λ³ + 26λ² − 169λ + 144
양변에 −1을 곱해 최고차 계수를 1로 만든다.
λ³ − 26λ² + 169λ − 144 = 0
계수를 따로 검산하자. 3×3 특성다항식의 계수에는 이름이 있다.
계수 세 개를 독립적으로 확인
1차 계수 — tr G. 대각합이다. 2 + 14 + 10 = 26 ✓
2차 계수 — 2×2 주소행렬식의 합. 같은 번호의 행·열을 함께 지운다.
1행1열 지움: det[[14,3],[3,10]] = 140 − 9 = 131
2행2열 지움: det[[2,3],[3,10]] = 20 − 9 = 11
3행3열 지움: det[[2,1],[1,14]] = 28 − 1 = 27
합 = 131 + 11 + 27 = 169 ✓ (169 = 13²이다)
상수항 — det G. 6장에서 구한 값, = 144 = 12² ✓
세 계수 26, 169, 144가 전개 결과와 정확히 일치한다.
여기서 미리 역방향 검산도 해 두자. 근이 16, 9, 1이라면 근과 계수의 관계가 성립해야 한다.
근의 합 16 + 9 + 1 = 26 ✓ = tr G
쌍곱의 합 16·9 + 16·1 + 9·1 = 144+16+9 = 169 ✓ = 주소행렬식 합
근의 곱 16 · 9 · 1 = 144 ✓ = det G
tr G = 26은 1장에서 ‖A‖F² = 26으로도 나왔다. 그것이 고유값의 합이었다.
(나) 삼차방정식을 손으로 푼다
삼차방정식은 근의 공식이 아니라 유리근 정리rational root theorem로 한 근을 찾고 차수를 낮추는 것이 정석이다. 최고차 계수가 1, 상수항이 −144이므로 유리근이 있다면 144의 약수여야 한다.
144 의 약수 : 1, 2, 3, 4, 6, 8, 9, 12, 16, 18, 24, 36, 48, 72, 144
가장 작은 것부터 넣어 본다. p(λ) = λ³ − 26λ² + 169λ − 144.
유리근 찾기 → 조립제법
p(1) = 1 − 26 + 169 − 144 = 0 → 첫 시도에 걸렸다. λ = 1은 근이다.
따라서 (λ − 1)로 나누어떨어진다. 조립제법으로 몫을 구한다.
1 | 1 −26 169 −144
| 1 −25 144
-----------------------------
1 −25 144 | 0 ← 나머지 0, 확인
몫은 λ² − 25λ + 144. 이제 이차방정식이다.
인수분해를 시도한다. 곱이 144, 합이 25인 두 수를 찾는다. 16 × 9 = 144, 16 + 9 = 25. 바로 나온다.
λ² − 25λ + 144 = (λ − 16)(λ − 9) = 0
판별식으로도 확인: 25² − 4·144 = 625 − 576 = 49 = 7². 완전제곱이므로 λ = (25 ± 7)/2 = 16, 9 ✓
고유값 λ₁ = 16, λ₂ = 9, λ₃ = 1. 계산기 없이 끝났다.
셋 다 양의 실수다. 실수인 것은 스펙트럼 정리 ①이 보장했다. 양수인 것은 아직 보장받은 적이 없는데, 이것이 15장 양정치의 주제다. 미리 말하면 G = ATA 꼴이고 A의 열이 독립이면 고유값이 반드시 양수가 된다.
(다) 각 고유값의 고유벡터를 구한다
(G − λI)v = 0을 푼다. λ가 고유값이므로 G − λI는 반드시 특이행렬이고, 영공간이 1차원일 것이다(3·5장).
λ₁ = 16
⎡ −14 1 3 ⎤
G − 16I = ⎢ 1 −2 3 ⎥
⎣ 3 3 −6 ⎦
3행이 3으로 나누어떨어진다. r₃′ : x₁ + x₂ − 2x₃ = 0.
2행 그대로: r₂ : x₁ − 2x₂ + 3x₃ = 0.
r₂ − r₃′: (−2 − 1)x₂ + (3 + 2)x₃ = −3x₂ + 5x₃ = 0 → x₂ = 53x₃
분수를 없애려고 x₃ = 3으로 잡는다. 그러면 x₂ = 5.
r₃′에 넣으면 x₁ = 2x₃ − x₂ = 6 − 5 = 1.
v₁ = (1, 5, 3)
검산 — 쓰지 않은 1행에 넣어 본다: −14(1) + 1(5) + 3(3) = −14 + 5 + 9 = 0 ✓
원식으로도: Gv₁ = (2+5+9, 1+70+9, 3+15+30) = (16, 80, 48) = 16(1,5,3) ✓
λ₂ = 9
⎡ −7 1 3 ⎤
G − 9I = ⎢ 1 5 3 ⎥
⎣ 3 3 1 ⎦
2행과 3행을 쓴다. r₂ : x₁ + 5x₂ + 3x₃ = 0, r₃ : 3x₁ + 3x₂ + x₃ = 0.
r₃에서 x₃ = −3x₁ − 3x₂. 이것을 r₂에 대입한다.
x₁ + 5x₂ + 3(−3x₁ − 3x₂) = x₁ + 5x₂ − 9x₁ − 9x₂ = −8x₁ − 4x₂ = 0
→ x₂ = −2x₁. x₁ = 1로 잡으면 x₂ = −2, x₃ = −3(1) − 3(−2) = −3 + 6 = 3.
v₂ = (1, −2, 3)
검산 — 1행: −7(1) + 1(−2) + 3(3) = −7 − 2 + 9 = 0 ✓
원식: Gv₂ = (2−2+9, 1−28+9, 3−6+30) = (9, −18, 27) = 9(1,−2,3) ✓
λ₃ = 1
⎡ 1 1 3 ⎤
G − I = ⎢ 1 13 3 ⎥
⎣ 3 3 9 ⎦
3행이 1행의 3배다 — 한눈에 특이행렬임이 보인다. 1·2행만 쓴다.
r₂ − r₁: (13 − 1)x₂ = 12x₂ = 0 → x₂ = 0
r₁에 넣으면 x₁ + 3x₃ = 0, 즉 x₁ = −3x₃.
x₃ = −1로 잡으면 x₁ = 3.
v₃ = (3, 0, −1)
검산 — 원식: Gv₃ = (6+0−3, 3+0−3, 9+0−10) = (3, 0, −1) = 1·(3,0,−1) ✓
세 고유벡터가 전부 정수로 떨어졌다. 보통 교과서 예제라면 여기서 (1, 2.317, −0.884) 같은 것이 나와 손계산이 끊긴다. 1장에서 "이 A를 왜 골랐는가"를 이야기한 이유가 이 지점이다.
(라) 직교성 확인과 Q 만들기
스펙트럼 정리 ②를 내적으로 직접 본다.
v₁·v₂ = (1)(1) + (5)(−2) + (3)(3) = 1 − 10 + 9 = 0 ✓
v₁·v₃ = (1)(3) + (5)(0) + (3)(−1) = 3 + 0 − 3 = 0 ✓
v₂·v₃ = (1)(3) + (−2)(0) + (3)(−1) = 3 + 0 − 3 = 0 ✓
세 쌍 모두 0이다. 우리가 한 일은 각 고유값마다 따로따로 영공간을 푼 것뿐이고 직교성은 요구한 적이 없다. 그런데도 저절로 나왔다. 이것이 식 (14.3)이 말한 내용이다.
이제 길이를 1로 만든다. 길이 제곱부터.
‖v₁‖² = 1 + 25 + 9 = 35 ‖v₂‖² = 1 + 4 + 9 = 14 ‖v₃‖² = 9 + 0 + 1 = 10
√35, √14, √10은 무리수라 여기서 처음 정수가 깨진다. 그래서 이 시리즈는 분모에 근호를 남겨 두는 표기를 쓴다 — 16장 SVD도 같다.
⎡ 1/√35 1/√14 3/√10 ⎤
Q = ⎢ 5/√35 −2/√14 0 ⎥ Λ = diag(16, 9, 1)
⎣ 3/√35 3/√14 −1/√10 ⎦
(14.8) 열 순서는 고유값 내림차순. 각 열의 부호는 뒤집어도 여전히 고유벡터다 — 코드에서 이 자유도를 다룬다.QTQ = I 확인
대각 — 각 열의 길이 제곱이 1이어야 한다.
(1² + 5² + 3²)/35 = 35/35 = 1 ✓ (1² + 2² + 3²)/14 = 14/14 = 1 ✓ (3² + 0² + 1²)/10 = 10/10 = 1 ✓
비대각 — (라) 맨 위에서 이미 분자가 0임을 확인했다. 분모가 무엇이든 0/분모 = 0이다.
q₁·q₂ = 0/√(35·14) = 0 ✓ q₁·q₃ = 0 ✓ q₂·q₃ = 0 ✓
따라서 QTQ = I, 곧 Q−1 = QT. 역행렬을 구하는 계산이 통째로 사라졌다.
(마) 랭크1 분해로 G를 다시 조립한다
식 (14.5)를 우리 숫자로 적는다. qiqiT = viviT / ‖vi‖²이므로 분모가 깔끔하게 정리된다.
세 바깥곱을 적어 두자. 전부 대칭이고 랭크 1이다.
⎡ 1 5 3 ⎤ ⎡ 1 −2 3 ⎤ ⎡ 9 0 −3 ⎤
v₁v₁ᵀ = ⎢ 5 25 15 ⎥ v₂v₂ᵀ = ⎢−2 4 −6 ⎥ v₃v₃ᵀ = ⎢ 0 0 0 ⎥
⎣ 3 15 9 ⎦ ⎣ 3 −6 9 ⎦ ⎣−3 0 1 ⎦
성분 여섯 개를 분수로 검산
대칭이므로 위쪽 삼각 여섯 개만 보면 된다. 통분 분모는 35, 14, 10의 최소공배수 70.
(1,1): 1635(1) + 914(1) + 110(9) = 3270 + 4570 + 6370 = 14070 = 2 ✓
(2,2): 1635(25) + 914(4) + 0 = 40035 + 3614 = 807 + 187 = 987 = 14 ✓
(3,3): 1635(9) + 914(9) + 110(1) = 28870 + 40570 + 770 = 70070 = 10 ✓
(1,2): 1635(5) + 914(−2) + 0 = 167 − 97 = 77 = 1 ✓
(1,3): 1635(3) + 914(3) + 110(−3) = 9670 + 13570 − 2170 = 21070 = 3 ✓
(2,3): 1635(15) + 914(−6) + 0 = 487 − 277 = 217 = 3 ✓
여섯 성분 전부 G와 일치한다. 무리수 분모가 제곱되어 전부 사라졌다.
이제 (마)의 요점이다. 세 항의 비중이 같지 않다. 고유값이 곧 그 방향에 실린 무게이고, 총합은 tr G = 26이다.
| 항 | 고유값 | 몫 | 누적 | ‖G − 부분합‖F |
|---|---|---|---|---|
| 16 q₁q₁T | 16 | 16/26 = 61.5% | 61.5% | √82 ≈ 9.055 |
| 9 q₂q₂T | 9 | 9/26 = 34.6% | 96.2% | 1 |
| 1 q₃q₃T | 1 | 1/26 = 3.8% | 100% | 0 |
첫 항 하나로 G의 61.5%가 설명되고, 두 항이면 96.2%다. 마지막 항은 3.8%밖에 안 된다. 버려도 되는 방향이 있다는 발상이 여기서 시작하고, 18장 저랭크 근사가 그것을 정리로 만든다. 잔차 노름이 √82 → 1 → 0으로 줄어드는데, 남은 항들의 고유값 제곱합 √(9²+1²) = √82, √(1²) = 1과 정확히 같다는 점을 눈여겨보자 — 이것이 에카르트-영 정리의 예고편이다.
왜 A의 열은 직교가 아닌데 G의 고유벡터는 직교하는가
혼동이 자주 일어나는 지점이다. A의 열은 c₁ = (1,0,1,0), c₂ = (1,2,0,3), c₃ = (0,0,3,1)이고, c₁·c₂ = 1 ≠ 0이다. 직교가 아니다. 애초에 G의 비대각이 0이 아니라는 말이 정확히 그 뜻이었다(1장).
그런데 방금 G의 고유벡터는 직교했다. 모순이 아니다. 두 가지는 서로 다른 대상이기 때문이다.
열 cj는 ℝ⁴에 산다. 그리고 그것은 그냥 자료가 준 방향이다. 특징 1을 어떤 단위로 쟀는지, 특징 2와 얼마나 같이 움직이는지 — 전부 세상이 정한 것이고 서로 직각일 이유가 없다.
고유벡터 vi는 ℝ³에 산다. 그것은 자료가 준 방향이 아니라 G가 골라낸 방향이다. "이 세 열을 어떤 비율로 섞으면 G가 늘이기만 하고 돌리지는 않는가"라는 질문의 답이다. v₁ = (1,5,3)은 ℝ⁴의 벡터가 아니라 섞는 비율이다.
정리하면 이렇다. cj는 재료이고 vi는 배합비다. 재료끼리 직각일 필요는 없지만, 대칭행렬이 고르는 배합비는 언제나 서로 직각이다.
그리고 이 배합비를 실제로 ℝ⁴에 태우면 어떻게 될까. Av₁ = (6,10,10,18), Av₂ = (−1,−4,10,−3), Av₃ = (3,0,0,−1)인데 — Av₁·Av₂ = −6 − 40 + 100 − 54 = 0이다. 비직교인 열들을 이 비율로 섞으면 ℝ⁴에서도 직교하는 방향이 나온다. 이것이 16장 SVD의 좌특이벡터이고, 17장 PCA에서는 주성분이라고 불린다.
PCA가 하는 일을 한 문장으로 줄이면 이렇다. 서로 얽혀 있는 원래 특징들을, 공분산행렬이 고른 배합비로 다시 섞어 직교하는 새 특징을 만든다. 배합비가 직교한다는 보장이 바로 이 장의 스펙트럼 정리다.
코드 — 손계산과 소수점까지 대조
특성다항식 계수, eigh 고유쌍, 랭크1 재조립을 한꺼번에 확인한다. 고유벡터는 부호가 자유이므로 대조 전에 손계산 쪽에 부호를 맞춰야 한다 — 빠뜨리면 멀쩡한 결과가 틀린 것처럼 보인다.
import numpy as np
np.set_printoptions(precision=6, suppress=True)
G = np.array([[2,1,3],[1,14,3],[3,3,10]], dtype=float)
# (1) 특성다항식 계수 — 손계산 λ³ − 26λ² + 169λ − 144 와 대조
minors = (np.linalg.det(G[1:,1:]) + np.linalg.det(G[np.ix_([0,2],[0,2])])
+ np.linalg.det(G[:2,:2]))
print("tr G =", np.trace(G), "| 주소행렬식 합 =", round(minors), "| det G =", round(np.linalg.det(G)))
print("np.poly(G) :", np.round(np.poly(G), 10))
print("손계산 : [ 1. -26. 169. -144.]")
# (2) eigh 와 손계산 고유쌍 대조 (열 부호 보정)
lam, Q = np.linalg.eigh(G) # eigh 는 오름차순
o = np.argsort(-lam); lam, Q = lam[o], Q[:, o]
hand = np.array([[1,5,3],[1,-2,3],[3,0,-1]], float).T
hand = hand / np.linalg.norm(hand, axis=0) # ‖v‖² = 35, 14, 10
Q = Q * np.sign((Q*hand).sum(axis=0)) # 부호만 손계산에 맞춘다
print("\n고유값 :", np.round(lam, 10), " 손계산 [16 9 1]")
print("eigh Q :\n", np.round(Q, 6))
print("손계산 :\n", np.round(hand, 6))
print("최대 차이 :", np.abs(Q-hand).max(), "| QᵀQ=I :", np.allclose(Q.T@Q, np.eye(3)))
# (3) 랭크1 분해 G = Σ λ q qᵀ 를 한 항씩 쌓아 복원
acc = np.zeros((3,3))
for k in range(3):
acc += lam[k] * np.outer(Q[:,k], Q[:,k])
print(f"\n{k+1}항까지 (λ={lam[k]:.0f} 누적) :\n", np.round(acc, 4))
print(f" ‖G−합‖_F = {np.linalg.norm(G-acc):.6f} | 에너지 {lam[:k+1].sum()/lam.sum()*100:.1f}%")
np.poly(G) : [ 1. -26. 169. -144.]
손계산 : [ 1. -26. 169. -144.]
고유값 : [16. 9. 1.] 손계산 [16 9 1]
eigh Q :
[[ 0.169031 0.267261 0.948683]
[ 0.845154 -0.534522 -0. ]
[ 0.507093 0.801784 -0.316228]]
손계산 :
[[ 0.169031 0.267261 0.948683]
[ 0.845154 -0.534522 0. ]
[ 0.507093 0.801784 -0.316228]]
최대 차이 : 1.1102230246251565e-16 | QᵀQ=I : True
1항까지 (λ=16 누적) :
[[ 0.4571 2.2857 1.3714]
[ 2.2857 11.4286 6.8571]
[ 1.3714 6.8571 4.1143]]
‖G−합‖_F = 9.055385 | 에너지 61.5%
2항까지 (λ=9 누적) :
[[ 1.1 1. 3.3]
[ 1. 14. 3. ]
[ 3.3 3. 9.9]]
‖G−합‖_F = 1.000000 | 에너지 96.2%
3항까지 (λ=1 누적) :
[[ 2. 1. 3.]
[ 1. 14. 3.]
[ 3. 3. 10.]]
‖G−합‖_F = 0.000000 | 에너지 100.0%
최대 차이가 1.11 × 10−16 — 배정밀도의 기계 엡실론 수준이다. 손으로 푼 (1,5,3)을 √35로 나눈 값과 LAPACK이 반복법으로 얻은 값이 표현 가능한 마지막 자리까지 같다. 첫 줄 0.169031 = 1/√35, 둘째 줄 0.845154 = 5/√35다. ‖G−합‖F가 9.055385 → 1 → 0으로 떨어진 것도 (마)의 표와 일치한다 — √82 = 9.055385…이다.
야코비 회전법 — 대각화를 눈으로 보기
eigh가 내부에서 하는 일을 가장 직관적으로 보여 주는 알고리즘이 야코비 회전법Jacobi rotation method이다. 비대각 성분 한 쌍을 골라 그 자리만 0으로 만드는 회전을 가한다. 회전 J는 직교행렬이라 JTSJ는 여전히 대칭이고 고유값도 그대로다. 한 쌍을 0으로 만들면 다른 쌍이 조금 되살아나지만 비대각 전체의 노름은 반드시 줄어든다. 줄어들기만 하면 언젠가 0이 되고, 그때 남은 대각이 곧 고유값이다.
회전각은 2×2 블록 [[spp, spq],[spq, sqq]]를 대각화하는 각이다.
쌍 고르는 법을 두 가지로 비교한다. 절댓값이 가장 큰 비대각을 고르는 방식, 그리고 (0,1) → (0,2) → (1,2)를 그냥 돌리는 순환 방식이다.
import numpy as np
G = np.array([[2,1,3],[1,14,3],[3,3,10]], dtype=float)
off = lambda M: np.sqrt((M**2).sum() - (np.diag(M)**2).sum())
def jacobi(pick, label):
S, V, hist = G.copy(), np.eye(3), [off(G)]
print(f"\n--- {label} --- 시작 비대각 노름 = {off(S):.8f}")
for it in range(30):
p, q = pick(S, it)
if abs(S[p,q]) < 1e-14:
if label.startswith("순환") and it % 3: continue
break
theta = 0.5*np.arctan2(2*S[p,q], S[q,q]-S[p,p]) # 2×2 블록을 0으로
c, s = np.cos(theta), np.sin(theta)
J = np.eye(3); J[p,p]=J[q,q]=c; J[p,q]=s; J[q,p]=-s
S, V = J.T @ S @ J, V @ J
hist.append(off(S))
print(f" {it+1:2d}회전 (p,q)=({p},{q}) θ={theta:+.6f} 비대각 = {off(S):.8f}")
print(" 대각성분 :", np.round(np.sort(np.diag(S))[::-1], 10),
"| VᵀV=I :", np.allclose(V.T@V, np.eye(3)),
"| VΛVᵀ=G :", np.allclose(V@S@V.T, G))
return hist
greedy = jacobi(lambda S, it: max(((i,j) for i in range(3) for j in range(i+1,3)),
key=lambda ij: abs(S[ij])), "최대원소 선택")
cyclic = jacobi(lambda S, it: [(0,1),(0,2),(1,2)][it % 3], "순환 방식")
fmt = lambda h: " ".join(f"{x:.2e}" for x in h)
print("\n최대원소 비대각 이력 :", fmt(greedy))
print("순환 비대각 이력 :", fmt(cyclic))
print("eigh 고유값 :", np.round(np.sort(np.linalg.eigvalsh(G))[::-1], 10))
--- 최대원소 선택 --- 시작 비대각 노름 = 6.16441400
1회전 (p,q)=(0,2) θ=+0.321751 비대각 = 4.47213595
2회전 (p,q)=(1,2) θ=+1.006854 비대각 = 0.00000000
대각성분 : [16. 9. 1.] | VᵀV=I : True | VΛVᵀ=G : True
--- 순환 방식 --- 시작 비대각 노름 = 6.16441400
1회전 (p,q)=(0,1) θ=+0.082574 비대각 = 6.00000000
2회전 (p,q)=(0,2) θ=+0.298097 비대각 = 4.57811989
3회전 (p,q)=(1,2) θ=+1.026551 비대각 = 1.34460179
4회전 (p,q)=(0,1) θ=-0.062033 비대각 = 1.15033171
5회전 (p,q)=(0,2) θ=-0.054230 비대각 = 0.07131256
6회전 (p,q)=(1,2) θ=+0.007193 비대각 = 0.00386542
7회전 (p,q)=(0,1) θ=+0.000342 비대각 = 0.00002781
8회전 (p,q)=(0,2) θ=+0.000001 비대각 = 0.00000000
9회전 (p,q)=(1,2) θ=+0.000000 비대각 = 0.00000000
대각성분 : [16. 9. 1.] | VᵀV=I : True | VΛVᵀ=G : True
최대원소 비대각 이력 : 6.16e+00 4.47e+00 0.00e+00
순환 비대각 이력 : 6.16e+00 6.00e+00 4.58e+00 1.34e+00 1.15e+00 7.13e-02 3.87e-03 2.78e-05 0.00e+00 0.00e+00
eigh 고유값 : [16. 9. 1.]
읽을 것이 셋 있다.
두 방식 모두 VTV = I이고 VΛVT = G다. 회전을 몇 번 하든 어떤 순서로 하든 직교성이 중간에 깨지지 않는다 — 회전행렬의 곱은 여전히 회전행렬이기 때문이고, 이것이 야코비법이 수치적으로 안전한 이유다.
시각화 — QΛQT가 하는 일
식 (14.4)를 오른쪽부터 읽으면 Sx = Q(Λ(QTx))다. 세 단계가 순서대로 들어 있다.

다음은 3×3. G의 세 고유방향은 ℝ³에서 직각인 축 세 개를 이룬다.

랭크1 분해 식 (14.9)는 쌓아 가는 그림으로 보는 것이 좋다.

마지막으로 야코비 회전이 대각화해 가는 과정이다. 세로축은 비대각 노름, 로그 눈금이다.

다섯 그림을 한 줄로 묶으면 이렇다. 대칭행렬은 직교하는 축 위에서 늘이기만 하는 변환이고, 스펙트럼 분해는 그 축과 배율을 찾아내는 일이다. 15장은 여기에 "배율이 전부 양수인가"를 더하고, 16장은 대칭이 아닌 A 자체로 확장한다.
이것만 기억하자
- 대칭이면 전부 보장된다. 고유값은 실수(ST = S를 한 번 쓰면 나온다), 서로 다른 고유값의 고유벡터는 자동으로 직교(식 14.3), 따라서 S = QΛQT가 반드시 존재한다. 13장의 복소수·결손 사고는 대칭행렬에서 일어날 수 없다.
- Q−1 = QT가 실무의 전부다. 일반 대각화 PΛP−1에서는 P의 조건수가 얼마든지 나빠질 수 있지만 직교행렬의 조건수는 항상 1이다. 대칭행렬에 eig 대신 eigh를 쓰는 이유가 이것이다.
- S = ΣλiqiqiT — 크기순으로 정렬된 랭크1 조각들. 우리 G에서는 첫 조각이 61.5%, 두 조각이면 96.2%다. 어떤 조각을 버려도 되는가라는 질문이 저랭크 근사(18장)와 PCA(17장)의 출발점이다.
흔한 오해
- "모든 행렬의 고유벡터는 직교한다." — 전혀 아니다. 직교성은 대칭에서만 나온다. 13장의 전단행렬은 고유벡터가 하나뿐이었고, 일반 행렬의 고유벡터들은 서로 몹시 기울어져 있을 수 있다. 그 기울어짐이 곧 P−1의 수치 불안정이다.
- "A의 열이 직교가 아니니 G의 고유벡터도 직교가 아닐 것이다." — 둘은 다른 공간의 다른 대상이다. 열 cj는 ℝ⁴의 재료이고 고유벡터 vi는 ℝ³의 배합비다. 재료가 아무리 얽혀 있어도 대칭행렬이 고르는 배합비는 언제나 직교한다 — 그리고 그 배합비로 재료를 섞으면 Avi들도 직교한다. 이것이 PCA다.
- "고유값이 실수라는 건 대칭이니 당연한 것 아닌가." — 당연하지 않다. 증명에 ST = S가 정확히 어디서 쓰이는지 보라. 그 등호가 RT = −R로 바뀌는 순간 같은 계산에서 λ = ±i가 나온다. 정리와 반례가 한 줄 차이다.
- "고유벡터는 유일하다." — 아니다. 상수배만큼 자유롭고, 정규화한 뒤에도 부호가 자유롭다. numpy의 eigh와 손계산을 대조할 때 부호를 맞춰 주지 않으면 멀쩡한 결과가 틀린 것처럼 보인다. 고유값이 겹치면 자유도는 더 커져서 그 고유공간 안의 어떤 직교 기저든 정답이 된다.
'선형대수' 카테고리의 다른 글
| 손으로 푸는 선형대수 16장 — SVD: 모든 행렬의 정규분해 (0) | 2026.09.24 |
|---|---|
| 손으로 푸는 선형대수 15장 — 양정치와 이차형식: 공분산이 왜 대칭 양반정치인가 (0) | 2026.09.24 |
| 손으로 푸는 선형대수 13장 — 고유값과 고유벡터: 방향이 변하지 않는 축 (0) | 2026.09.24 |
| 손으로 푸는 선형대수 12장 — 최소제곱 세 가지 방법: 정규방정식·QR·SVD (0) | 2026.09.24 |
| 손으로 푸는 선형대수 11장 — 그람-슈미트와 QR: 정규방정식을 피하는 법 (0) | 2026.09.24 |
댓글