16 손으로 푸는 선형대수 · 제4부 고유값과 특이값
SVD — 모든 행렬의 정규분해
13장에서 대각화되지 않는 행렬을 보았고, 애초에 정사각이 아니면 고유값을 말할 수조차 없다. 그런데 특이값 분해는 모든 행렬에 존재한다. 예외가 없다. 이 장은 A = UΣVT를 15장의 양반정치성 하나에서 유도하고, 우리 A의 SVD를 분수와 근호만으로 종이 위에서 끝까지 완성한다. 2장이 "네 번째 읽기가 16장의 본체"라고 예고했던 그 약속을 여기서 회수한다.
- 직관 — 회전 → 늘이기 → 회전
- 수식 읽는 법 — ATA에서 존재를 끌어낸다
- 손으로 풀기 — U, Σ, V 전부를 정수로
- 코드 — np.linalg.svd와 소수점까지 대조
- 시각화 — 단위구가 타원체가 되기까지
약어 및 기호 정의
- 특이값 분해 singular value decomposition, SVD
- A = UΣVT. U는 m×m 직교, V는 n×n 직교, Σ는 m×n 대각. 실수 행렬이면 언제나 존재한다.
- 특이값 σi singular value
- ATA 고유값의 양의 제곱근. 관례상 σ1 ≥ σ2 ≥ … ≥ 0으로 내림차순 정렬한다. 우리 A는 σ = 4, 3, 1.
- 우특이벡터 vi right singular vector
- V의 열. ATA의 정규직교 고유벡터다. 입력 쪽(ℝn)에 산다.
- 좌특이벡터 ui left singular vector
- U의 열. AAT의 정규직교 고유벡터다. 출력 쪽(ℝm)에 산다.
- 축소형 SVD thin / reduced SVD
- σ > 0인 r개만 남긴 A = UrΣrVrT. 우리 A는 r = 3이라 Ur이 4×3이 된다.
- 완전형 SVD full SVD
- U를 m×m까지 채운 것. 남는 열은 좌영공간의 기저가 된다 — 우리 A에서는 n = (−1,5,1,−3) 하나.
- 직교행렬 orthogonal matrix
- QTQ = QQT = I. 길이와 각도를 보존한다(9장). 기하로는 회전 또는 반사.
- 바깥곱 uvT outer product
- 랭크1 행렬 한 장(2장). SVD는 A를 이 장들의 가중합으로 다시 쓴 것이다.
- 의사역행렬 A+ pseudoinverse
- A+ = VΣ+UT. 0이 아닌 σ만 뒤집는다. 역행렬이 없어도 존재한다(19장).
- 크로네커 델타 δij Kronecker delta
- i = j면 1, 아니면 0. "정규직교"를 한 줄로 쓰는 기호다.
직관 — 어떤 행렬이든 회전 · 늘이기 · 회전이다
13장에서 고유값 이야기를 끝내고 나면 두 가지 불만이 남는다.
SVD는 이 둘을 한꺼번에 없앤다. 모양을 가리지 않고, 랭크를 가리지 않고, 실수 행렬이면 무조건 존재한다. 그리고 양쪽 기저가 둘 다 직교다. 대가는 하나 — 들어가는 기저와 나오는 기저가 다를 수 있다는 것을 받아들여야 한다. 고유벡터가 "들어간 방향이 그대로 나오는" 방향이라면, 특이벡터는 "들어간 직교틀이 나올 때도 직교틀인" 방향이다. 방향이 아니라 직각이 보존된다.
기하로 보면 한 문장이다. 특이값 분해SVD는 임의의 선형사상을 회전 → 축별 늘이기 → 회전 세 동작으로 쪼갠다.
x ──VT──► 회전(입력 기저를 표준축에 맞춤)
──Σ ──► 축별로 σ₁, σ₂, σ₃ 배 늘이기 (여기서만 모양이 변한다)
──U ──► 회전(표준축을 출력 기저에 맞춤)
회전은 길이도 각도도 바꾸지 않는다. 모양을 바꾸는 것은 가운데 단계뿐이다. 그래서 행렬이 하는 일 중 "정보"에 해당하는 부분은 전부 σ 세 개에 들어 있다. 그림으로 말하면 ℝ³의 단위구가 ℝ⁴ 안의 타원체가 된다 — 반축 길이가 σ₁, σ₂, σ₃ = 4, 3, 1, 반축 방향이 u₁, u₂, u₃다.

이 그림이 앞 장들과 그대로 이어진다. 조건수 κ₂(A) = σ₁/σ₃ = 4는 타원체가 얼마나 찌그러졌나(8장), 랭크 3은 몇 차원인가(4장), √det G = 12 = 4·3·1은 부피 배율(7장)이다. 전부 같은 세 숫자에서 나온다.
수식 읽는 법 — 존재를 ATA에서 끌어낸다
먼저 목표를 정확히 쓴다.
A = U Σ VT, UTU = Im, VTV = In, Σ = diag(σ₁, …, σr) 확장형
(16.1) A가 m×n이면 U는 m×m, Σ는 m×n, V는 n×n. 우리 A는 4×3이므로 U가 4×4, Σ가 4×3, V가 3×3이다.존재를 증명하는 가장 짧은 길은 이미 가진 것에서 시작하는 것이다. 우리가 이미 가진 것은 15장의 결과다.
15장이 준 것 — 이 유도의 유일한 재료
G = ATA는 대칭이고((ATA)T = ATA) 양반정치다(xTGx = ‖Ax‖² ≥ 0). 따라서 14장의 스펙트럼 정리에 의해 G는 정규직교 고유벡터 v₁,…,vn를 가지고, 고유값은 전부 λi ≥ 0이다.
이 두 줄이 전부다. SVD의 존재는 새 정리가 아니라 15장 정리의 번역이다.
이제 네 걸음이면 끝난다.
uiTuj = (Avi)T(Avj)σiσj = viT(ATA)vjσiσj = viT(λj vj)σiσj = σj2σiσj viTvj = σjσi δij = δij
A = rΣi=1 σi ui viT = Ur Σr VrT
(16.2) 2장 식 (2.4)의 네 번째 읽기 그 자체다. 무게가 σ이고 양쪽 벡터가 정규직교인 랭크1 합. 2장에서 "이 읽기가 후반부를 지배한다"고 한 것이 이것이다.U를 m×m으로 채우려면 나머지 직교보완 기저를 붙이면 된다. u₁,…,ur이 이미 C(A)를 꽉 채우므로 남는 열은 C(A)⊥ = N(AT) — 좌영공간의 정규직교 기저다. 4장의 네 부분공간이 여기서 U와 V의 열로 갈라진다.
AAT 쪽에서 보면
같은 σ가 반대쪽에서도 나온다. 식 (16.1)을 그대로 대입하면
ATA = (UΣVT)T(UΣVT) = VΣTUTUΣVT = V (ΣTΣ) VT
AAT = (UΣVT)(UΣVT)T = UΣVTVΣTUT = U (ΣΣT) UT
(16.3) 둘 다 14장의 스펙트럼 분해 QΛQT 꼴이다. ΣTΣ는 n×n, ΣΣT는 m×m이지만 0이 아닌 대각성분은 σi²로 똑같다.즉 V는 ATA의, U는 AAT의 고유벡터 행렬이고 둘은 0이 아닌 고유값을 공유한다. 우리 예제에서 G는 3×3에 고유값 16, 9, 1, AAT는 4×4에 16, 9, 1, 0이다. 남는 0 하나가 좌영공간이다. 같은 정보를 작은 쪽에서 계산하면 된다는 것 — 17장 PCA의 실무 요령이 정확히 이것이다.

손으로 풀기 — U, Σ, V를 분수와 근호로 끝까지
14장에서 G = ATA의 스펙트럼 분해를 이미 끝냈다. 그 결과를 그대로 가져온다.
⎡ 1 1 0 ⎤ ⎡ 2 1 3 ⎤
A = ⎢ 0 2 0 ⎥ G = ⎢ 1 14 3 ⎥ det G = 144 = 12²
⎢ 1 0 3 ⎥ ⎣ 3 3 10 ⎦
⎣ 0 3 1 ⎦
고유값 λ = 16, 9, 1 고유벡터 (정수, 서로 직교)
λ₁=16 : (1, 5, 3) ‖·‖² = 1+25+9 = 35
λ₂= 9 : (1, −2, 3) ‖·‖² = 1+4+9 = 14
λ₃= 1 : (3, 0, −1) ‖·‖² = 9+0+1 = 10
(16.4)(가) Σ — 제곱근 한 번
σi = √λi다. 그뿐이다.
σ₁ = √16 = 4 σ₂ = √9 = 3 σ₃ = √1 = 1
⎡ 4 0 0 ⎤
Σ = ⎢ 0 3 0 ⎥ (4×3. 마지막 행은 전부 0 — u₄ 에 대응하는 σ 가 없다)
⎢ 0 0 1 ⎥
⎣ 0 0 0 ⎦
(16.5) 특이값이 정수로 떨어지는 4×3 정수행렬은 흔하지 않다. 1장에서 A를 이렇게 고른 이유가 여기 있다.(나) V — 정규화만 하면 된다
고유벡터를 각자의 길이로 나눈다. 14장에서 서로 직교인 것은 이미 확인했으니 정규화만 남았다.
v₁ = 1√35(1, 5, 3) v₂ = 1√14(1, −2, 3) v₃ = 1√10(3, 0, −1)
⎡ 1/√35 1/√14 3/√10 ⎤
V = ⎢ 5/√35 −2/√14 0 ⎥ (3×3, 직교행렬)
⎣ 3/√35 3/√14 −1/√10 ⎦
(16.6)(다) U — u = Av/σ를 세 번
이제 핵심 계산이다. A에 정수 고유벡터를 먹이고 σ와 길이로 나눈다. 정수벡터에 먼저 곱하고 나중에 나누는 것이 요령이다.
u₁ — λ = 16 쪽
⎡ 1 1 0 ⎤ ⎡ 1 ⎤ ⎡ 1·1 + 1·5 + 0·3 ⎤ ⎡ 6 ⎤
A(1,5,3) = ⎢ 0 2 0 ⎥ ⎢ 5 ⎥ = ⎢ 0·1 + 2·5 + 0·3 ⎥ = ⎢ 10 ⎥
⎢ 1 0 3 ⎥ ⎣ 3 ⎦ ⎢ 1·1 + 0·5 + 3·3 ⎥ ⎢ 10 ⎥
⎣ 0 3 1 ⎦ ⎣ 0·1 + 3·5 + 1·3 ⎦ ⎣ 18 ⎦
정규화 인자는 σ₁‖v₁의 정수형‖ = 4·√35다. 짝수를 약분하면
u₁ = A(1,5,3)4√35 = (6, 10, 10, 18)4√35 = (3, 5, 5, 9)2√35
검산: ‖(6,10,10,18)‖² = 36 + 100 + 100 + 324 = 560. 그리고 (4√35)² = 16·35 = 560. 정확히 같으니 ‖u₁‖ = 1이다. 약분형으로도 9+25+25+81 = 140 = 4·35 = (2√35)² — 일치한다.
u₂ — λ = 9 쪽
A(1,−2,3) = (1−2+0, 0−4+0, 1+0+9, 0−6+3) = (−1, −4, 10, −3)
u₂ = (−1, −4, 10, −3)3√14
검산: 1 + 16 + 100 + 9 = 126 = 9·14 = (3√14)². 일치.
u₃ — λ = 1 쪽
A(3, 0, −1) = (3+0+0, 0+0+0, 3+0−3, 0+0−1) = (3, 0, 0, −1)
u₃ = (3, 0, 0, −1)1·√10 = (3, 0, 0, −1)√10
검산: 9 + 0 + 0 + 1 = 10 = (1·√10)². 일치. σ₃ = 1이라 늘이기가 없었고, 그래서 길이가 √10 그대로다.
세 검산이 전부 ‖Avi‖² = σi²‖vi‖²라는 같은 항등식이다 — 560 = 16·35, 126 = 9·14, 10 = 1·10. "행렬이 vi 방향을 정확히 σi배 늘인다"의 숫자판이다.
(라) 직교성 확인, 그리고 u₄의 정체
정리에 따르면 u₁, u₂, u₃는 자동으로 직교해야 한다. 분모는 전부 양수이니 분자 정수벡터끼리 내적해 보면 된다.
(3,5,5,9)·(−1,−4,10,−3) = −3 − 20 + 50 − 27 = 0 ✓
(3,5,5,9)·(3,0,0,−1) = 9 + 0 + 0 − 9 = 0 ✓
(−1,−4,10,−3)·(3,0,0,−1) = −3 + 0 + 0 + 3 = 0 ✓
(16.7)세 개의 0이 우연이 아니라는 것이 STEP 02의 세 번째 걸음이었다. 이제 U를 4×4로 채울 네 번째 열이 필요하다. ℝ⁴에서 u₁,u₂,u₃에 모두 수직인 방향은 1차원뿐이고, 우리는 그것을 4장에서 이미 구해 두었다.
좌영공간 기저 n = (−1, 5, 1, −3), ‖n‖² = 1 + 25 + 1 + 9 = 36, ‖n‖ = 6
u₄ = n6 = (−1, 5, 1, −3)6
(16.8) ATn = 0은 4장에서 확인했다. 따라서 u₄는 C(A) 전체에, 즉 u₁,u₂,u₃ 전부에 수직이다.직접 확인:
(3,5,5,9)·(−1,5,1,−3) = −3 + 25 + 5 − 27 = 0 ✓
(−1,−4,10,−3)·(−1,5,1,−3) = 1 − 20 + 10 + 9 = 0 ✓
(3,0,0,−1)·(−1,5,1,−3) = −3 + 0 + 0 + 3 = 0 ✓
여기가 이 장에서 가장 만족스러운 지점이다. n = (−1,5,1,−3)은 4장에서 "행들 사이에 숨은 선형관계", 10장에서 "잔차 방향", 12장에서 "SSE = 36을 만드는 벡터"였다. 이제 그것은 U의 마지막 열이다. σ₄는 없다 — 랭크가 3이라 짝지을 v₄가 없기 때문이다.
⎡ 3/2√35 −1/3√14 3/√10 −1/6 ⎤
U = ⎢ 5/2√35 −4/3√14 0 5/6 ⎥ (4×4, 직교행렬)
⎢ 5/2√35 10/3√14 0 1/6 ⎥
⎣ 9/2√35 −3/3√14 −1/√10 −3/6 ⎦
(16.9) 소수로는 u₁ ≈ (0.253546, 0.422577, 0.422577, 0.760639). STEP 04에서 numpy 출력과 소수점 여섯째 자리까지 맞춰 본다.(마) 랭크1 분해 — 2장의 네 번째 읽기
식 (16.2)를 우리 숫자로 쓴다. 여기서 아름다운 약분이 일어난다.
σi ui viT = σi · A wiσi‖wi‖ · wiT‖wi‖ = (A wi) wiT‖wi‖² (wi = 정수 고유벡터)
(16.10) σ도 근호도 전부 약분되어 정수 나누기 정수만 남는다. 손계산이 여기서 갑자기 쉬워진다.4 u₁v₁T = 135 ⎡ 6⎤ 3 u₂v₂T = 114 ⎡ −1⎤ 1 u₃v₃T = 110 ⎡ 3⎤
⎢10⎥ (1, 5, 3) ⎢ −4⎥ (1,−2,3) ⎢ 0⎥ (3, 0,−1)
⎢10⎥ ⎢ 10⎥ ⎢ 0⎥
⎣18⎦ ⎣ −3⎦ ⎣−1⎦
= 135⎡ 6 30 18⎤ = 114⎡ −1 2 −3⎤ = 110⎡ 9 0 −3⎤
⎢10 50 30⎥ ⎢ −4 8 −12⎥ ⎢ 0 0 0⎥
⎢10 50 30⎥ ⎢ 10 −20 30⎥ ⎢ 0 0 0⎥
⎣18 90 54⎦ ⎣ −3 6 −9⎦ ⎣−3 0 1⎦
(16.11)세 장을 더하면 A가 나와야 한다. 첫 행만 확인해 보자. 분모 70으로 통분한다.
1열: 635 − 114 + 910 = 1270 − 570 + 6370 = 7070 = 1 ✓
2열: 3035 + 214 + 0 = 6070 + 1070 + 0 = 7070 = 1 ✓
3열: 1835 − 314 − 310 = 3670 − 1570 − 2170 = 0 ✓
→ 첫 행 (1, 1, 0) — A 의 첫 행 그대로.
덤으로 얻는 것이 있다. 앞의 두 장만 더하면 랭크2 최적 근사 A₂가 되는데, 우리 예제에서는 그것이 십분의 일 단위로 딱 떨어진다.
⎡ 0.1 1.0 0.3 ⎤ ⎡ 0.9 0 −0.3 ⎤
A₂ = ⎢ 0.0 2.0 0.0 ⎥ A−A₂ = ⎢ 0.0 0 0.0 ⎥ = 1·u₃v₃T
⎢ 1.0 0.0 3.0 ⎥ ⎢ 0.0 0 0.0 ⎥
⎣ 0.3 3.0 0.9 ⎦ ⎣−0.3 0 0.1 ⎦
‖A−A₂‖F² = 0.81 + 0.09 + 0.09 + 0.01 = 1.00 = σ₃²
(16.12) 버린 항의 크기가 정확히 σ₃ = 1이다. 18장의 에카르트–영 정리가 이것을 일반화한다: 랭크 k 근사의 오차는 버린 특이값들이 전부 말해 준다.(바) 네 가지 검산 — 앞 장들이 전부 돌아온다
특이값 세 개를 구했으면 앞에서 손으로 구해 둔 네 숫자와 반드시 맞춰 본다. 하나라도 어긋나면 계산이 틀린 것이다.
| 검산 | 특이값으로 | 앞 장에서 구한 값 | 출처 |
|---|---|---|---|
| 곱 | σ₁σ₂σ₃ = 4·3·1 = 12 | √det G = √144 = 12 | 7장 행렬식 |
| 제곱합 | σ₁²+σ₂²+σ₃² = 16+9+1 = 26 | ‖A‖F² = tr(G) = 26 | 2장 · 8장 |
| 최대 | σ₁ = 4 | ‖A‖₂ = 4 | 8장 스펙트럼 노름 |
| 비율 | σ₁/σ₃ = 4/1 = 4 | κ₂(A) = 4 | 8장 조건수 |
네 개 전부 맞는다. 특히 두 번째 줄을 보자. 2장에서 ‖A‖F² = 26은 관측 네 건의 길이 제곱 합이었다(2+4+10+10). 여기서는 늘이기 배율의 제곱 합이다(16+9+1). 같은 숫자를 행 쪽에서도, 방향 쪽에서도 셀 수 있다 — 2장이 예고한 다리가 이것이다.
첫 줄도 기하로 읽힌다. 단위구(부피 1)가 반축 4, 3, 1짜리 타원체가 되었으니 부피가 12배다. 정사각이 아닌 행렬의 부피 배율을 말해 주는 것이 √det(ATA)이고, 그 값이 곧 특이값의 곱이다(7장).
(사) 의사역행렬 — σ만 뒤집는다
SVD가 손에 들어오면 의사역행렬pseudoinverse은 공짜다. 늘인 것을 되돌리면 된다.
A+ = V Σ+ UT = 3Σi=1 1σi vi uiT (0 인 σ 는 건드리지 않는다)
(16.13)12장의 최소제곱 해 β̂ = A+b를 이 식으로 다시 구해 보자. b = (1,7,5,1)이다. 계수는 uiTb / σi다.
u₁Tb = (6,10,10,18)·(1,7,5,1)4√35 = 6+70+50+184√35 = 1444√35 = 36√35
u₂Tb = −1−28+50−33√14 = 183√14 = 6√14
u₃Tb = 3+0+0−1√10 = 2√10
β̂ = 14·36√35·(1,5,3)√35 + 13·6√14·(1,−2,3)√14 + 2√10·(3,0,−1)√10
= 935(1,5,3) + 17(1,−2,3) + 15(3,0,−1)
1성분: 9+5+2135 = 1 2성분: 45−10+035 = 1 3성분: 27+15−735 = 1
→ β̂ = (1, 1, 1) ✓ 10장·12장과 같은 답
정규방정식도, QR도, 소거도 쓰지 않았다. b를 세 특이방향으로 분해하고 늘이기를 되돌렸을 뿐이다. b에는 u₄ 성분도 있는데(n·b = 36, 즉 u₄Tb = 6) 식 (16.13)이 그 성분을 아예 쳐다보지 않는다. 버려진 그것이 정확히 잔차 n이고 SSE = 36이다. 최소제곱이 "잔차를 버린다"는 말은 SVD에서 "σ가 없는 방향을 건너뛴다"가 된다.

고유분해와 SVD — 무엇이 다른가
| 고유분해 A = XΛX−1 | 특이값분해 A = UΣVT | |
|---|---|---|
| 적용 조건 | 정사각이어야 하고, 고유벡터가 기저를 이뤄야 한다(13장) | 조건 없음. 모든 실수 행렬 |
| 기저 | 입출력 기저가 같다. 대신 직교가 아닐 수 있다 | 입출력 기저가 다르다. 대신 둘 다 직교 |
| 되돌리기 | X−1을 따로 구해야 한다 | UT, VT로 끝 — 전치가 곧 역 |
| 값의 부호 | λ는 음수·복소수 가능 | σ ≥ 0 항상 실수 |
| 수치 안정성 | X의 조건수가 나쁘면 위험 | 직교행렬만 쓰므로 안정 |
| 세는 것 | 변하지 않는 방향 | 늘어나는 배율 |
둘이 일치하는 경우가 있다. S가 대칭 양정치면(15장) 스펙트럼 분해 S = QΛQT 자체가 SVD다 — U = V = Q, Σ = Λ. 우리 G가 그렇다: G의 고유값 16, 9, 1이 곧 G의 특이값이다. 대칭인데 음의 고유값이 있으면 σi = |λi|가 되고 부호가 ui = −vi로 U 쪽에 넘어간다.
코드 — 손계산과 np.linalg.svd를 소수점까지 맞춘다
먼저 손으로 구한 U, Σ, V를 그대로 조립해 A가 복원되는지 보고, 그다음 numpy 결과와 대조한다. 부호 규약은 라이브러리마다 다르므로 열마다 부호 하나만 맞춰 준다.
import numpy as np
np.set_printoptions(precision=6, suppress=True)
A = np.array([[1,1,0],[0,2,0],[1,0,3],[0,3,1]], float)
n = np.array([-1,5,1,-3], float)
# ── 손계산 SVD 를 그대로 조립한다 ───────────────────────────
Vint = np.array([[1,5,3],[1,-2,3],[3,0,-1]], float) # 14장의 정수 고유벡터
Vhand = (Vint / np.linalg.norm(Vint, axis=1)[:,None]).T # 열이 v1 v2 v3
shand = np.array([4., 3., 1.]) # σ = √(16,9,1)
Uhand = (A @ Vhand) / shand # u_i = A v_i / σ_i
print("U (손계산, 열 = u1 u2 u3)"); print(Uhand)
print("UᵀU = I :", np.allclose(Uhand.T@Uhand, np.eye(3)),
" VᵀV = I :", np.allclose(Vhand.T@Vhand, np.eye(3)))
print("UΣVᵀ = A :", np.allclose(Uhand@np.diag(shand)@Vhand.T, A))
# ── numpy 와 소수점까지 대조 (부호 규약만 보정) ───────────────
U, s, Vt = np.linalg.svd(A)
print("\nσ 손계산 :", shand, " numpy :", s)
print("σ 최대오차 :", np.abs(s-shand).max())
sg = np.sign((Vt[:3].T * Vhand).sum(axis=0)) # 열마다 부호 하나
print("부호 규약(numpy/손) :", sg.astype(int))
print("V 최대오차 :", np.abs(Vt[:3].T*sg - Vhand).max())
print("U 최대오차 :", np.abs(U[:,:3]*sg - Uhand).max())
# ── U 의 네 번째 열 = 좌영공간 ──────────────────────────────
u4 = U[:,3]
print("\nu4 (numpy) :", u4)
print("n/6 :", n/6)
print("‖u4‖² =", round(float(u4@u4),12), " Aᵀu4 =", np.round(A.T@u4, 12))
print("u4 = ±n/6 :", np.allclose(np.abs(u4), np.abs(n/6)))
U (손계산, 열 = u1 u2 u3)
[[ 0.253546 -0.089087 0.948683]
[ 0.422577 -0.356348 0. ]
[ 0.422577 0.890871 -0. ]
[ 0.760639 -0.267261 -0.316228]]
UᵀU = I : True VᵀV = I : True
UΣVᵀ = A : True
σ 손계산 : [4. 3. 1.] numpy : [4. 3. 1.]
σ 최대오차 : 4.440892098500626e-16
부호 규약(numpy/손) : [ 1 1 -1]
V 최대오차 : 4.440892098500626e-16
U 최대오차 : 3.885780586188048e-16
u4 (numpy) : [ 0.166667 -0.833333 -0.166667 0.5 ]
n/6 : [-0.166667 0.833333 0.166667 -0.5 ]
‖u4‖² = 1.0 Aᵀu4 = [-0. 0. 0.]
u4 = ±n/6 : True
읽을 것이 세 가지다.
다음은 의사역행렬과 랭크1 재조립이다.
import numpy as np
np.set_printoptions(precision=6, suppress=True)
A = np.array([[1,1,0],[0,2,0],[1,0,3],[0,3,1]], float)
b = np.array([1,7,5,1], float); n = np.array([-1,5,1,-3], float)
Vint = np.array([[1,5,3],[1,-2,3],[3,0,-1]], float)
V = (Vint/np.linalg.norm(Vint,axis=1)[:,None]).T
s = np.array([4.,3.,1.]); U = (A@V)/s
# ── ① SVD 로 만든 의사역행렬 A⁺ = V Σ⁻¹ Uᵀ ──────────────────
Ap = V @ np.diag(1/s) @ U.T
print("144·A⁺ (정수로 떨어진다)"); print(np.round(Ap*144, 9))
print("A⁺b =", Ap@b, " ← β̂ = (1,1,1)")
print("A⁺A = I₃ :", np.allclose(Ap@A, np.eye(3)))
print("AA⁺ = P = I − nnᵀ/36 :", np.allclose(A@Ap, np.eye(4)-np.outer(n,n)/36))
# ── ② 랭크1 항을 한 겹씩 쌓아 A 를 복원 ──────────────────────
print("\n-- σᵢuᵢvᵢᵀ 를 한 겹씩 --")
S = np.zeros((4,3))
for i in range(3):
S += s[i]*np.outer(U[:,i], V[:,i])
print(f"k={i+1} ‖A−A_k‖_F = {np.linalg.norm(A-S):.6f}"
f" ‖A−A_k‖₂ = {np.linalg.norm(A-S,2):.6f}")
print("복원한 A ="); print(np.round(S,10)+0.0)
# ── ③ 네 가지 검산 ──────────────────────────────────────────
print("\nσ1σ2σ3 =", round(float(np.prod(s)),9),
" | √det(AᵀA) =", round(float(np.sqrt(np.linalg.det(A.T@A))),9))
print("Σσᵢ² =", round(float((s**2).sum()),9),
" | ‖A‖_F² =", round(float((A**2).sum()),9))
print("σ1 =", s[0], " | ‖A‖₂ =", round(float(np.linalg.norm(A,2)),9))
print("σ1/σ3 =", s[0]/s[2], " | κ₂(A) =", round(float(np.linalg.cond(A)),9))
144·A⁺ (정수로 떨어진다)
[[130. -2. 14. -42.]
[ 10. 22. -10. 30.]
[-42. -6. 42. 18.]]
A⁺b = [1. 1. 1.] ← β̂ = (1,1,1)
A⁺A = I₃ : True
AA⁺ = P = I − nnᵀ/36 : True
-- σᵢuᵢvᵢᵀ 를 한 겹씩 --
k=1 ‖A−A_k‖_F = 3.162278 ‖A−A_k‖₂ = 3.000000
k=2 ‖A−A_k‖_F = 1.000000 ‖A−A_k‖₂ = 1.000000
k=3 ‖A−A_k‖_F = 0.000000 ‖A−A_k‖₂ = 0.000000
복원한 A =
[[1. 1. 0.]
[0. 2. 0.]
[1. 0. 3.]
[0. 3. 1.]]
σ1σ2σ3 = 12.0 | √det(AᵀA) = 12.0
Σσᵢ² = 26.0 | ‖A‖_F² = 26.0
σ1 = 4.0 | ‖A‖₂ = 4.0
σ1/σ3 = 4.0 | κ₂(A) = 4.0
144A+가 정수로 떨어지는 것은 우연이 아니다. A+ = (ATA)−1AT = adj(G)AT/144이고(6장·7장) adj(G)가 정수행렬이기 때문이다. 근호투성이 U, V로 만들었는데 정수가 나온다는 것이 둘이 정확하다는 또 하나의 증거다.
AA+ = I − nnT/36도 눈여겨보자. 10장의 투영행렬이 여기서 SVD로 다시 유도된다. AA+ = U(ΣΣ+)UT이고 ΣΣ+ = diag(1,1,1,0)이므로 u₁u₁T+u₂u₂T+u₃u₃T = I − u₄u₄T. 투영행렬이란 "σ > 0인 방향만 1로 두는 대각행렬"이었던 것이다.
누적 오차 √10, 1, 0은 18장의 주제다. 미리 말해 두면 ‖A−Ak‖F² = σk+1²+…+σr²(우리 값 9+1과 1), ‖A−Ak‖₂ = σk+1(정확히 3과 1).
시각화 — 겹이 쌓여 행렬이 된다
식 (16.11)의 세 장이 어떻게 A가 되는지를 숫자로 그린다. 한 겹씩 얹을 때마다 A에 가까워지고, 남는 오차가 정확히 다음 σ다.

왜 이 장이 시리즈의 정점인가
지금까지 따로 배운 것들이 전부 σ = 4, 3, 1과 U, V의 열로 환원된다. 한 표로 정리한다.
| 장 | 그때 배운 것 | SVD 언어로 다시 쓰면 |
|---|---|---|
| 2장 | 행렬 곱의 네 번째 읽기 — 랭크1 합 | A = Σ σiuiviT 그 자체 |
| 4장 | 랭크 3, 좌영공간 n = (−1,5,1,−3) | 랭크 = σ > 0의 개수. n/6 = u₄ |
| 7장 | det G = 144 | (σ₁σ₂σ₃)² = 12² = 144 |
| 8장 | ‖A‖₂ = 4, ‖A‖F² = 26, κ = 4 | σ₁, Σσi², σ₁/σ₃ |
| 10장 | 투영행렬 P = I − nnT/36 | u₁u₁T+u₂u₂T+u₃u₃T |
| 12장 | 최소제곱 β̂ = (1,1,1) | A+b = Σ (uiTb / σi) vi |
| 14장 | G의 스펙트럼 분해 QΛQT | Q = V, Λ = ΣTΣ = diag(16,9,1) |
| 15장 | G가 대칭 양정치 | SVD 존재 증명의 유일한 재료 |
제4부는 여기서 끝난다. 13장에서 "방향이 변하지 않는 축"을 물었고, 14장에서 대칭행렬이면 그 축들이 직교한다는 것을 보았고, 15장에서 ATA가 언제나 그 조건을 만족한다는 것을 확인했다. 16장은 그 세 걸음을 하나로 접어 모든 행렬에 적용했다. 이제 남은 것은 쓰는 일이다. 17장은 이 V가 데이터를 중심화했을 때 무엇이 되는지를 묻고(그것이 PCA다), 18장은 이 σ 막대를 어디서 자를지를 묻는다. 두 장 모두 새 정리를 배우지 않는다 — 이 장에서 구한 U, Σ, V를 읽는 방법만 배운다.
이것만 기억하자
- SVD가 항상 존재하는 이유는 ATA가 대칭 양반정치이기 때문이다. 새 정리가 아니라 15장 정리의 번역이다. σi = √λi(ATA)로 두고 ui = Avi/σi로 정의하면 uiTuj = δij가 한 줄로 따라 나온다.
- 기하는 "회전 → 늘이기 → 회전"이고 정보는 전부 가운데에 있다. σ = 4, 3, 1에서 부피 배율 12 = √det G, 찌그러짐 κ = 4, 에너지 16+9+1 = 26 = ‖A‖F²가 전부 나온다.
- U와 V는 네 부분공간의 정규직교 기저를 한 번에 준다. V의 앞 r열이 행공간, U의 앞 r열이 열공간, 나머지가 각각 영공간과 좌영공간. 우리 A에서는 u₄ = n/6 — 4장의 그 벡터가 U의 마지막 열로 돌아온다.
흔한 오해
- "특이값은 고유값의 절댓값이다." — 대칭행렬에서만 맞는 말이다. 일반 행렬에서는 아무 관계가 없다. [[0,10],[0,0]]은 고유값이 0, 0인데 특이값은 10, 0이다. 고유값은 "변하지 않는 방향"을, 특이값은 "늘어나는 배율"을 센다.
- "SVD는 유일하다." — 특이값의 목록만 유일하다. (ui, vi)를 동시에 뒤집어도 σiuiviT가 그대로이므로 부호만큼 자유도가 있고, 특이값이 겹치면 그 부분공간 안의 회전만큼 더 자유롭다. STEP 04에서 numpy가 세 번째 열의 부호를 뒤집은 이유다.
- "SVD를 쓰려면 ATA를 만들어 고유분해하면 된다." — 유도는 그렇게 했지만 계산은 그러면 안 된다. κ(ATA) = κ(A)²이라 조건수가 제곱되고(4 → 16), 작은 σ는 σ²에서 더 작아져 반올림에 묻힌다(8장). LAPACK의 gesdd는 A를 이중대각화해 바로 다루며 ATA를 만들지 않는다.
- "Σ의 0인 자리는 그냥 0으로 나눈 셈 치면 된다." — Σ+는 0이 아닌 σ만 뒤집고 0인 자리는 0으로 둔다. 실무에서는 σ < τ를 0으로 간주하는 절단값을 쓰며, numpy.linalg.pinv의 rcond가 그것이다. 이 절단이 저랭크 근사로 이어진다(18장).
'선형대수' 카테고리의 다른 글
| 손으로 푸는 선형대수 18장 — 저랭크 근사: 무엇을 버리는가 (0) | 2026.09.24 |
|---|---|
| 손으로 푸는 선형대수 17장 — PCA는 결국 SVD다 (0) | 2026.09.24 |
| 손으로 푸는 선형대수 15장 — 양정치와 이차형식: 공분산이 왜 대칭 양반정치인가 (0) | 2026.09.24 |
| 손으로 푸는 선형대수 14장 — 대칭행렬의 스펙트럼 분해 (0) | 2026.09.24 |
| 손으로 푸는 선형대수 13장 — 고유값과 고유벡터: 방향이 변하지 않는 축 (0) | 2026.09.24 |
댓글