1 이 절의 위치
Ch.4 전체 흐름에서 §4.1은 출발점이다.
§4.1 네 부분공간의 직교성 ← 지금 여기
↓ "잔차가 열공간에 수직"이라는 사실로
§4.2 투영 (Projection)
↓ "최소 오차를 내는 해"로
§4.3 최소제곱법 (Least Squares)
↓ "기저를 직교 정규화"하려면
§4.4 Gram-Schmidt & QR 분해
§4.1의 핵심 메시지는 하나다.
행공간 \(\perp\) 영공간, 열공간 \(\perp\) 좌영공간 — 이 두 쌍이 각각 \(\mathbb{R}^n\) 과 \(\mathbb{R}^m\) 을 빠짐없이 채운다.
2 Ch.3 복습: 네 부분공간
\(m \times n\) 행렬 \(\mathbf{A}\) (랭크 \(r\))의 네 부분공간을 간략히 정리한다.
| 부분공간 | 기호 | 속하는 공간 | 차원 |
|---|---|---|---|
| 행공간 (Row Space) | \(C(\mathbf{A}^\top)\) | \(\mathbb{R}^n\) | \(r\) |
| 영공간 (Null Space) | \(N(\mathbf{A})\) | \(\mathbb{R}^n\) | \(n - r\) |
| 열공간 (Column Space) | \(C(\mathbf{A})\) | \(\mathbb{R}^m\) | \(r\) |
| 좌영공간 (Left Null Space) | \(N(\mathbf{A}^\top)\) | \(\mathbb{R}^m\) | \(m - r\) |
Part 1 (Ch.3)에서 이 차원 관계를 증명했다. Part 2 (Ch.4 §4.1)에서는 같은 공간에 속하는 두 부분공간이 90도를 이룬다는 사실을 증명한다.
직관: 행공간 차원 \(r\) 과 영공간 차원 \(n - r\) 의 합이 정확히 \(n\) 이다. 수직인 두 공간이 전체 공간을 빠짐없이 채우는 것 — 이것이 직교 보공간(orthogonal complement)이다.
3 직교 벡터와 직교 부분공간 — 빠른 복습
3.1 직교 벡터
\(\mathbf{v}^\top \mathbf{w} = 0 \iff \mathbf{v} \perp \mathbf{w}\)
피타고라스 동치 조건: \(\|\mathbf{v}\|^2 + \|\mathbf{w}\|^2 = \|\mathbf{v} + \mathbf{w}\|^2\)
왜 피타고라스가 동치인가: \(\|\mathbf{v} + \mathbf{w}\|^2 = (\mathbf{v}+\mathbf{w})^\top(\mathbf{v}+\mathbf{w}) = \|\mathbf{v}\|^2 + 2\mathbf{v}^\top\mathbf{w} + \|\mathbf{w}\|^2\) 이므로, 교차항 \(2\mathbf{v}^\top\mathbf{w}\) 가 0이 될 때 피타고라스가 성립한다.
3.2 직교 부분공간
두 부분공간 \(V\) 와 \(W\) 가 직교한다 \(\iff\) \(V\) 의 모든 벡터가 \(W\) 의 모든 벡터와 직교한다.
\[V \perp W \iff \mathbf{v}^\top \mathbf{w} = 0 \quad \forall\, \mathbf{v} \in V, \; \forall\, \mathbf{w} \in W\]
핵심 성질: 두 부분공간이 직교하면 영벡터에서만 만난다.
\(\mathbf{v} \in V \cap W\) 이면 \(\mathbf{v}\) 는 \(V\) 에도 속하고 \(W\) 에도 속하므로 \(\mathbf{v}^\top\mathbf{v} = 0\). 즉 \(\|\mathbf{v}\|^2 = 0\) 이므로 \(\mathbf{v} = \mathbf{0}\) 이다.
반례 — 두 평면은 \(\mathbb{R}^3\) 에서 직교 부분공간이 될 수 없다:
교실의 바닥(xy 평면)과 벽(xz 평면)은 언뜻 수직처럼 보인다. 그러나 두 평면은 \(x\) 축을 공유한다. \(x\) 축의 벡터는 자기 자신과의 내적이 0이 아니므로, 두 평면은 직교 부분공간이 아니다.
일반화: \(\dim V + \dim W > \dim(\text{전체 공간})\) 이면 두 공간은 직교 부분공간이 될 수 없다. 두 공간이 비자명 교점을 가지기 때문이다 (Strang, 2009, Ch.4).
import numpy as np
import matplotlib.pyplot as plt
# 직교 부분공간 예시: xy 평면과 z 축 (R^3 에서)
# V = xy 평면 (dim=2), W = z 축 (dim=1), dim V + dim W = 3 = dim R^3 → 가능
v1 = np.array([1, 0, 0]) # xy 평면의 기저
v2 = np.array([0, 1, 0])
w = np.array([0, 0, 1]) # z 축의 기저
print("=== xy 평면과 z 축 (직교 부분공간 O) ===")
print(f"v1 · w = {np.dot(v1, w)}") # 0
print(f"v2 · w = {np.dot(v2, w)}") # 0
# 반례: xy 평면과 xz 평면
u1 = np.array([1, 0, 0]) # 공유 벡터
print(f"\n=== xy 평면과 xz 평면 (직교 부분공간 X) ===")
print(f"공유 벡터 u1 · u1 = {np.dot(u1, u1)}") # 1 ≠ 0 → 직교 불가4 정리 1: 행공간 \(\perp\) 영공간
4.1 명제
\(m \times n\) 행렬 \(\mathbf{A}\) 에 대해:
\[C(\mathbf{A}^\top) \perp N(\mathbf{A}) \quad \text{(in } \mathbb{R}^n \text{)}\]
즉, \(\mathbf{A}\) 의 행공간의 모든 벡터는 \(\mathbf{A}\) 의 영공간의 모든 벡터와 직교한다.
4.2 증명 방법 1 — 행 단위 해석 (직관적)
\(\mathbf{x} \in N(\mathbf{A})\) 이면 \(\mathbf{A}\mathbf{x} = \mathbf{0}\) 이다. 이것을 행 단위로 전개하면:
\[\mathbf{A}\mathbf{x} = \begin{bmatrix} \mathbf{a}_1^\top \\ \mathbf{a}_2^\top \\ \vdots \\ \mathbf{a}_m^\top \end{bmatrix} \mathbf{x} = \begin{bmatrix} \mathbf{a}_1^\top \mathbf{x} \\ \mathbf{a}_2^\top \mathbf{x} \\ \vdots \\ \mathbf{a}_m^\top \mathbf{x} \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ \vdots \\ 0 \end{bmatrix}\]
각 방정식 \(\mathbf{a}_i^\top \mathbf{x} = 0\) 이 “\(\mathbf{A}\) 의 \(i\) 번째 행이 \(\mathbf{x}\) 에 수직”임을 의미한다. 모든 행 \(\mathbf{a}_i\) 가 \(\mathbf{x}\) 에 수직이므로, 행들의 임의 선형결합도 \(\mathbf{x}\) 에 수직이다. 행공간 = 행들의 선형결합의 집합이므로, 행공간 전체가 \(\mathbf{x}\) 에 수직이다. \(\square\)
직관: \(Ax = 0\) 이라는 연립방정식을 쓰는 순간, 이미 “각 행이 \(x\) 에 수직”임을 선언하는 것이다.
4.3 증명 방법 2 — 행렬 단축 증명 (우아한)
행공간의 임의 벡터를 \(\mathbf{A}^\top \mathbf{y}\) 로 쓸 수 있다 (행공간 = \(\mathbf{A}^\top\) 의 열공간). \(\mathbf{x} \in N(\mathbf{A})\) 이면:
\[\mathbf{x}^\top (\mathbf{A}^\top \mathbf{y}) = (\mathbf{A}\mathbf{x})^\top \mathbf{y} = \mathbf{0}^\top \mathbf{y} = 0\]
핵심 트릭: \((AB)^\top = B^\top A^\top\) 을 이용해 \(\mathbf{A}\) 를 \(\mathbf{x}\) 쪽으로 이동시킨다. \(\mathbf{A}\mathbf{x} = \mathbf{0}\) 이라는 조건이 마지막에 사용된다. \(\square\)
두 증명의 의미: - 방법 1은 “왜”를 직접 보여준다 — 각 행이 수직이므로 행공간 전체가 수직 - 방법 2는 \(\mathbf{A}\) 와 \(\mathbf{A}^\top\) 이 기본정리에 함께 등장하는 이유를 보여준다
4.4 수치 예시
\[\mathbf{A} = \begin{bmatrix} 1 & 3 & 4 \\ 5 & 2 & 7 \end{bmatrix}, \quad \mathbf{x} = \begin{bmatrix} 1 \\ 1 \\ -1 \end{bmatrix}\]
\(\mathbf{A}\mathbf{x} = \begin{bmatrix} 1+3-4 \\ 5+2-7 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \end{bmatrix}\) 이므로 \(\mathbf{x} \in N(\mathbf{A})\).
행 1과 \(\mathbf{x}\) 의 내적: \(1 \cdot 1 + 3 \cdot 1 + 4 \cdot (-1) = 0\) ✓ 행 2와 \(\mathbf{x}\) 의 내적: \(5 \cdot 1 + 2 \cdot 1 + 7 \cdot (-1) = 0\) ✓
이 두 행의 임의 선형결합 \(\alpha \mathbf{a}_1 + \beta \mathbf{a}_2\) 도 \(\mathbf{x}\) 에 수직이다. 예: \(\mathbf{a}_1 + \mathbf{a}_2 = (6, 5, 11)\), 내적 \(= 6 + 5 - 11 = 0\) ✓
import numpy as np
A = np.array([[1, 3, 4],
[5, 2, 7]], dtype=float)
x = np.array([1, 1, -1], dtype=float)
print("=== 행공간 ⊥ 영공간 수치 확인 ===")
print(f"Ax = {A @ x}") # [0, 0] — 영공간 확인
print(f"행 1 · x = {A[0] @ x}") # 0
print(f"행 2 · x = {A[1] @ x}") # 0
# 행들의 선형결합 (행공간의 임의 벡터)
alpha, beta = 2.0, -1.0
row_combination = alpha * A[0] + beta * A[1]
print(f"\n행 선형결합 {alpha}*r1 + {beta}*r2 = {row_combination}")
print(f"내적: {row_combination @ x}") # 0 ✓
# 방법 2 검증
y = np.array([3.0, -1.0]) # 임의의 y
row_vec = A.T @ y # 행공간의 벡터
print(f"\n행공간 벡터 A^T y = {row_vec}")
print(f"x^T (A^T y) = {x @ row_vec}") # 0
print(f"(Ax)^T y = {(A @ x) @ y}") # 0 ✓5 정리 2: 열공간 \(\perp\) 좌영공간
5.1 명제
\[C(\mathbf{A}) \perp N(\mathbf{A}^\top) \quad \text{(in } \mathbb{R}^m \text{)}\]
즉, \(\mathbf{A}\) 의 열공간의 모든 벡터는 \(\mathbf{A}\) 의 좌영공간의 모든 벡터와 직교한다.
5.2 증명 — 대칭성 활용
\(\mathbf{A}^\top\) 에 정리 1을 적용하면 된다.
- \(\mathbf{A}^\top\) 의 영공간 = \(N(\mathbf{A}^\top)\) (좌영공간)
- \(\mathbf{A}^\top\) 의 행공간 = \(C((\mathbf{A}^\top)^\top) = C(\mathbf{A})\) (열공간)
정리 1에 의해: \(N(\mathbf{A}^\top) \perp C(\mathbf{A})\) \(\square\)
시각적 증명: \(\mathbf{y} \in N(\mathbf{A}^\top)\) 이면 \(\mathbf{A}^\top \mathbf{y} = \mathbf{0}\) 이다.
\[\mathbf{A}^\top \mathbf{y} = \begin{bmatrix} \mathbf{c}_1^\top \\ \mathbf{c}_2^\top \\ \vdots \\ \mathbf{c}_n^\top \end{bmatrix} \mathbf{y} = \begin{bmatrix} \mathbf{c}_1^\top \mathbf{y} \\ \mathbf{c}_2^\top \mathbf{y} \\ \vdots \\ \mathbf{c}_n^\top \mathbf{y} \end{bmatrix} = \mathbf{0}\]
여기서 \(\mathbf{c}_i\) 는 \(\mathbf{A}\) 의 \(i\) 번째 열이다. 각 열 \(\mathbf{c}_i\) 와 \(\mathbf{y}\) 의 내적이 0이므로, 열공간 전체가 \(\mathbf{y}\) 에 수직이다.
직관: “좌영공간의 원소 \(\mathbf{y}\) 는 \(\mathbf{A}\) 의 모든 열과 수직이다.”
A = np.array([[1, 3, 4],
[5, 2, 7]], dtype=float)
# 좌영공간: A^T y = 0
# A^T = [[1,5],[3,2],[4,7]] → 소거법으로 y 구하기
# [1 5 | 0]
# [3 2 | 0]
# 소거: R2 - 3R1 → [0 -13 | 0] → y2 = 0, y1 = 0
# 이 경우 랭크 2, 좌영공간은 {0} (m=2, r=2, dim=0)
print("A의 랭크:", np.linalg.matrix_rank(A)) # 2 → 좌영공간은 영벡터만
# 다른 예시: rank-deficient 행렬
B = np.array([[1, 2, 3],
[2, 4, 6],
[1, 2, 3]], dtype=float) # 세 행이 모두 같음 → rank 1
# 좌영공간 기저 계산
from numpy.linalg import svd
U, S, Vt = svd(B)
# 0에 가까운 특이값에 대응하는 U의 열이 좌영공간 기저
tol = 1e-10
left_null = U[:, np.where(S < tol)[0]]
if left_null.size == 0:
# S의 작은 값들 확인
left_null_idx = np.where(np.abs(S) < 1e-10)[0]
# rank 1이면 m-r = 3-1 = 2차원 좌영공간
print(f"\nB의 랭크: {np.linalg.matrix_rank(B)}") # 1
print(f"좌영공간 차원: m - r = {3 - np.linalg.matrix_rank(B)}") # 2
# 좌영공간 기저 (직접 계산)
# B^T y = 0: [1 2 1]^T y = [1,2,1]^T * y...
# B^T = [[1,2,1],[2,4,2],[3,6,3]] → 모두 [1,2,3]의 배수
# y1 + 2*y2 + y3 = 0 → 2차원
y1 = np.array([-2, 1, 0]) # 자유변수 y2=1, y3=0
y2 = np.array([-1, 0, 1]) # 자유변수 y2=0, y3=1
print(f"\n좌영공간 기저:")
print(f"y1 = {y1}, B^T y1 = {B.T @ y1}") # [0,0,0]
print(f"y2 = {y2}, B^T y2 = {B.T @ y2}") # [0,0,0]
# 열공간 기저 (열 [1,2,1])
col = B[:, 0] # [1, 2, 1]
print(f"\n열공간 기저: {col}")
print(f"col · y1 = {col @ y1}") # 0 ✓
print(f"col · y2 = {col @ y2}") # 0 ✓
print("→ C(B) ⊥ N(B^T) 확인!")6 직교 vs. 직교 보공간 — 차원이 핵심이다
지금까지 행공간과 영공간이 직교한다는 것을 증명했다. 그런데 “직교(orthogonal)”와 “직교 보공간(orthogonal complement)”은 다른 개념이다.
6.1 직교 부분공간의 한계
\(\mathbb{R}^3\) 에서 두 직선 \(L_1 = \text{span}\{(1,0,0)\}\) 과 \(L_2 = \text{span}\{(0,1,0)\}\) 은 직교한다. 그러나 \(L_1\) 과 \(L_2\) 는 \(\mathbb{R}^3\) 의 직교 보공간 쌍이 아니다. 왜냐하면 \(\dim L_1 + \dim L_2 = 1 + 1 = 2 \neq 3 = \dim \mathbb{R}^3\) 이기 때문이다.
\(L_1\) 의 진정한 직교 보공간은 \(L_1^\perp = \text{span}\{(0,1,0), (0,0,1)\}\) — 2차원 평면이다.
6.2 직교 보공간의 정의
부분공간 \(V\) 의 직교 보공간 \(V^\perp\) 는 \(V\) 의 모든 벡터와 직교하는 모든 벡터의 집합이다.
\[V^\perp = \{\mathbf{w} \in \mathbb{R}^n \mid \mathbf{v}^\top \mathbf{w} = 0 \; \forall\, \mathbf{v} \in V\}\]
핵심 성질: \(\dim V + \dim V^\perp = n\) (전체 공간의 차원)
직관: 직교 보공간은 “수직인 방향을 모두 모은, 가장 큰 직교 부분공간”이다. 작은 직교 부분공간들이 여러 개 있을 수 있지만, 직교 보공간은 그것들 중 유일한 최대 원소다.
6.3 행공간과 영공간은 직교 보공간 쌍이다
정리 1은 \(N(\mathbf{A}) \perp C(\mathbf{A}^\top)\) 를 증명했다. 더 나아가: \(N(\mathbf{A}) = C(\mathbf{A}^\top)^\perp\) 이다.
역방향 증명: \(\mathbf{v} \in \mathbb{R}^n\) 이 영공간에 수직이라고 하자 (\(\mathbf{v}^\top \mathbf{n} = 0, \; \forall\, \mathbf{n} \in N(\mathbf{A})\)). 이 \(\mathbf{v}\) 가 행공간에 속한다는 것을 보여야 한다.
만약 \(\mathbf{v}\) 가 행공간에 속하지 않는다면, \(\mathbf{v}\) 를 \(\mathbf{A}\) 에 새 행으로 추가해도 영공간이 변하지 않는다. 그러면 행공간의 차원이 \(r+1\) 이 되는데, 이는 \(r + (n-r) = n\) 을 위반한다. 모순. \(\square\)
차원 체크가 결정적이다:
\[\dim C(\mathbf{A}^\top) + \dim N(\mathbf{A}) = r + (n - r) = n\]
두 공간이 직교하고 차원의 합이 \(n\) 이면, 두 공간은 자동으로 직교 보공간 쌍이 된다.
# 직교 vs. 직교 보공간 차이 수치 확인
# 예시: A = [[1,2,3],[4,5,6],[7,8,9]] (rank 2)
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=float)
r = np.linalg.matrix_rank(A)
n = A.shape[1] # 3
m = A.shape[0] # 3
print(f"랭크 r = {r}")
print(f"행공간 차원 = {r}, 영공간 차원 = {n - r}")
print(f"합 = {r} + {n - r} = {n} = dim(R^n) ✓")
print(f"\n열공간 차원 = {r}, 좌영공간 차원 = {m - r}")
print(f"합 = {r} + {m - r} = {m} = dim(R^m) ✓")
# 영공간 기저 계산
from scipy.linalg import null_space
N_basis = null_space(A) # 영공간 기저 벡터들 (열로)
R_basis = null_space(A.T).T # 행공간 기저 (A의 행공간 = A^T의 열공간)
# 행공간 기저 직접 계산 (pivot rows)
# rref로 pivot rows 식별
print(f"\n영공간 기저:\n{N_basis}")
# 행공간과 영공간이 직교함을 확인
# R^3에서 행공간 기저 구하기 (A의 행공간은 rank 2, 차원 2)
# SVD로 행공간 기저 구하기
U, S, Vt = np.linalg.svd(A)
row_space_basis = Vt[:r, :] # 상위 r개 행 = 행공간 기저
null_space_basis = Vt[r:, :] # 나머지 = 영공간 기저
print(f"\n행공간 기저 (SVD):")
for i, v in enumerate(row_space_basis):
print(f" r{i+1} = {v}")
print(f"\n영공간 기저 (SVD):")
for i, v in enumerate(null_space_basis):
print(f" n{i+1} = {v}")
# 직교성 확인
print(f"\n직교성 확인:")
for i, rv in enumerate(row_space_basis):
for j, nv in enumerate(null_space_basis):
print(f" r{i+1} · n{j+1} = {rv @ nv:.2e}") # ≈ 07 선형대수 기본정리 Part 2
이제 두 정리를 공식으로 정리한다.
\(m \times n\) 행렬 \(\mathbf{A}\) (랭크 \(r\))에 대해:
\[N(\mathbf{A}) = C(\mathbf{A}^\top)^\perp \quad \text{(in } \mathbb{R}^n \text{)}\] \[N(\mathbf{A}^\top) = C(\mathbf{A})^\perp \quad \text{(in } \mathbb{R}^m \text{)}\]
직합 분해:
\[\mathbb{R}^n = C(\mathbf{A}^\top) \oplus N(\mathbf{A}), \qquad \mathbb{R}^m = C(\mathbf{A}) \oplus N(\mathbf{A}^\top)\]
여기서 \(\oplus\) 는 직합(direct sum) — 두 부분공간이 전체 공간을 빠짐없이, 겹치지 않게 채운다.
Part 1 vs Part 2 비교:
| Part 1 (Ch.3) | Part 2 (Ch.4 §4.1) | |
|---|---|---|
| 내용 | 네 부분공간의 차원 | 네 부분공간의 방향(직교성) |
| 핵심 관계 | \(r + (n-r) = n\) | \(C(\mathbf{A}^\top) \perp N(\mathbf{A})\) |
| 의미 | 공간을 얼마나 채우는가 | 어떤 방향으로 채우는가 |
8 벡터 분해: \(\mathbf{x} = \mathbf{x}_r + \mathbf{x}_n\)
Part 2의 직합 분해는 구체적으로 다음을 의미한다.
모든 \(\mathbf{x} \in \mathbb{R}^n\) 은 다음과 같이 유일하게 분해된다.
\[\mathbf{x} = \mathbf{x}_r + \mathbf{x}_n\]
- \(\mathbf{x}_r \in C(\mathbf{A}^\top)\) (행공간 성분)
- \(\mathbf{x}_n \in N(\mathbf{A})\) (영공간 성분)
- \(\mathbf{x}_r \perp \mathbf{x}_n\) (두 성분은 직교)
8.1 직관: 두 축으로의 투영
\(\mathbb{R}^2\) 에서 임의 벡터를 \(x\) 축과 \(y\) 축 성분으로 분해하는 것의 고차원 일반화다. 단, 두 “축”이 행공간과 영공간으로 대체된다.
\(\mathbb{R}^2\) 의 경우: \((3, 4) = (3, 0) + (0, 4)\) 고차원: \(\mathbf{x} = \mathbf{x}_r + \mathbf{x}_n\) (행공간 방향 + 영공간 방향)
8.2 분해 후 \(\mathbf{A}\) 를 곱하면
\[\mathbf{A}\mathbf{x} = \mathbf{A}\mathbf{x}_r + \mathbf{A}\mathbf{x}_n = \mathbf{A}\mathbf{x}_r + \mathbf{0} = \mathbf{A}\mathbf{x}_r\]
영공간 성분은 \(\mathbf{A}\) 에 의해 사라지고, 행공간 성분만 열공간으로 이동한다.
그림으로 보면:
R^n R^m
┌─────────────────┐ ┌──────────────┐
│ 행공간 │ A │ 열공간 │
│ x_r ──────────────────→ Ax_r = Ax │
│ │ │ │
│ 영공간 │ │ 좌영공간 │
│ x_n ──────────────────→ 0 │
└─────────────────┘ └──────────────┘
행공간 → 열공간 매핑은 일대일:
\(\mathbf{A}\mathbf{x}_r = \mathbf{A}\mathbf{x}_r'\) 이라 하면, 차이 \(\mathbf{x}_r - \mathbf{x}_r'\) 는 영공간에도, 행공간에도 속한다. 두 공간이 직교하므로 \(\mathbf{x}_r - \mathbf{x}_r' = \mathbf{0}\), 즉 \(\mathbf{x}_r = \mathbf{x}_r'\). 따라서 행공간 → 열공간은 전단사(bijective) 이다.
의미: 행렬 \(\mathbf{A}\) 안에는 \(r \times r\) 가역 행렬이 숨어있다. 두 영공간을 제거하고 행공간→열공간 부분만 보면, \(\mathbf{A}\) 는 가역이다. 이것이 유사역행렬(pseudoinverse) \(\mathbf{A}^+\) 의 기초다.
8.3 교재 예시: \(\mathbf{A} = \begin{bmatrix} 1 & 2 \\ 3 & 6 \end{bmatrix}\), \(\mathbf{x} = \begin{bmatrix} 4 \\ 3 \end{bmatrix}\)
(Strang, 2009, Ch.4)
행공간 기저: \(\mathbf{r} = (1, 2)^\top\) (피벗 행 방향) 영공간 기저: \(\mathbf{n} = (-2, 1)^\top\) (특수해: \(x_1 + 2x_2 = 0\))
행공간 성분 (정사영 — §4.2에서 상세히 다룸):
\[\mathbf{x}_r = \frac{\mathbf{x}^\top \mathbf{r}}{\|\mathbf{r}\|^2} \mathbf{r} = \frac{4 \cdot 1 + 3 \cdot 2}{1^2 + 2^2} \begin{bmatrix} 1 \\ 2 \end{bmatrix} = \frac{10}{5} \begin{bmatrix} 1 \\ 2 \end{bmatrix} = \begin{bmatrix} 2 \\ 4 \end{bmatrix}\]
\[\mathbf{x}_n = \mathbf{x} - \mathbf{x}_r = \begin{bmatrix} 4 \\ 3 \end{bmatrix} - \begin{bmatrix} 2 \\ 4 \end{bmatrix} = \begin{bmatrix} 2 \\ -1 \end{bmatrix}\]
검증: - \(\mathbf{x}_r + \mathbf{x}_n = (4, 3)^\top = \mathbf{x}\) ✓ - \(\mathbf{x}_r \perp \mathbf{x}_n\): \(2 \cdot 2 + 4 \cdot (-1) = 0\) ✓ - \(\mathbf{x}_n \in N(\mathbf{A})\): \(\mathbf{A}\mathbf{x}_n = \begin{bmatrix} 2-2 \\ 6-6 \end{bmatrix} = \mathbf{0}\) ✓ - \(\mathbf{A}\mathbf{x} = \mathbf{A}\mathbf{x}_r\): \(\mathbf{A}(4,3)^\top = (10, 30)^\top\), \(\mathbf{A}(2,4)^\top = (10, 30)^\top\) ✓
A = np.array([[1, 2],
[3, 6]], dtype=float)
x = np.array([4, 3], dtype=float)
# 행공간 기저 벡터
r = np.array([1, 2], dtype=float)
# 영공간 기저 벡터
n = np.array([-2, 1], dtype=float)
# 행공간 성분 (정사영)
x_r = (x @ r) / (r @ r) * r
x_n = x - x_r
print("=== 벡터 분해: x = x_r + x_n ===")
print(f"x = {x}")
print(f"x_r = {x_r} (행공간 성분)")
print(f"x_n = {x_n} (영공간 성분)")
print(f"x_r + x_n = {x_r + x_n} ✓")
print(f"\n직교 확인:")
print(f"x_r · x_n = {x_r @ x_n}") # 0
print(f"\n영공간 확인:")
print(f"A x_n = {A @ x_n}") # [0, 0]
print(f"\n열공간으로의 이동:")
print(f"A x = {A @ x}") # [10, 30]
print(f"A x_r = {A @ x_r}") # [10, 30] ✓
# 시각화
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 왼쪽: R^2 에서 벡터 분해
ax = axes[0]
vectors = {
'x': (x, 'steelblue', 'solid'),
'$x_r$ (행공간)', (x_r, 'coral', 'solid'),
'$x_n$ (영공간)': (x_n, 'green', 'dashed'),
}
ax.quiver(0, 0, x[0], x[1], angles='xy', scale_units='xy', scale=1,
color='steelblue', label=r'$\mathbf{x} = (4,3)$')
ax.quiver(0, 0, x_r[0], x_r[1], angles='xy', scale_units='xy', scale=1,
color='coral', label=r'$\mathbf{x}_r = (2,4)$ — 행공간')
ax.quiver(x_r[0], x_r[1], x_n[0], x_n[1], angles='xy', scale_units='xy', scale=1,
color='green', linestyle='dashed',
label=r'$\mathbf{x}_n = (2,-1)$ — 영공간')
# 행공간 직선 (span{(1,2)})
t = np.linspace(-1, 4, 50)
ax.plot(t, 2*t, 'coral', alpha=0.3, linestyle='--', label='행공간 = span{(1,2)}')
# 영공간 직선 (span{(-2,1)})
ax.plot(-2*t, t, 'green', alpha=0.3, linestyle=':', label='영공간 = span{(-2,1)}')
ax.axhline(0, color='k', linewidth=0.5)
ax.axvline(0, color='k', linewidth=0.5)
ax.set_xlim(-2, 5); ax.set_ylim(-2, 5)
ax.set_aspect('equal')
ax.legend(fontsize=9)
ax.set_title(r'$\mathbf{x} = \mathbf{x}_r + \mathbf{x}_n$', fontsize=12)
ax.grid(True, alpha=0.3)
# 오른쪽: A가 x를 열공간으로 보내는 과정
ax2 = axes[1]
Ax = A @ x
Ax_r = A @ x_r
Ax_n = A @ x_n
ax2.quiver(0, 0, Ax_r[0], Ax_r[1], angles='xy', scale_units='xy', scale=1,
color='coral', label=r'$\mathbf{A}\mathbf{x}_r = (10,30)$')
ax2.quiver(0, 0, Ax_n[0], Ax_n[1], angles='xy', scale_units='xy', scale=1,
color='green', label=r'$\mathbf{A}\mathbf{x}_n = (0,0)$')
ax2.quiver(0, 0, Ax[0], Ax[1], angles='xy', scale_units='xy', scale=1,
color='steelblue', linestyle='dashed', alpha=0.5,
label=r'$\mathbf{A}\mathbf{x} = (10,30)$')
ax2.axhline(0, color='k', linewidth=0.5)
ax2.axvline(0, color='k', linewidth=0.5)
ax2.set_xlim(-2, 12); ax2.set_ylim(-5, 35)
ax2.legend(fontsize=9)
ax2.set_title(r'$\mathbf{A}$ 가 $\mathbf{x}$ 를 열공간으로 보낸다', fontsize=12)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('four_subspaces_decomp.png', dpi=100, bbox_inches='tight')
plt.show()9 완전한 예시: 랭크 1 행렬
네 부분공간 모두를 명시적으로 구하고 직교성을 검증한다.
\[\mathbf{A} = \begin{bmatrix} 1 & 2 \\ 3 & 6 \end{bmatrix}\]
네 부분공간 계산:
\(\mathbf{A}\) 는 랭크 1 (\(r = 1\))이다. \(m = n = 2\).
| 부분공간 | 차원 | 기저 |
|---|---|---|
| 행공간 \(C(\mathbf{A}^\top)\) | 1 | \((1, 2)^\top\) |
| 영공간 \(N(\mathbf{A})\) | 1 | \((-2, 1)^\top\) |
| 열공간 \(C(\mathbf{A})\) | 1 | \((1, 3)^\top\) |
| 좌영공간 \(N(\mathbf{A}^\top)\) | 1 | \((-3, 1)^\top\) |
직교성 확인:
행공간 \(\perp\) 영공간: \((1,2) \cdot (-2,1) = -2 + 2 = 0\) ✓ 열공간 \(\perp\) 좌영공간: \((1,3) \cdot (-3,1) = -3 + 3 = 0\) ✓
차원 합 확인:
\(1 + 1 = 2 = n\) ✓ (행공간 + 영공간) \(1 + 1 = 2 = m\) ✓ (열공간 + 좌영공간)
A = np.array([[1, 2],
[3, 6]], dtype=float)
print("=== 랭크 1 행렬의 네 부분공간 완전 분석 ===")
print(f"A =\n{A}")
print(f"랭크: {np.linalg.matrix_rank(A)}")
# 행공간 기저: A의 독립 행
row_basis = np.array([1, 2])
# 영공간 기저: Ax = 0 특수해
null_basis = np.array([-2, 1])
# 열공간 기저: A의 독립 열
col_basis = np.array([1, 3])
# 좌영공간 기저: A^T y = 0 특수해
left_null = np.array([-3, 1])
print(f"\n행공간 기저: {row_basis}")
print(f"영공간 기저: {null_basis}")
print(f"열공간 기저: {col_basis}")
print(f"좌영공간 기저: {left_null}")
print(f"\n직교성 검증:")
print(f"행공간 · 영공간 = {row_basis @ null_basis}") # 0
print(f"열공간 · 좌영공간 = {col_basis @ left_null}") # 0
print(f"\n영공간 확인: A(-2,1)^T = {A @ null_basis}") # [0,0]
print(f"좌영공간 확인: A^T(-3,1)^T = {A.T @ left_null}") # [0,0]
# 임의 x 분해
x = np.array([5, 7], dtype=float)
x_r = (x @ row_basis) / (row_basis @ row_basis) * row_basis
x_n = x - x_r
print(f"\n임의 벡터 x = {x}")
print(f"행공간 성분 x_r = {x_r}")
print(f"영공간 성분 x_n = {x_n}")
print(f"x_r + x_n = {x_r + x_n} ✓")
print(f"Ax = {A @ x} = Ax_r = {A @ x_r} ✓")10 랭크 2 행렬 완전 예시
더 일반적인 경우를 살펴본다.
\[\mathbf{B} = \begin{bmatrix} 1 & 2 & 3 \\ 0 & 1 & 2 \\ 0 & 0 & 0 \end{bmatrix} \quad (m=3, n=3, r=2)\]
| 부분공간 | 차원 | 기저 (벡터) |
|---|---|---|
| 행공간 \(C(\mathbf{B}^\top)\) | 2 | \((1,2,3)^\top\), \((0,1,2)^\top\) |
| 영공간 \(N(\mathbf{B})\) | 1 | \((1,-2,1)^\top\) |
| 열공간 \(C(\mathbf{B})\) | 2 | \((1,0,0)^\top\), \((2,1,0)^\top\) |
| 좌영공간 \(N(\mathbf{B}^\top)\) | 1 | \((0,0,1)^\top\) |
직교 관계: - 행공간 ⊥ 영공간: \((1,2,3)\cdot(1,-2,1) = 1-4+3 = 0\) ✓, \((0,1,2)\cdot(1,-2,1) = 0-2+2 = 0\) ✓ - 열공간 ⊥ 좌영공간: \((1,0,0)\cdot(0,0,1) = 0\) ✓, \((2,1,0)\cdot(0,0,1) = 0\) ✓
B = np.array([[1, 2, 3],
[0, 1, 2],
[0, 0, 0]], dtype=float)
print("=== 랭크 2 행렬의 네 부분공간 완전 분석 ===")
print(f"B =\n{B}")
print(f"랭크 r = {np.linalg.matrix_rank(B)}")
# 영공간 기저
n_vec = np.array([1, -2, 1], dtype=float)
# 좌영공간 기저
l_vec = np.array([0, 0, 1], dtype=float)
# 행공간 기저 (피벗 행)
r1 = np.array([1, 2, 3], dtype=float)
r2 = np.array([0, 1, 2], dtype=float)
# 열공간 기저 (피벗 열)
c1 = np.array([1, 0, 0], dtype=float)
c2 = np.array([2, 1, 0], dtype=float)
print(f"\n직교성 검증 (행공간 ⊥ 영공간):")
print(f" r1 · n = {r1 @ n_vec}") # 0
print(f" r2 · n = {r2 @ n_vec}") # 0
print(f"\n직교성 검증 (열공간 ⊥ 좌영공간):")
print(f" c1 · l = {c1 @ l_vec}") # 0
print(f" c2 · l = {c2 @ l_vec}") # 0
print(f"\n차원 합:")
print(f" dim(행공간) + dim(영공간) = 2 + 1 = 3 = n ✓")
print(f" dim(열공간) + dim(좌영공간) = 2 + 1 = 3 = m ✓")
# 임의 x ∈ R^3 분해
x = np.array([3, 1, 4], dtype=float)
# 행공간으로의 투영 (Gram-Schmidt 이용)
# r2가 이미 r1에 수직이 아니므로 정규직교화 먼저
r1_norm = r1 / np.linalg.norm(r1)
r2_orth = r2 - (r2 @ r1_norm) * r1_norm
r2_norm = r2_orth / np.linalg.norm(r2_orth)
x_r = (x @ r1_norm) * r1_norm + (x @ r2_norm) * r2_norm
x_n = x - x_r
print(f"\n임의 벡터 x = {x}")
print(f"행공간 성분 x_r = {x_r}")
print(f"영공간 성분 x_n = {x_n}")
print(f"x_r · x_n = {x_r @ x_n:.2e}") # ≈ 0
print(f"B x_n = {B @ x_n}") # [0, 0, 0]
print(f"B x = {B @ x}")
print(f"B x_r = {B @ x_r}") # B x와 같아야 함11 직교성이 \(Ax = b\) 의 해가능성에 미치는 영향
11.1 Fredholm의 대안 정리
직교성은 연립방정식의 해가능성을 판별하는 강력한 도구다.
다음 두 명제 중 정확히 하나만 성립한다.
- \(\mathbf{A}\mathbf{x} = \mathbf{b}\) 는 해를 가진다 (\(\mathbf{b} \in C(\mathbf{A})\))
- \(\mathbf{A}^\top \mathbf{y} = \mathbf{0}\) 인 \(\mathbf{y}\) 가 존재하여 \(\mathbf{y}^\top \mathbf{b} = 1\) (즉 \(\mathbf{b} \notin C(\mathbf{A})\))
직관: \(\mathbf{b}\) 가 열공간 밖에 있으면, \(\mathbf{b}\) 는 좌영공간과 비자명 내적을 갖는다. \(\mathbf{y}^\top \mathbf{b} \neq 0\) 이라는 것은 \(\mathbf{b}\) 가 \(C(\mathbf{A})\) 에 수직이지 않음 — 즉 \(C(\mathbf{A})\) 밖에 있음을 뜻한다.
예시: \(\mathbf{A}\mathbf{x} = \mathbf{b}\) 가 해가 없는 경우
\[\begin{aligned} x + 2y + 2z &= 5 \\ 2x + 2y + 3z &= 5 \\ 3x + 4y + 5z &= 9 \end{aligned}\]
세 번째 방정식은 첫 번째와 두 번째의 합이어야 하는데, \(5 + 5 = 10 \neq 9\). \(\mathbf{y} = (1, 1, -1)^\top\) 을 취하면: \(\mathbf{A}^\top \mathbf{y} = \mathbf{0}\) 이고 \(\mathbf{y}^\top \mathbf{b} = 5 + 5 - 9 = 1 \neq 0\).
이 \(\mathbf{y}\) 는 좌영공간에 속하며, \(\mathbf{b}\) 와의 내적이 0이 아니므로 해가 없다.
# Fredholm의 대안 수치 확인
A_fh = np.array([[1, 2, 2],
[2, 2, 3],
[3, 4, 5]], dtype=float)
b_fh = np.array([5, 5, 9], dtype=float)
# 해 존재 여부 (랭크 비교)
rank_A = np.linalg.matrix_rank(A_fh)
aug = np.column_stack([A_fh, b_fh])
rank_aug = np.linalg.matrix_rank(aug)
print(f"rank(A) = {rank_A}, rank([A|b]) = {rank_aug}")
print(f"해 존재: {rank_A == rank_aug}") # False → 해 없음
# y가 좌영공간에 속하는지 확인
y_fh = np.array([1, 1, -1], dtype=float)
print(f"\ny = {y_fh}")
print(f"A^T y = {A_fh.T @ y_fh}") # [0, 0, 0] — 좌영공간 확인
print(f"y^T b = {y_fh @ b_fh}") # 1 ≠ 0 → 해 없음 확인12 응용: \(A^\top A\) 의 성질
§4.3 최소제곱법에서 핵심 역할을 하는 \(\mathbf{A}^\top \mathbf{A}\) 의 성질을 직교성으로 이해한다.
12.1 \(N(\mathbf{A}^\top \mathbf{A}) = N(\mathbf{A})\)
\(\mathbf{A}^\top \mathbf{A} \mathbf{x} = \mathbf{0}\) 이면 \(\mathbf{A}\mathbf{x} = \mathbf{0}\) 이다.
증명: \(\mathbf{A}^\top \mathbf{A} \mathbf{x} = \mathbf{0}\) 양변에 \(\mathbf{x}^\top\) 을 곱하면:
\[\mathbf{x}^\top \mathbf{A}^\top \mathbf{A} \mathbf{x} = (\mathbf{A}\mathbf{x})^\top (\mathbf{A}\mathbf{x}) = \|\mathbf{A}\mathbf{x}\|^2 = 0\]
\(\|\mathbf{A}\mathbf{x}\|^2 = 0\) 이면 \(\mathbf{A}\mathbf{x} = \mathbf{0}\) 이다.
직관: \(\mathbf{A}\mathbf{x}\) 는 열공간에 속한다. \(\mathbf{A}^\top \mathbf{A} \mathbf{x} = \mathbf{A}^\top (\mathbf{A}\mathbf{x}) = \mathbf{0}\) 이면, \(\mathbf{A}\mathbf{x}\) 는 좌영공간에도 속한다. 열공간과 좌영공간은 직교하므로 \(\mathbf{A}\mathbf{x} = \mathbf{0}\).
결론: \(\mathbf{A}\) 의 열이 선형 독립이면 \(N(\mathbf{A}) = \{\mathbf{0}\}\) 이고 \(N(\mathbf{A}^\top \mathbf{A}) = \{\mathbf{0}\}\). 따라서 \(\mathbf{A}^\top \mathbf{A}\) 는 가역 — 최소제곱법의 정규방정식을 풀 수 있다.
# A^T A의 영공간 = A의 영공간 수치 확인
A = np.array([[1, 2],
[3, 6]], dtype=float) # rank 1
ATA = A.T @ A
print("A^T A =")
print(ATA)
print(f"\nrank(A) = {np.linalg.matrix_rank(A)}")
print(f"rank(A^T A) = {np.linalg.matrix_rank(ATA)}") # 같음
# 영공간 기저 확인
x_null = np.array([-2, 1], dtype=float)
print(f"\nN(A) 기저: {x_null}")
print(f"A x_null = {A @ x_null}") # [0, 0]
print(f"A^T A x_null = {ATA @ x_null}") # [0, 0] ✓
# 독립 열을 가진 행렬의 경우
C = np.array([[1, 0],
[0, 1],
[1, 1]], dtype=float) # rank 2, 열 독립
CTC = C.T @ C
print(f"\nC^T C =\n{CTC}")
print(f"rank(C^T C) = {np.linalg.matrix_rank(CTC)}") # 2 → 가역
print(f"det(C^T C) = {np.linalg.det(CTC):.2f}") # > 0 → 양정치13 핵심 요약
행렬 A (m×n, 랭크 r)
↓
R^n = 행공간(차원 r) ⊕ 영공간(차원 n-r)
↑ ↑
직교 보공간 쌍 (Part 2)
R^m = 열공간(차원 r) ⊕ 좌영공간(차원 m-r)
↑ ↑
직교 보공간 쌍 (Part 2)
Ax = 0 → x ⊥ 모든 행 → N(A) ⊥ C(A^T)
A^Ty = 0 → y ⊥ 모든 열 → N(A^T) ⊥ C(A)
x = x_r + x_n (행공간 + 영공간으로 유일 분해)
Ax = Ax_r (영공간 성분은 사라짐)
행공간 → 열공간 매핑은 일대일 (숨겨진 r×r 가역 행렬)
14 관련 주제
선행 지식
- Ch.4 Overview — 직교성 개요 — 챕터 전체 로드맵
- Ch.3 §3.6 — 네 부분공간의 차원 — Part 1 (차원 관계)
동일 챕터 후속
- Ch.4 §4.2 — Projections — 투영 공식으로 \(\mathbf{x}_r\) 구하기
- Ch.4 §4.3 — Least Squares Approximations — 정규방정식 \(\mathbf{A}^\top\mathbf{A}\hat{\mathbf{x}} = \mathbf{A}^\top\mathbf{b}\)
- Ch.4 §4.4 — Orthogonal Bases and Gram-Schmidt — 직교 기저 구성
응용 연결
- SVD (예정) — 행공간·열공간의 직교 기저를 한 번에 구성하는 최종 도구
- 최소제곱 회귀 — 직교성이 통계에서 잔차의 수직 조건으로 나타남