跳轉到

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\) 。

也就是:

\[ (AB)\mathbf{v} = A(B\mathbf{v}) \]

注意 讀的方向:離 \(\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})\) **:

\[ A(B\mathbf{e_1}) = A\begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 0 \\ 1 \end{bmatrix}, \qquad A(B\mathbf{e_2}) = A\begin{bmatrix} -1 \\ 0 \end{bmatrix} = \begin{bmatrix} -2 \\ 0 \end{bmatrix} \]
\[ \Rightarrow\quad AB = \begin{bmatrix} 0 & -2 \\ 1 & 0 \end{bmatrix} \]

用 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)\)#

交換律失效,但 結合律 成立:

\[ (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 = \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{bmatrix}, \qquad M\begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x \\ y \\ x+y \end{bmatrix} \]

兩行只有兩行 —— \(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\) 的跨維旅行

練習

  1. 手算 \(S R\) ( \(S\) 為剪切、 \(R\) 為旋轉 \(90°\) ),並用 2.2.3 的圖檢查你的答案對應哪一張網格圖。
  2. 用隨機 \(2\times2\) 矩陣驗證 \(A(B + C) = AB + AC\) (分配律幾何上是什麼意思?提示:先做「組合」再做變換)。
  3. 用 np.allclose 驗證兩個旋轉矩陣 \(R_{30°}, R_{60°}\) 彼此可交換,並解釋為什麼。
  4. 計算並畫出 \(D = \begin{bmatrix} 1.2 & 0 \\ 0 & 0.8 \end{bmatrix}\) 的 \(D^{20}\) 網格圖:水平方向爆脹、垂直方向壓縮 —— 這種「方向性的成長」就是特徵值的嗅覺(第 5 章)。
  5. 挑戰:寫一個函式 compose(*mats),把一串矩陣由右至左複合成一個矩陣(提示:functools.reduce)。