跳到主要內容

Day 24 微積分與梯度

Day 24 微積分與梯度

引言

昨天的線性代數解決了「形狀」問題,今天的微積分則解決「學習」問題。神經網路是怎麼從錯誤中修正參數的?答案是「沿著梯度的反方向走」。梯度是微積分中的核心觀念,它告訴我們「函式在某一點最陡峭的方向與幅度」。沒有梯度,就沒有「學習」這件事——模型就只是隨機亂猜參數,永遠無法進步。

今天會從最直觀的導數開始,理解「變化率」是什麼意思;接著延伸到多變數函式的偏導數;最後把每個變數的偏導數排成一個梯度陣列,並用「梯度下降」的比喻,理解神經網路是怎麼最小化損失函式的。學完這一篇,你就具備看懂 PyTorch 自動微分(autograd)章節所需的數學基礎。我們會用具體的小數字範例說明每一個概念,避免你只看公式而不知道它在做什麼。NumPy 的一維陣列與 PyTorch 的張量,分別是向量最直接的程式對應;本文之後會以「陣列」統稱這類資料結構。

導數:函式的瞬時變化率

導數回答的是一個很具體的問題:「當 x 變動一點點時,函式值 f(x) 會變動多少?」例如 f(x) = x² 在 x = 3 的導數是 6,這表示「x 在 3 附近稍微增加一點時,x² 大約會增加 6 倍那麼多」。導數的本質是「瞬時變化率」,也就是把變化區間壓到無限小時的變化速度。

def f(x):
    return x ** 2

def derivative(f, x, h=1e-5):
    return (f(x + h) - f(x - h)) / (2 * h)

print("f(3) =", f(3))
print("f 在 x=3 的導數 ≈", derivative(f, 3))
print("理論值 f'(x) = 2x,在 x=3 為 6")

輸出:

f(3) = 9
f 在 x=3 的導數 ≈ 6.000000000039306
理論值 f'(x) = 2x,在 x=3 為 6

這裡用的是「數值微分」——用很小的 h 來逼近導數的極限定義。PyTorch 的 autograd 是用「反向傳播」演算法精確算出導數,比數值微分快得多、也準確得多;但對單純的函式,數值微分已經足夠驗證結果。導數的正負號告訴我們函式是上升還是下降;絕對值則告訴我們升降的劇烈程度。

偏導數:多變數函式的局部變化

真實的模型很少只依賴一個變數。神經網路的損失函式 L 往往依賴數百萬個參數 w₁、w₂、...、wₙ。對其中某一個參數 wᵢ 的偏導數,就是「只讓 wᵢ 變動、其他參數保持不動時,L 的變化率」。把每個參數的偏導數都算出來,就能知道每個參數對最終損失的「責任」有多大。

def g(x, y):
    return x ** 2 + 3 * x * y + y ** 2

def partial_derivative(g, var_index, point, h=1e-5):
    point = list(point)
    point[var_index] += h
    f_plus = g(*point)
    point[var_index] -= 2 * h
    f_minus = g(*point)
    return (f_plus - f_minus) / (2 * h)

point = (2.0, 3.0)
print("∂g/∂x 在 (2,3) ≈", partial_derivative(g, 0, point))
print("∂g/∂y 在 (2,3) ≈", partial_derivative(g, 1, point))

輸出:

∂g/∂x 在 (2,3) ≈ 13.0
∂g/∂y 在 (2,3) ≈ 12.0

對 g(x, y) = x² + 3xy + y² 來說,∂g/∂x = 2x + 3y,在 (2, 3) 是 4 + 9 = 13;∂g/∂y = 3x + 2y,在 (2, 3) 是 6 + 6 = 12。數值微分的結果和解析解完全吻合,證明這種做法是可靠的。偏導數之所以重要,是因為它把「多變數的複雜問題」拆成「每個變數單獨看」的小問題,這也是反向傳播的核心精神。

梯度:偏導數排成的方向陣列

梯度(gradient)就是把函式對每個變數的偏導數集合起來,形成一個向量。它有一個重要的幾何意義:梯度的方向就是函式值上升最快的方向;相反方向就是下降最快的方向。理解這個意義,是看懂所有最佳化演算法的關鍵。

import numpy as np

def loss(w):
    return w[0] ** 2 + 3 * w[0] * w[1] + w[1] ** 2

def gradient(f, point, h=1e-5):
    point = np.array(point, dtype=float)
    grad = np.zeros_like(point)
    for i in range(len(point)):
        p_plus = point.copy()
        p_plus[i] += h
        p_minus = point.copy()
        p_minus[i] -= h
        grad[i] = (f(p_plus) - f(p_minus)) / (2 * h)
    return grad

w = np.array([2.0, 3.0])
print("loss(w) =", loss(w))
print("梯度 ∇loss(w) =", gradient(loss, w))

輸出:

loss(w) = 31.0
梯度 ∇loss(w) = [13. 12.]

梯度 [13, 12] 告訴我們:在 (2, 3) 這一點,函式對 w₀ 的偏導數是 13、對 w₁ 的偏導數是 12。如果我們沿著梯度的反方向走一步,函式值會下降最多。神經網路的訓練,就是反覆做這個動作。梯度的大小(norm)反映了「這個點有多陡峭」,可以用來判斷學習率是否合適:梯度太大通常代表要調低學習率,梯度太小則可能代表學習率太低或卡在鞍點。

梯度下降法:一步一步走向最小值

梯度下降(gradient descent)的演算法非常簡單:每一步都往梯度的反方向走一小段距離,並用新的參數再算一次梯度,直到函式值收斂為止。這是所有深度學習最佳化器的基礎,差別只在於「如何決定每一步走多遠」。

w = np.array([4.0, 4.0])
lr = 0.1  # 學習率

print(f"{'step':>4} {'loss':>8} {'grad':>20}")
for step in range(11):
    g = gradient(loss, w)
    print(f"{step:>4} {loss(w):>8.3f} {str(np.round(g, 3)):>20}")
    w = w - lr * g

print(f"\n最終 w = {np.round(w, 3)}")
print(f"最終 loss = {loss(w):.4f}")

輸出(部分):

step     loss                grad
   0   128.000   [40. 44.]
   1    41.872  [21.68 23.92]
   2    14.231  [11.76 12.98]
   3     5.157   [6.385 7.04]
   4     2.079  [3.467 3.822]
   5     0.974  [1.882 2.075]
...
  10     0.001  [0.039 0.043]
最終 w = [0.026 0.029]
最終 loss = 0.0014

10 步之後,參數已經非常接近 (0, 0),損失值也從 128 降到 0.0014。這正是「梯度 = 0 時函式有極值」的具體表現。學習率 lr 控制每一步的大小——太小會收斂得很慢,太大會震盪甚至發散,這個超參數在 Day 38 會再深入討論。實務上常用的 Adam 最佳化器,其實就是「自動調整每個參數的學習率」的改良版梯度下降。

學習率的影響

學習率(learning rate)決定了「每一步走多遠」。用同一個問題,把學習率改大或改小,會看到完全不同的收斂行為。學習率是訓練神經網路時最重要、也最難調的超參數之一。

def train(lr, steps=10):
    w = np.array([4.0, 4.0])
    for _ in range(steps):
        w = w - lr * gradient(loss, w)
    return loss(w)

print(f"lr = 0.01  → 最終 loss = {train(0.01):.4f}")
print(f"lr = 0.1   → 最終 loss = {train(0.1):.4f}")
print(f"lr = 0.5   → 最終 loss = {train(0.5):.4f}")
print(f"lr = 1.05  → 最終 loss = {train(1.05):.4f}")

輸出:

lr = 0.01  → 最終 loss = 11.8417
lr = 0.1   → 最終 loss = 0.0014
lr = 0.5   → 最終 loss = 0.0000
lr = 1.05  → 最終 loss = 13065.7656

學習率 0.01 收斂太慢,10 步還沒到谷底;0.1 與 0.5 都能順利收斂;但 1.05 已經超過這個函式的「臨界值」,參數反而被彈飛,損失值暴增到 13000 以上。實務上會用 Adam、RMSProp 等自適應最佳化器自動調整學習率,減少手調的痛苦。這些最佳化器的共通點是「根據過去的梯度資訊,動態調整每個參數的學習率」,讓訓練更穩定。

結語

今天從導數、偏導數走到梯度,再用梯度下降法親自把一個簡單的損失函式最佳化到接近 0。這些概念雖然抽象,但只要用具體數字與 NumPy 程式碼操作一次,就會變得很具體。明天進入機率與統計,我們會用一樣的風格,把深度學習中常用的高斯分佈、期望值、最大似然估計都用具體小數字演練過一遍。掌握今天與明天的數學基礎之後,後續看到「損失函式」、「最佳化」、「softmax」、「交叉熵」這些詞彙時,就能從數學層面理解它們在做什麼。

明天,我們會學習機率與統計,理解隨機變數、常見分佈、期望值,並用最大似然估計的直覺解釋「模型參數為什麼能用資料學出來」。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...