前面介紹了神經(jīng)網(wǎng)絡(luò)斯议,并通過(guò)數(shù)值微分計(jì)算了神經(jīng)網(wǎng)絡(luò)的權(quán)重參數(shù)以及偏置量(bias)鞍爱。
雖然數(shù)值微分實(shí)現(xiàn)起來(lái)比較容易敲才,但是在計(jì)算上花費(fèi)的時(shí)間卻比較多樟遣。
下面重點(diǎn)介紹一個(gè)高效計(jì)算權(quán)重以及偏置量的梯度方法——誤差反向傳播法喘批。
要點(diǎn)具體如下。
·激活函數(shù)層的實(shí)現(xiàn)。
·Affine層的實(shí)現(xiàn)。
·Softmax層的實(shí)現(xiàn)低矮。
·整體實(shí)現(xiàn)。
·正則化懲罰被冒。
激活函數(shù)層的實(shí)現(xiàn) 通過(guò)計(jì)算圖來(lái)理解誤差反向傳播法這個(gè)思想是參考了CS231n(斯坦福大學(xué)的深度學(xué)習(xí)課程)军掂,計(jì)算圖被定義為有向圖,其中昨悼,節(jié)點(diǎn)對(duì)應(yīng)于數(shù)學(xué)運(yùn)算蝗锥,計(jì)算圖是表達(dá)和評(píng)估數(shù)學(xué)表達(dá)式的一種方式。
例如率触, 我們可以繪制上述數(shù)學(xué)等式的計(jì)算圖具有一個(gè)加法節(jié)點(diǎn)终议,這個(gè)節(jié)點(diǎn)有兩個(gè)輸入變量x和y,以及一個(gè)輸出q葱蝗。
下面我們?cè)賮?lái)列舉一個(gè)示例穴张,稍微復(fù)雜一些,等式的計(jì)算圖两曼,(x+y)*z的計(jì)算圖ReLU反向傳播實(shí)現(xiàn) 現(xiàn)在陆馁,我們利用計(jì)算圖的思路來(lái)實(shí)現(xiàn)ReLU激活函數(shù)的反向傳播,首先我們回顧一下激活函數(shù)ReLU的前向傳播:
如果前向傳播時(shí)的輸入x大于0合愈,則將這個(gè)x原封不動(dòng)地傳給下一層;如果輸入的x小于0击狮,則將0傳給下一層佛析。具體表達(dá)方程式如下:
通過(guò)上述方程式,我們可以求出y關(guān)于x的導(dǎo)數(shù)彪蓬,其中寸莫,dout為上一層傳過(guò)來(lái)的導(dǎo)數(shù):
ReLU前向傳播利用Python實(shí)現(xiàn)的代碼如下:
class Relu:
? ?def __init__(self):
? ? ? ?self.x = None
? ?def forward(self,x):
? ? ? ?self.x = np.maximum(0,x)
? ? ? ?out = self.x
? ? ? ?return out
? ?def backward(self,dout):
? ? ? ?dx = dout
dx[self.x <=0] = 0
? ? ? ?return dx
Sigmoid反向傳播實(shí)現(xiàn) 接下來(lái),我們來(lái)實(shí)現(xiàn)Sigmoid函數(shù)的反向傳播档冬,Sigmoid函數(shù)公式如下所示:
如果使用計(jì)算圖來(lái)表示的話膘茎,Sigmoid計(jì)算圖 現(xiàn)在,從右向左依次解說(shuō)如下酷誓。 對(duì)于第一個(gè)步驟y=1/1+exp(-x)披坏,可以設(shè)置為x=1+exp(-x),那么盐数,又因?yàn)榘舴鳎宰詈蟆?
對(duì)于第二個(gè)步驟1+exp(-x),進(jìn)行反向傳播時(shí)帚屉,會(huì)將上游的值-y2乘以本階段的導(dǎo)數(shù)谜诫,對(duì)于1+exp(-x)求導(dǎo)得到的導(dǎo)數(shù)為-exp(-x),因?yàn)閑-x的導(dǎo)數(shù)為-e-x攻旦。
所以第二步的導(dǎo)數(shù)為-y2*(-e-x)=y2*(e-x)喻旷。 第三個(gè)步驟的加法運(yùn)算不會(huì)改變導(dǎo)數(shù)值,接著-x的導(dǎo)數(shù)為-1牢屋,所以對(duì)于這個(gè)階段需要將y2*(e-x)*-1且预,最后乘法運(yùn)算還需要乘以-1。
所以最終求得的導(dǎo)數(shù)為y2*exp(-x)伟阔,進(jìn)行一下整理得到的輸出為y(1-y)辣之,最后乘以上一層的求導(dǎo)結(jié)果,就會(huì)作為本階段Sigmoid函數(shù)的求導(dǎo)結(jié)果了皱炉,最后將這個(gè)結(jié)果傳給下一層(一般來(lái)說(shuō)應(yīng)該是Affine層)怀估。
對(duì)于Python實(shí)現(xiàn)來(lái)說(shuō),具體實(shí)現(xiàn)代碼如下合搅。 ?
class _sigmoid:
? ?def __init__(self):
? ? ? ?self.out = None
def forward(self,x):
? ? ? ?out = 1/ (1+np.exp(-x))
? ? ? ?self.out = out
? ? ? ?return out
? ?def backward(self,dout):
? ? ? ?dx = dout *self.out*(1-self.out)
? ? ? ?return dx
Affine層的實(shí)現(xiàn) Affine的英文翻譯是神經(jīng)網(wǎng)絡(luò)中的一個(gè)全連接層多搀。
仿射(Affine)的意思是前面一層中的每一個(gè)神經(jīng)元都連接到當(dāng)前層中的每一個(gè)神經(jīng)元。在許多方面灾部,這是神經(jīng)網(wǎng)絡(luò)的“標(biāo)準(zhǔn)”層
仿射層通常被加在卷積神經(jīng)網(wǎng)絡(luò)或循環(huán)神經(jīng)網(wǎng)絡(luò)中作為最終預(yù)測(cè)前的輸出的頂層康铭。
仿射層的一般形式為y=f(W*x+b),其中赌髓,x是層輸入从藤,w是參數(shù),b是一個(gè)偏置量锁蠕,f是一個(gè)非線性激活函數(shù)夷野。
對(duì)X(矩陣)的求導(dǎo),可以參看如下公式(此處省略推導(dǎo)過(guò)程)需要注意的是荣倾,X和形狀相同悯搔,W和的形狀相同): ? ?之前列舉的示例是對(duì)于一個(gè)X,如果是多個(gè)X舌仍,那么其形狀將從原來(lái)的(2妒貌,)變?yōu)椋∟,2)铸豁。
如果加上偏置量的話灌曙,偏置量會(huì)被加到各個(gè)X·W中去,比如N=3(數(shù)據(jù)為3個(gè)的時(shí)候)推姻,偏置量會(huì)被分別加到這3個(gè)數(shù)據(jù)中去平匈,因此偏置量,f是一個(gè)非線性激活函數(shù)。
對(duì)X(矩陣)的求導(dǎo)增炭,可以參看如下公式(此處省略推導(dǎo)過(guò)程)需要注意的是忍燥,X和形狀相同,W和的形狀相同): ? ?之前列舉的示例是對(duì)于一個(gè)X隙姿,如果是多個(gè)X梅垄,那么其形狀將從原來(lái)的(2,)變?yōu)椋∟输玷,2)队丝。
如果加上偏置量的話,偏置量會(huì)被加到各個(gè)X·W中去欲鹏,比如N=3(數(shù)據(jù)為3個(gè)的時(shí)候)机久,偏置量會(huì)被分別加到這3個(gè)數(shù)據(jù)中去,因此偏置量的反向傳播會(huì)對(duì)這三個(gè)數(shù)據(jù)的導(dǎo)數(shù)按照第0軸的方向上的元素進(jìn)行求和赔嚎。
其中每一個(gè)偏置量的求導(dǎo)公式都可以表示為: ? ?對(duì)于上述所講的內(nèi)容膘盖,其Python實(shí)現(xiàn)代碼如下:
class Affine:
? ?def __init__(self,W,b):
? ? ? ?self.W = W
? ? ? ?self.b = b
? ? ? ?self.x = None
? ? ? ?self.dW = None
? ? ? ?self.db = None
? ?def forward(self,x):
? ? ? ?self.x = x
? ? ? ?out = np.dot(x,self.W) + self.b
? ? ? ?return out
? ?def backward(self,dout):
? ? ? ?dx = np.dot(dout,self.W.T)
? ? ? ?self.dW = np.dot(self.x.T,dout)
? ? ? ?self.db = np.sum(dout,axis=0)
? ? ? ?return dx
Softmaxwithloss層的實(shí)現(xiàn) 假設(shè)網(wǎng)絡(luò)最后一層的輸出為z,經(jīng)過(guò)Softmax后輸出為p尤误,真實(shí)標(biāo)簽為y(one-hot編碼)侠畔,其中,C表示共有C個(gè)類別损晤,那么損失函數(shù)為:
因?yàn)閜是z經(jīng)過(guò)Softmax函數(shù)計(jì)算后的輸出软棺,即p=softmax(z)。其中尤勋, ? ?求導(dǎo)過(guò)程分為i=j和i喘落!=j兩種情況,分別如下:
當(dāng)i=j的時(shí)候最冰,得到的求導(dǎo)解為pj(1-pj)揖盘。 當(dāng)i!=j的時(shí)候锌奴,得到的求導(dǎo)解為-pipj。 最終整理一下可以得到憾股,Loss對(duì)z的求導(dǎo)為: ? ?其Python的實(shí)現(xiàn)代碼具體如下:
class SoftmaxWithLoss:
? ?def __init__(self):
? ? ? ?self.loss = None #損失
? ? ? ?self.p = None # Softmax的輸出
? ? ? ?self.y = None #監(jiān)督數(shù)據(jù)代表真值鹿蜀,one-hot vector
? ?def forward(self,x,y):
? ? ? ?self.y = y
? ? ? ?self.p = softmax(x)
? ? ? ?self.loss = cross_entropy_error(self.p,self.y)
? ? ? ?return self.loss
? ?def backward(self,dout=1):
? ? ? ?batch_size = self.y.shape[0]
dx = (self.p - self.y) / batch_size
? ? ? ?return dx ?
上述代碼實(shí)現(xiàn)是利用了之前實(shí)現(xiàn)的Softmax和cross_entropy_error函數(shù),值得注意的是服球,進(jìn)行反向傳播的時(shí)候茴恰,應(yīng)將需要傳播的值除以批的大小(batch_size)斩熊,并將單個(gè)數(shù)據(jù)的誤差傳遞給前面的層往枣。
基于數(shù)值微分和誤差反向傳播的比較 到目前為止,我們介紹了兩種求梯度的方法:
一種是基于數(shù)值微分的方法,另一種是基于誤差反向傳播的方法分冈,對(duì)于數(shù)值微分來(lái)說(shuō)圾另,它的計(jì)算非常耗費(fèi)時(shí)間,如果讀者對(duì)于誤差反向傳播掌握得非常好的話雕沉,那么根本就沒(méi)有必要使用到數(shù)值微分〖牵現(xiàn)在的問(wèn)題是,我們?yōu)槭裁匆榻B數(shù)值微分呢坡椒?
原因很簡(jiǎn)單扰路,數(shù)值微分的優(yōu)點(diǎn)就在于其實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,一般情況下倔叼,數(shù)值微分實(shí)現(xiàn)起來(lái)不太容易出錯(cuò)汗唱,而誤差反向傳播法的實(shí)現(xiàn)就非常復(fù)雜,且很容易出錯(cuò)丈攒,所以經(jīng)常會(huì)比較數(shù)值微分和誤差反向傳播的結(jié)果(兩者的結(jié)果應(yīng)該是非常接近的)哩罪,以確認(rèn)我們書寫的反向傳播邏輯是正確的。這樣的操作就稱為梯度確認(rèn)(gradientcheck)肥印。
數(shù)值微分和誤差反向傳播這兩者的比較誤差應(yīng)該是非常小的识椰,實(shí)現(xiàn)代碼具體如下:
from collections import OrderedDict
class TwoLayerNet:
? ?def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
? ? ? ?#初始化權(quán)重
? ? ? ?self.params = {}
? ? ? ?self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
? ? ? ?self.params['b1'] = np.zeros(hidden_size)
? ? ? ?self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
? ? ? ?self.params['b2'] = np.zeros(output_size)
? ? ? ?#生成層
? ? ? ?self.layers = OrderedDict()
? ? ? ?self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
? ? ? ?self.layers['Relu1'] = Relu()
? ? ? ?self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
? ? ? ?self.layers['Relu2'] = Relu()
? ? ? ?self.lastLayer = SoftmaxWithLoss()
? ?def predict(self, x):
? ? ? ?for layer in self.layers.values():
? ? ? ? ? ?x = layer.forward(x)
? ? ? ?return x
? ?# x:輸入數(shù)據(jù), y:監(jiān)督數(shù)據(jù)
? ?def loss(self, x, y):
? ? ? ?p = self.predict(x)
? ? ? ?return self.lastLayer.forward(p, y)
? ?def accuracy(self, x, y):
? ? ? ?p = self.predict(x)
? ? ? ?p = np.argmax(y, axis=1)
? ? ? ?if y.ndim != 1 : y = np.argmax(y, axis=1)
? ? ? ?accuracy = np.sum(p == y) / float(x.shape[0])
? ? ? ?return accuracy
? ?# x:輸入數(shù)據(jù), y:監(jiān)督數(shù)據(jù)
? ?def numerical_gradient(self, x, y):
? ? ? ?loss_W = lambda W: self.loss(x, y)
? ? ? ?grads = {}
? ? ? ?grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
? ? ? ?grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
? ? ? ?grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
? ? ? ?grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
? ? ? ?return grads
? ?def gradient(self, x, y):
? ? ? ?# forward
? ? ? ?self.loss(x, y)
? ? ? ?# backward
? ? ? ?dout = 1
? ? ? ?dout = self.lastLayer.backward(dout)
? ? ? ?layers = list(self.layers.values())
? ? ? ?layers.reverse()
? ? ? ?for layer in layers:
? ? ? ? ? ?dout = layer.backward(dout)
? ? ? ?#設(shè)定
? ? ? ?grads = {}
? ? ? ?grads['W1'], grads['b1'] = self.layers['Affine1'].dW, ?self.layers['Affine1'].db
? ? ? ?grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
? ? ? ?return grads
network = TwoLayerNet(input_size=784,hidden_size=50,output_size=10)
x_batch = x_train[:100]
y_batch = y_train[:100]
grad_numerical = network.numerical_gradient(x_batch,y_batch)
grad_backprop = network.gradient(x_batch,y_batch)
for key in grad_numerical.keys():
? ?diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
? ?print(key + ":" + str(diff)) ?從以下輸出結(jié)果中,我們可以觀察到深碱,它們兩者的差值并不是很大腹鹉。
W1:5.9329106471124405e-05
b1:1.844024470884823e-09
W2:0.0007755803070111151
b2:9.234723605880401e-08
這里需要補(bǔ)充一點(diǎn)的是,我們?cè)诖a中使用了OrderedDict這個(gè)類敷硅,OrderedDict是有序字典功咒,“有序”是指它可以“記住”我們向這個(gè)類里添加元素的順序,因此神經(jīng)網(wǎng)絡(luò)的前向傳播只需要按照添加元素的順序調(diào)用各層的Forward方法即可完成處理绞蹦,而相對(duì)的誤差反向傳播則只需要按照前向傳播相反的順序調(diào)用各層的backward方法即可力奋。