跳轉到

4.4 內積與正交:投影的幾何#

本節目標#

  • 掌握內積的兩種身分:代數運算與幾何量測(長度、夾角)
  • 理解 正交 = 內積為零 的幾何意義
  • 導出並畫出 投影公式 \(\mathrm{proj}_{\mathbf{u}}\mathbf{v} = \frac{\mathbf{v}\cdot\mathbf{u}}{\mathbf{u}\cdot\mathbf{u}}\mathbf{u}\)
  • 把投影從「投影到一條線」推廣到「投影到子空間」(法向量法)
  • 建立核心直覺:投影 = 最近的點,為第 6 章最小平方法鋪路

內積:一支量測角度與長度的尺#

兩個向量的 內積(點積),代數上是「對應分量相乘再相加」:

\[ \mathbf{v}\cdot\mathbf{u} = v_1 u_1 + v_2 u_2 \]

幾何上,它同時編碼了兩個向量 各自的長度 與 之間的夾角:

\[ \mathbf{v}\cdot\mathbf{u} = \|\mathbf{v}\|\,\|\mathbf{u}\|\cos\theta \]

由內積可以反推兩個最重要的幾何量:

import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import plot_vectors

v = np.array([3.0, 2.0])
u = np.array([1.0, 1.0])

length_v = np.linalg.norm(v)                    # ||v|| = sqrt(v·v) ≈ 3.606
cos_theta = v @ u / (np.linalg.norm(v) * np.linalg.norm(u))
theta = np.degrees(np.arccos(cos_theta))
print(f"||v|| = {length_v:.3f}, 夾角 = {theta:.2f}°")

注意 NumPy 的 v @ u 就是內積(對一維陣列而言),np.linalg.norm 就是長度 \(\sqrt{\mathbf{v}\cdot\mathbf{v}}\) 。


正交:內積為零#

夾角公式裡,最特別的情況是 \(\cos\theta = 0\) ,即 \(\theta = 90°\) :

正交(垂直) \(\iff \mathbf{v}\cdot\mathbf{u} = 0\)

這是把 4.3 節「四個基本子空間的正交補」看得見的那把尺:零空間的每個向量都與列空間的每個向量內積為零 —— 它們在空間中真的互相垂直。

a, b = np.array([1.0, 2.0]), np.array([-2.0, 1.0])
print(a @ b)      # 0.0 -> 垂直!

# 3D 中也一樣:法向量與平面上的向量都垂直
n = np.array([1.0, 1.0, 1.0])          # 平面 x+y+z=0 的法向量
p1, p2 = np.array([1.0, -1.0, 0.0]), np.array([1.0, 0.0, -1.0])
print(n @ p1, n @ p2)                  # 0 0

投影:把向量「壓扁」到一條線上#

問題:給定方向 \(\mathbf{u}\) ,向量 \(\mathbf{v}\) 在 \(\mathbf{u}\) 這條線上的「影子」是什麼? 從 \(\mathbf{v}\) 的終點對這條線作垂線,垂足就是答案 \(\mathbf{p}\) 。

因為 \(\mathbf{p}\) 在 \(\mathbf{u}\) 方向上,可寫成 \(\mathbf{p} = c\,\mathbf{u}\) ; 而 \(\mathbf{v} - \mathbf{p}\) (誤差向量)與 \(\mathbf{u}\) 垂直:

\[ (\mathbf{v} - c\,\mathbf{u})\cdot\mathbf{u} = 0 \;\Longrightarrow\; c = \frac{\mathbf{v}\cdot\mathbf{u}}{\mathbf{u}\cdot\mathbf{u}} \;\Longrightarrow\; \boxed{\;\mathrm{proj}_{\mathbf{u}}\mathbf{v} = \frac{\mathbf{v}\cdot\mathbf{u}}{\mathbf{u}\cdot\mathbf{u}}\,\mathbf{u}\;} \]

注意這個公式只用了「垂直」這一個條件就推導完成 —— 內積是整座橋。

def proj(u, v):
    """v 投影到 u 方向直線上。"""
    return (v @ u) / (u @ u) * u

u = np.array([2.0, 1.0])
v = np.array([1.0, 3.0])
p = proj(u, v)
e = v - p                              # 誤差向量
print("投影 p =", p, " 誤差 e =", e)
print("檢查垂直:u · e =", u @ e)      # 應接近 0

畫圖看「影子」與「垂線」:

plt.figure(figsize=(6, 6))
plot_vectors([u, v, p, e], color=["gray", "green", "red", "orange"],
             labels=["u(投影目標線)", "v", "proj_u(v)", "誤差 v-p"])
plt.plot([v[0], p[0]], [v[1], p[1]], "--", color="orange", lw=1)  # 垂線虛線
plt.gca().set_aspect("equal")
plt.title("v 投影到 u 的方向")
plt.show()

你會看到橘色誤差向量與灰色目標線恰好垂直 —— 這就是投影的定義性質。


投影到子空間:法向量法#

目標線只是一維子空間。若要投影到一個 平面(例如 3D 中的平面 \(S\) ), 有一個優雅的捷徑:先算出平面的法向量 \(\mathbf{n}\) ,把 \(\mathbf{v}\) 投影到法向量上,再從 \(\mathbf{v}\) 減掉:

\[ \mathrm{proj}_{S}\,\mathbf{v} = \mathbf{v} - \mathrm{proj}_{\mathbf{n}}\,\mathbf{v} = \mathbf{v} - \frac{\mathbf{v}\cdot\mathbf{n}}{\mathbf{n}\cdot\mathbf{n}}\,\mathbf{n} \]

幾何邏輯: \(\mathbf{v}\) = (平面內的成分)+(垂直平面的成分)。 垂直成分就是「投影到法向量」的結果,減掉它,剩下的就是平面內成分。

# 平面 S: x + y + z = 0,法向量 n = (1,1,1)
n = np.array([1.0, 1.0, 1.0])
v = np.array([3.0, 1.0, 2.0])

v_perp = (v @ n) / (n @ n) * n     # 垂直平面的成分
p_S   = v - v_perp                 # 平面內的成分 = 投影

print("投影到平面:", p_S)
print("檢查落在平面上 x+y+z =", p_S.sum())        # ≈ 0
print("檢查誤差垂直平面:", n @ (v - p_S))          # ≈ 0

為什麼投影是「最近的點」?#

這是本節最重要的一句話:

在子空間的所有點中,投影點離 \(\mathbf{v}\) 最近, 而且它與其他任何點 \(q\) 的距離滿足 畢氏定理: \(\|\mathbf{v} - q\|^2 = \|\mathbf{v} - \mathbf{p}\|^2 + \|\mathbf{p} - q\|^2 > \|\mathbf{v} - \mathbf{p}\|^2\) 。

幾何上看: \(\mathbf{v}\) 到平面的最短距離一定是 垂線(想像從高樓丟球到地面,垂直落點最近)。 任何其他點 \(q\) 都要「先沿垂線、再在平面內繞路」,只會更遠。

用數值實驗親自驗證:

rng = np.random.randn(1000, 3)
qs = rng * 2                                        # 平面附近隨機撒 1000 個候選點
qs = qs - (qs @ n / (n @ n))[:, None] * n           # 把候選點都搬到平面 S 上
d_proj = np.linalg.norm(v - p_S)                    # 到投影點的距離
d_others = np.linalg.norm(qs - v, axis=1)           # 到其他點的距離
print(f"到投影的距離 = {d_proj:.4f}")
print(f"到其他平面點的最小距離 = {d_others.min():.4f}(必 > d_proj)")

這個「最近的點」觀點正是最小平方法的靈魂(第 6 章): 資料點 \(\mathbf{b}\) 往往不在行空間 \(C(A)\) 裡(方程組無解), 我們退而求其次,找 \(C(A)\) 中離 \(\mathbf{b}\) 最近的點 \(A\hat{\mathbf{x}}\) —— 那正是 \(\mathbf{b}\) 在行空間上的投影,而垂足條件 \(A^T(\mathbf{b} - A\hat{\mathbf{x}}) = \mathbf{0}\) 就推出正規方程 \(A^TA\hat{\mathbf{x}} = A^T\mathbf{b}\) 。 今天建立的直覺,兩章後會完整回收。


實驗:畫向量與其投影#

把 2D 投影做成一個可重複玩的小實驗,觀察改變 \(\mathbf{v}\) 或 \(\mathbf{u}\) 時影子的變化:

def show_projection(u, v):
    p, e = proj(u, v), v - proj(u, v)
    plt.figure(figsize=(5, 5))
    t = np.linspace(-6, 6, 2)
    plt.plot(t * u[0] / np.linalg.norm(u), t * u[1] / np.linalg.norm(u),
             color="gray", alpha=0.5, label="span{u}")
    plot_vectors([v, p, e], color=["green", "red", "orange"],
                 labels=["v", "投影", "誤差"])
    plt.plot([v[0], p[0]], [v[1], p[1]], "--", color="orange", lw=1)
    plt.gca().set_aspect("equal"); plt.legend(); plt.grid(alpha=0.3)
    plt.title(f"夾角 {np.degrees(np.arccos(v @ u / np.linalg.norm(v) / np.linalg.norm(u))):.1f}°")
    plt.show()
    print(f"投影長度 = {np.linalg.norm(p):.3f}(= ||v||cosθ = {np.linalg.norm(v)*np.cos(np.arccos(v@u/np.linalg.norm(v)/np.linalg.norm(u))):.3f})")

show_projection(np.array([2.0, 1.0]), np.array([1.0, 3.0]))
show_projection(np.array([2.0, 1.0]), np.array([-1.0, 3.0]))   # 鈍角:投影翻到反方向
show_projection(np.array([1.0, 1.0]), np.array([1.0, -1.0]))   # 90°:投影縮成零向量

三個案例的規律:投影長度 \(= \|\mathbf{v}\|\cos\theta\) ,夾角 90° 時影子完全消失 —— 內積為零的世界裡,沒有任何「影子」留下。


小結與練習#

本節重點 1. 內積 \(\mathbf{v}\cdot\mathbf{u} = \|\mathbf{v}\|\|\mathbf{u}\|\cos\theta\) ,同時給出長度與夾角。 2. 正交 \(\iff\) 內積為零;這是 4.3 節正交補關係的幾何基礎。 3. 投影公式只靠「誤差垂直於目標」這一個條件導出;投影到子空間可用法向量法 \(\mathbf{v} - \mathrm{proj}_{\mathbf{n}}\mathbf{v}\) 。 4. 投影是子空間上離 \(\mathbf{v}\) 最近的點 —— 這是最小平方法(第 6 章)的幾何核心。

練習 1. 手算 \(\mathbf{v} = (4, 1)\) 投影到 \(\mathbf{u} = (2, 1)\) 的結果,再用 proj 驗證。 2. 修改 show_projection,固定 \(\mathbf{v} = (3, 0)\) ,讓 \(\mathbf{u}\) 從 0° 旋轉到 180°,畫出「投影長度 vs 夾角」的曲線(應為 \(\|\mathbf{v}\|\cos\theta\) )。 3. 對平面 \(x + 2y - z = 0\) ,用法向量法求 \(\mathbf{v} = (2, 2, 4)\) 的投影,並驗證結果落在平面上。 4. (思考題)4.3 節說 \(\mathbb{R}^n = C(A^T) \oplus N(A)\) (正交補分解)。用本節的投影語言說明:任何 \(\mathbf{b} \in \mathbb{R}^m\) 都可以唯一分解成「行空間成分 + 左零空間成分」。