2.2 矩陣乘法與複合變換: \((AB)\mathbf{v} = A(B\mathbf{v})\)#
本節目標#
- 把矩陣乘法從「列乘行的背誦公式」,翻譯成 「先做變換 \(B\) 、再做變換 \(A\) 」
- 手算 \(2\times2\) 乘 \(2\times2\) ,逐項對照
np.dot/@ - 用實驗證明 交換律失效:旋轉×剪切 ≠ 剪切×旋轉(畫圖看差別)
- 理解結合律為何成立,以及矩陣冪 \(A^2\) 的幾何意義
- 預告非方陣: \(3\times2\) 矩陣把平面「丟進」三維空間
2.2.1 為什麼要發明矩陣乘法?#
1.1 節說過:變換就是函數。函數可以 複合 —— 先旋轉、再剪切,是一個新的變換。
問題:複合後的新變換,矩陣是什麼?總不能每次都分兩步算吧?矩陣乘法就是這個問題的答案:
若先做 \(B\) 再做 \(A\) ,複合變換的矩陣就是 \(AB\) 。
也就是:
注意 讀的方向:離 \(\mathbf{v}\) 近的先動作。 \(AB\) 是「先 \(B\) 後 \(A\) 」,跟中文語序剛好相反。
2.2.2 手算 \(2\times2 \times 2\times2\) ,與 NumPy 對照#
設 \(A = \begin{bmatrix} 2 & 0 \\ 0 & 1 \end{bmatrix}\) (水平拉伸 2 倍), \(B = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}\) (逆時針轉 \(90°\) )。
手算 \(AB\) :用 2.1 的雙重觀點 —— ** \(AB\) 的第 \(j\) 行 = \(A(B\mathbf{e_j})\) **:
用 NumPy 驗證(並且真的「分兩步」檢查複合公式):
import numpy as np
A = np.array([[2., 0.],
[0., 1.]])
B = np.array([[0., -1.],
[1., 0.]])
v = np.array([3., 1.])
print(A @ B) # [[ 0. -2.]
# [ 1. 0.]] 與手算一致
print(np.dot(A, B)) # 老式寫法,結果相同
print(A @ (B @ v)) # [ -2. 3.] 先轉再拉
print((A @ B) @ v) # [ -2. 3.] 一次到位:相同!
print(np.allclose(A @ (B @ v), (A @ B) @ v)) # True
2.2.3 交換律失效:旋轉×剪切 ≠ 剪切×旋轉#
數的乘法有 \(ab = ba\) ,矩陣 沒有。直覺理由:「先穿衣再照鏡子」和「先照鏡子再穿衣」結果不同 —— 動作的順序有意義。
實驗: \(R\) 為旋轉 \(90°\) , \(S\) 為剪切 \(\begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}\) ,比較 \(RS\) 與 \(SR\) :
import numpy as np
import matplotlib.pyplot as plt
from linalg_viz import setup_axes, draw_grid_transform
th = np.pi / 2
R = np.array([[np.cos(th), -np.sin(th)],
[np.sin(th), np.cos(th)]])
S = np.array([[1., 1.],
[0., 1.]])
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
for ax, M, name in [(axes[0], R @ S, r"$RS$: 先剪切後旋轉"),
(axes[1], S @ R, r"$SR$: 先旋轉後剪切")]:
setup_axes(ax, xlim=(-3, 3), ylim=(-3, 3))
draw_grid_transform(ax, M, xlim=(-3, 3), ylim=(-3, 3))
ax.set_title(name)
plt.show()
print(np.allclose(R @ S, S @ R)) # False —— 交換律失效!
print(R @ S)
print(S @ R)
兩張圖明顯不同: \(RS\) 得到「斜著轉的網格」、 \(SR\) 得到「轉過去之後再斜」的網格。單位方格網的最終形狀,洩漏了動作順序。
flowchart TD
v["v"] --> B1["先剪切 S"] --> B2["再旋轉 R"] --> w1["RS v"]
v --> C1["先旋轉 R"] --> C2["再剪切 S"] --> w2["SR v"]
w1 -. "兩者不同!" .- w2
例外:有些矩陣確實可交換,例如旋轉矩陣彼此(先轉 \(30°\) 再轉 \(60°\) = 先轉 \(60°\) 再轉 \(30°\) )。練習 3 會請你驗證。
2.2.4 結合律成立: \((AB)C = A(BC)\)#
交換律失效,但 結合律 成立:
幾何證明一句話:兩邊都是「先 \(C\) 、再 \(B\) 、再 \(A\) 」,動作順序完全一樣,當然是同一個變換。代數上用 NumPy 對隨機矩陣驗證:
rng = np.random.default_rng(0)
A, B, C = (rng.standard_normal((2, 2)) for _ in range(3))
print(np.allclose((A @ B) @ C, A @ (B @ C))) # True
# 所以寫 A @ B @ C 不加括號也不會有歧義
這條「無聊但重要」的性質,是第 5 章對角化( \(A = PDP^{-1} \Rightarrow A^n = PD^nP^{-1}\) )能成立的前提之一。
2.2.5 矩陣冪: \(A^2 = AA\)#
矩陣能和自己相乘: \(A^2 = A @ A\) 意思是「同一個變換做兩次」。
S = np.array([[1., 1.],
[0., 1.]])
print(S @ S) # [[1. 2.]
# [0. 1.]] 剪切兩次:滑動量翻倍
print(np.linalg.matrix_power(S, 10)) # [[1. 10.]
# [0. 1.]] 剪十次
剪切 \(n\) 次的上三角元素變成 \(n\) —— 重複變換往往產生 可預測的成長模式,這正是第 5 章(特徵值、Fibonacci 數列)的主題。
2.2.6 預告:非方陣 —— 把平面丟進空間#
到目前為止都是 \(2\times2\) 。若換成 \(3\times2\) :
兩行只有兩行 —— \(M\mathbf{e_1}, M\mathbf{e_2}\) 各是三維向量。這個變換把整個二維平面搬進三維空間,貼成一片斜的「紙」(其實是一個平面子空間)。
M = np.array([[1., 0.],
[0., 1.],
[1., 1.]])
v = np.array([2., 1.])
print(M @ v) # [2. 1. 3.] 二維輸入、三維輸出
print(M.shape) # (3, 2):行數 = 輸出維度,列數 = 輸入維度
記憶法: \(m\times n\) 矩陣把 \(\mathbb{R}^n\) 打進 \(\mathbb{R}^m\) 。方陣是「同空間內的變換」,長方陣是「跨空間的旅行」。這在 4.2(座標變換)與第 7 章 SVD 會大放異彩。
同時也注意維度規則: \((2\times3) @ (3\times4) = 2\times4\) —— 內側維度必須相等,外側是輸出。這規則幾何上就是「上一個變換的輸出空間,要塞得進下一個變換的輸入空間」。
小結與練習#
本節重點
- \(AB\) 就是複合變換「先 \(B\) 後 \(A\) 」: \((AB)\mathbf{v} = A(B\mathbf{v})\)
- \(AB\) 的第 \(j\) 行 \(= A(B\mathbf{e_j})\) :手算不用背,用「追蹤基底向量」來算
- 交換律一般失效(順序有意義),結合律永遠成立
- \(A^n\) = 重複變換 \(n\) 次;
np.linalg.matrix_power(A, n) - \(m\times n\) 矩陣: \(\mathbb{R}^n \to \mathbb{R}^m\) 的跨維旅行
練習
- 手算 \(S R\) ( \(S\) 為剪切、 \(R\) 為旋轉 \(90°\) ),並用 2.2.3 的圖檢查你的答案對應哪一張網格圖。
- 用隨機 \(2\times2\) 矩陣驗證 \(A(B + C) = AB + AC\) (分配律幾何上是什麼意思?提示:先做「組合」再做變換)。
- 用
np.allclose驗證兩個旋轉矩陣 \(R_{30°}, R_{60°}\) 彼此可交換,並解釋為什麼。 - 計算並畫出 \(D = \begin{bmatrix} 1.2 & 0 \\ 0 & 0.8 \end{bmatrix}\) 的 \(D^{20}\) 網格圖:水平方向爆脹、垂直方向壓縮 —— 這種「方向性的成長」就是特徵值的嗅覺(第 5 章)。
- 挑戰:寫一個函式
compose(*mats),把一串矩陣由右至左複合成一個矩陣(提示:functools.reduce)。