學(xué)基石:從導(dǎo)數(shù)本質(zhì)到梯度下降的Python實(shí)戰(zhàn))
1. 項(xiàng)目緣起為什么AI從業(yè)者必須重拾導(dǎo)數(shù)如果你正在學(xué)習(xí)或從事人工智能尤其是機(jī)器學(xué)習(xí)、深度學(xué)習(xí)那么“導(dǎo)數(shù)”這個(gè)詞對(duì)你來(lái)說(shuō)一定不陌生。你可能在無(wú)數(shù)篇論文、教程和代碼注釋里見(jiàn)過(guò)它公式里那些帶著撇號(hào)f(x)或微分符號(hào)df/dx的表達(dá)式看起來(lái)既熟悉又陌生。很多朋友包括我自己在早期都陷入過(guò)一個(gè)誤區(qū)覺(jué)得會(huì)用框架比如PyTorch、TensorFlow的autograd自動(dòng)求導(dǎo)就夠了背后的數(shù)學(xué)原理可以“黑箱”處理。直到在實(shí)際項(xiàng)目中模型不收斂、梯度爆炸或消失、調(diào)參毫無(wú)方向感時(shí)才猛然發(fā)現(xiàn)不理解導(dǎo)數(shù)就像開(kāi)車(chē)不懂油門(mén)和剎車(chē)的關(guān)系只能憑感覺(jué)亂撞。這個(gè)項(xiàng)目正是源于這種切膚之痛。它不是一堂抽象的數(shù)學(xué)課而是一次面向?qū)嵺`的“基建”工作。我們將徹底拋開(kāi)應(yīng)試教育的包袱不再死記硬背公式而是聚焦于一個(gè)核心目標(biāo)理解導(dǎo)數(shù)如何刻畫(huà)“變化”并親手用Python將這個(gè)抽象概念“計(jì)算”出來(lái)為后續(xù)理解梯度下降、反向傳播等AI核心算法打下堅(jiān)不可摧的基礎(chǔ)。你會(huì)發(fā)現(xiàn)導(dǎo)數(shù)并非高深莫測(cè)它其實(shí)就是我們理解世界變化規(guī)律最自然的數(shù)學(xué)語(yǔ)言。2. 導(dǎo)數(shù)的本質(zhì)兩個(gè)經(jīng)典模型與一種思維方式在教科書(shū)上導(dǎo)數(shù)的定義是函數(shù)在某一點(diǎn)的變化率是極限值。這個(gè)定義嚴(yán)謹(jǐn)?shù)燥@冰冷。為了建立直覺(jué)我們從兩個(gè)更生動(dòng)的模型入手。2.1 瞬時(shí)速度模型從平均到瞬間的飛躍設(shè)想一輛智能小車(chē)沿直線(xiàn)運(yùn)動(dòng)它的位移s單位米與時(shí)間t單位秒的關(guān)系由函數(shù)s f(t)描述。我們想知道它在t3秒這一瞬間的速度。平均速度我們很容易計(jì)算從3秒到4秒這1秒內(nèi)的平均速度(f(4)-f(3)) / (4-3)。但這只是3到4秒之間的整體表現(xiàn)無(wú)法代表t3那一剎那的真實(shí)狀態(tài)。逼近瞬間為了更接近“瞬間”我們把時(shí)間間隔縮小。計(jì)算3秒到3.1秒的平均速度(f(3.1)-f(3)) / 0.1。再縮小到3到3.01秒(f(3.01)-f(3)) / 0.01。導(dǎo)數(shù)的出現(xiàn)當(dāng)我們讓時(shí)間間隔Δt無(wú)限趨近于0時(shí)這個(gè)平均速度的極限值就是小車(chē)在t3秒時(shí)的瞬時(shí)速度也就是位移函數(shù)sf(t)在t3處的導(dǎo)數(shù)記作f(3)或ds/dt|_{t3}。注意這里“極限”的思想是關(guān)鍵。我們不是計(jì)算一個(gè)長(zhǎng)度為0的間隔那沒(méi)有意義而是觀(guān)察當(dāng)間隔無(wú)限變小時(shí)比值穩(wěn)定趨向的那個(gè)唯一的值。這個(gè)值就是導(dǎo)數(shù)。在A(yíng)I中損失函數(shù)L(θ)關(guān)于參數(shù)θ的導(dǎo)數(shù)dL/dθ直觀(guān)上就是“當(dāng)參數(shù)θ發(fā)生極其微小的變化時(shí)損失值L會(huì)以多快的速度變化”這正是梯度下降算法決定參數(shù)更新方向與步長(zhǎng)的核心依據(jù)。2.2 切線(xiàn)斜率模型幾何視角的直觀(guān)理解將函數(shù)yf(x)畫(huà)成曲線(xiàn)。曲線(xiàn)上有一點(diǎn)P(x0, f(x0))。如何定義過(guò)P點(diǎn)的切線(xiàn)割線(xiàn)到切線(xiàn)的演變?cè)谇€(xiàn)上另取一點(diǎn)Q(x0Δx, f(x0Δx))連接PQ得到一條割線(xiàn)。割線(xiàn)的斜率是(f(x0Δx) - f(x0)) / Δx。動(dòng)點(diǎn)Q無(wú)限逼近P讓點(diǎn)Q沿著曲線(xiàn)無(wú)限靠近點(diǎn)P即讓?duì) - 0。此時(shí)割線(xiàn)PQ繞點(diǎn)P旋轉(zhuǎn)其極限位置就是曲線(xiàn)在點(diǎn)P的切線(xiàn)。導(dǎo)數(shù)即斜率上述割線(xiàn)斜率的極限值就是切線(xiàn)斜率也就是函數(shù)在x0處的導(dǎo)數(shù)f(x0)。這個(gè)幾何模型極其重要。在二維優(yōu)化問(wèn)題中損失函數(shù)的圖像是一個(gè)曲面或曲線(xiàn)。某一點(diǎn)的導(dǎo)數(shù)梯度在某一維的分量就指明了該點(diǎn)處最陡峭的上升方向。梯度下降法所做的就是沿著該點(diǎn)切線(xiàn)或切平面的反方向即最陡峭下降方向前進(jìn)一小步以尋找更低點(diǎn)最小化損失。2.3 核心思維方式線(xiàn)性逼近上述兩個(gè)模型共同揭示出導(dǎo)數(shù)更深層的哲學(xué)局部線(xiàn)性化。在函數(shù)某一點(diǎn)附近一個(gè)復(fù)雜的非線(xiàn)性函數(shù)可以被其切線(xiàn)很好地近似。即f(x0 Δx) ≈ f(x0) f(x0) * Δx這意味著知道了某點(diǎn)的函數(shù)值和導(dǎo)數(shù)我們就能大致預(yù)測(cè)輸入發(fā)生微小變化時(shí)輸出會(huì)如何變化。這正是反向傳播算法的基礎(chǔ)通過(guò)鏈?zhǔn)椒▌t將最終損失的變化一層層線(xiàn)性地通過(guò)導(dǎo)數(shù)分配回每一個(gè)網(wǎng)絡(luò)參數(shù)上。3. 導(dǎo)數(shù)運(yùn)算的核心規(guī)則與Python符號(hào)推導(dǎo)理解了本質(zhì)我們還需要掌握計(jì)算的工具。手動(dòng)計(jì)算復(fù)雜函數(shù)的導(dǎo)數(shù)既繁瑣又易錯(cuò)。幸運(yùn)的是Python的SymPy庫(kù)可以幫我們進(jìn)行精確的符號(hào)數(shù)學(xué)計(jì)算。下面我們結(jié)合關(guān)鍵規(guī)則用代碼實(shí)現(xiàn)。首先確保安裝SymPypip install sympy。import sympy as sp # 定義符號(hào)變量 x, y, a, n sp.symbols(x y a n) # 定義函數(shù) f sp.Function(f) g sp.Function(g)3.1 基本初等函數(shù)求導(dǎo)這是構(gòu)建復(fù)雜函數(shù)導(dǎo)數(shù)的基石。# 1. 常數(shù)函數(shù): f(x) C, f(x) 0 print(常數(shù)導(dǎo)數(shù):, sp.diff(5, x)) # 輸出 0 # 2. 冪函數(shù): f(x) x^n, f(x) n*x^(n-1) print(冪函數(shù)導(dǎo)數(shù):, sp.diff(x**n, x)) # 輸出 n*x**(n-1) print(示例 x^3:, sp.diff(x**3, x)) # 輸出 3*x**2 # 3. 指數(shù)函數(shù): f(x) a^x, f(x) a^x * ln(a) print(指數(shù)函數(shù)(a^x)導(dǎo)數(shù):, sp.diff(a**x, x)) # 輸出 a**x*log(a) # 特例: f(x) e^x, f(x) e^x print(自然指數(shù)函數(shù)導(dǎo)數(shù):, sp.diff(sp.exp(x), x)) # 輸出 exp(x) # 4. 對(duì)數(shù)函數(shù): f(x) log_a(x), f(x) 1/(x * ln(a)) print(對(duì)數(shù)函數(shù)(log_a(x))導(dǎo)數(shù):, sp.diff(sp.log(x, a), x)) # 輸出 1/(x*log(a)) # 特例: f(x) ln(x), f(x) 1/x print(自然對(duì)數(shù)函數(shù)導(dǎo)數(shù):, sp.diff(sp.ln(x), x)) # 輸出 1/x # 5. 三角函數(shù) print(正弦函數(shù)導(dǎo)數(shù):, sp.diff(sp.sin(x), x)) # 輸出 cos(x) print(余弦函數(shù)導(dǎo)數(shù):, sp.diff(sp.cos(x), x)) # 輸出 -sin(x)3.2 四則運(yùn)算求導(dǎo)法則復(fù)雜函數(shù)多由基本函數(shù)通過(guò)加、減、乘、除組合而成。# 定義兩個(gè)函數(shù) u sp.sin(x) v x**2 1 # 1. 加法法則: (uv) u v f_add u v print(加法法則:, sp.diff(f_add, x)) # 輸出: 2*x cos(x) # 2. 減法法則: (u-v) u - v f_sub u - v print(減法法則:, sp.diff(f_sub, x)) # 輸出: -2*x cos(x) # 3. 乘法法則: (u*v) u*v u*v f_mul u * v print(乘法法則:, sp.diff(f_mul, x)) # 輸出: (x**2 1)*cos(x) 2*x*sin(x) # 4. 除法法則: (u/v) (u*v - u*v) / v^2 (v ! 0) f_div u / v print(除法法則:, sp.diff(f_div, x)) # 輸出: (-2*x*sin(x) (x**2 1)*cos(x))/(x**2 1)**23.3 鏈?zhǔn)椒▌t深度學(xué)習(xí)的核心鏈?zhǔn)椒▌t是處理復(fù)合函數(shù)函數(shù)嵌套函數(shù)求導(dǎo)的利器也是神經(jīng)網(wǎng)絡(luò)反向傳播的理論核心。公式為若yf(u),ug(x)則dy/dx (dy/du) * (du/dx)。# 例子: y sin(x^2 1) # 令 u x^2 1, 則 y sin(u) u x**2 1 y sp.sin(u) # 手動(dòng)應(yīng)用鏈?zhǔn)椒▌t: dy/dx cos(u) * (2x) cos(x^21) * 2x print(鏈?zhǔn)椒▌t求導(dǎo):, sp.diff(y, x)) # 輸出: 2*x*cos(x**2 1) # SymPy會(huì)自動(dòng)應(yīng)用鏈?zhǔn)椒▌t對(duì)于更復(fù)雜的嵌套也游刃有余 y_complex sp.log(sp.exp(x**2) 1) print(復(fù)雜復(fù)合函數(shù)求導(dǎo):, sp.diff(y_complex, x)) # 輸出: (2*x*exp(x**2))/(exp(x**2) 1)實(shí)操心得在手動(dòng)推導(dǎo)反向傳播時(shí)鏈?zhǔn)椒▌t就是你的“導(dǎo)航儀”。它允許你將損失函數(shù)對(duì)深層網(wǎng)絡(luò)權(quán)重的導(dǎo)數(shù)分解為一系列局部導(dǎo)數(shù)的乘積。每一步你只需要關(guān)心當(dāng)前層的輸入、輸出和激活函數(shù)的導(dǎo)數(shù)這使得計(jì)算變得模塊化且可并行化。不理解鏈?zhǔn)椒▌t就無(wú)法真正理解autograd在做什么。4. 從定義出發(fā)用Python實(shí)現(xiàn)數(shù)值求導(dǎo)雖然符號(hào)求導(dǎo)精確但有時(shí)函數(shù)形式未知比如是一個(gè)黑盒模擬器或者我們想驗(yàn)證符號(hào)求導(dǎo)的結(jié)果就需要數(shù)值求導(dǎo)。其核心思想正是回到我們第一節(jié)講的“瞬時(shí)速度”模型用極限的近似值來(lái)估算導(dǎo)數(shù)。4.1 前向差分法這是最直觀(guān)的方法直接用差分代替微分f(x) ≈ (f(xh) - f(x)) / h其中h是一個(gè)很小的正數(shù)稱(chēng)為步長(zhǎng)。import numpy as np def derivative_forward(f, x, h1e-5): 使用前向差分法計(jì)算函數(shù)f在點(diǎn)x處的導(dǎo)數(shù)近似值。 參數(shù): f: 函數(shù)對(duì)象接受一個(gè)數(shù)值輸入。 x: 求導(dǎo)點(diǎn)。 h: 差分步長(zhǎng)默認(rèn)1e-5。 返回: 導(dǎo)數(shù)的近似值。 return (f(x h) - f(x)) / h # 測(cè)試函數(shù) f(x) x^2 def f_test(x): return x**2 x_val 2.0 true_derivative 2 * x_val # 真實(shí)導(dǎo)數(shù)為 2x在x2處為4 approx_derivative derivative_forward(f_test, x_val) print(f前向差分求導(dǎo) (x{x_val}): {approx_derivative}) print(f真實(shí)導(dǎo)數(shù): {true_derivative}) print(f絕對(duì)誤差: {abs(approx_derivative - true_derivative)})4.2 中心差分法更優(yōu)的選擇前向差分有一個(gè)理論缺陷它的截?cái)嗾`差是O(h)量級(jí)。中心差分法通過(guò)對(duì)稱(chēng)地取點(diǎn)將誤差降低到O(h^2)通常更精確f(x) ≈ (f(xh) - f(x-h)) / (2h)def derivative_central(f, x, h1e-5): 使用中心差分法計(jì)算函數(shù)f在點(diǎn)x處的導(dǎo)數(shù)近似值。 return (f(x h) - f(x - h)) / (2 * h) approx_central derivative_central(f_test, x_val) print(f\n中心差分求導(dǎo) (x{x_val}): {approx_central}) print(f真實(shí)導(dǎo)數(shù): {true_derivative}) print(f絕對(duì)誤差: {abs(approx_central - true_derivative)}) # 通常可以看到中心差分的誤差遠(yuǎn)小于前向差分4.3 步長(zhǎng)h的選取藝術(shù)精度與穩(wěn)定性的權(quán)衡步長(zhǎng)h的選擇是個(gè)微妙的平衡也是數(shù)值計(jì)算中常見(jiàn)的“坑”。h太大如0.1差分公式的“近似”效果太差截?cái)嗾`差大結(jié)果不準(zhǔn)確。h太小如1e-15在浮點(diǎn)數(shù)計(jì)算中f(xh)和f(x)的差值可能因?yàn)樯崛胝`差而嚴(yán)重失真甚至得到0導(dǎo)數(shù)。這稱(chēng)為舍入誤差主導(dǎo)。def compare_h(f, x, true_der): 比較不同步長(zhǎng)下的誤差 hs [1e-1, 1e-3, 1e-5, 1e-7, 1e-9, 1e-11, 1e-13] print(f{步長(zhǎng)(h):10} {中心差分值:20} {絕對(duì)誤差:20}) print(- * 50) for h in hs: approx derivative_central(f, x, h) error abs(approx - true_der) print(f{h:10.0e} {approx:20.15f} {error:20.15f}) compare_h(f_test, 2.0, 4.0)運(yùn)行上述代碼你會(huì)發(fā)現(xiàn)誤差隨著h從1e-1減小到1e-7而減小但繼續(xù)減小到1e-13時(shí)誤差反而開(kāi)始增大。對(duì)于大多數(shù)雙精度浮點(diǎn)數(shù)計(jì)算h在1e-6到1e-8之間通常是一個(gè)較好的選擇。在實(shí)際的AI優(yōu)化庫(kù)如NumPy、PyTorch的梯度檢查功能中也常采用這個(gè)范圍的默認(rèn)值。踩坑實(shí)錄我曾在一個(gè)自定義激活函數(shù)的實(shí)現(xiàn)中用數(shù)值梯度來(lái)驗(yàn)證符號(hào)梯度的正確性。一開(kāi)始用了h1e-4發(fā)現(xiàn)梯度匹配得很好。但當(dāng)我把網(wǎng)絡(luò)加深后訓(xùn)練變得極不穩(wěn)定。排查了很久才發(fā)現(xiàn)在深層網(wǎng)絡(luò)中梯度值本身可能非常小如1e-7此時(shí)h1e-4的數(shù)值梯度相對(duì)誤差巨大給了我“梯度計(jì)算正確”的假象。后來(lái)改用h1e-6并同時(shí)檢查相對(duì)誤差才發(fā)現(xiàn)了問(wèn)題。教訓(xùn)是數(shù)值梯度檢查時(shí)不僅要看值更要看相對(duì)誤差并且要根據(jù)參數(shù)的典型尺度調(diào)整h。5. 實(shí)戰(zhàn)為AI中常見(jiàn)函數(shù)手動(dòng)求導(dǎo)與驗(yàn)證現(xiàn)在我們將知識(shí)應(yīng)用于A(yíng)I中幾個(gè)至關(guān)重要的函數(shù)。目標(biāo)是1. 手動(dòng)推導(dǎo)其導(dǎo)數(shù)公式2. 用SymPy驗(yàn)證3. 用數(shù)值方法驗(yàn)證。5.1 Sigmoid函數(shù)及其導(dǎo)數(shù)Sigmoid函數(shù)σ(x) 1 / (1 e^{-x})是早期神經(jīng)網(wǎng)絡(luò)中常用的激活函數(shù)其導(dǎo)數(shù)有一個(gè)漂亮的特性可以用自身表示。手動(dòng)推導(dǎo) 令σ σ(x)dσ/dx d(1/(1e^{-x})) / dx將其視為復(fù)合函數(shù)或直接使用除法法則。更巧妙的方法是σ(x) (1e^{-x})^{-1}使用鏈?zhǔn)椒▌t令u 1 e^{-x}則σ u^{-1}dσ/du -1 * u^{-2} -1/(1e^{-x})^2du/dx -e^{-x}所以dσ/dx (dσ/du) * (du/dx) [-1/(1e^{-x})^2] * (-e^{-x}) e^{-x} / (1e^{-x})^2注意到σ 1/(1e^{-x})所以1-σ e^{-x}/(1e^{-x})。 經(jīng)過(guò)簡(jiǎn)單代數(shù)變換可得dσ/dx σ(x) * (1 - σ(x))Python驗(yàn)證# 1. 符號(hào)推導(dǎo)驗(yàn)證 x_sym sp.symbols(x) sigmoid 1 / (1 sp.exp(-x_sym)) sigmoid_derivative_sym sp.diff(sigmoid, x_sym) print(Sigmoid函數(shù)的符號(hào)導(dǎo)數(shù)表達(dá)式:) sp.pprint(sigmoid_derivative_sym) # 顯示原始推導(dǎo)結(jié)果 # 化簡(jiǎn)為sigmoid*(1-sigmoid)形式 simplified sp.simplify(sigmoid_derivative_sym) print(\n化簡(jiǎn)后的表達(dá)式:) sp.pprint(simplified) # 驗(yàn)證是否等于 sigmoid*(1-sigmoid) print(\n是否等于 σ*(1-σ)?, sp.simplify(simplified - sigmoid*(1-sigmoid)) 0) # 2. 數(shù)值驗(yàn)證 def sigmoid_func(x): return 1 / (1 np.exp(-x)) def sigmoid_derivative_true(x): s sigmoid_func(x) return s * (1 - s) x_test np.array([-2, -1, 0, 1, 2]) print(f\n測(cè)試點(diǎn) x: {x_test}) print(f數(shù)值導(dǎo)數(shù) (中心差分): {[derivative_central(sigmoid_func, xi) for xi in x_test]}) print(f解析導(dǎo)數(shù) (σ*(1-σ)): {sigmoid_derivative_true(x_test)})這個(gè)性質(zhì)在反向傳播中意義重大計(jì)算激活函數(shù)的梯度極其廉價(jià)只需要前向傳播時(shí)已經(jīng)計(jì)算好的sigmoid輸出值無(wú)需額外計(jì)算指數(shù)函數(shù)。5.2 Softmax函數(shù)與交叉熵?fù)p失的梯度在多分類(lèi)任務(wù)中這是最經(jīng)典的組合。設(shè)網(wǎng)絡(luò)輸出原始分?jǐn)?shù)logits為向量z經(jīng)過(guò)Softmax得到概率分布pp_i e^{z_i} / Σ_j e^{z_j}損失函數(shù)采用交叉熵?fù)p失L -Σ y_i * log(p_i)其中y是one-hot編碼的真實(shí)標(biāo)簽。其梯度是深度學(xué)習(xí)面試的經(jīng)典題。結(jié)論是對(duì)于真實(shí)類(lèi)別為k的樣本損失L對(duì)logitsz的梯度為?L/?z_i p_i - y_i即梯度是預(yù)測(cè)概率分布減去真實(shí)標(biāo)簽分布。這個(gè)結(jié)果簡(jiǎn)潔而優(yōu)美也是torch.nn.CrossEntropyLoss將Softmax和交叉熵合并計(jì)算并優(yōu)化實(shí)現(xiàn)的原因。手動(dòng)推導(dǎo)思路先求Softmax的雅可比矩陣輸出p對(duì)輸入z的導(dǎo)數(shù)這是一個(gè)n x n的矩陣。再求交叉熵?fù)p失L對(duì)p的導(dǎo)數(shù)。應(yīng)用鏈?zhǔn)椒▌t?L/?z (?p/?z)^T * (?L/?p)。經(jīng)過(guò)計(jì)算非對(duì)角線(xiàn)項(xiàng)會(huì)相互抵消最終得到上述簡(jiǎn)潔形式。Python驗(yàn)證 由于涉及向量求導(dǎo)我們用一個(gè)具體例子來(lái)驗(yàn)證。def softmax(z): exp_z np.exp(z - np.max(z)) # 減去最大值防止數(shù)值溢出 return exp_z / np.sum(exp_z) def cross_entropy_loss(p, y): # 假設(shè)y是one-hot向量 return -np.sum(y * np.log(p 1e-15)) # 加小量防止log(0) def grad_softmax_crossentropy_analytic(z, y): 計(jì)算解析梯度 ?L/?z p - y p softmax(z) return p - y def grad_numerical(z, y, epsilon1e-6): 使用中心差分法計(jì)算數(shù)值梯度 grad np.zeros_like(z) for i in range(len(z)): z_plus z.copy() z_minus z.copy() z_plus[i] epsilon z_minus[i] - epsilon loss_plus cross_entropy_loss(softmax(z_plus), y) loss_minus cross_entropy_loss(softmax(z_minus), y) grad[i] (loss_plus - loss_minus) / (2 * epsilon) return grad # 測(cè)試 np.random.seed(42) z_test np.random.randn(3) # 3個(gè)類(lèi)別的logits y_test np.array([0, 1, 0]) # 真實(shí)類(lèi)別是第1類(lèi)下標(biāo)從0開(kāi)始 grad_analytic grad_softmax_crossentropy_analytic(z_test, y_test) grad_numeric grad_numerical(z_test, y_test) print(Logits z:, z_test) print(True label y (one-hot):, y_test) print(預(yù)測(cè)概率 p:, softmax(z_test)) print(\n解析梯度 (p - y):, grad_analytic) print(數(shù)值梯度 (中心差分):, grad_numeric) print(梯度最大絕對(duì)誤差:, np.max(np.abs(grad_analytic - grad_numeric))) # 誤差應(yīng)該在1e-7量級(jí)或更小驗(yàn)證了公式的正確性。核心要點(diǎn)這個(gè)梯度公式p - y是理解分類(lèi)任務(wù)反向傳播的鑰匙。它意味著如果模型預(yù)測(cè)p已經(jīng)和真實(shí)標(biāo)簽y完全一致梯度為零網(wǎng)絡(luò)將停止更新。否則梯度會(huì)推動(dòng)預(yù)測(cè)概率p向y靠近。在代碼實(shí)現(xiàn)時(shí)現(xiàn)代深度學(xué)習(xí)框架將Softmax和交叉熵合并為一個(gè)數(shù)值穩(wěn)定的操作避免了單獨(dú)計(jì)算softmax再取log可能出現(xiàn)的數(shù)值問(wèn)題如上溢或下溢。6. 導(dǎo)數(shù)在A(yíng)I中的靈魂角色梯度下降算法初窺我們花了大量篇幅討論導(dǎo)數(shù)的計(jì)算最終都是為了服務(wù)它——梯度下降這個(gè)讓機(jī)器學(xué)習(xí)模型得以“學(xué)習(xí)”的優(yōu)化引擎。6.1 直觀(guān)理解如何下山假設(shè)你站在一座山上損失函數(shù)曲面目標(biāo)是找到山谷的最低點(diǎn)最小化損失。你環(huán)顧四周導(dǎo)數(shù)或者說(shuō)梯度在多維情況下告訴你每個(gè)方向的海拔變化率。梯度方向是上升最快的方向。那么要下山自然就沿著負(fù)梯度方向走。用數(shù)學(xué)公式表示參數(shù)θ的更新θ_new θ_old - η * ?L(θ_old)其中?L(θ)是損失函數(shù)L在θ處的梯度所有偏導(dǎo)數(shù)組成的向量。η是學(xué)習(xí)率決定了你每一步邁多大。6.2 用Python實(shí)現(xiàn)一元函數(shù)的梯度下降讓我們用一個(gè)具體的函數(shù)f(x) x^2 5*sin(x)來(lái)模擬整個(gè)過(guò)程。它的最小值點(diǎn)不難用求導(dǎo)找到但我們假裝不知道用梯度下降來(lái)尋找。import numpy as np import matplotlib.pyplot as plt def f(x): 目標(biāo)函數(shù) return x**2 5*np.sin(x) def grad_f(x): 目標(biāo)函數(shù)的導(dǎo)數(shù)梯度這里是一維的 return 2*x 5*np.cos(x) def gradient_descent(start_x, learning_rate, n_iters): 執(zhí)行梯度下降 參數(shù): start_x: 起始點(diǎn) learning_rate: 學(xué)習(xí)率 n_iters: 迭代次數(shù) 返回: history: 記錄每次迭代的x和f(x) x start_x history {x: [], f: []} for i in range(n_iters): history[x].append(x) history[f].append(f(x)) # 核心更新步驟x_new x_old - η * f(x_old) gradient grad_f(x) x x - learning_rate * gradient return history # 執(zhí)行梯度下降 start_x 5 # 起始點(diǎn)可以故意設(shè)得離最小值遠(yuǎn)一些 lr 0.1 # 學(xué)習(xí)率 iters 50 # 迭代次數(shù) history gradient_descent(start_x, lr, iters) # 可視化 xs np.linspace(-6, 6, 400) plt.figure(figsize(12, 5)) # 繪制函數(shù)曲線(xiàn)和下降路徑 plt.subplot(1, 2, 1) plt.plot(xs, f(xs), b-, labelf(x) x^2 5sin(x), linewidth2) plt.scatter(history[x], history[f], cr, s20, labelGradient Descent Path) plt.plot(history[x], history[f], r--, alpha0.5) plt.xlabel(x) plt.ylabel(f(x)) plt.title(Gradient Descent on a 1D Function) plt.legend() plt.grid(True) # 繪制損失下降曲線(xiàn) plt.subplot(1, 2, 2) plt.plot(range(iters), history[f], g-o, linewidth2, markersize4) plt.xlabel(Iteration) plt.ylabel(f(x)) plt.title(Loss Value During Descent) plt.grid(True) plt.tight_layout() plt.show() print(f起始點(diǎn) x0 {start_x}, f(x0) {f(start_x):.4f}) print(f最終點(diǎn) x {history[x][-1]:.4f}, f(x) {history[f][-1]:.4f}) print(f理論最小值點(diǎn)通過(guò)求導(dǎo)解方程 f(x)0大約在 x ≈ -1.306, f(x) ≈ -2.720)運(yùn)行這段代碼你會(huì)看到一個(gè)紅色的點(diǎn)從起始位置沿著函數(shù)曲線(xiàn)一步步“滾”向谷底。右邊的圖展示了損失值隨著迭代下降的過(guò)程。6.3 學(xué)習(xí)率η梯度下降的“油門(mén)”與“剎車(chē)”學(xué)習(xí)率是梯度下降中最重要的超參數(shù)之一沒(méi)有“之一”。η太大如1.0步子邁得太大可能會(huì)直接跨過(guò)最低點(diǎn)甚至導(dǎo)致?lián)p失值震蕩發(fā)散無(wú)法收斂。# 嘗試大學(xué)習(xí)率 history_big_lr gradient_descent(start_x2.0, learning_rate1.0, n_iters20) print(f大學(xué)習(xí)率最終損失: {history_big_lr[f][-1]:.4f}) # 可能是一個(gè)很大的數(shù)η太小如0.001步子太小下山速度極慢需要非常多的迭代次數(shù)才能收斂計(jì)算成本高。# 嘗試小學(xué)習(xí)率 history_small_lr gradient_descent(start_x2.0, learning_rate0.001, n_iters2000) print(f小學(xué)習(xí)率迭代2000次后損失: {history_small_lr[f][-1]:.4f}) # 可能離最小值還很遠(yuǎn)η合適如0.1能以較快的速度穩(wěn)定地收斂到最小值附近。在實(shí)際的神經(jīng)網(wǎng)絡(luò)訓(xùn)練中我們還會(huì)使用更高級(jí)的優(yōu)化器如Adam、RMSProp它們可以自適應(yīng)地調(diào)整每個(gè)參數(shù)的學(xué)習(xí)率但基本原理仍是梯度下降。理解導(dǎo)數(shù)你就能理解為什么梯度方向是更新方向理解學(xué)習(xí)率你就能理解為什么訓(xùn)練需要調(diào)參。7. 超越基礎(chǔ)方向?qū)?shù)、偏導(dǎo)數(shù)與梯度的關(guān)系當(dāng)函數(shù)輸入從一元x變?yōu)槎嘣?x1, x2, ..., xn)時(shí)導(dǎo)數(shù)概念推廣為偏導(dǎo)數(shù)和梯度。偏導(dǎo)數(shù)衡量函數(shù)沿某個(gè)坐標(biāo)軸方向的變化率。例如f(x,y)對(duì)x的偏導(dǎo)數(shù)?f/?x就是將y視為常數(shù)后函數(shù)沿x軸方向的變化率。梯度是一個(gè)向量其每個(gè)分量是函數(shù)對(duì)該變量的偏導(dǎo)數(shù)。?f(x,y) (?f/?x, ?f/?y)。梯度方向是函數(shù)在該點(diǎn)上升最快的方向其模長(zhǎng)表示變化率的最大值。方向?qū)?shù)函數(shù)在任意給定方向上的變化率??梢宰C明函數(shù)在點(diǎn)P沿單位向量u的方向?qū)?shù)等于該點(diǎn)梯度?f與u的點(diǎn)積D_u f ?f · u。Python計(jì)算示例import sympy as sp # 定義二元函數(shù) x, y sp.symbols(x y) f_xy x**2 * y sp.sin(x*y) # 計(jì)算偏導(dǎo)數(shù) partial_x sp.diff(f_xy, x) partial_y sp.diff(f_xy, y) print(f函數(shù) f(x,y) {f_xy}) print(f偏導(dǎo)數(shù) ?f/?x {partial_x}) print(f偏導(dǎo)數(shù) ?f/?y {partial_y}) # 梯度就是由偏導(dǎo)數(shù)組成的向量 gradient_vector sp.Matrix([partial_x, partial_y]) print(f梯度向量 ?f {gradient_vector}) # 計(jì)算在點(diǎn)(1, 2)處的梯度值 grad_at_point gradient_vector.subs({x: 1, y: 2}) print(f在點(diǎn)(1,2)處的梯度: {grad_at_point}) print(f梯度值 (數(shù)值): {[float(val) for val in grad_at_point]})在神經(jīng)網(wǎng)絡(luò)中損失函數(shù)L依賴(lài)于成千上萬(wàn)個(gè)參數(shù)權(quán)重和偏置θ1, θ2, ..., θn。梯度?L(θ)就是一個(gè)包含所有偏導(dǎo)數(shù)?L/?θ_i的向量。梯度下降算法同時(shí)更新所有參數(shù)θ_i_new θ_i_old - η * ?L/?θ_i。反向傳播就是高效計(jì)算這個(gè)巨大梯度向量的算法。從一元導(dǎo)數(shù)到多元梯度概念一脈相承。理解了一元導(dǎo)數(shù)如何衡量變化、如何指導(dǎo)搜索就握住了打開(kāi)深度學(xué)習(xí)優(yōu)化大門(mén)的第一把鑰匙。后續(xù)所有復(fù)雜的模型和算法都是在這個(gè)堅(jiān)實(shí)的地基上建造起來(lái)的。當(dāng)你下次看到loss.backward()這行代碼時(shí)希望你能會(huì)心一笑知道它背后正在進(jìn)行的是一場(chǎng)基于導(dǎo)數(shù)計(jì)算的、精妙的多維空間下山之旅。