Ch.4 §4.1 — Orthogonality of the Four Subspaces

행공간·영공간·열공간·좌영공간이 만드는 두 쌍의 직교 구조 — 선형대수 기본정리 Part 2

행렬의 네 부분공간이 서로 수직인 이유를 두 가지 방식으로 증명하고, ’직교’와 ’직교 보공간’의 차이를 차원 관점에서 명확히 한다. 모든 벡터가 행공간 성분과 영공간 성분으로 유일하게 분해되며, A는 행공간에서 열공간으로 일대일 대응을 이룬다는 사실을 직관·수식·코드로 상세히 정리한다.

Math
Linear Algebra
저자

Kwangmin Kim

공개

2026년 04월 10일

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 직교 보공간의 정의

정의: 직교 보공간 (Orthogonal Complement)

부분공간 \(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}")  # ≈ 0

7 선형대수 기본정리 Part 2

이제 두 정리를 공식으로 정리한다.

선형대수 기본정리 Part 2 (Fundamental Theorem of Linear Algebra, 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의 대안 정리

직교성은 연립방정식의 해가능성을 판별하는 강력한 도구다.

Fredholm의 대안 (Fredholm’s Alternative)

다음 두 명제 중 정확히 하나만 성립한다.

  1. \(\mathbf{A}\mathbf{x} = \mathbf{b}\) 는 해를 가진다 (\(\mathbf{b} \in C(\mathbf{A})\))
  2. \(\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 관련 주제

선행 지식

동일 챕터 후속

응용 연결

  • SVD (예정) — 행공간·열공간의 직교 기저를 한 번에 구성하는 최종 도구
  • 최소제곱 회귀 — 직교성이 통계에서 잔차의 수직 조건으로 나타남

Subscribe

Enjoy this blog? Get notified of new posts by email: