Ch.4 §4.4 — Orthogonal Bases and Gram-Schmidt (정규직교 기저와 그람-슈미트)

정규직교 행렬 Q, 투영의 단순화, Gram-Schmidt 과정, A=QR 분해

직교에 “단위 길이” 조건을 더한 정규직교(orthonormal) 기저가 왜 “계산의 천국”인지 보인다. \(Q^\top Q = I\) 가 만드는 투영 공식의 단순화, 길이·각도 보존, Gram-Schmidt 과정의 기하학적 해석, \(A = QR\) 분해 유도, 최소제곱법이 후치환 한 번으로 풀리는 과정까지 직관과 수식으로 정리한다.

Math
Linear Algebra
저자

Kwangmin Kim

공개

2026년 04월 10일

1 이 절의 위치

Ch.4의 마지막 절이다. 앞 절들이 “왜”를 다뤘다면, §4.4는 “어떻게 효율적으로”를 다룬다.

§4.1 네 부분공간의 직교성 — 왜 열공간에 투영할 수 있는가
  ↓
§4.2 투영 — p = A(AᵀA)⁻¹Aᵀb
  ↓
§4.3 최소제곱법 — AᵀAx̂ = Aᵀb (정규방정식)
  ↓  "그런데 AᵀA의 역행렬 계산이 비싸고 수치적으로 불안정하다"
§4.4 Gram-Schmidt & QR 분해 ← 지금 여기

§4.4의 핵심 메시지는 단 하나다.

기저가 정규직교(orthonormal)하면, \(\mathbf{A}^\top \mathbf{A} = \mathbf{I}\) 가 되어 모든 공식이 극적으로 단순해진다.

그리고 어떤 선형 독립 기저라도 Gram-Schmidt로 정규직교 기저로 변환할 수 있다.


2 정규직교의 정의

2.1 직교 + 단위 길이

정의: 정규직교 (Orthonormal) 벡터

벡터 \(\mathbf{q}_1, \ldots, \mathbf{q}_n\) 이 정규직교하다 ⟺ 다음 두 조건을 모두 만족한다.

\[\mathbf{q}_i^\top \mathbf{q}_j = \begin{cases} 0 & i \neq j \quad \text{(직교)} \\ 1 & i = j \quad \text{(단위 길이)} \end{cases}\]

크로네커 델타 기호로 쓰면: \(\mathbf{q}_i^\top \mathbf{q}_j = \delta_{ij}\)

직관: “직교 기저(orthogonal basis)”는 서로 수직인 막대 \(n\) 개, “정규직교 기저(orthonormal basis)”는 그 막대들을 모두 길이 1로 맞춘 것. 표준 기저 \(\mathbf{e}_1, \mathbf{e}_2, \ldots, \mathbf{e}_n\) 이 가장 익숙한 예시다.

2.2 왜 굳이 “단위 길이”를 요구하는가

직교만으로도 많은 것이 된다. 그러나 단위 길이까지 확보하면 분모에 나타나는 \(\|\mathbf{q}_i\|^2\) 항이 모두 1이 되어 사라진다. 이 “0.1cm의 차이”가 실제 계산에서는 “1톤의 단순화”를 만든다.


3 행렬 \(\mathbf{Q}\) 의 정의

3.1 \(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}\)

정규직교 벡터들을 열로 가진 행렬을 \(\mathbf{Q}\) 라 부른다.

\[\mathbf{Q} = \begin{bmatrix} | & | & & | \\ \mathbf{q}_1 & \mathbf{q}_2 & \cdots & \mathbf{q}_n \\ | & | & & | \end{bmatrix}\]

\(\mathbf{Q}^\top \mathbf{Q}\)\((i, j)\) 성분은 \(\mathbf{q}_i^\top \mathbf{q}_j = \delta_{ij}\) 이므로:

\[\boxed{\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}_n}\]

주의: \(\mathbf{Q}\) 가 직사각 (\(m > n\))이면 \(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}_n\) 은 성립하지만 \(\mathbf{Q}\mathbf{Q}^\top \neq \mathbf{I}_m\) 이다. \(\mathbf{Q}^\top\)왼쪽 역원일 뿐이다.

3.2 정사각 \(\mathbf{Q}\): “직교 행렬(orthogonal matrix)”

\(\mathbf{Q}\)\(n \times n\) 정사각 행렬이고 \(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}\) 이면, \(\mathbf{Q}^\top = \mathbf{Q}^{-1}\) 이다. 그러면 \(\mathbf{Q}\mathbf{Q}^\top = \mathbf{I}\) 도 자동으로 성립한다.

용어 주의

수학 용어의 역사적 실수: “직교 행렬(orthogonal matrix)”이라고 부르지만, 실제로는 정규직교 열을 가진 정사각 행렬이다. “정규직교 행렬(orthonormal matrix)”이 더 정확한 이름이었겠지만, 전통을 따라 “직교 행렬”로 부른다. 정사각이 아닌 \(\mathbf{Q}\) 는 “직교 행렬”이라 부르지 않는다 — 단지 “정규직교 열을 가진 행렬”이라고 한다.


4 직교 행렬의 대표 예시

4.1 예시 1: 회전 행렬

\[\mathbf{Q} = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}\]

검증: - 열 1 길이: \(\cos^2\theta + \sin^2\theta = 1\) ✓ - 열 2 길이: \(\sin^2\theta + \cos^2\theta = 1\) ✓ - 두 열 내적: \(-\cos\theta\sin\theta + \sin\theta\cos\theta = 0\)

직관: 회전은 “단순한 좌표계 이동”이다. 모든 벡터가 같은 각도만큼 돌아간다. 길이가 변하지 않는 것은 당연하고, 이것이 \(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}\) 의 기하적 의미이다.

\(\mathbf{Q}^{-1}\)\(-\theta\) 만큼 회전. \(\cos(-\theta) = \cos\theta\), \(\sin(-\theta) = -\sin\theta\) 이므로 \(\mathbf{Q}^{-1} = \mathbf{Q}^\top\) 이 자동 성립.

4.2 예시 2: 치환 행렬

\[\mathbf{P} = \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}\]

각 열이 단위 벡터 \(\mathbf{e}_i\) 이므로 모두 길이 1. 서로 다른 단위 벡터는 직교. 따라서 모든 치환 행렬은 직교 행렬이다.

직관: 치환은 “성분의 순서만 바꾸는” 변환이므로 길이와 각도가 보존된다. 당연히 직교.

4.3 예시 3: 반사 행렬 (하우스홀더 반사)

단위 벡터 \(\mathbf{u}\) 에 대해 \(\mathbf{Q} = \mathbf{I} - 2\mathbf{u}\mathbf{u}^\top\) 로 정의되는 반사 행렬.

검증:

\[\mathbf{Q}^\top \mathbf{Q} = (\mathbf{I} - 2\mathbf{u}\mathbf{u}^\top)(\mathbf{I} - 2\mathbf{u}\mathbf{u}^\top) = \mathbf{I} - 4\mathbf{u}\mathbf{u}^\top + 4\mathbf{u}(\mathbf{u}^\top \mathbf{u})\mathbf{u}^\top\]

\(\mathbf{u}\) 가 단위 벡터이므로 \(\mathbf{u}^\top \mathbf{u} = 1\):

\[\mathbf{Q}^\top \mathbf{Q} = \mathbf{I} - 4\mathbf{u}\mathbf{u}^\top + 4\mathbf{u}\mathbf{u}^\top = \mathbf{I}\]

직관: 거울에 대한 반사. 두 번 반사하면 원래대로 돌아온다 (\(\mathbf{Q}^2 = \mathbf{I}\)). \(\mathbf{Q} = \mathbf{Q}^\top = \mathbf{Q}^{-1}\) 이 한꺼번에 성립하는 특별한 직교 행렬.

이 반사 행렬이 수치적으로 가장 안정적인 QR 분해의 도구가 된다 (§ 뒤에서 언급).


5 직교 행렬의 기하학적 성질

5.1 길이 보존

\(\mathbf{Q}\) 가 정규직교 열을 가지면:

\[\|\mathbf{Q}\mathbf{x}\|^2 = (\mathbf{Q}\mathbf{x})^\top (\mathbf{Q}\mathbf{x}) = \mathbf{x}^\top \mathbf{Q}^\top \mathbf{Q} \mathbf{x} = \mathbf{x}^\top \mathbf{I} \mathbf{x} = \mathbf{x}^\top \mathbf{x} = \|\mathbf{x}\|^2\]

\[\boxed{\|\mathbf{Q}\mathbf{x}\| = \|\mathbf{x}\|}\]

직관: 직교 변환은 “자(ruler)를 돌리거나 뒤집는 것”이다. 자의 눈금은 변하지 않는다.

5.2 각도 보존 (내적 보존)

\[(\mathbf{Q}\mathbf{x})^\top (\mathbf{Q}\mathbf{y}) = \mathbf{x}^\top \mathbf{Q}^\top \mathbf{Q} \mathbf{y} = \mathbf{x}^\top \mathbf{y}\]

내적이 보존되므로 \(\cos\theta = \mathbf{x}^\top\mathbf{y} / (\|\mathbf{x}\|\|\mathbf{y}\|)\) 도 보존. 두 벡터 사이의 각도가 그대로다.

의미: 직교 변환은 강체 운동(rigid motion) 이다. 도형을 구부리거나 늘이지 않고, 단지 회전/반사/조합만 한다.

5.3 수치 안정성

“길이를 늘리지 않는다”는 성질이 수치 계산에서 결정적이다. 행렬을 반복해서 곱할 때 일반 행렬은 오차가 지수적으로 증폭될 수 있지만, \(\mathbf{Q}\) 를 곱하는 연산은 오차를 증폭시키지 않는다.

LAPACK, BLAS 같은 수치 라이브러리가 가능한 한 직교 행렬로 연산을 대체하는 이유다.


6 투영 공식의 단순화: \(\mathbf{A}\) 대신 \(\mathbf{Q}\)

여기가 §4.4의 심장부다. §4.2에서 유도한 투영 공식에 \(\mathbf{A} \to \mathbf{Q}\) 를 대입한다.

6.1 일반 공식 (§4.2 복습)

공식
최소제곱 해 \(\hat{\mathbf{x}} = (\mathbf{A}^\top \mathbf{A})^{-1} \mathbf{A}^\top \mathbf{b}\)
투영 벡터 \(\mathbf{p} = \mathbf{A}(\mathbf{A}^\top \mathbf{A})^{-1} \mathbf{A}^\top \mathbf{b}\)
투영 행렬 \(\mathbf{P} = \mathbf{A}(\mathbf{A}^\top \mathbf{A})^{-1} \mathbf{A}^\top\)

6.2 \(\mathbf{Q}\) 를 대입

\(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}\) 이므로 \((\mathbf{Q}^\top \mathbf{Q})^{-1} = \mathbf{I}\) 이고, 역행렬 계산이 완전히 사라진다.

공식
최소제곱 해 \(\hat{\mathbf{x}} = \mathbf{Q}^\top \mathbf{b}\)
투영 벡터 \(\mathbf{p} = \mathbf{Q}\mathbf{Q}^\top \mathbf{b}\)
투영 행렬 \(\mathbf{P} = \mathbf{Q}\mathbf{Q}^\top\)

세 번 강조:

\[\boxed{\hat{\mathbf{x}} = \mathbf{Q}^\top \mathbf{b}, \quad \mathbf{p} = \mathbf{Q}\mathbf{Q}^\top \mathbf{b}, \quad \mathbf{P} = \mathbf{Q}\mathbf{Q}^\top}\]

6.3 “n 개의 독립적인 1차원 투영”

\(\hat{\mathbf{x}} = \mathbf{Q}^\top \mathbf{b}\) 의 의미를 풀어쓰면:

\[\hat{x}_i = \mathbf{q}_i^\top \mathbf{b}\]

\(i\) 번째 좌표는 단순히 \(\mathbf{b}\)\(\mathbf{q}_i\) 의 내적이다. 투영 벡터는:

\[\mathbf{p} = \mathbf{Q}\hat{\mathbf{x}} = \sum_{i=1}^n \hat{x}_i \mathbf{q}_i = \sum_{i=1}^n (\mathbf{q}_i^\top \mathbf{b}) \mathbf{q}_i\]

직관: \(\mathbf{b}\) 를 각 \(\mathbf{q}_i\) 방향으로 독립적으로 투영한 뒤 그 결과들을 단순히 더한다. 각 방향의 투영이 서로 간섭하지 않는다 (coupling-free).

일반 기저였다면 \(\mathbf{A}^\top \mathbf{A}\) 의 비대각 원소가 “다른 방향과의 간섭”을 표현했다. 정규직교 기저에서는 \(\mathbf{Q}^\top \mathbf{Q} = \mathbf{I}\) 이므로 비대각 원소가 모두 0이다. 즉 기저 벡터들이 정보를 중복하지 않고 완전히 분리된 방향으로 기여한다.

6.4 정사각 \(\mathbf{Q}\): 전체 공간 재구성

\(\mathbf{Q}\) 가 정사각 (\(m = n\))이면 \(C(\mathbf{Q}) = \mathbb{R}^n\) 이고, \(\mathbf{b}\) 를 전체 공간에 투영하는 것이므로 \(\mathbf{p} = \mathbf{b}\). 그러면:

\[\mathbf{b} = (\mathbf{q}_1^\top \mathbf{b})\mathbf{q}_1 + (\mathbf{q}_2^\top \mathbf{b})\mathbf{q}_2 + \cdots + (\mathbf{q}_n^\top \mathbf{b})\mathbf{q}_n\]

이것이 푸리에 급수의 일반화 형태다. 함수를 sin/cos 기저로 분해하는 것은 무한 차원 벡터 공간에서의 위 공식과 같다. 각 “계수”는 함수와 기저의 내적으로 구해진다. 디지털 신호의 FFT, 이미지의 DCT, 웨이블릿 변환 — 모두 같은 아이디어의 변주다.

import numpy as np
import matplotlib.pyplot as plt

# Strang 교재 예시 4
Q = np.array([[-1, 2, 2],
              [2, -1, 2],
              [2, 2, -1]], dtype=float) / 3

print("Q =")
print(Q)
print(f"\nQᵀQ =\n{Q.T @ Q}")   # 거의 단위행렬
print(f"\nQQᵀ =\n{Q @ Q.T}")   # 거의 단위행렬 (정사각이므로)

# b = (0, 0, 1)를 세 개의 1차원 투영으로 분해
b = np.array([0, 0, 1], dtype=float)

# 각 qᵢ 방향 성분
for i in range(3):
    q_i = Q[:, i]
    coeff = q_i @ b
    p_i = coeff * q_i
    print(f"\nq{i+1}ᵀ b = {coeff:.4f}")
    print(f"{coeff:.4f} * q{i+1} = {p_i}")

# 모두 더하면 b 복원
x_hat = Q.T @ b
reconstruction = Q @ x_hat
print(f"\n재구성 p = QQᵀb = {reconstruction}")
print(f"원래 b      = {b}")
print(f"(정사각 Q → p = b ✓)")

7 Gram-Schmidt 과정: 직교화 알고리즘

7.1 질문

임의의 선형 독립 벡터 \(\mathbf{a}, \mathbf{b}, \mathbf{c}\) 가 주어졌을 때, 같은 부분공간을 스팬하는 정규직교 벡터 \(\mathbf{q}_1, \mathbf{q}_2, \mathbf{q}_3\) 를 어떻게 만드는가?

7.2 핵심 아이디어

“새 벡터에서 이미 확정된 방향 성분을 빼면, 그 방향에 수직인 부분만 남는다.”

이것이 Gram-Schmidt의 모든 것이다. 한 번에 한 방향씩 직교화한다.

7.3 Step 1: 첫 벡터 그대로

\(\mathbf{A} = \mathbf{a}\) (방향 선택은 자유로우므로 \(\mathbf{a}\) 를 그대로 쓴다.)

7.4 Step 2: 두 번째 벡터에서 \(\mathbf{A}\) 성분 제거

\(\mathbf{b}\) 에서 \(\mathbf{A}\) 방향 투영 성분을 빼면 \(\mathbf{A}\) 에 수직인 부분만 남는다.

\[\mathbf{B} = \mathbf{b} - \frac{\mathbf{A}^\top \mathbf{b}}{\mathbf{A}^\top \mathbf{A}} \mathbf{A}\]

수직성 검증: \(\mathbf{A}^\top \mathbf{B} = \mathbf{A}^\top \mathbf{b} - \frac{\mathbf{A}^\top \mathbf{b}}{\mathbf{A}^\top \mathbf{A}}\mathbf{A}^\top \mathbf{A} = \mathbf{A}^\top \mathbf{b} - \mathbf{A}^\top \mathbf{b} = 0\)

직관: 이것은 §4.2에서 본 “직선 위의 투영”의 나머지 = 수직 부분을 구하는 과정. \(\mathbf{B}\) 는 “\(\mathbf{b}\) 중에서 \(\mathbf{A}\) 와 수직인 부분”이다.

\(\mathbf{B}\) 는 영벡터가 아닌가: 만약 \(\mathbf{B} = \mathbf{0}\) 이라면 \(\mathbf{b}\)\(\mathbf{A}\) 의 스칼라 배수 — 즉 \(\mathbf{a}\) 와 종속이다. 입력 벡터들이 선형 독립이라는 전제에 모순.

7.5 Step 3: 세 번째 벡터에서 \(\mathbf{A}, \mathbf{B}\) 두 성분 제거

\(\mathbf{c}\) 에서 \(\mathbf{A}\) 방향과 \(\mathbf{B}\) 방향 성분을 모두 뺀다.

\[\mathbf{C} = \mathbf{c} - \frac{\mathbf{A}^\top \mathbf{c}}{\mathbf{A}^\top \mathbf{A}} \mathbf{A} - \frac{\mathbf{B}^\top \mathbf{c}}{\mathbf{B}^\top \mathbf{B}} \mathbf{B}\]

수직성 검증:

\[\mathbf{A}^\top \mathbf{C} = \mathbf{A}^\top \mathbf{c} - \frac{\mathbf{A}^\top \mathbf{c}}{\mathbf{A}^\top \mathbf{A}}\mathbf{A}^\top \mathbf{A} - \frac{\mathbf{B}^\top \mathbf{c}}{\mathbf{B}^\top \mathbf{B}}\underbrace{\mathbf{A}^\top \mathbf{B}}_{=0} = 0\]

(마지막 항이 0인 이유는 Step 2에서 \(\mathbf{B}\) 가 이미 \(\mathbf{A}\) 에 수직이도록 만들었기 때문이다.) \(\mathbf{B}^\top \mathbf{C} = 0\) 도 같은 방식으로 확인 가능.

7.6 Step 4: 정규화

마지막으로 각 직교 벡터를 길이로 나누어 단위 벡터로 만든다.

\[\mathbf{q}_1 = \frac{\mathbf{A}}{\|\mathbf{A}\|}, \quad \mathbf{q}_2 = \frac{\mathbf{B}}{\|\mathbf{B}\|}, \quad \mathbf{q}_3 = \frac{\mathbf{C}}{\|\mathbf{C}\|}\]

왜 마지막에 정규화하는가: 중간 단계에서 정규화하면 나중의 투영 계산이 “나누어떨어지지 않는 수”로 가득찬다. 마지막까지 \(\mathbf{A}, \mathbf{B}, \mathbf{C}\) 형태를 유지하면 계산이 더 깔끔하다.

7.7 기하학적 그림

Step 1:  A = a (그대로)
Step 2:  b를 A에 투영 → 뺀 것이 B (A에 수직)
Step 3:  c를 A에 투영하고 B에도 투영 → 둘 다 뺀 것이 C (A, B 둘 다에 수직)
Step 4:  A, B, C를 각각 단위화 → q₁, q₂, q₃

8 Gram-Schmidt 예시

Strang 교재의 고전 예시 (Ch.4 Example 5):

\[\mathbf{a} = \begin{bmatrix} 1 \\ -1 \\ 0 \end{bmatrix}, \quad \mathbf{b} = \begin{bmatrix} 2 \\ 0 \\ -2 \end{bmatrix}, \quad \mathbf{c} = \begin{bmatrix} 3 \\ -3 \\ 3 \end{bmatrix}\]

8.1 Step 1: \(\mathbf{A} = \mathbf{a}\)

\(\mathbf{A}^\top \mathbf{A} = 1 + 1 + 0 = 2\)

8.2 Step 2: \(\mathbf{B}\) 계산

\(\mathbf{A}^\top \mathbf{b} = 1 \cdot 2 + (-1) \cdot 0 + 0 \cdot (-2) = 2\)

\[\mathbf{B} = \mathbf{b} - \frac{2}{2}\mathbf{A} = \begin{bmatrix} 2 \\ 0 \\ -2 \end{bmatrix} - \begin{bmatrix} 1 \\ -1 \\ 0 \end{bmatrix} = \begin{bmatrix} 1 \\ 1 \\ -2 \end{bmatrix}\]

검증: \(\mathbf{A}^\top \mathbf{B} = 1 - 1 + 0 = 0\) ✓, \(\mathbf{B}^\top \mathbf{B} = 1 + 1 + 4 = 6\)

8.3 Step 3: \(\mathbf{C}\) 계산

\(\mathbf{A}^\top \mathbf{c} = 3 + 3 + 0 = 6\), \(\mathbf{B}^\top \mathbf{c} = 3 - 3 - 6 = -6\)

\[\mathbf{C} = \mathbf{c} - \frac{6}{2}\mathbf{A} - \frac{-6}{6}\mathbf{B} = \mathbf{c} - 3\mathbf{A} + \mathbf{B}\]

\[= \begin{bmatrix} 3 \\ -3 \\ 3 \end{bmatrix} - \begin{bmatrix} 3 \\ -3 \\ 0 \end{bmatrix} + \begin{bmatrix} 1 \\ 1 \\ -2 \end{bmatrix} = \begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix}\]

검증: - \(\mathbf{A}^\top \mathbf{C} = 1 - 1 + 0 = 0\) ✓ - \(\mathbf{B}^\top \mathbf{C} = 1 + 1 - 2 = 0\)

8.4 Step 4: 정규화

  • \(\|\mathbf{A}\| = \sqrt{2}\), \(\|\mathbf{B}\| = \sqrt{6}\), \(\|\mathbf{C}\| = \sqrt{3}\)

\[\mathbf{q}_1 = \frac{1}{\sqrt{2}}\begin{bmatrix} 1 \\ -1 \\ 0 \end{bmatrix}, \quad \mathbf{q}_2 = \frac{1}{\sqrt{6}}\begin{bmatrix} 1 \\ 1 \\ -2 \end{bmatrix}, \quad \mathbf{q}_3 = \frac{1}{\sqrt{3}}\begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix}\]

주의: 원래 벡터들 \(\mathbf{a}, \mathbf{b}, \mathbf{c}\) 는 정수로 깔끔했지만, 직교 벡터 \(\mathbf{A}, \mathbf{B}, \mathbf{C}\) 도 우연히 깔끔했다. 하지만 정규화하면 거의 항상 제곱근이 등장한다. 이것이 Gram-Schmidt의 “작은 흠”이다.

import numpy as np

# Strang 예시 5
a = np.array([1, -1, 0], dtype=float)
b_vec = np.array([2, 0, -2], dtype=float)
c = np.array([3, -3, 3], dtype=float)

# Gram-Schmidt 수동 실행
A = a.copy()
B = b_vec - (A @ b_vec) / (A @ A) * A
C = c - (A @ c) / (A @ A) * A - (B @ c) / (B @ B) * B

print(f"A = {A}")
print(f"B = {B}")
print(f"C = {C}")

# 직교성 확인
print(f"\nA·B = {A @ B}")  # 0
print(f"A·C = {A @ C}")  # 0
print(f"B·C = {B @ C}")  # 0

# 정규화
q1 = A / np.linalg.norm(A)
q2 = B / np.linalg.norm(B)
q3 = C / np.linalg.norm(C)

print(f"\nq1 = {q1}  (‖q1‖ = {np.linalg.norm(q1):.4f})")
print(f"q2 = {q2}  (‖q2‖ = {np.linalg.norm(q2):.4f})")
print(f"q3 = {q3}  (‖q3‖ = {np.linalg.norm(q3):.4f})")

# Q 행렬 구성 및 QᵀQ = I 확인
Q = np.column_stack([q1, q2, q3])
print(f"\nQᵀQ =\n{np.round(Q.T @ Q, 10)}")

9 일반 Gram-Schmidt 공식

\(k\) 번째 단계에서, 새 벡터 \(\mathbf{a}_k\) 에서 이전에 확정된 모든 방향 \(\mathbf{q}_1, \ldots, \mathbf{q}_{k-1}\) 의 성분을 뺀다.

\[\mathbf{u}_k = \mathbf{a}_k - \sum_{j=1}^{k-1} (\mathbf{q}_j^\top \mathbf{a}_k) \mathbf{q}_j\]

\[\mathbf{q}_k = \frac{\mathbf{u}_k}{\|\mathbf{u}_k\|}\]

모든 단계에서 같은 아이디어 반복: 그동안 쌓은 직교 기저에 대한 투영을 모두 빼면, 나머지가 새로운 직교 방향. 이것이 수학적 귀납법처럼 작동한다 — “\(k-1\) 단계까지 직교 기저가 있다” ⟹ “\(k\) 단계의 \(\mathbf{q}_k\) 도 직교”이다.

9.1 Modified Gram-Schmidt (수치 안정성)

위 공식은 “한 번에 모든 투영을 뺀다” (Classical Gram-Schmidt). 실제 수치 계산에서는 “한 번에 하나씩 투영을 뺀다” 가 더 안정적이다.

k 번째 단계, 수정된 방식:
  u = a_k
  for j = 1 to k-1:
    u = u - (q_j^T u) q_j  ← 누적된 u에서 투영을 뺀다 (원래 a_k가 아니라)
  q_k = u / ‖u‖

차이점: Classical은 모두 \(\mathbf{a}_k\) 기준으로 투영을 계산한다. Modified는 “그 전 단계까지 직교화된 결과” 기준으로 투영을 계산한다. 수학적으로는 같지만, 부동소수점 오차가 축적되는 양상이 다르다. Modified 방식이 약간 더 안정적이다.


10 \(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 분해

10.1 동기: “어떻게 \(\mathbf{A}\)\(\mathbf{Q}\) 가 연결되는가”

Gram-Schmidt 시작: \(\mathbf{A}\) 의 열 \(\mathbf{a}_1, \mathbf{a}_2, \ldots, \mathbf{a}_n\). Gram-Schmidt 결과: 정규직교 벡터 \(\mathbf{q}_1, \mathbf{q}_2, \ldots, \mathbf{q}_n\).

두 행렬 사이에 어떤 관계가 있어야 한다. 그 관계가 \(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 이고, 여기서 \(\mathbf{R}\)상삼각 행렬이다.

10.2\(\mathbf{R}\) 이 상삼각인가

Gram-Schmidt의 결정적 성질 — “나중 \(\mathbf{q}\) 는 이전 \(\mathbf{a}\) 에 관여하지 않는다”:

  • \(\mathbf{a}_1\)\(\mathbf{q}_1\) 만의 배수
  • \(\mathbf{a}_2\)\(\mathbf{q}_1, \mathbf{q}_2\) 의 조합 (단, \(\mathbf{q}_3, \mathbf{q}_4, \ldots\) 는 쓰이지 않음)
  • \(\mathbf{a}_k\)\(\mathbf{q}_1, \ldots, \mathbf{q}_k\) 의 조합 (단, \(\mathbf{q}_{k+1}, \ldots\) 는 쓰이지 않음)

이것을 행렬 곱 형태로 쓰면:

\[\mathbf{A} = \mathbf{Q}\mathbf{R} = \begin{bmatrix} | & | & & | \\ \mathbf{q}_1 & \mathbf{q}_2 & \cdots & \mathbf{q}_n \\ | & | & & | \end{bmatrix} \begin{bmatrix} r_{11} & r_{12} & \cdots & r_{1n} \\ 0 & r_{22} & \cdots & r_{2n} \\ \vdots & & \ddots & \vdots \\ 0 & 0 & \cdots & r_{nn} \end{bmatrix}\]

\(\mathbf{R}\)\(k\) 번째 열은 “\(\mathbf{a}_k\)\(\mathbf{q}_1, \ldots, \mathbf{q}_k\) 로 표현한 계수들”이고, \(k\) 번째 행 이후는 모두 0이다. 이것이 \(\mathbf{R}\) 이 상삼각이 되는 기하학적 이유다.

10.3 \(\mathbf{R}\) 의 성분 계산

\(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 양변에 \(\mathbf{Q}^\top\) 을 왼쪽에서 곱하면 \(\mathbf{Q}^\top \mathbf{A} = \mathbf{Q}^\top \mathbf{Q} \mathbf{R} = \mathbf{R}\).

\[\boxed{\mathbf{R} = \mathbf{Q}^\top \mathbf{A}}\]

성분별로: \(r_{ij} = \mathbf{q}_i^\top \mathbf{a}_j\). \(i > j\) 이면 \(\mathbf{q}_i\)\(\mathbf{a}_j\) 가 만들어낸 부분공간 \(\text{span}\{\mathbf{a}_1, \ldots, \mathbf{a}_j\}\) 에 수직이므로 \(r_{ij} = 0\) — 이것이 상삼각 구조.

10.4 구체 예시 (위 Strang 예시 계속)

\(\mathbf{A} = [\mathbf{a} \mid \mathbf{b} \mid \mathbf{c}]\) 의 QR 분해는:

\[\mathbf{A} = \begin{bmatrix} 1 & 2 & 3 \\ -1 & 0 & -3 \\ 0 & -2 & 3 \end{bmatrix}\]

\(\mathbf{R} = \mathbf{Q}^\top \mathbf{A}\) 성분별 계산:

  • \(r_{11} = \mathbf{q}_1^\top \mathbf{a} = \|\mathbf{A}\| = \sqrt{2}\)
  • \(r_{12} = \mathbf{q}_1^\top \mathbf{b} = \sqrt{2}\)
  • \(r_{13} = \mathbf{q}_1^\top \mathbf{c} = \sqrt{18} = 3\sqrt{2}\)
  • \(r_{22} = \mathbf{q}_2^\top \mathbf{b} = \|\mathbf{B}\| = \sqrt{6}\)
  • \(r_{23} = \mathbf{q}_2^\top \mathbf{c} = -\sqrt{6}\)
  • \(r_{33} = \mathbf{q}_3^\top \mathbf{c} = \|\mathbf{C}\| = \sqrt{3}\)
  • 하삼각 (\(r_{21}, r_{31}, r_{32}\)) = 모두 0

\[\mathbf{R} = \begin{bmatrix} \sqrt{2} & \sqrt{2} & 3\sqrt{2} \\ 0 & \sqrt{6} & -\sqrt{6} \\ 0 & 0 & \sqrt{3} \end{bmatrix}\]

대각 성분의 의미: \(r_{kk} = \|\mathbf{u}_k\|\) — 각 직교화 단계에서 “새로 만들어진 직교 방향의 길이”다. 이것이 모두 양수이면 \(\mathbf{R}\) 이 가역이고, 따라서 원래 벡터들이 선형 독립이라는 증거가 된다.

# QR 분해 수치 계산
A_mat = np.column_stack([a, b_vec, c])
print("A =")
print(A_mat)

# NumPy QR
Q_np, R_np = np.linalg.qr(A_mat)
print(f"\nQ =\n{Q_np}")
print(f"\nR =\n{R_np}")

# 손 계산 결과와 비교 (부호 차이 가능)
print(f"\nQᵀQ =\n{np.round(Q_np.T @ Q_np, 10)}")  # 단위행렬
print(f"\nQR 복원 = A?\n{np.round(Q_np @ R_np, 10)}")
print(f"A =\n{A_mat}")

# R 대각 성분 = ‖A‖, ‖B‖, ‖C‖ (부호 무시)
print(f"\nR 대각 (절대값): {np.abs(np.diag(R_np))}")
print(f"이론 값:           [{np.sqrt(2):.4f}, {np.sqrt(6):.4f}, {np.sqrt(3):.4f}]")

11 QR로 최소제곱 풀기

§4.3의 정규방정식 \(\mathbf{A}^\top \mathbf{A} \hat{\mathbf{x}} = \mathbf{A}^\top \mathbf{b}\) 가 QR 분해로 극적으로 단순해진다.

11.1 유도

\(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 을 대입:

\[\mathbf{A}^\top \mathbf{A} = (\mathbf{Q}\mathbf{R})^\top (\mathbf{Q}\mathbf{R}) = \mathbf{R}^\top \mathbf{Q}^\top \mathbf{Q} \mathbf{R} = \mathbf{R}^\top \mathbf{R}\]

\[\mathbf{A}^\top \mathbf{b} = \mathbf{R}^\top \mathbf{Q}^\top \mathbf{b}\]

정규방정식은:

\[\mathbf{R}^\top \mathbf{R} \hat{\mathbf{x}} = \mathbf{R}^\top \mathbf{Q}^\top \mathbf{b}\]

\(\mathbf{R}^\top\) 은 가역이므로 양변에 왼쪽에서 \((\mathbf{R}^\top)^{-1}\) 을 곱하면:

\[\boxed{\mathbf{R}\hat{\mathbf{x}} = \mathbf{Q}^\top \mathbf{b}}\]

11.2 왜 이것이 획기적인가

\(\mathbf{R}\)상삼각이다. 상삼각 행렬의 방정식은 후치환(back substitution) 한 번으로 풀린다. \(n \times n\) 상삼각 시스템은 \(\mathcal{O}(n^2)\) 연산만 필요하다 (일반 역행렬은 \(\mathcal{O}(n^3)\)).

더 중요한 이점: 수치적 안정성. \(\mathbf{A}^\top \mathbf{A}\) 를 직접 계산하면 조건수가 제곱된다 (\(\kappa(\mathbf{A}^\top \mathbf{A}) = \kappa(\mathbf{A})^2\)). QR 분해를 쓰면 조건수가 \(\kappa(\mathbf{A})\) 그대로 유지된다.

따라서 LAPACK, NumPy, MATLAB 등 모든 현대 수치 라이브러리는 최소제곱을 QR (또는 SVD)로 푼다. 정규방정식은 이론적 이해의 도구일 뿐이다.

# QR로 최소제곱 풀기
# 예시: §4.3에서 쓴 3점 직선 적합
t = np.array([0, 1, 2], dtype=float)
b = np.array([6, 0, 0], dtype=float)
A = np.column_stack([np.ones_like(t), t])

# 방법 1: 정규방정식 (교과서 방식)
x_normal = np.linalg.solve(A.T @ A, A.T @ b)
print(f"정규방정식 x̂ = {x_normal}")

# 방법 2: QR 분해 + 후치환
Q, R = np.linalg.qr(A)
# R x̂ = Qᵀ b
rhs = Q.T @ b
# 상삼각이므로 후치환
x_qr = np.linalg.solve(R, rhs)
print(f"QR 분해   x̂ = {x_qr}")

# 일치 확인
print(f"차이: {np.abs(x_normal - x_qr).max():.2e}")

# NumPy lstsq (내부적으로 SVD 사용)
x_lstsq, *_ = np.linalg.lstsq(A, b, rcond=None)
print(f"lstsq     x̂ = {x_lstsq}")

12 QR 분해의 두 형태: Reduced vs Full

\(\mathbf{A}\)\(m \times n\) 직사각 행렬 (\(m > n\))일 때 QR 분해는 두 가지 형태로 표현 가능.

형태 \(\mathbf{Q}\) 크기 \(\mathbf{R}\) 크기 설명
Reduced (Economy) \(m \times n\) \(n \times n\) \(\mathbf{Q}\) 의 열이 \(C(\mathbf{A})\) 의 정규직교 기저만
Full (Complete) \(m \times m\) \(m \times n\) \(\mathbf{Q}\) 가 정사각. 추가 \(m - n\) 개 열은 \(C(\mathbf{A})^\perp = N(\mathbf{A}^\top)\) 기저

Full 형태는 네 부분공간을 완전히 보여준다 — \(\mathbf{Q}\) 의 처음 \(n\) 열은 열공간, 나머지 \(m - n\) 열은 좌영공간의 기저. Reduced 형태는 필요한 부분만 계산해서 메모리를 절약한다.

대부분의 실무에서는 Reduced 형태를 사용한다.

12.1 정사각 \(\mathbf{A}\) 의 역행렬도 QR 로 계산할 수 있다

\(\mathbf{A}\)\(n \times n\) 정사각 가역 행렬이면 \(\mathbf{Q}\) 도 정사각이므로 \(\mathbf{Q}^{-1} = \mathbf{Q}^\top\). 따라서:

\[\mathbf{A}^{-1} = (\mathbf{Q}\mathbf{R})^{-1} = \mathbf{R}^{-1} \mathbf{Q}^{-1} = \mathbf{R}^{-1} \mathbf{Q}^\top\]

\(\mathbf{Q}^\top\)전치만 하면 되므로 \(\mathcal{O}(n^2)\) 에 구해지고, \(\mathbf{R}^{-1}\)상삼각 행렬의 역이므로 후치환으로 \(\mathcal{O}(n^2)\) 에 적용할 수 있다. 수치적으로도 직접 가우스-조르단 역행렬 계산보다 훨씬 안정적.

실무 주의: 이것도 결국 “역행렬 계산”이므로 np.linalg.inv 와 마찬가지로 피해야 할 경우가 많다. \(\mathbf{A}^{-1}\mathbf{b}\) 를 구하고 싶다면 \(\mathbf{R}\mathbf{x} = \mathbf{Q}^\top \mathbf{b}\) 후치환 한 번으로 끝난다 — 역행렬을 먼저 만들고 곱하는 것보다 빠르다. 역행렬을 명시적으로 들고 있어야 하는 경우에만 \(\mathbf{R}^{-1}\mathbf{Q}^\top\) 을 쓴다.

# Reduced vs Full QR
A = np.random.randn(5, 3)  # 5x3 직사각 행렬

Q_red, R_red = np.linalg.qr(A, mode='reduced')
Q_full, R_full = np.linalg.qr(A, mode='complete')

print(f"Reduced: Q {Q_red.shape}, R {R_red.shape}")
print(f"Full:    Q {Q_full.shape}, R {R_full.shape}")

# Full 형태에서 추가된 열은 좌영공간의 기저
left_null_basis = Q_full[:, 3:]
print(f"\n좌영공간 차원: {left_null_basis.shape[1]}")
print(f"Aᵀ (좌영공간 기저) = {np.round(A.T @ left_null_basis, 10)}")  # 0

13 Householder 반사: 실전에서 더 쓰이는 방법

13.1 동기

Modified Gram-Schmidt도 큰 행렬에서는 수치 오차가 쌓인다. 완전히 다른 접근이 필요하다. LAPACK 같은 최고 수준의 라이브러리는 Householder 반사(reflection) 를 쓴다.

13.2 아이디어

앞서 본 반사 행렬 \(\mathbf{H} = \mathbf{I} - 2\mathbf{u}\mathbf{u}^\top\) 을 영리하게 선택하여 \(\mathbf{A}\) 의 첫 열의 아랫부분을 모두 0으로 만든다. 이 연산을 반복하면 \(\mathbf{A}\) 가 상삼각 \(\mathbf{R}\) 로 변환되고, 누적된 반사들의 곱이 \(\mathbf{Q}\) 가 된다.

\[\mathbf{H}_n \mathbf{H}_{n-1} \cdots \mathbf{H}_1 \mathbf{A} = \mathbf{R} \implies \mathbf{A} = \underbrace{(\mathbf{H}_1 \cdots \mathbf{H}_n)}_{\mathbf{Q}} \mathbf{R}\]

장점: 1. 각 반사가 직교 행렬이므로 오차가 증폭되지 않음 2. 연산량이 Gram-Schmidt와 비슷하면서 수치적으로 훨씬 안정 3. 행렬이 sparse할 때 특히 유리

NumPy의 np.linalg.qr 는 내부적으로 LAPACK의 Householder 기반 구현을 호출한다.

의미: Gram-Schmidt는 교육용·개념용이고, Householder는 실전용이다. 하지만 이 두 방법 모두 같은 \(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 분해를 만들어낸다.


14 \(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 의 요약

측면 내용
정의 \(\mathbf{A}\) = (정규직교 열 행렬 \(\mathbf{Q}\)) × (상삼각 행렬 \(\mathbf{R}\))
존재 조건 \(\mathbf{A}\) 의 열이 선형 독립이면 존재
\(\mathbf{R}\) 의 구성 \(r_{ij} = \mathbf{q}_i^\top \mathbf{a}_j\), 상삼각
대각 성분 \(r_{kk} = \|\mathbf{u}_k\| > 0\) (직교화 후 잔여 길이)
최소제곱 \(\mathbf{R}\hat{\mathbf{x}} = \mathbf{Q}^\top \mathbf{b}\) (후치환)
역행렬 (\(\mathbf{A}\) 정사각) \(\mathbf{A}^{-1} = \mathbf{R}^{-1}\mathbf{Q}^\top\)

15 응용

15.1 1. 푸리에 급수와 신호처리

정사각 \(\mathbf{Q}\)\(\mathbf{b} = \mathbf{Q}\mathbf{Q}^\top \mathbf{b}\) 분해는 변환의 본질이다. 푸리에 행렬, 이산 코사인 변환(DCT), 웨이블릿 변환 — 모두 정규직교 기저로의 분해다. 각 계수는 내적으로 독립 계산되어 효율적이고 수치 안정적이다.

15.2 2. PCA (주성분 분석)

데이터 공분산 행렬의 고유벡터들이 정규직교 기저를 이룬다. 데이터를 이 기저로 분해하면 성분들이 서로 무상관 — 정보가 중복되지 않는 방향으로 표현된다. 차원 축소 시 가장 정보가 많은 몇 개의 방향만 선택할 수 있다.

15.3 3. QR 알고리즘 (고윳값 계산)

\(\mathbf{A} = \mathbf{Q}\mathbf{R}\) 로 분해한 뒤 \(\mathbf{A}' = \mathbf{R}\mathbf{Q}\) 를 만들면, \(\mathbf{A}'\) 의 고윳값이 \(\mathbf{A}\) 와 같다. 이것을 반복하면 \(\mathbf{A}\) 가 상삼각 행렬로 수렴하고, 그 대각선이 고윳값이 된다. 거의 모든 수치 고윳값 계산이 이 QR 알고리즘을 기반으로 한다.

15.4 4. 최소제곱 회귀

위에서 설명한 대로, 모든 현대 통계·머신러닝 라이브러리의 선형회귀 구현은 QR 또는 SVD를 사용한다. sklearn.linear_model.LinearRegression, R의 lm(), MATLAB의 \ 모두 QR/SVD 기반이다.

분야 QR의 역할
통계 회귀 수치 안정적 계수 추정
신호처리 직교 변환 (DFT, DCT)
머신러닝 PCA, 선형회귀 기반 모델
수치해석 고윳값 계산 (QR 알고리즘)
컴퓨터 그래픽스 회전·반사 변환의 분해
양자 역학 상태 벡터의 직교 기저 전개

16 핵심 요약

정규직교: qᵢᵀ qⱼ = δᵢⱼ
  ↓
Q 행렬: QᵀQ = I
  정사각 Q: Qᵀ = Q⁻¹ (직교 행렬)
  길이·각도·내적 보존

투영 공식의 단순화 (A → Q)
  x̂ = Qᵀb            (역행렬 없음!)
  p = QQᵀb
  P = QQᵀ
  → n 개의 독립 1차원 투영의 합

Gram-Schmidt (선형 독립 → 정규직교)
  A = a
  B = b - proj_A(b)
  C = c - proj_A(c) - proj_B(c)
  qₖ = (해당 벡터) / (길이)

A = QR 분해
  Q: 정규직교 열, R: 상삼각
  R = QᵀA, rᵢⱼ = qᵢᵀaⱼ
  최소제곱: Rx̂ = Qᵀb (후치환)
  수치 안정성: κ(AᵀA) = κ(A)² 회피

실전에서는: LAPACK이 Householder 반사로 QR을 계산한다
Gram-Schmidt는 개념 이해용

17 관련 주제

선행 지식

동일 챕터 연결

응용 연결

  • SVD (예정) — QR의 “양쪽 정규직교” 버전, 네 부분공간의 직교 기저 완성
  • 푸리에 급수 (예정) — 함수 공간의 정규직교 기저
  • PCA (예정) — 데이터 공분산의 정규직교 주성분

Subscribe

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