練六天BP:反向傳播的誤差分配與調(diào)試實(shí)戰(zhàn))
連續(xù)做了六天 BP 練習(xí)之后我對“BP”這個縮寫的感覺發(fā)生了一個比較明顯的變化。剛開始我以為只要看懂一張神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)圖、能寫出三層網(wǎng)絡(luò)的正向傳播就算入門了。但到了第六天我才確認(rèn)一件事真正決定你能不能把網(wǎng)絡(luò)用起來的不是正向傳播而是誤差怎么從最后一層準(zhǔn)確送回前面每一層。這個回傳過程才是 BP 練習(xí)中最值得花時間的地方。網(wǎng)上搜索“BP”會同時碰到“bp神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)圖”“bp神經(jīng)網(wǎng)絡(luò)原理”“多層感知機(jī)mlp與bp網(wǎng)絡(luò)”“sap bp”甚至一些工具類技術(shù)文檔。這說明這個縮寫在不同技術(shù)領(lǐng)域里撞車很嚴(yán)重。為了避免誤解這篇文章只圍繞一個方向展開BP 神經(jīng)網(wǎng)絡(luò)中的反向傳播算法以及連續(xù)練習(xí)到第六天時最值得關(guān)注哪些問題。如果你也處在“公式能看懂代碼能跑起來但一旦不收斂就不知道從哪里開始排查”的狀態(tài)下面這段經(jīng)驗(yàn)可能對你有用。它不是一個從入門到精通的完整教程而是從真實(shí)練習(xí)中提煉出的框架怎么理解 BP、怎么著手跑通、出問題時該怎么定位。1. 連續(xù)練了六天 BP我更確定它真正難的不是公式而是誤差分配第一天練習(xí)時我打開筆記本的計(jì)劃是“背公式、看結(jié)構(gòu)、抄一段代碼”。結(jié)果發(fā)現(xiàn)一個很容易被騙過去的假象BP 公式看起來只是幾個偏導(dǎo)數(shù)相乘但等到你真正要把式子對應(yīng)到代碼里的矩陣乘法時才發(fā)現(xiàn)自己并不知道哪一行算的是哪一層梯度。BP 不是損失函數(shù)也不是激活函數(shù)更不是神經(jīng)網(wǎng)絡(luò)里某一個獨(dú)立模塊。它發(fā)生在前向傳播完成、損失函數(shù)已經(jīng)得到預(yù)測值和真實(shí)值差異之后。此時需要回答一個問題每個參數(shù)應(yīng)該向哪個方向調(diào)整多少這個問題的答案靠的是把損失相對于輸出的導(dǎo)數(shù)從輸出層逐層往回傳。數(shù)學(xué)做法是鏈?zhǔn)角髮?dǎo)訓(xùn)練程序里的實(shí)際過程是梯度回傳。沒有 BP多層感知機(jī)就沒有辦法通過誤差修正來學(xué)習(xí)。1.1 BP 網(wǎng)絡(luò)不等于“神經(jīng)網(wǎng)絡(luò)”本身通常說的“BP 神經(jīng)網(wǎng)絡(luò)”在結(jié)構(gòu)上往往是一個多層感知機(jī)也叫 MLP。輸入層負(fù)責(zé)接收特征隱藏層負(fù)責(zé)做非線性變換輸出層給出結(jié)果。BP 則是訓(xùn)練這個 MLP 時的核心梯度算法。所以嚴(yán)格來說結(jié)構(gòu)層面它是層與層之間的矩陣和激活函數(shù)訓(xùn)練層面它依賴前向傳播得到預(yù)測再依賴 BP 得到梯度更新層面它把計(jì)算出的梯度結(jié)合學(xué)習(xí)率調(diào)整網(wǎng)絡(luò)中的權(quán)重和偏置。很多人會把“我用的是 BP 神經(jīng)網(wǎng)絡(luò)”當(dāng)成一種模型類型但實(shí)際上這更多是在描述“我用反向傳播來訓(xùn)練一個多層前饋網(wǎng)絡(luò)”。兩者不是同一個層級的概念雖然日常表達(dá)中經(jīng)常混在一起。1.2 為什么反復(fù)抄公式還是會忘我見過不少筆記這樣寫先從輸出層算誤差然后乘權(quán)重轉(zhuǎn)置再乘激活函數(shù)導(dǎo)數(shù)再往前一層??雌饋砻恳徊蕉紱]錯但第二天合上筆記再寫一遍還是會卡在“為什么是轉(zhuǎn)置”“為什么要逐元素相乘”這兩個問題上。原因是記憶里只有操作步驟沒有“誤差分配”的邏輯。反向傳播的實(shí)質(zhì)是做一件事把輸出端產(chǎn)生的誤差按每一層權(quán)重的影響比例分?jǐn)偦厝ゲ⒏嬖V每個權(quán)重它到底該承擔(dān)多少責(zé)任。這樣理解之后公式里的轉(zhuǎn)置、連乘、激活函數(shù)導(dǎo)數(shù)就不是機(jī)械記憶了。權(quán)重矩陣的形狀決定梯度能不能沿著正確維度傳回去激活函數(shù)的導(dǎo)數(shù)決定信號經(jīng)過該神經(jīng)元后能保留多少多個層之間的連乘決定梯度是否會衰減或放大。你可以把 BP 看成一套歸因邏輯誤差不是憑空出現(xiàn)在最后一層它是由前面很多層共同造成的。要對每一層分別開責(zé)任清單就需要從后往前逐層推導(dǎo)。所以練習(xí)第六天我對“BP 到底在練什么”的判斷已經(jīng)變得清晰它練的不是計(jì)算能力而是建立一種誤差分配直覺??匆娨粋€不收斂的網(wǎng)絡(luò)如果只想調(diào)學(xué)習(xí)率說明你還停留在“調(diào)參”階段如果會先想“梯度到底是在哪一層斷掉的”才說明 BP 的作用被你真正接住了。2. 真正讓我卡住的是誤差傳導(dǎo)鏈從 z 到 a 再到參數(shù)畫結(jié)構(gòu)圖很容易難的是在幾十行代碼里盯住每個矩陣的維度變化。很多人在寫 Python 實(shí)現(xiàn)時第一個報錯往往不是“網(wǎng)絡(luò)不收斂”而是“矩陣形狀配不上”。這背后通常是同一個問題對前向傳播和反向傳播之間的數(shù)據(jù)形狀變化沒有建立連接。2.1 前向傳播負(fù)責(zé)預(yù)測反向傳播負(fù)責(zé)把“責(zé)任”送回去一個最簡單的三層層級關(guān)系大概是輸入特征 X 經(jīng)過輸入層到隱藏層的權(quán)重 W1得到 z1z1 經(jīng)激活函數(shù)得到 a1a1 經(jīng)過隱藏層到輸出層的權(quán)重 W2得到 z2z2 經(jīng)過輸出激活函數(shù)得到預(yù)測值 y_pred。在這個過程里每一層的矩陣形狀必須匹配。反向傳播時則是反過來先計(jì)算損失對輸出激活前信號的導(dǎo)數(shù)再計(jì)算它對 W2 和 b2 的導(dǎo)數(shù)然后透過 W2 把信號傳回 a1再通過激活函數(shù)導(dǎo)數(shù)傳到 z1最后得到對 W1 和 b1 的導(dǎo)數(shù)。這里面最容易出錯的地方有兩處。第一誤差信號在反向回傳時要乘的是前一層的激活輸出而不是當(dāng)前層的輸入?;貍魈荻鹊男螤钐烊灰竽阕鼍仃囖D(zhuǎn)置。第二誤差信號經(jīng)過激活函數(shù)時要逐元素乘上激活函數(shù)在對應(yīng)位置的導(dǎo)數(shù)。這意味著激活函數(shù)的輸出范圍會直接影響梯度量級。當(dāng)你打印出每一層的 shape 卻發(fā)現(xiàn)完全對齊時只能說明“流動的通路”沒問題不等于“流動的信號”合理。信號有沒有消失、有沒有爆炸是另一層問題。2.2 激活函數(shù)不只是做非線性變換它同時決定了反向傳播的“通量”第六天練習(xí)里我刻意對比了兩種常見的隱藏層激活方式sigmoid 和 tanh。如果你用過經(jīng)典教材里的三層 BP 網(wǎng)絡(luò)大概率會默認(rèn)選擇 sigmoid 或 tanh。它們在 0 附近有比較好的非線性但有一個共同特點(diǎn)兩端導(dǎo)數(shù)趨近于 0神經(jīng)元飽和后反向傳播得到的梯度會非常小。更關(guān)鍵的是深度網(wǎng)絡(luò)中梯度要經(jīng)過多層導(dǎo)數(shù)連乘。如果每一層都處于飽和區(qū)這些接近 0 的小數(shù)相乘后傳到前面層級時梯度可能已經(jīng)趨近于 0前面層幾乎收不到有效更新訓(xùn)練會變得極慢。這就是常說的梯度消失。另一種情況如果初始化權(quán)重偏大梯度連乘可能迅速放大出現(xiàn)梯度爆炸訓(xùn)練損失經(jīng)常變成 NaN。現(xiàn)代實(shí)踐里ReLU 一族激活函數(shù)更常用正是因?yàn)檎齾^(qū)間導(dǎo)數(shù)為 1能在一定程度上保持梯度傳導(dǎo)。ReLU 也不是沒有缺點(diǎn)負(fù)區(qū)間導(dǎo)數(shù)為 0積少成多會讓部分神經(jīng)元死亡。所以激活函數(shù)的選用從來不只是影響表達(dá)能力的“非線性函數(shù)”它同時是反向傳播鏈路上控制信號衰減還是保留的閥門。2.3 參數(shù)初始化會直接左右第一次反向傳播的梯度剛接觸 BP 時很多人初始化權(quán)重直接寫np.random.randn覺得隨機(jī)就行。第六天我發(fā)現(xiàn)這個環(huán)節(jié)對后續(xù)訓(xùn)練影響比想象中大。輸入特征如果整體數(shù)值量級是 0.1 到 1而初始化權(quán)重矩陣的標(biāo)準(zhǔn)差達(dá)到 0.5 到 1那么經(jīng)過多層矩陣乘法和激活函數(shù)后信號會非常容易進(jìn)入飽和區(qū)。第一次反向傳播計(jì)算出的梯度可能已經(jīng)小到幾乎沒有任何更新意義。這也是為什么實(shí)踐中建議把小隨機(jī)數(shù)初始化作為一種默認(rèn)選擇。比較保守的一種寫法是把標(biāo)準(zhǔn)差設(shè)置成 0.01 到 0.1 之間或者使用專門設(shè)計(jì)的初始化方法。具體選擇與激活函數(shù)有關(guān)核心目的都是讓網(wǎng)絡(luò)在早期避免過飽和。手工練習(xí)時你不需要追求復(fù)雜公式但要有意識地做一次權(quán)重標(biāo)準(zhǔn)差對照實(shí)驗(yàn)用 0.5 和用 0.05訓(xùn)練曲線可能差非常多。這個現(xiàn)象背后其實(shí)說明了一個容易被忽略的事實(shí)BP 不是獨(dú)立決定模型效果的網(wǎng)絡(luò)初始化決定了第一次梯度從什么狀態(tài)開始傳導(dǎo)學(xué)習(xí)率決定了每一次梯度被使用的程度數(shù)據(jù)標(biāo)準(zhǔn)化決定矩陣運(yùn)算的實(shí)數(shù)尺度。第六天開始我傾向于把 BP 放在整個訓(xùn)練流程里看待而不是只盯著一組求導(dǎo)結(jié)果。3. 第六天的最小練習(xí)用一個手工反向傳播跑通小樣本既然練習(xí)進(jìn)入第六天我不建議再繼續(xù)“只看不寫”。最有效的方法是把搭建框架的工作放下來用 NumPy 或普通 Python 寫一個小網(wǎng)絡(luò)完成一次完整的前向、損失計(jì)算、反向傳播、參數(shù)更新。下面是用 NumPy 演示一個 3 層小網(wǎng)絡(luò)的最小實(shí)現(xiàn)。這個例子只用于理解 BP 的執(zhí)行順序不用做生產(chǎn)模型。它展示了每個矩陣在反向傳播中應(yīng)該怎樣轉(zhuǎn)換。3.1 準(zhǔn)備一組極小的輸入和標(biāo)簽我習(xí)慣用 4 到 5 個樣本做玩具數(shù)據(jù)。數(shù)據(jù)越少越容易打印每個中間變量也越容易在紙上核對一個樣本的完整計(jì)算鏈。這里準(zhǔn)備 4 條樣本每條 3 個特征import numpy as np def sigmoid(x): return 1 / (1 np.exp(-x)) X np.array([ [0.1, 0.8, 0.3], [0.7, 0.4, 0.5], [0.2, 0.1, 0.9], [0.5, 0.9, 0.3] ]) y np.array([[0], [1], [1], [0]])在真正跑通大規(guī)模數(shù)據(jù)之前這樣一組小樣本足夠讓你看清向前傳播和反向更新的完整鏈路。如果需要打印中間值網(wǎng)絡(luò)越簡單越容易追蹤。3.2 初始化權(quán)重并完成一次完整訓(xùn)練循環(huán)輸入層 3 個特征隱藏層用 4 個神經(jīng)元輸出層 1 個神經(jīng)元。隱藏層激活用 tanh輸出層激活用 sigmoid二分類損失用交叉熵np.random.seed(42) # 3 - 4 w1 np.random.normal(0, 0.1, size(3, 4)) b1 np.zeros((1, 4)) # 4 - 1 w2 np.random.normal(0, 0.1, size(4, 1)) b2 np.zeros((1, 1)) learning_rate 0.5 n_samples X.shape[0] for epoch in range(1, 4001): # 前向傳播 z1 X w1 b1 a1 np.tanh(z1) z2 a1 w2 b2 y_pred sigmoid(z2) # 損失函數(shù)二分類交叉熵加 1e-8 防止 log(0) loss -np.mean( y * np.log(y_pred 1e-8) (1 - y) * np.log(1 - y_pred 1e-8) ) # 反向傳播 # 輸出層交叉熵 sigmoid 合成后梯度簡化為 y_pred - y delta2 (y_pred - y) / n_samples dw2 a1.T delta2 db2 np.sum(delta2, axis0, keepdimsTrue) # 誤差繼續(xù)回到隱藏層 delta1 (delta2 w2.T) * (1 - a1 ** 2) dw1 X.T delta1 db1 np.sum(delta1, axis0, keepdimsTrue) # 參數(shù)更新 w1 - learning_rate * dw1 b1 - learning_rate * db1 w2 - learning_rate * dw2 b2 - learning_rate * db2 if epoch % 500 0: print(fepoch {epoch}, loss {loss:.6f})這里有幾個點(diǎn)值得停下來想一下。delta2計(jì)算的是損失函數(shù)對輸出層激活前信號 z2 的梯度。因?yàn)槎诸惤徊骒丶?sigmoid 的組合在實(shí)際推導(dǎo)中會出現(xiàn)化簡最終得到y(tǒng)_pred - y。初學(xué)者如果拿均方誤差或其它損失硬套這個式子就不成立。這提醒我們在實(shí)際調(diào)試時不要盲抄別人代碼里的“梯度公式”要弄清楚損失類型和輸出激活函數(shù)之間的搭配關(guān)系。delta1中1 - a1 ** 2來自 tanh 的導(dǎo)數(shù)。如果想換成 sigmoid 隱藏層就要換成a1 * (1 - a1)。這種替換不是簡單怕上下文而是數(shù)學(xué)鏈上對應(yīng)不同的變化率。3.3 手動練習(xí)中應(yīng)該重點(diǎn)觀察哪些量跑通代碼只算第一層目標(biāo)。第六天練習(xí)我建議你在循環(huán)里額外檢查epoch 前幾次和后幾次y_pred分別在什么范圍dw1和dw2的數(shù)量級差距如果學(xué)習(xí)率從 0.5 改成 2loss是更快下降還是迅速變成 NaN如果初始權(quán)重標(biāo)準(zhǔn)差從 0.1 改成 1訓(xùn)練初期梯度會變成什么樣子。不要每一步都只盯著 “l(fā)oss 降了沒有”。你應(yīng)該能解釋 loss 變化和前向輸出分布之間的關(guān)系。比如損失下降變慢可能是網(wǎng)絡(luò)更接近局部最優(yōu)也可能是學(xué)習(xí)率太小如果訓(xùn)練集很小模型很容易記住樣本此時 loss 很低不代表泛化能力就好。4. 連做幾天練習(xí)后我把調(diào)參經(jīng)驗(yàn)收成一套五步排查法如果你在第六天已經(jīng)能跑通上面的小例子下一步不是繼續(xù)加層也不是立刻換大型框架而是學(xué)會在沒有教程的情況下做排查。第六天到第七天之間可以嘗試故意破壞網(wǎng)絡(luò)然后通過一套固定次序來定位問題。這是我總結(jié)出來的排查路徑優(yōu)先從成本最低、最容易越過的環(huán)節(jié)開始。4.1 先查輸入和標(biāo)簽而不是一上來調(diào)參網(wǎng)絡(luò)訓(xùn)練失敗時最常見的問題是數(shù)據(jù)本身。特征是否出現(xiàn)了 NaN 或無窮值特征量級是否相差巨大比如一列是 0.1另一列是 10000隱藏層做矩陣乘法時數(shù)值波動會把訓(xùn)練帶偏。標(biāo)簽是否連續(xù)值和分類值類型混用訓(xùn)練集和驗(yàn)證集是否真的互相獨(dú)立這個步驟的成本最低但很多場景下最先被忽略??吹?loss 不降人的第一反應(yīng)通常是調(diào)學(xué)習(xí)率或換網(wǎng)絡(luò)結(jié)構(gòu)可真正原因可能是輸入數(shù)據(jù)里有臟值。4.2 再查損失函數(shù)與輸出層是否匹配損失選擇錯了反向傳播公式可能完全不同。如果輸出層是 sigmoid用交叉熵是一個常見組合如果輸出層是線性輸出又用了交叉熵就可能出現(xiàn)log(負(fù)值)之類的計(jì)算錯誤。用均方誤差也沒問題但梯度公式會改變前幾個 epoch 的學(xué)習(xí)曲線也會不同。所以當(dāng)現(xiàn)象是 loss 出現(xiàn)明顯異常比如保持在固定數(shù)字不下降、突然變成 NaN 時先回頭檢查損失函數(shù)和數(shù)據(jù)標(biāo)簽的格式。4.3 然后打印每一層梯度的 shape 與數(shù)值范圍很多人覺得梯度檢查很難其實(shí)手動實(shí)現(xiàn)時只要打印每個關(guān)鍵變量就好print(fepoch {epoch}, y_pred[0]: {y_pred[0][0]:.4f}) print(fdw1 mean: {np.mean(np.abs(dw1)):.6f}) print(fdw2 mean: {np.mean(np.abs(dw2)):.6f})如果dw1相對dw2小了非常多說明梯度在往前面層回傳時已經(jīng)衰減嚴(yán)重。這時就要檢查初始化標(biāo)準(zhǔn)差、隱藏層激活函數(shù)選擇以及網(wǎng)絡(luò)層數(shù)。如果兩個梯度都出現(xiàn)了極大數(shù)字則要懷疑梯度爆炸調(diào)整方向是降低學(xué)習(xí)率、縮小初始化方差或檢查輸入數(shù)據(jù)量級。4.4 再觀察 loss 曲線的形態(tài)手動練習(xí)里最常見的三種 loss 形態(tài)是一直不降先檢查輸入與標(biāo)簽順序是否對齊再檢查梯度是否幾乎為 0。下降后反彈并最終變成 NaN學(xué)習(xí)率偏高或數(shù)據(jù)里有異常極大值。前幾百輪下降快之后完全停滯可能只是接近當(dāng)前結(jié)構(gòu)的能力上限此時加深或加寬網(wǎng)絡(luò)意義不大要看特征工程、數(shù)據(jù)量和模型結(jié)構(gòu)是否匹配。4.5 給調(diào)整動作做一次單變量實(shí)驗(yàn)不要一鼓作氣改三個參數(shù)調(diào)參實(shí)驗(yàn)時避免同時改學(xué)習(xí)率、改隱藏層節(jié)點(diǎn)數(shù)、改初始化方式。第六天我自己踩過的一個最明顯的坑就是一開始把一堆參數(shù)全部改了等到 loss 終于正常反而說不清是哪個動作起了作用。后來改成一次只改一個維度記錄網(wǎng)絡(luò)能否收斂、收斂到多少、每輪耗時是多少。針對 BP 練習(xí)可以整理成一張極簡登記表調(diào)整項(xiàng)修改前修改后loss 變化影響判斷學(xué)習(xí)率0.10.5下降變快但波動增加方向正確需觀察初始化標(biāo)準(zhǔn)差0.50.05前期能更新初始飽和問題隱藏層激活sigmoidtanh前期梯度變大與網(wǎng)絡(luò)結(jié)構(gòu)匹配這張表本身不是答案但它能幫你把第六天獲得的經(jīng)驗(yàn)沉淀成可反復(fù)使用的判斷依據(jù)。否則練習(xí)再多也只是在重復(fù)試參數(shù)。5. BP 練習(xí)的價值邊界什么情況該用它什么情況不該靠堆層解決BP 在神經(jīng)網(wǎng)絡(luò)訓(xùn)練中的地位非常重要但它解決的不是所有問題。用第六天的心態(tài)復(fù)盤我會這樣給 BP 劃清適用邊界。5.1 表格型、非線性、中小規(guī)模問題適合先練手對一個樣本量幾千、特征數(shù)量幾十或幾百、以表格和數(shù)據(jù)為主的二分類或多分類問題搭建一個手動 BP 網(wǎng)絡(luò)或小規(guī)模 MLP 是完全可行的。它沒有卷積核那種超大參數(shù)復(fù)雜性也沒有注意力機(jī)制等結(jié)構(gòu)正反向傳播都能用矩陣運(yùn)算清楚表達(dá)。在這樣的場景里練習(xí)你能看到每一層權(quán)重變化訓(xùn)練過程相對透明排錯路徑也比較直觀。這也是第 1 到第 6 天最適合反復(fù)做的核心動作。5.2 圖像、文本、超大規(guī)模場景需要交給更復(fù)雜結(jié)構(gòu)BP 的思想在 CNN、RNN、Transformer 等現(xiàn)代網(wǎng)絡(luò)里依然作為梯度計(jì)算框架存在但框架會利用自動微分替你完成導(dǎo)數(shù)計(jì)算。自動微分同樣基于反向模式只是把局部導(dǎo)數(shù)規(guī)則固化成了計(jì)算圖。因此你必須意識到練好 BP 不等于能夠手工推導(dǎo)所有大模型的全部梯度。它更像是一把鑰匙。你通過 BP 理解了梯度從輸出到輸入是怎么流動的再去看 PyTorch 或 TensorFlow 的反向傳播機(jī)制時才不會覺得那是一個黑盒。5.3 不要把 BP 問題等同于“過擬合”“欠擬合”問題第 6 天很常見的一個反直覺現(xiàn)象是同一份玩具數(shù)據(jù)網(wǎng)絡(luò)在訓(xùn)練集上 loss 很低但很難在新樣本上保持效果。這不是 BP 算法本身的鍋而是數(shù)據(jù)規(guī)模太小、模型容量過高、訓(xùn)練輪次過多共同造成的結(jié)果。BP 提供的是“如何修正權(quán)重”的梯度路徑但它不會自動阻止模型死記硬背。真正需要補(bǔ)的是正則化、早停、更多數(shù)據(jù)或調(diào)整模型復(fù)雜度。如果只學(xué) BP卻不知道過擬合的存在就會把問題錯誤歸因到優(yōu)化算法上。6. 第六天結(jié)束時的真實(shí)狀態(tài)我還需要一套每天都可執(zhí)行的復(fù)盤流程如果能順利走完前五部分這個第六天已經(jīng)是有收獲的。但要讓練習(xí)繼續(xù)有價值最好把獲得經(jīng)驗(yàn)放到一個持續(xù)運(yùn)行的循環(huán)里。6.1 每個練習(xí)日只定一個目標(biāo)我常見的做法是以每第 n 天為單位設(shè)定可驗(yàn)證的成果。例如第 1 天能獨(dú)立畫出 3 層網(wǎng)絡(luò)結(jié)構(gòu)并標(biāo)出輸入、權(quán)重、激活、輸出位置。第 2 天能說明前向傳播中每個矩陣乘法的作用。第 3 天能用數(shù)值差分法核對一個權(quán)重分量的梯度。第 4 天能解釋常見激活函數(shù)在反向傳播中的導(dǎo)數(shù)表現(xiàn)。第 5 天能在十行代碼里定位矩陣 shape 導(dǎo)致的問題。第 6 天能在一個手動實(shí)現(xiàn)網(wǎng)絡(luò)中完成單次反向傳播的逐步打印。到了第 6 天你已經(jīng)不是為了學(xué)“BP 是什么”而練習(xí)而是開始用它做排查和分析。這種感覺和第一天完全不同。第一天更多是在背知識第六天你已經(jīng)把 BP 當(dāng)成一套判斷工具來對待。6.2 下一個練習(xí)日最適合做的三件事如果讓我給準(zhǔn)備跨過第 6 天的自己一個建議我會選下面三件事第一把手工實(shí)現(xiàn)中的損失換成均方誤差重新推導(dǎo)一遍輸出層梯度。這樣可以打破“抄代碼”式的舒適區(qū)逼迫自己理解公式變化的原因。第二讓模型在同一個數(shù)據(jù)集上完成一次梯度檢查。所謂梯度檢查是用數(shù)值近似去驗(yàn)證解析梯度是否正確。實(shí)現(xiàn)方法是在某個權(quán)重上做一個極小的擾動看看損失變化方向和幅度是否與你推導(dǎo)的梯度一致。這個過程可能花掉一部分時間但它能幫你確認(rèn)反向傳播實(shí)現(xiàn)沒有隱藏的形狀錯位或公式錯誤。第三記錄三份“失敗現(xiàn)象和分析”。不要只記成功的運(yùn)行結(jié)果更要記訓(xùn)練不收斂時的日志。一周以后再回看這些記錄你會發(fā)現(xiàn)自己對每個異?,F(xiàn)象背后的原因明顯更敏感。epoch 2000, loss 0.0158 params: lr0.5, init_std0.1, hidden4 問題記錄剛開始用 lr2前 100 輪 loss 反而變成 NaN 調(diào)整為 0.5 后能正常下降。 原因初始梯度和輸入量級被放大發(fā)生爆炸。6.3 關(guān)于“BP到底是什么”的總結(jié)性判斷練習(xí)到第六天我會給出一個綜合描述BP 不是某個獨(dú)立工具也不是某個模型格式而是一種把損失信號沿著前向網(wǎng)絡(luò)回傳的梯度計(jì)算方法。它是一種算法思想?yún)s在機(jī)器學(xué)習(xí)工程里改變了整套訓(xùn)練方式。沒有它早期多層感知機(jī)的能力就難被有效發(fā)掘。但要把這個方法用得得心應(yīng)手靠的是重復(fù)練習(xí)、調(diào)試意識和對數(shù)據(jù)與參數(shù)的敏感。第 1 天到第 6 天真正發(fā)生變化的不是“會描述 BP”的能力而是“訓(xùn)練失敗時對問題層的估計(jì)能力”。如果讀到這里的你也在某個類似練習(xí)的第 6 天感到收效有限建議先別急著學(xué)更復(fù)雜結(jié)構(gòu)回到自己手動網(wǎng)絡(luò)里故意制造一個小毛病再按輸入、損失、梯度、更新、觀測這五步順序去修復(fù)一次。這往往比再看十篇資料更有效。