php梯度区间计算,快速计算梯度的魔法--反向传播算法
2.1 計(jì)算梯度的數(shù)值方法
第一次實(shí)驗(yàn)我留的一個(gè)課后作業(yè)里問你是否能夠想出一個(gè)求解梯度的辦法,其實(shí)不難想到一種簡單的辦法就是使用“數(shù)值法”計(jì)算梯度。
辦法很簡單,就是對(duì)于損失函數(shù)中的一個(gè)初始取值為a0的參數(shù)a,先計(jì)算當(dāng)前的損失函數(shù)值J0,再保持其他參數(shù)不變,而使a改變一個(gè)很小的量,比如變成a0+0.000001,再求改變之后的損失函數(shù)值J1。然后(J1-J0)/0.000001就是J對(duì)于a的偏導(dǎo)的近似值。我們對(duì)每一個(gè)參數(shù)采用類似的方法求偏導(dǎo),最后將偏導(dǎo)的值組成一個(gè)向量,即為梯度向量。
這個(gè)辦法看上去很簡單,但卻無法應(yīng)用在實(shí)際的神經(jīng)網(wǎng)絡(luò)當(dāng)中。一方面的原因是,我們很難知道對(duì)參數(shù)的改變,有多小才算足夠小,即我們很難保證最后求出的梯度是準(zhǔn)確的。
另一方面的原因是,這種方法計(jì)算量太大,現(xiàn)在的神經(jīng)網(wǎng)絡(luò)中經(jīng)常會(huì)有上億個(gè)參數(shù),而這里每求一個(gè)分量的偏導(dǎo)都要把所有參數(shù)值代入損失函數(shù)求兩次損失函數(shù)值,而且每個(gè)分量都要執(zhí)行這樣的計(jì)算。相當(dāng)于每計(jì)算一次梯度需要2x1億x1億次計(jì)算,而梯度下降算法又要求我們多次(可能是上萬次)計(jì)算梯度。這樣巨大的計(jì)算量即使是超級(jí)計(jì)算機(jī)也很難承受(世界第一的“神威·太湖之光”超級(jí)計(jì)算機(jī)峰值性能為12.5億億次/秒,每秒也只能計(jì)算大概6次梯度)。
所以,我們需要更加高效準(zhǔn)確的算法來計(jì)算梯度,而反向傳播算法正好能滿足我們的需求。
2.2 “計(jì)算圖(compute graph)”與鏈?zhǔn)椒▌t
其實(shí)如果你已經(jīng)理解了鏈?zhǔn)椒▌t,那么可以說,你幾乎已經(jīng)學(xué)會(huì)反向傳播算法了。讓人感到很愉快對(duì)不對(duì),好像什么都還沒做,我們就已經(jīng)掌握了一個(gè)名字看起來有些嚇人的算法。
為了幫助我們真正理解反向傳播算法,我們先來看一下什么是“計(jì)算圖”,我們以第一次實(shí)驗(yàn)提到的sigmoid函數(shù)為例:
它的計(jì)算圖,是這樣的:
此處輸入圖片的描述
我們將sigmoid函數(shù)視為一個(gè)復(fù)合函數(shù),并將其中的每一個(gè)子函數(shù)都視為一個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)按照復(fù)合函數(shù)實(shí)際的運(yùn)算順序鏈接起來,最終得到的F其實(shí)就是sigmoid函數(shù)本身。
根據(jù)求導(dǎo)法則,我們可以求得每一個(gè)節(jié)點(diǎn)對(duì)它直接子節(jié)點(diǎn)的導(dǎo)函數(shù):
此處輸入圖片的描述
最重要的地方來了,再根據(jù)求導(dǎo)鏈?zhǔn)椒▌t,我們現(xiàn)在可以輕易寫出圖中任意一個(gè)高層節(jié)點(diǎn)對(duì)其任意后代節(jié)點(diǎn)的導(dǎo)函數(shù):只需要把連接它們的路徑上的所有部分導(dǎo)函數(shù)都乘起來就可以了。
比如:
dF/dC=(dF/dE)(dE/dC)=(-1/E^2)1=-1/E^2
dF/dA=(dF/dE)(dE/dC)(dC/dB)(dB/dA)=(-1/E^2)(1)(e^B)(-1)=e^B/E^2
2.3 反向傳播算法
到這里反向傳播算法已經(jīng)呼之欲出了,對(duì)于一個(gè)具體的參數(shù)值,我們只需要把每個(gè)節(jié)點(diǎn)的值代入求得的導(dǎo)函數(shù)公式就可以求得導(dǎo)數(shù)(偏導(dǎo)數(shù)),進(jìn)而得到梯度。
這很簡單,我們先從計(jì)算圖的底部開始向上,逐個(gè)節(jié)點(diǎn)計(jì)算函數(shù)值并保存下來。這個(gè)步驟,叫做前向計(jì)算(forward)。
然后,我們從計(jì)算圖的頂部開始向下,逐步計(jì)算損失函數(shù)對(duì)每個(gè)子節(jié)點(diǎn)的導(dǎo)函數(shù),代入前向計(jì)算過程中得到的節(jié)點(diǎn)值,得到導(dǎo)數(shù)值。這個(gè)步驟,叫做反向傳播(backward)或者更明確一點(diǎn)叫做反向梯度傳播。
我們來具體實(shí)踐一下,對(duì)于上圖中的sigmoid函數(shù),計(jì)算x=0時(shí)的導(dǎo)數(shù):
前向計(jì)算:
A=0, B=0, C=1, D=1, E=2, F=-1/4
反向傳播:
dF/dE=-1/E^2=-1/2^2=-1/4
dF/dC=dF/dEdE/dC=-1/4
dF/dB=dF/dCdC/dB=-1/4e^B=-1/41=-1/4
dF/dA=dF/dBdB/dA=-1/4(-1)=1/4
以上就是反向傳播算法的全部內(nèi)容。對(duì)于有1億個(gè)參數(shù)的損失函數(shù),我們只需要2*1億次計(jì)算就可以求出梯度。復(fù)雜度大大降低,速度將大大加快。
2.4 將sigmoid視為一個(gè)整體
sigmoid函數(shù)中沒有參數(shù),在實(shí)際的神經(jīng)網(wǎng)絡(luò)中,我們都是將sigmoid函數(shù)視為一個(gè)整體來對(duì)待,沒必要求它的內(nèi)部節(jié)點(diǎn)的導(dǎo)函數(shù)。
sigmoid函數(shù)的導(dǎo)函數(shù)是什么呢?你可以自己求導(dǎo)試試,實(shí)際上sigmoid(x)'=sigmoid(x)*(1-sigmoid(x))。
2.5 反向傳播算法--動(dòng)手實(shí)現(xiàn)
激動(dòng)人心的時(shí)刻到了,我們終于要開始用python代碼實(shí)現(xiàn)深度神經(jīng)網(wǎng)絡(luò)的過程,這里我們打算對(duì)第一次實(shí)驗(yàn)中的神經(jīng)網(wǎng)絡(luò)示例圖中的“復(fù)合函數(shù)”編寫反向傳播算法。不過為了循序漸進(jìn),我們考慮第一層(輸入層)只有兩個(gè)節(jié)點(diǎn),第二層只有一個(gè)節(jié)點(diǎn)的情況,即如下圖:
此處輸入圖片的描述
注意我們將sigmoid函數(shù)圖像放在了b1節(jié)點(diǎn)后面,代表我們這里對(duì)b1運(yùn)用sigmoid函數(shù)得到了最終的輸出h1。
如果你對(duì)自己比較有信心,可以不看接下來實(shí)現(xiàn)的代碼,自己動(dòng)手試一試。
我們可以先把圖中包含的函數(shù)表達(dá)式寫出來,方便我們之后寫代碼參考:
b1=w11a1+w12a2+bias1
h1=sigmoid(b1)
h1=sigmoid(w11a1+w12a2+bias1)
現(xiàn)在我們創(chuàng)建bp.py文件,開始編寫代碼。先來編寫從第一層到第二層之間的代碼:
import numpy as np
class FullyConnect:
def init(self, l_x, l_y): # 兩個(gè)參數(shù)分別為輸入層的長度和輸出層的長度
self.weights = np.random.randn(l_y, l_x) # 使用隨機(jī)數(shù)初始化參數(shù)
self.bias = np.random.randn(1) # 使用隨機(jī)數(shù)初始化參數(shù)
def forward(self, x):
self.x = x # 把中間結(jié)果保存下來,以備反向傳播時(shí)使用
self.y = np.dot(self.weights, x) + self.bias # 計(jì)算w11*a1+w12*a2+bias1
return self.y # 將這一層計(jì)算的結(jié)果向前傳遞
def backward(self, d):
self.dw = d * self.x # 根據(jù)鏈?zhǔn)椒▌t,將反向傳遞回來的導(dǎo)數(shù)值乘以x,得到對(duì)參數(shù)的梯度
self.db = d
self.dx = d * self.weights
return self.dw, self.db # 返回求得的參數(shù)梯度,注意這里如果要繼續(xù)反向傳遞梯度,應(yīng)該返回self.dx
注意在神經(jīng)網(wǎng)絡(luò)中,我們將層與層之間的每個(gè)點(diǎn)都有連接的層叫做全連接(fully connect)層,所以我們將這里的類命名為FullyConnect。
上面的代碼非常清楚簡潔,我們的全連接層完成了三個(gè)工作:
隨機(jī)初始化網(wǎng)絡(luò)參數(shù)
根據(jù)x計(jì)算這層的輸出y,并前向傳遞給下一層
運(yùn)用求導(dǎo)鏈?zhǔn)椒▌t,將前面的網(wǎng)絡(luò)層向后傳遞的導(dǎo)數(shù)值與本層的相關(guān)數(shù)值相乘,得到最后一層對(duì)本層參數(shù)的梯度。注意這里如果要繼續(xù)反向傳遞梯度(如果后面還有別的層的話),backward()應(yīng)該返回self.dx
然后是第二層的輸入到最后的輸出之間的代碼,也就是我們的sigmoid層:
class Sigmoid:
def init(self): # 無參數(shù),不需初始化
pass
def sigmoid(self, x):
return 1 / (1 + np.exp(x))
def forward(self, x):
self.x = x
self.y = self.sigmoid(x)
return self.y
def backward(self): # 這里sigmoid是最后一層,所以從這里開始反向計(jì)算梯度
sig = self.sigmoid(self.x)
self.dx = sig * (1 - sig)
return self.dx # 反向傳遞梯度
由于我們要多次使用sigmoid函數(shù),所以我們單獨(dú)的把sigmoid寫成了類的一個(gè)成員函數(shù)。
我們這里同樣完成了三個(gè)工作。只不過由于Sigmoid層沒有參數(shù),所以不需要進(jìn)行參數(shù)初始化。同時(shí)由于這里需要反向傳播梯度,所以backward()函數(shù)必須返回self.dx
把上面的兩層拼起來,就完成了我們的總體的網(wǎng)絡(luò)結(jié)構(gòu):
def main():
fc = FullyConnect(2, 1)
sigmoid = Sigmoid()
x = np.array([[1], [2]])
print 'weights:', fc.weights, ' bias:', fc.bias, ' input: ', x
# 執(zhí)行前向計(jì)算
y1 = fc.forward(x)
y2 = sigmoid.forward(y1)
print 'forward result: ', y2
# 執(zhí)行反向傳播
d1 = sigmoid.backward()
dx = fc.backward(d1)
print 'backward result: ', dx
if name == 'main':
main()
請(qǐng)你自行運(yùn)行上面的代碼,并修改輸入的x值。觀察輸出的中間值和最終結(jié)果,并手動(dòng)驗(yàn)證我們計(jì)算的梯度是否正確。
此處輸入圖片的描述
如果你發(fā)現(xiàn)你不知道如何手動(dòng)計(jì)算驗(yàn)證結(jié)果,那說明你還沒有理解反向傳播算法的原理,請(qǐng)回過頭去再仔細(xì)看一下之前的講解。
這里給出完整代碼的下載鏈接,但我還是希望你能盡量自己嘗試寫出代碼,至少自己動(dòng)手將上面的代碼重新敲一遍。這樣學(xué)習(xí)效果會(huì)好得多。
完整代碼文件下載:
上面的代碼將每個(gè)網(wǎng)絡(luò)層寫在不同的類里,并且類里面的接口都是一致的(forward 和 backward),這樣做有很多好處,一是最大程度地降低了不同模塊之間的耦合程度,如果某一個(gè)層里面的代碼需要修改,則只需要修改該層的代碼就夠了,不需要關(guān)心其他層是怎么實(shí)現(xiàn)的。另一方面可以完全自由地組合不同的網(wǎng)絡(luò)層(我們最后會(huì)介紹神經(jīng)網(wǎng)絡(luò)里其他種類的網(wǎng)絡(luò)層)。
實(shí)際上,目前很多用于科研和工業(yè)生產(chǎn)的深度學(xué)習(xí)框架很多都是采用這種結(jié)構(gòu),你可以找一個(gè)深度學(xué)習(xí)框架(比如caffe)看看它的源碼,你會(huì)發(fā)現(xiàn)里面就是這樣一個(gè)個(gè)寫好的網(wǎng)絡(luò)層。
三、實(shí)驗(yàn)總結(jié)
本次實(shí)驗(yàn),我們完全地掌握了梯度下降算法中的關(guān)鍵--反向傳播算法。至此,神經(jīng)網(wǎng)絡(luò)中最基本的東西你已經(jīng)全部掌握了。你現(xiàn)在完全可以自己嘗試構(gòu)建神經(jīng)網(wǎng)絡(luò)并使用反向傳播算法優(yōu)化網(wǎng)絡(luò)中的參數(shù)。
如果你把到此為止講的東西差不多都弄懂了,那非常恭喜你,你應(yīng)該為自己感到驕傲。如果你暫時(shí)還有些東西沒有理解,不要?dú)怵H,回過頭去仔細(xì)看看,到網(wǎng)上查查資料,如果實(shí)在無法理解,問問我們實(shí)驗(yàn)樓的助教,我相信你最終也能理解。
本次實(shí)驗(yàn),我們學(xué)習(xí)了:
使用計(jì)算圖理解反向傳播算法
層次化的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)
四、課后作業(yè)
[選做]請(qǐng)你自己嘗試將我們上面實(shí)現(xiàn)的第二層網(wǎng)絡(luò)的節(jié)點(diǎn)改為2個(gè)(或多個(gè)),注意這里涉及到對(duì)矩陣求導(dǎo),如果你沒學(xué)過相關(guān)知識(shí)可能無法下手。
總結(jié)
以上是生活随笔為你收集整理的php梯度区间计算,快速计算梯度的魔法--反向传播算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [字符串]与[数组]的互相转换
- 下一篇: Windows11 开发版bt磁力链种子