欧美,精品,综合,亚洲,好吊妞视频免新费观看,免费观看三级吃奶,一级a片女人自慰免费看

您當前的位置是:  首頁 > 新聞 > 專家觀點 >
 首頁 > 新聞 > 專家觀點 >

李理:自動梯度求解——cs231n的notes

2016-12-16 14:20:56   作者:   來源:CTI論壇   評論:0  點擊:


  本系列文章面向深度學習研發(fā)者,希望通過Image Caption Generation,一個有意思的具體任務,深入淺出地介紹深度學習的知識。本系列文章涉及到很多深度學習流行的模型,如CNN,RNN/LSTM,Attention等。本文為第5篇。
  作者:李理
  目前就職于環(huán)信,即時通訊云平臺和全媒體智能客服平臺,在環(huán)信從事智能客服和智能機器人相關工作,致力于用深度學習來提高智能機器人的性能。
  相關文章:
  Optimization
  這一部分內(nèi)容來自:CS231n Convolutional Neural Networks for Visual Recognition
  簡介
  我們的目標:x是一個向量,f(x)是一個函數(shù),它的輸入是一個向量(或者認為是多變量的函數(shù),這個輸入向量就是自變量),輸出是一個實數(shù)值。我們需要計算的是f對每一個自變量的導數(shù),然后把它們排成一個向量,也就是梯度。
\
  為什么要求這個呢?前面我們也講了,我們的神經(jīng)網(wǎng)絡的損失函數(shù)最終可以看成是權重weights和bias的函數(shù),我們的目標就是調整這些參數(shù),使得損失函數(shù)最小。
  簡單的表達式和梯度的解釋
  首先我們看一個很簡單的函數(shù)f(x,y)=xy,求f對x和y的偏導數(shù)很簡單:
\
  首先來看導數(shù)的定義:
\
  函數(shù)在某個點的導數(shù)就是函數(shù)曲線在這個點的斜率,也就是f(x)隨x的變化率。
  比如上面的例子,當x=4,y=-3時f(x,y)=-12,f對x的偏導數(shù)
\
  也就是說,如果我們固定y=4,然后給x一個很小的變化h,那么f(x,y)的變化大約是-3*h。
  因此乘法的梯度就是
\
  同樣,加法的梯度更簡單:
\
  最后一個簡單函數(shù)是max函數(shù):
\
  這個導數(shù)是ReLU(x)=max(x,0)的導數(shù),其實也簡單,如果x>=y,那么max(x,y)=x,則導數(shù)是1,否則max(x,y)=0,那么對x求導就是0.
  復雜表達式的鏈式法則
  接下來看一個稍微復雜一點的函數(shù)f(x,y,z)=(x+y)z。我們引入一個中間變量q,f=qz,q=x+y,我們可以使用鏈式法則求f對x和y的導數(shù)。
\
  對y的求導也是類似的。
  下面是用python代碼來求f對x和y的導數(shù)在某一個點的值。
# 設置自變量的值
x = -2; y = 5; z = -4

# “前向”計算f
q = x + y # q becomes 3
f = q * z # f becomes -12

# 從“后”往前“反向”計算
# 首先是 f = q * z
dfdz = q # 因為df/dz = q, 所以f對z的梯度是 3
dfdq = z # 因為df/dq = z, 所以f對q的梯度是 -4
# 然后 q = x + y
dfdx = 1.0 * dfdq # 因為dq/dx = 1,所以使用鏈式法則計算dfdx=-4
dfdy = 1.0 * dfdq # 因為dq/dy = 1,所以使用鏈式法則計算dfdy=-4
  我們也可以用計算圖來表示和計算:
\
  綠色的值是feed forward的結果,而紅色的值是backprop的結果。
  不過我覺得cs231n課程的這個圖沒有上面blog的清晰,原因是雖然它標示出來了最終的梯度,但是沒有標示出local gradient,我在下面會畫出完整的計算過程。
  反向傳播算法的直覺解釋
  我們?nèi)绻延嬎銏D的每一個點看成一個“門”(或者一個模塊),或者說一個函數(shù)。它有一個輸入(向量),也有一個輸出(標量)。對于一個門來說有兩個計算,首先是根據(jù)輸入,計算輸出,這個一般很容易。還有一種計算就是求輸出對每一個輸入的偏導數(shù),或者說輸出對輸入向量的”局部“梯度(local gradient)。一個復雜計算圖(神經(jīng)網(wǎng)絡)的計算首先就是前向計算,然后反向計算,反向計算公式可能看起來很復雜,但是如果在計算圖上其實就是簡單的用local gradient乘以從后面?zhèn)鬟^來的gradient,然后加起來。
  Sigmoid模塊的例子
  接下來我們看一個更復雜的例子:
\
  這個函數(shù)是一個比較復雜的復合函數(shù),但是構成它的基本函數(shù)是如下4個簡單函數(shù):
\
  下面是用計算圖畫出這個計算過程:
\
  這個圖有4種gate,加法,乘法,指數(shù)和倒數(shù)。加法有加一個常數(shù)和兩個變量相加,乘法也是一樣。
  上圖綠色的值是前向計算的結果,而紅色的值是反向計算的結果,localgraident并沒有標示出來,所以看起來可能有些跳躍,下面我在紙上詳細的分解了其中的步驟,請讀者跟著下圖自己動手計算一遍。
\
  上圖就是前向計算的過程,比較簡單。
\
  第二個圖是計算local gradient,對于兩個輸入的乘法和加法,local gradient也是兩個值,local gradient的值我是放到圖的節(jié)點上了。
\
  第三個圖是具體計算一個乘法的local gradient的過程,因為上圖可能看不清,所以單獨放大了這一步。
\
  最后計算真正的梯度,是把local gradient乘以來自上一步的gradient。不過這個例子一個節(jié)點只有一個輸出,如果有多個的話,梯度是加起來的,可以參考1.4的
\
  上面我們看到把
\
  分解成最基本的加法,乘法,導數(shù)和指數(shù)函數(shù),但是我們也可以不分解這么細。之前我們也學習過了sigmoid函數(shù),那么我們可以這樣分解:
\
  σ(x)σ(x)的導數(shù)我們之前已經(jīng)推導過一次了,這里再列一下:
\
  因此我們可以把后面一長串的gate”壓縮“成一個gate:
\
  我們來比較一下,之前前向計算σ(x)σ(x)需要一次乘法,一次exp,一次加法導數(shù);而反向計算需要分別計算這4個gate的導數(shù)。
  而壓縮后前向計算是一樣的,但是反向計算可以”利用“前向計算的結果
\
  這只需要一次減法和一次乘法!當然如果不能利用前向的結果,我們?nèi)绻枰匦掠嬎?sigma;(x)σ(x),那么壓縮其實沒有什么用處。能壓縮的原因在于σ函數(shù)導數(shù)的特殊形式。而神經(jīng)網(wǎng)絡的關鍵問題是在訓練,訓練性能就取決于這些細節(jié)。如果是我們自己來實現(xiàn)反向傳播算法,我們就需要利用這樣的特性。而如果是使用工具,那么就依賴于工具的優(yōu)化水平了。
  下面我們用代碼來實現(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
  上面的例子用了一個小技巧,就是所謂的stagedbackpropagation,說白了就是給中間的計算節(jié)點起一個名字。比如dot。為了讓大家熟悉這種技巧,下面有一個例子。
  Staged computation練習
\
  我們用代碼來計算這個函數(shù)對x和y的梯度在某一點的值
  前向計算
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)
  反向計算
# 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
  需要注意的兩點:1.前向的結果都要保存下來,反向的時候要用的。2.如果某個變量有多個出去的邊,第一次是等于,第二次就是+=,因為我們要把不同出去點的梯度加起來。
  下面我們來逐行分析反向計算:
  (8)f=num*invden
  local gradient
\
  而上面?zhèn)鬟^來的梯度是1,所以dnum=1*invden。注意變量的命名規(guī)則,df/dnum就命名為dnum【省略了df,因為默認我們是求f對所有變量的偏導數(shù)】
  同理:dinvden=num
  (7)invden=1.0/den
  local gradient是(-1.0/(den**2)),然后乘以上面來的dinvden
  (6)den=sigx+xpysqr
  這個函數(shù)有兩個變量sigx和xpysqr,所以需要計算兩個local梯度,然后乘以dden
  加法的local梯度是1,所以就是(1)*dden
  (5)xpysqr=xpy**2
  localgradient是2*xpy,再乘以dxpysqr
  (4)xpy=x+y
  還是一個加法,local gradient是1,所以dx和dy都是dxpy乘1
  (3)sigx=1.0/(1+math.exp(-x))
  這是sigmoid函數(shù),local gradient是(1-sigx)*sigx,再乘以dsigx。
  不過需要注意的是這是dx的第二次出現(xiàn),所以是+=,表示來自不同路徑反向傳播過來給x的梯度值
  (2)num=x+sigy
  還是個很簡單的加法,local gradient是1.需要注意的是dx是+=,理由同上。
  (1)sigy=1.0/(1+math.exp(-y))
  最后是sigmoid(y)和前面(3)一樣的。
  請仔細閱讀上面反向計算的每一步代碼,確保自己理解了之后再往下閱讀。
  梯度的矩陣運算
  前面都是對一個標量的計算,在實際實現(xiàn)時用矩陣運算一次計算一層的所有梯度會更加高效。因為矩陣乘以向量和向量乘以向量都可以看出矩陣乘以矩陣的特殊形式,所以下面我們介紹矩陣乘法怎么求梯度。
  首先我們得定義什么叫矩陣對矩陣的梯度!
  我查閱了很多資料,也沒找到哪里有矩陣對矩陣的梯度的定義,如果哪位讀者知道,請告訴我,謝謝!唯一比較接近的是Andrew Ng的課程cs294的背景知識介紹的slides linalg的4.1節(jié)定義了gradient of Matrix,關于矩陣對矩陣的梯度我會有一個猜測性的解釋,可能會有問題。
  首先介紹graiden to fmatrix
  假設f:Rm×n→R是一個函數(shù),輸入是一個m×n的實數(shù)值矩陣,輸出是一個實數(shù)。那么f對A的梯度是如下定義的:
\
  看起來定義很復雜?其實很簡單,我們把f看成一個mn個自變量的函數(shù),因此我們可以求f對這mn個自變量的偏導數(shù),然后把它們排列成m*n的矩陣就行了。為什么要多此一舉把變量拍成矩陣把他們的偏導數(shù)也排成矩陣?想想我們之前的神經(jīng)網(wǎng)絡的weights矩陣,這是很自然的定義,同時我們需要計算loss對weights矩陣的每一個變量的偏導數(shù),寫出這樣的形式計算起來比較方便。
  那么什么是矩陣對矩陣的梯度呢?我們先看實際神經(jīng)網(wǎng)絡的一個計算情況。對于全連接的神經(jīng)網(wǎng)絡,我們有一個矩陣乘以向量D=WxD=Wx【我們這里把向量x看成矩陣】,F(xiàn)在我們需要計算loss對某一個 WijWij的偏導數(shù),根據(jù)我們之前的計算圖, WijWij有多少條出邊,那么就有多少個要累加的梯度乘以local梯度。
  假設W是m×n的矩陣,x是n×p的矩陣,則D是m×p的矩陣
\
  根據(jù)矩陣乘法的定義
\
  我們可以計算:
\
  請仔細理解上面這一步,如果k≠i,則不論s是什么,WksWij不是同一個變量,所以導數(shù)就是0;如果k=i,sWisxsl=xjl,也就求和的下標s取j的時候有WijWij。
  因此
\
  上面計算了loss對一個Wijj的偏導數(shù),如果把它寫成矩陣形式就是:
\
  前面我們推導出了對Wij的偏導數(shù)的計算公式,下面我們把它寫成矩陣乘法的形式并驗證【證明】它。
\
  為什么可以寫成這樣的形式呢?
\
  上面的推導似乎很復雜,但是我們只要能記住就行,記法也很簡單——把矩陣都變成最特殊的11的矩陣(也就是標量,一個實數(shù))。D=wx,這個導數(shù)很容易吧,對w求導就是local gradientx,然后乘以得到dW=dDx;同理dx=dDW。
  但是等等,剛才那個公式里還有矩陣的轉置,這個怎么記?這里有一個小技巧,就是矩陣乘法的條件,兩個矩陣能相乘他們的大小必須匹配,比如D=W x,W是m n,x是n p,也就是第二個矩陣的行數(shù)等于第一個的列數(shù)。
  現(xiàn)在我們已經(jīng)知道dW是dD”乘以“x了,dW的大小和W一樣是m n,而dD和D一樣是m p,而x是n p,那么為了得到一個mn的矩陣,唯一的辦法就是dD∗xT
  同理dx是np,dD是mp,W是m*n,唯一的乘法就是WT∗dD 
  下面是用python代碼來演示,numpy的dot就是矩陣乘法,可以用numpy.dot(A,B),也可以直接調用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部分告一段落。在接下來的文章中,作者將為大家詳細講述關于常見的深度學習框架/工具的使用方法、使用自動求導來實現(xiàn)多層神經(jīng)網(wǎng)絡等內(nèi)容,敬請期待。

專題