6.1 Gram-Schmidt 正交化#
本節目標#
- 理解為什麼「斜的基座標」難算,而「正交基座標」好算
- 學會 Gram-Schmidt 正交化演算法:逐個減去投影,把歪的基扳正
- 手算 \(2 \times 2\) 與 \(3 \times 3\) 的完整例子,並畫圖觀察每一步
- 用
np.linalg.qr驗證手算結果,最後正規化成 orthonormal 基
動機:斜基座標有多難算?#
想像你在測量一棟建築,卻只有兩支不垂直的捲尺,一支指向東北方,一支指向北方偏 30 度。 要描述一個點,你得知道「沿著這兩支尺各走多遠」,而且兩支尺會互相干擾—— 你在東北方向的移動也會稍微改變北方的讀數。
在線性代數裡,這就是「斜基座標難算」:
若 \(\mathbf{v}_1\) 與 \(\mathbf{v}_2\) 不垂直,求係數 \(c_1, c_2\) 得解聯立方程組。 但若基是正交的(彼此垂直),情況奇蹟般地簡單:
只要做內積,連方程組都不用解。原因很幾何: \(\mathbf{v}_2\) 垂直於 \(\mathbf{v}_1\) , 所以 \(\mathbf{v}_2\) 在 \(\mathbf{v}_1\) 上的投影是零,兩個座標互不干擾。
正交基好算,所以整個線性代數都在追求它:QR 分解、最小平方法、 對稱矩陣的特徵分解(6.4 節),背後都是同一件事——把歪基扳正。
Gram-Schmidt 演算法:一個個扳正#
問題:給定一組線性獨立的基 \(\mathbf{v}_1, \dots, \mathbf{v}_n\) ,製造一組張成同樣空間的正交基 \(\mathbf{u}_1, \dots, \mathbf{u}_n\) 。
核心想法極其簡單:每一個新向量,減掉它在「已處理好的向量」上的所有投影。 剩下的部分,正是與所有已處理向量垂直的成分:
投影的幾何意義(第 2 章學過):把 \(\mathbf{v}_k\) 影子落在 \(\mathbf{u}_i\) 方向的那一段「剪掉」。 剪掉之後,殘餘的部分自然垂直於 \(\mathbf{u}_i\) 。用程式描述:
import numpy as np
def gram_schmidt(V):
"""V 的每一「行」是一個基向量,回傳同樣張成空間的正交基"""
V = V.astype(float)
U = np.zeros_like(V)
for k in range(V.shape[1]):
u = V[:, k].copy()
for i in range(k):
u -= (U[:, i] @ V[:, k]) / (U[:, i] @ U[:, i]) * U[:, i]
U[:, k] = u
return U
手算例子一: \(2 \times 2\)#
取 \(\mathbf{v}_1 = (3, 1)\) 、 \(\mathbf{v}_2 = (1, 2)\) 。
第一步: \(\mathbf{u}_1 = \mathbf{v}_1 = (3, 1)\) 。
第二步:計算 \(\mathbf{v}_2\) 在 \(\mathbf{u}_1\) 上的投影:
驗證: \(\mathbf{u}_1 \cdot \mathbf{u}_2 = 3(-0.5) + 1(1.5) = 0\) 。垂直了! (可用 plot_vectors 畫出 \(\mathbf{v}_1, \mathbf{v}_2, \mathbf{u}_1, \mathbf{u}_2\) 四支箭頭。)
畫圖時你會看到: \(\mathbf{u}_2\) 正是 \(\mathbf{v}_2\) 往 \(\mathbf{u}_1\) 的影子被剪掉後剩下的部分, 兩支箭頭呈直角。
手算例子二: \(3 \times 3\)#
取
第一步: \(\mathbf{u}_1 = (1, 1, 0)\) 。
第二步:
第三步:要扣掉 \(\mathbf{v}_3\) 在 \(\mathbf{u}_1\) 和 \(\mathbf{u}_2\) 兩個方向的投影:
import numpy as np
from linalg_viz import plot_vectors
V = np.array([[1., 1., 0.],
[1., 0., 1.],
[0., 1., 1.]]) # 每行一個基向量
U = gram_schmidt(V) # 用前面定義的函式
print(U)
print(U.T @ U) # 應為對角矩陣:非對角全是 0
# 3D 圖:灰色是原向量,彩色是正交化後的結果
plot_vectors([V[:, 0], V[:, 1], V[:, 2]], labels=['v1', 'v2', 'v3'], color='gray')
plot_vectors([U[:, 0], U[:, 1], U[:, 2]], labels=['u1', 'u2', 'u3'])
注意 \(\mathbf{u}_3\) 有三個方向要同時垂直,所以它扣掉了兩個投影。 向量越多,要剪掉的影子越多——但演算法不變。
用 np.linalg.qr 驗證手算結果#
Gram-Schmidt 太重要了,NumPy 直接內建:np.linalg.qr 把矩陣 \(A\) 分解成 \(A = QR\) ,其中 \(Q\) 的行就是 orthonormal 基(下一節細講 \(R\) )。
import numpy as np
V = np.array([[1., 1., 0.],
[1., 0., 1.],
[0., 1., 1.]])
Q, R = np.linalg.qr(V)
print(Q)
# 手算結果(未正規化)的方向,應與 Q 的行一致
U = np.array([[1, 1/2, -2/3],
[1, -1/2, 2/3],
[0, 1, 2/3]])
Un = U / np.linalg.norm(U, axis=0) # 逐行正規化
print(np.abs(np.abs(Q.T @ Un) - np.eye(3)).max()) # ~0:只差正負號
小提醒:
qr給出的行可能與手算差一個正負號(演算法內部選了方向), 檢查時比對「方向的絕對值」即可。
最後補上正規化,把正交基升級成 orthonormal 基(單位長 + 互相垂直)—— 其實上面驗證時的 Un 就是它,滿足 \(E^T E = I\) 。
小結與練習#
本節重點 1. 正交基的座標用內積就能算出,互不干擾——這是它好算的根本原因。 2. Gram-Schmidt:每個新向量減去它在先前向量上的投影,殘餘部分自動垂直。 3. np.linalg.qr 是 Gram-Schmidt 的工業級版本,直接把 \(A\) 拆成 orthonormal 基 \(Q\) 與上三角 \(R\) 。 4. 再除以長度,就得到 orthonormal 基 \(E\) ,滿足 \(E^T E = I\) 。
練習 1. 對 \(\mathbf{v}_1 = (2, 1)\) 、 \(\mathbf{v}_2 = (1, 3)\) 手算 Gram-Schmidt,再用 np.linalg.qr 驗證。 2. 用本節的 gram_schmidt 函式處理三個向量 \((1,0,0), (1,1,0), (1,1,1)\) ,畫圖觀察 \(\mathbf{u}_3\) 與 \(\mathbf{v}_3\) 差多少。 3. 把 \(\mathbf{v}_2\) 換成 \(\mathbf{v}_1\) 的倍數(線性相依),觀察演算法發生什麼事——這正是「線性獨立」假設的用意。