作者:李理
目前就職于環(huán)信,即時(shí)通訊云平臺(tái)和全媒體智能客服平臺(tái),在環(huán)信從事智能客服和智能機(jī)器人相關(guān)工作,致力于用深度學(xué)習(xí)來(lái)提高智能機(jī)器人的性能。
相關(guān)文章:
Optimization
這一部分內(nèi)容來(lái)自:CS231n Convolutional Neural Networks for Visual Recognition
簡(jiǎn)介
我們的目標(biāo):x是一個(gè)向量,f(x)是一個(gè)函數(shù),它的輸入是一個(gè)向量(或者認(rèn)為是多變量的函數(shù),這個(gè)輸入向量就是自變量),輸出是一個(gè)實(shí)數(shù)值。我們需要計(jì)算的是f對(duì)每一個(gè)自變量的導(dǎo)數(shù),然后把它們排成一個(gè)向量,也就是梯度。
為什么要求這個(gè)呢?前面我們也講了,我們的神經(jīng)網(wǎng)絡(luò)的損失函數(shù)最終可以看成是權(quán)重weights和bias的函數(shù),我們的目標(biāo)就是調(diào)整這些參數(shù),使得損失函數(shù)最小。
簡(jiǎn)單的表達(dá)式和梯度的解釋
首先我們看一個(gè)很簡(jiǎn)單的函數(shù)f(x,y)=xy,求f對(duì)x和y的偏導(dǎo)數(shù)很簡(jiǎn)單:
首先來(lái)看導(dǎo)數(shù)的定義:
函數(shù)在某個(gè)點(diǎn)的導(dǎo)數(shù)就是函數(shù)曲線(xiàn)在這個(gè)點(diǎn)的斜率,也就是f(x)隨x的變化率。
比如上面的例子,當(dāng)x=4,y=-3時(shí)f(x,y)=-12,f對(duì)x的偏導(dǎo)數(shù)
也就是說(shuō),如果我們固定y=4,然后給x一個(gè)很小的變化h,那么f(x,y)的變化大約是-3*h。
因此乘法的梯度就是
同樣,加法的梯度更簡(jiǎn)單:
最后一個(gè)簡(jiǎn)單函數(shù)是max函數(shù):
這個(gè)導(dǎo)數(shù)是ReLU(x)=max(x,0)的導(dǎo)數(shù),其實(shí)也簡(jiǎn)單,如果x>=y,那么max(x,y)=x,則導(dǎo)數(shù)是1,否則max(x,y)=0,那么對(duì)x求導(dǎo)就是0.
復(fù)雜表達(dá)式的鏈?zhǔn)椒▌t
接下來(lái)看一個(gè)稍微復(fù)雜一點(diǎn)的函數(shù)f(x,y,z)=(x+y)z。我們引入一個(gè)中間變量q,f=qz,q=x+y,我們可以使用鏈?zhǔn)椒▌t求f對(duì)x和y的導(dǎo)數(shù)。
對(duì)y的求導(dǎo)也是類(lèi)似的。
下面是用python代碼來(lái)求f對(duì)x和y的導(dǎo)數(shù)在某一個(gè)點(diǎn)的值。
# 設(shè)置自變量的值
x = -2; y = 5; z = -4
# “前向”計(jì)算f
q = x + y # q becomes 3
f = q * z # f becomes -12
# 從“后”往前“反向”計(jì)算
# 首先是 f = q * z
dfdz = q # 因?yàn)閐f/dz = q, 所以f對(duì)z的梯度是 3
dfdq = z # 因?yàn)閐f/dq = z, 所以f對(duì)q的梯度是 -4
# 然后 q = x + y
dfdx = 1.0 * dfdq # 因?yàn)閐q/dx = 1,所以使用鏈?zhǔn)椒▌t計(jì)算dfdx=-4
dfdy = 1.0 * dfdq # 因?yàn)閐q/dy = 1,所以使用鏈?zhǔn)椒▌t計(jì)算dfdy=-4
我們也可以用計(jì)算圖來(lái)表示和計(jì)算:
綠色的值是feed forward的結(jié)果,而紅色的值是backprop的結(jié)果。
不過(guò)我覺(jué)得cs231n課程的這個(gè)圖沒(méi)有上面blog的清晰,原因是雖然它標(biāo)示出來(lái)了最終的梯度,但是沒(méi)有標(biāo)示出local gradient,我在下面會(huì)畫(huà)出完整的計(jì)算過(guò)程。
反向傳播算法的直覺(jué)解釋
我們?nèi)绻延?jì)算圖的每一個(gè)點(diǎn)看成一個(gè)“門(mén)”(或者一個(gè)模塊),或者說(shuō)一個(gè)函數(shù)。它有一個(gè)輸入(向量),也有一個(gè)輸出(標(biāo)量)。對(duì)于一個(gè)門(mén)來(lái)說(shuō)有兩個(gè)計(jì)算,首先是根據(jù)輸入,計(jì)算輸出,這個(gè)一般很容易。還有一種計(jì)算就是求輸出對(duì)每一個(gè)輸入的偏導(dǎo)數(shù),或者說(shuō)輸出對(duì)輸入向量的”局部“梯度(local gradient)。一個(gè)復(fù)雜計(jì)算圖(神經(jīng)網(wǎng)絡(luò))的計(jì)算首先就是前向計(jì)算,然后反向計(jì)算,反向計(jì)算公式可能看起來(lái)很復(fù)雜,但是如果在計(jì)算圖上其實(shí)就是簡(jiǎn)單的用local gradient乘以從后面?zhèn)鬟^(guò)來(lái)的gradient,然后加起來(lái)。
Sigmoid模塊的例子
接下來(lái)我們看一個(gè)更復(fù)雜的例子:
這個(gè)函數(shù)是一個(gè)比較復(fù)雜的復(fù)合函數(shù),但是構(gòu)成它的基本函數(shù)是如下4個(gè)簡(jiǎn)單函數(shù):
下面是用計(jì)算圖畫(huà)出這個(gè)計(jì)算過(guò)程:
這個(gè)圖有4種gate,加法,乘法,指數(shù)和倒數(shù)。加法有加一個(gè)常數(shù)和兩個(gè)變量相加,乘法也是一樣。
上圖綠色的值是前向計(jì)算的結(jié)果,而紅色的值是反向計(jì)算的結(jié)果,localgraident并沒(méi)有標(biāo)示出來(lái),所以看起來(lái)可能有些跳躍,下面我在紙上詳細(xì)的分解了其中的步驟,請(qǐng)讀者跟著下圖自己動(dòng)手計(jì)算一遍。
上圖就是前向計(jì)算的過(guò)程,比較簡(jiǎn)單。
第二個(gè)圖是計(jì)算local gradient,對(duì)于兩個(gè)輸入的乘法和加法,local gradient也是兩個(gè)值,local gradient的值我是放到圖的節(jié)點(diǎn)上了。
第三個(gè)圖是具體計(jì)算一個(gè)乘法的local gradient的過(guò)程,因?yàn)樯蠄D可能看不清,所以單獨(dú)放大了這一步。
最后計(jì)算真正的梯度,是把local gradient乘以來(lái)自上一步的gradient。不過(guò)這個(gè)例子一個(gè)節(jié)點(diǎn)只有一個(gè)輸出,如果有多個(gè)的話(huà),梯度是加起來(lái)的,可以參考1.4的
上面我們看到把
分解成最基本的加法,乘法,導(dǎo)數(shù)和指數(shù)函數(shù),但是我們也可以不分解這么細(xì)。之前我們也學(xué)習(xí)過(guò)了sigmoid函數(shù),那么我們可以這樣分解:
σ(x)σ(x)的導(dǎo)數(shù)我們之前已經(jīng)推導(dǎo)過(guò)一次了,這里再列一下:
因此我們可以把后面一長(zhǎng)串的gate”壓縮“成一個(gè)gate:
我們來(lái)比較一下,之前前向計(jì)算σ(x)σ(x)需要一次乘法,一次exp,一次加法導(dǎo)數(shù);而反向計(jì)算需要分別計(jì)算這4個(gè)gate的導(dǎo)數(shù)。
而壓縮后前向計(jì)算是一樣的,但是反向計(jì)算可以”利用“前向計(jì)算的結(jié)果
這只需要一次減法和一次乘法!當(dāng)然如果不能利用前向的結(jié)果,我們?nèi)绻枰匦掠?jì)算σ(x)σ(x),那么壓縮其實(shí)沒(méi)有什么用處。能壓縮的原因在于σ函數(shù)導(dǎo)數(shù)的特殊形式。而神經(jīng)網(wǎng)絡(luò)的關(guān)鍵問(wèn)題是在訓(xùn)練,訓(xùn)練性能就取決于這些細(xì)節(jié)。如果是我們自己來(lái)實(shí)現(xiàn)反向傳播算法,我們就需要利用這樣的特性。而如果是使用工具,那么就依賴(lài)于工具的優(yōu)化水平了。
下面我們用代碼來(lái)實(shí)現(xiàn)一下:
w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]
# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function
# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit
上面的例子用了一個(gè)小技巧,就是所謂的stagedbackpropagation,說(shuō)白了就是給中間的計(jì)算節(jié)點(diǎn)起一個(gè)名字。比如dot。為了讓大家熟悉這種技巧,下面有一個(gè)例子。
Staged computation練習(xí)
我們用代碼來(lái)計(jì)算這個(gè)函數(shù)對(duì)x和y的梯度在某一點(diǎn)的值
前向計(jì)算
x = 3 # example values
y = -4
# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # 分子上的sigmoid #(1)
num = x + sigy # 分子 #(2)
sigx = 1.0 / (1 + math.exp(-x)) # 分母上的sigmoid #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # 分母 #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)
反向計(jì)算
# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
# done! phew
需要注意的兩點(diǎn):1.前向的結(jié)果都要保存下來(lái),反向的時(shí)候要用的。2.如果某個(gè)變量有多個(gè)出去的邊,第一次是等于,第二次就是+=,因?yàn)槲覀円巡煌鋈c(diǎn)的梯度加起來(lái)。
下面我們來(lái)逐行分析反向計(jì)算:
(8)f=num*invden
local gradient
而上面?zhèn)鬟^(guò)來(lái)的梯度是1,所以dnum=1*invden。注意變量的命名規(guī)則,df/dnum就命名為dnum【省略了df,因?yàn)槟J(rèn)我們是求f對(duì)所有變量的偏導(dǎo)數(shù)】
同理:dinvden=num
(7)invden=1.0/den
local gradient是(-1.0/(den**2)),然后乘以上面來(lái)的dinvden
(6)den=sigx+xpysqr
這個(gè)函數(shù)有兩個(gè)變量sigx和xpysqr,所以需要計(jì)算兩個(gè)local梯度,然后乘以dden
加法的local梯度是1,所以就是(1)*dden
(5)xpysqr=xpy**2
localgradient是2*xpy,再乘以dxpysqr
(4)xpy=x+y
還是一個(gè)加法,local gradient是1,所以dx和dy都是dxpy乘1
(3)sigx=1.0/(1+math.exp(-x))
這是sigmoid函數(shù),local gradient是(1-sigx)*sigx,再乘以dsigx。
不過(guò)需要注意的是這是dx的第二次出現(xiàn),所以是+=,表示來(lái)自不同路徑反向傳播過(guò)來(lái)給x的梯度值
(2)num=x+sigy
還是個(gè)很簡(jiǎn)單的加法,local gradient是1.需要注意的是dx是+=,理由同上。
(1)sigy=1.0/(1+math.exp(-y))
最后是sigmoid(y)和前面(3)一樣的。
請(qǐng)仔細(xì)閱讀上面反向計(jì)算的每一步代碼,確保自己理解了之后再往下閱讀。
梯度的矩陣運(yùn)算
前面都是對(duì)一個(gè)標(biāo)量的計(jì)算,在實(shí)際實(shí)現(xiàn)時(shí)用矩陣運(yùn)算一次計(jì)算一層的所有梯度會(huì)更加高效。因?yàn)榫仃嚦艘韵蛄亢拖蛄砍艘韵蛄慷伎梢钥闯鼍仃嚦艘跃仃嚨奶厥庑问,所以下面我們介紹矩陣乘法怎么求梯度。
首先我們得定義什么叫矩陣對(duì)矩陣的梯度!
我查閱了很多資料,也沒(méi)找到哪里有矩陣對(duì)矩陣的梯度的定義,如果哪位讀者知道,請(qǐng)告訴我,謝謝!唯一比較接近的是Andrew Ng的課程cs294的背景知識(shí)介紹的slides linalg的4.1節(jié)定義了gradient of Matrix,關(guān)于矩陣對(duì)矩陣的梯度我會(huì)有一個(gè)猜測(cè)性的解釋?zhuān)赡軙?huì)有問(wèn)題。
首先介紹graiden to fmatrix
假設(shè)f:Rm×n→R是一個(gè)函數(shù),輸入是一個(gè)m×n的實(shí)數(shù)值矩陣,輸出是一個(gè)實(shí)數(shù)。那么f對(duì)A的梯度是如下定義的:
看起來(lái)定義很復(fù)雜?其實(shí)很簡(jiǎn)單,我們把f看成一個(gè)mn個(gè)自變量的函數(shù),因此我們可以求f對(duì)這mn個(gè)自變量的偏導(dǎo)數(shù),然后把它們排列成m*n的矩陣就行了。為什么要多此一舉把變量拍成矩陣把他們的偏導(dǎo)數(shù)也排成矩陣?想想我們之前的神經(jīng)網(wǎng)絡(luò)的weights矩陣,這是很自然的定義,同時(shí)我們需要計(jì)算loss對(duì)weights矩陣的每一個(gè)變量的偏導(dǎo)數(shù),寫(xiě)出這樣的形式計(jì)算起來(lái)比較方便。
那么什么是矩陣對(duì)矩陣的梯度呢?我們先看實(shí)際神經(jīng)網(wǎng)絡(luò)的一個(gè)計(jì)算情況。對(duì)于全連接的神經(jīng)網(wǎng)絡(luò),我們有一個(gè)矩陣乘以向量D=WxD=Wx【我們這里把向量x看成矩陣】,F(xiàn)在我們需要計(jì)算loss對(duì)某一個(gè) WijWij的偏導(dǎo)數(shù),根據(jù)我們之前的計(jì)算圖, WijWij有多少條出邊,那么就有多少個(gè)要累加的梯度乘以local梯度。
假設(shè)W是m×n的矩陣,x是n×p的矩陣,則D是m×p的矩陣
根據(jù)矩陣乘法的定義
我們可以計(jì)算:
請(qǐng)仔細(xì)理解上面這一步,如果k≠i,則不論s是什么,Wks跟Wij不是同一個(gè)變量,所以導(dǎo)數(shù)就是0;如果k=i,∑sWisxsl=xjl,也就求和的下標(biāo)s取j的時(shí)候有WijWij。
因此
上面計(jì)算了loss對(duì)一個(gè)Wijj的偏導(dǎo)數(shù),如果把它寫(xiě)成矩陣形式就是:
前面我們推導(dǎo)出了對(duì)Wij的偏導(dǎo)數(shù)的計(jì)算公式,下面我們把它寫(xiě)成矩陣乘法的形式并驗(yàn)證【證明】它。
為什么可以寫(xiě)成這樣的形式呢?
上面的推導(dǎo)似乎很復(fù)雜,但是我們只要能記住就行,記法也很簡(jiǎn)單——把矩陣都變成最特殊的11的矩陣(也就是標(biāo)量,一個(gè)實(shí)數(shù))。D=wx,這個(gè)導(dǎo)數(shù)很容易吧,對(duì)w求導(dǎo)就是local gradientx,然后乘以得到dW=dDx;同理dx=dDW。
但是等等,剛才那個(gè)公式里還有矩陣的轉(zhuǎn)置,這個(gè)怎么記?這里有一個(gè)小技巧,就是矩陣乘法的條件,兩個(gè)矩陣能相乘他們的大小必須匹配,比如D=W x,W是m n,x是n p,也就是第二個(gè)矩陣的行數(shù)等于第一個(gè)的列數(shù)。
現(xiàn)在我們已經(jīng)知道dW是dD”乘以“x了,dW的大小和W一樣是m n,而dD和D一樣是m p,而x是n p,那么為了得到一個(gè)mn的矩陣,唯一的辦法就是dD∗xT
同理dx是np,dD是mp,W是m*n,唯一的乘法就是WT∗dD
下面是用python代碼來(lái)演示,numpy的dot就是矩陣乘法,可以用numpy.dot(A,B),也可以直接調(diào)用ndarray的dot函數(shù)——A。dot(B):
# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)
# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)
至此,本系列文章的第5部分告一段落。在接下來(lái)的文章中,作者將為大家詳細(xì)講述關(guān)于常見(jiàn)的深度學(xué)習(xí)框架/工具的使用方法、使用自動(dòng)求導(dǎo)來(lái)實(shí)現(xiàn)多層神經(jīng)網(wǎng)絡(luò)等內(nèi)容,敬請(qǐng)期待。