Day 23 線性代數
引言
從今天開始,我們要花三天的時間,把深度學習背後的數學基礎補齊。很多人在學神經網路時,會覺得「為什麼要學這些?」其實原因很簡單:神經網路本質上就是不斷做矩陣乘法、向量內積與梯度運算。如果對這些運算的意義與性質不熟,看到「形狀不符」或「梯度爆炸」的錯誤訊息時,就只能盲目試錯。
今天的主題是線性代數。我們會從最直觀的向量與矩陣出發,理解加減乘的規則與意義,再學會內積、矩陣乘法、轉置,並用 NumPy 把每個概念用具體的數字算一遍。雖然 NumPy 已經在前幾天介紹過,但今天會用更多「數學意義」的角度去用它。這篇不會列一大堆公式,而是盡量把每個概念都用具體的小數字例子展示,讓你看到結果、對照公式,理解就不會只停留在符號上。
向量:有序排列的一串數字
在資料科學中,向量(vector)就是一組有序的數字,可以用來表示一筆資料的多個特徵。例如一位顧客的「年齡、身高、體重」就可以寫成一個三維向量 [28, 168, 60]。向量空間是 NumPy 一維陣列(ndarray)最直接的數學對應。除了表示資料,向量也常用來表示「方向」與「大小」,這在後續理解梯度時特別重要。
import numpy as np
v = np.array([3, 4])
print("向量 v =", v)
print("維度 =", v.ndim)
print("形狀 =", v.shape)
print("長度 =", np.linalg.norm(v)) # 歐氏長度 sqrt(3^2 + 4^2) = 5
輸出:
向量 v = [3 4]
維度 = 2
形狀 = (2,)
長度 = 5.0
np.linalg.norm() 計算的是歐氏長度,公式是各分量平方相加再開根號。3-4-5 這個經典的直角三角形告訴我們,向量 [3, 4] 的長度剛好是 5,因為 3² + 4² = 5²。這個長度概念在 Day 24 計算梯度時會用到。另一個常用操作是「單位化」,把向量除以自己的長度,讓它的長度變成 1,這樣可以保留方向資訊但去除大小差異。
向量運算:加減、純量乘法、點對點運算
兩個向量只要長度相同,就可以做加減;向量也可以和一個純量(單一數字)相乘。NumPy 對向量運算的處理非常直觀,運算子會自動對應到每個元素。向量加減代表「對應位置的數值增減」,例如 [3, 4] + [1, 2] = [4, 6],就是把兩組數字逐項相加。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("a + b =", a + b)
print("a - b =", a - b)
print("2 * a =", 2 * a)
print("a * b =", a * b) # 點對點相乘
輸出:
a + b = [5 7 9]
a - b = [-3 -3 -3]
2 * a = [2 4 6]
a * b = [ 4 10 18]
注意 a * b 是「對應元素相乘」,這跟線性代數的內積完全不同。NumPy 用 * 表示 Hadamard 乘積(element-wise);真正的內積(dot product)要用 np.dot() 或 @ 運算子。這個區分非常重要,因為在神經網路中這兩種乘法會在不同的層次出現——點對點相乘常見於激活函式與遮罩,內積則是每一層線性變換的核心。
內積:兩個向量的「相似度」
兩個向量的內積(dot product)定義為對應元素相乘後相加:a · b = a₁b₁ + a₂b₂ + ... + aₙbₙ。在機器學習中,內積常用來表示「兩個特徵向量的相似程度」——值越大,代表兩個向量越接近同一個方向。這個解釋在推薦系統、文件相似度計算中尤其重要。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
dot1 = np.dot(a, b)
dot2 = a @ b # 等同於 np.dot
print("內積(np.dot) =", dot1)
print("內積(@ 運算子) =", dot2)
輸出:
內積(np.dot) = 32
內積(@ 運算子) = 32
計算過程:1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32。內積也能寫成「兩向量長度乘積再乘上夾角的餘弦」,這個性質在之後理解 cosine similarity(文件相似度)時會很關鍵。如果兩個向量互相垂直(夾角 90 度),內積為 0;如果方向完全相反,內積為負數。這些幾何意義在後續解釋「為什麼某些神經網路權重會趨近 0」時會派上用場。實務上推薦系統裡計算「使用者向量與物品向量的相似度」,也是內積的直接應用。值得注意的是,np.dot 與 @ 對一維陣列的行為是相同的,但對二維陣列就會對應到矩陣乘法,這種「依照陣列維度自動切換語意」的設計,是 NumPy 對新手友善但也容易踩坑的地方。
矩陣:二維的數字陣列
矩陣(matrix)就是二維陣列。在資料科學中,一個 m×n 的矩陣通常代表「m 筆資料、每筆 n 個特徵」。Pandas 的 DataFrame 可以想成是「有欄位名稱的矩陣」,但底層運算還是 NumPy。矩陣的概念比向量更高一維,可以同時儲存多筆資料或多組特徵。
A = np.array([
[1, 2, 3],
[4, 5, 6]
])
print("矩陣 A =")
print(A)
print("形狀 =", A.shape)
print("轉置後 =")
print(A.T)
輸出:
矩陣 A =
[[1 2 3]
[4 5 6]]
形狀 = (2, 3)
轉置後 =
[[1 4]
[2 5]
[3 6]]
A 是 2×3 矩陣(2 列、3 欄),轉置 A.T 之後變成 3×2 矩陣,原本的列變成欄、欄變成列。轉置在神經網路裡很常用,例如某些運算需要把輸入向量的形狀從「批次×特徵」轉成「特徵×批次」。學神經網路時記住一個原則:「左矩陣的欄數要等於右矩陣的列數」,矩陣乘法才合法。
矩陣乘法:神經網路的核心運算
神經網路最常做的一件事,就是「輸入矩陣乘上權重矩陣」。NumPy 用 @ 或 np.matmul() 進行矩陣乘法;它跟 * 的差異,要特別小心。矩陣乘法在幾何上的意義是「把一個向量透過線性變換映射到另一個空間」,這個觀念會在 Day 32 CNN 章節再次出現。
A = np.array([
[1, 2],
[3, 4],
[5, 6]
])
B = np.array([
[7, 8, 9],
[10, 11, 12]
])
print("A 形狀 =", A.shape)
print("B 形狀 =", B.shape)
print("A @ B 形狀 =", (A @ B).shape)
print("A @ B =")
print(A @ B)
輸出:
A 形狀 = (3, 2)
B 形狀 = (2, 3)
A @ B 形狀 = (3, 3)
A @ B =
[[ 27 30 33]
[ 61 68 75]
[ 95 106 117]]
矩陣乘法的形狀規則:左矩陣的「欄數」必須等於右矩陣的「列數」,結果的形狀是「左列數 × 右欄數」。這個規則在設計神經網路層時特別重要——每一層權重的形狀必須和輸入的形狀對得上。舉例來說,第一個矩陣乘法的結果 27 是這樣算出來的:1×7 + 2×10 = 27。如果忘了這個規則,就會在 PyTorch 訓練時一直碰到 RuntimeError: mat1 and mat2 shapes cannot be multiplied 的錯誤。
單位矩陣與反矩陣
單位矩陣(identity matrix)是對角線為 1、其他位置為 0 的方陣,扮演乘法中的「1」。反矩陣(inverse)則是讓 A 乘以 A 的反矩陣會得到單位矩陣。理解這兩個概念對之後學習「線性迴歸的解析解」會很有幫助。
I = np.eye(3)
print("3×3 單位矩陣:")
print(I)
A = np.array([
[4, 7],
[2, 6]
], dtype=float)
A_inv = np.linalg.inv(A)
print("\nA 反矩陣:")
print(A_inv)
print("\nA @ A_inv:")
print(A @ A_inv)
輸出:
3×3 單位矩陣:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
A 反矩陣:
[[ 0.6 -0.7]
[-0.2 0.4]]
A @ A_inv:
[[ 1. 0.]
[ 0. 1.]]
A 乘上 A 的反矩陣,結果非常接近單位矩陣(由於浮點數誤差可能看到 0.0000001 之類的微小數字,可以用 np.round() 處理)。反矩陣在線性迴歸公式 (XᵀX)⁻¹Xᵀy 中會用到,但實務上會用更穩定的數值方法,例如 np.linalg.solve()。另外要注意,不是每個矩陣都有反矩陣——當矩陣的行列式(determinant)為 0 時,稱為「奇異矩陣」,這時候無法求反矩陣。
結語
今天用 NumPy 把向量、矩陣、內積、轉置、矩陣乘法、單位矩陣、反矩陣都操作了一遍。線性代數看似抽象,但只要把每個概念都寫成 NumPy 程式碼跑一次,數學公式就不再只是符號。下次看到神經網路中的「X @ W + b」這個運算式,就能立刻反應出它的形狀變化與幾何意義。線性代數在機器學習中無所不在——從最基本的線性迴歸到深度學習的卷積運算,背後都是線性變換與矩陣操作。
明天,我們會進入微積分與梯度。理解導數、偏導數與梯度的意義,是看懂「神經網路是怎麼學的」的關鍵。
留言
張貼留言