<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                作者:[寒小陽](http://blog.csdn.net/han_xiaoyang?viewmode=contents) 時間:2015年12月。 出處:[http://blog.csdn.net/han_xiaoyang/article/details/50178505](http://blog.csdn.net/han_xiaoyang/article/details/50178505) 聲明:版權所有,轉載請聯系作者并注明出處 ### 1. 引言 上一節[深度學習與計算機視覺系列(3)_線性SVM與SoftMax分類器](http://blog.csdn.net/han_xiaoyang/article/details/49999583)中提到兩個對圖像識別至關重要的概念: 1. 用于把原始像素信息映射到不同類別得分的得分函數/score function 1. 用于評估參數W效果(評估該參數下每類得分和實際得分的吻合度)的損失函數/loss function 其中對于線性SVM,我們有: 1. 得分函數f(xi,W)=Wxi 1. 損失函數L=1N∑i∑j≠yi[max(0,f(xi;W)j?f(xi;W)yi+1)]+αR(W) 在取到合適的參數W的情況下,我們根據原始像素計算得到的預測結果和實際結果吻合度非常高,這時候損失函數得到的值就很小。 這節我們就講講,怎么得到這個合適的參數W,使得損失函數取值最小化。也就是最優化的過程。 ### 2. 損失函數可視化 我們在計算機視覺中看到的損失函數,通常都是定義在非常高維的空間里的(比如CIFAR-10的例子里一個線性分類器的權重矩陣W是10 x 3073維的,總共有30730個參數 -_-||),人要直接『看到』它的形狀/變化是非常困難的。但是機智的同學們,總是能想出一些辦法,把損失函數在某種程度上可視化的。比如說,我們可以把高維投射到一個向量/方向(1維)或者一個面(2維)上,從而能直觀地『觀察』到一些變化。 舉個例子說,我們可以對一個權重矩陣W(例如CIFAR?10中是30730個參數),可以找到W維度空間中的一條直線,然后沿著這條線,計算一下損失函數值的變化情況。具體一點說,就是我們找到一個方向W1(維度要和W一樣,才能表示W的維度空間的一個方向/一條直線),然后我們給不同的a值,計算L(W+aW1),這樣,如果a取得足夠密,其實我們就能夠在一定程度上描繪出損失函數沿著這個方向的變化了。 同樣,如果我們給兩個方向W1和W2,那么我們可以確定一個平面,我們再取不同值的a和b,計算L(W+aW1+bW2)的值,那么我們就可以大致繪出在這個平面上,損失函數的變化情況了。 根據上面的方法,我們畫出了下面3個圖。最上面的圖是調整a的不同取值,繪出的損失函數變化曲線(越高值越大);中間和最后一個圖是調整a與b的取值,繪出的損失函數變化圖(藍色表示損失小,紅色表示損失大),中間是在一個圖片樣本上計算的損失結果,最下圖為100張圖片上計算的損失結果的一個平均。顯然沿著直線方向得到的曲線底端為最小的損失值點,而曲面呈現的碗狀圖形`碗底`為損失函數取值最小處。 ![損失函數沿直線投影圖](https://box.kancloud.cn/2016-03-16_56e90ad13af17.png "") ![損失函數沿平面投影圖2](https://box.kancloud.cn/2016-03-16_56e90ad14b512.jpg "") ![損失函數沿平面投影圖2](https://box.kancloud.cn/2016-03-16_56e90ad161c38.jpg "") 我們從數學的角度,來嘗試解釋一下,上面的凹曲線是怎么出來的。對于第i個樣本,我們知道它的損失函數值為: Li=∑j≠yi[max(0,wTjxi?wTyixi+1)] 在所有的樣本上的損失函數值,是它們損失函數值(`max(0,-)`,因此最小值為0)的平均值。為了更好理解,我們假定訓練集里面有3個樣本,都是1維的,同時總共有3個類別。所以SVM損失(暫時不考慮正則化項)可以表示為如下的式子: L0=L1=L2=L=max(0,wT1x0?wT0x0+1)+max(0,wT2x0?wT0x0+1)max(0,wT0x1?wT1x1+1)+max(0,wT2x1?wT1x1+1)max(0,wT0x2?wT2x2+1)+max(0,wT1x2?wT2x2+1)(L0+L1+L2)/3 因為這個例子里的樣本都是1維的,因此其實xi和wj都是實數。拿w0舉例,損失函數里,大于0的值其實都和w0是線性關系的,而最小值為0。因此,我們可以想象成,三條折線『合體』得到的最終曲線,如下圖所示: ![曲線的形成](https://box.kancloud.cn/2016-03-16_56e90ad172249.png "") 插幾句題外話,從之前碗狀結構的示意圖,你可能會猜到SVM損失函數是一個凸函數,而對于凸函數的最小值求解方法有很多種。但之后當我們把損失函數f擴充到神經網絡之后,損失函數將變成一個非凸函數,而如果依舊可視化的話,我們看到的將不再是一個碗狀結構,而是凹凸不平的。 ### 3. 最優化 在我們現在這個問題中,所謂的『最優化』其實指的就是找到能讓損失函數最小的參數W。如果大家看過或者了解`凸優化`的話,我們下面介紹的方法,對你而言可能太簡單了,有點`原始`,但是大家別忘了,我們后期要處理的是神經網絡的損失函數,那可不是一個凸函數哦,所以我們還是一步步來一起看看,如果去實現最優化問題。 #### 3.1 策略1:隨機搜尋(不太實用) 以一個笨方法開始,我們知道,當我們手頭上有參數W后,我們是可以計算損失函數,評估參數合適程度的。所以最直接粗暴的方法就是,我們盡量多地去試參數,然后從里面選那個讓損失函數最小的,作為最后的W。代碼當然很簡單,如下: ~~~ # 假設 X_train 是訓練集 (例如. 3073 x 50,000) # 假設 Y_train 是類別結果 (例如. 1D array of 50,000) bestloss = float("inf") # 初始化一個最大的float值 for num in xrange(1000): W = np.random.randn(10, 3073) * 0.0001 # 隨機生成一組參數 loss = L(X_train, Y_train, W) # 計算損失函數 if loss < bestloss: # 比對已搜尋中最好的結果 bestloss = loss bestW = W print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss) # prints: # in attempt 0 the loss was 9.401632, best 9.401632 # in attempt 1 the loss was 8.959668, best 8.959668 # in attempt 2 the loss was 9.044034, best 8.959668 # in attempt 3 the loss was 9.278948, best 8.959668 # in attempt 4 the loss was 8.857370, best 8.857370 # in attempt 5 the loss was 8.943151, best 8.857370 # in attempt 6 the loss was 8.605604, best 8.605604 # ... (trunctated: continues for 1000 lines) ~~~ 一通隨機試驗和搜尋之后,我們會拿到試驗結果中最好的參數W,然后在測試集上看看效果: ~~~ # 假定 X_test 為 [3073 x 10000], Y_test 為 [10000 x 1] scores = Wbest.dot(Xte_cols) # 10 x 10000, 計算類別得分 # 找到最高得分作為結果 Yte_predict = np.argmax(scores, axis = 0) # 計算準確度 np.mean(Yte_predict == Yte) # 返回 0.1555 ~~~ 隨機搜尋得到的參數W,在測試集上的準確率為**15.5%**,總共10各類別,我們不做任何預測只是隨機猜的結果應該是10%,好像稍高一點,但是…大家也看到了…這個準確率…實在是沒辦法在實際應用中使用。 #### 3.2 策略2:隨機局部搜索 上一個策略完全就是盲搜,要想找到全局最優的那個結果基本是不可能的。它最大的缺點,就在于下一次搜索完全是隨機進行的,沒有一個指引方向。那我們多想想,就能想出一個在上個策略的基礎上,優化的版本,叫做『隨機局部搜索』。 這個策略的意思是,我們不每次都隨機產生一個參數矩陣W了,而是在現有的參數W基礎上,搜尋一下周邊臨近的參數,有沒有比現在參數更好的W,然后我們用新的W替換現在的W,接著在周圍繼續小范圍搜尋。這個過程呢,可以想象成,我們在一座山上,現在要下山,然后我們每次都伸腳探一探周邊,找一個比現在的位置下降一些的位置,然后邁一步,接著在新的位置上做同樣的操作,一步步直至下山。 從代碼實現的角度看,以上的過程,實際上就是對于一個當前W,我們每次實驗和添加δW′,然后看看損失函數是否比當前要低,如果是,就替換掉當前的W,代碼如下: ~~~ W = np.random.randn(10, 3073) * 0.001 # 初始化權重矩陣W bestloss = float("inf") for i in xrange(1000): step_size = 0.0001 Wtry = W + np.random.randn(10, 3073) * step_size loss = L(Xtr_cols, Ytr, Wtry) if loss < bestloss: W = Wtry bestloss = loss print 'iter %d loss is %f' % (i, bestloss) ~~~ 我們做了這么個小小的修正之后,我們再拿剛才一樣的測試集來測一下效果,結果發現準確率提升至21.4%,雖然離實際應用差很遠,但只是比剛才要進步一點點了。 但是還是有個問題,我們每次測試周邊點的損失函數,是一件非常耗時的事情。我們有沒有辦法能夠直接找到我們應該迭代的方向呢? #### 3.3 策略3:順著梯度下滑 剛才的策略,我們說了,最大的缺點是非常耗時,且計算量也很大。我們一直在做的事情,就是在當前的位置基礎上,想找到一個最合適的下降方向。我們依舊回到我們假設的那個情境,如果我們在山頂,要以最快的方式下山,我們會怎么做?我們可能會環顧四周,然后找到最陡的方向,邁一小步,然后再找當前位置最陡的下山方向,再邁一小步… 而這里提到的最陡的方向,其實對應的就是數學里『梯度』的概念,也就是說,其實我們無需『伸腳試探』周邊的陡峭程度,而是可以通過計算損失函數的梯度,直接取得這個方向。 我們知道在1個變量的函數里,某點的斜率/導數代表其變化率最大的方向。而對于多元的情況,梯度是上面情況的一個擴展,只不過這時候的變量不再是一個,而是多個,同時我們計算得到的『梯度方向』也是一個多維的向量。大家都知道數學上計算1維/元函數『梯度/導數』的表達式如下: df(x)dx=limh?→0f(x+h)?f(x)h 對于多元的情況,這個時候我們需要求的東西擴展成每個方向的『偏導數』,然后把它們合在一塊組成我們的梯度向量。 我們用幾張圖來說明這個過程: ![梯度下降1](https://box.kancloud.cn/2016-03-16_56e90ad1808eb.jpg "") ![梯度下降2](https://box.kancloud.cn/2016-03-16_56e90ad195594.jpg "") ![各種下降算法](https://box.kancloud.cn/2016-03-16_56e90ad1b383b.gif "") ### 4. 計算梯度 有兩種計算梯度的方法: 1. 慢一些但是簡單一些的`數值梯度/numerical gradient` 1. 速度快但是更容易出錯的`解析梯度/analytic gradient` #### 4.1 數值梯度 根據上面提到的導數求解公式,我們可以得到數值梯度計算法。下面是一段簡單的代碼,對于一個給定的函數`f`和一個向量`x`,求解這個點上的梯度: ~~~ def eval_numerical_gradient(f, x): """ 一個最基本的計算x點上f的梯度的算法 - f 為參數為x的函數 - x 是一個numpy的vector """ fx = f(x) # 計算原始點上函數值 grad = np.zeros(x.shape) h = 0.00001 # 對x的每個維度都計算一遍 it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) while not it.finished: # 計算x+h處的函數值 ix = it.multi_index old_value = x[ix] x[ix] = old_value + h # 加h fxh = f(x) # 計算f(x + h) x[ix] = old_value # 存儲之前的函數值 # 計算偏導數 grad[ix] = (fxh - fx) / h # 斜率 it.iternext() # 開始下一個維度上的偏導計算 return grad ~~~ 代碼的方法很簡單,對每個維度,都在原始值上加上一個很小的`h`,然后計算這個維度/方向上的偏導,最后組在一起得到梯度`grad`。 #### 4.1.1 實際計算中的提示 我們仔細看看導數求解的公式,會發現數學定義上h是要趨于0的,但實際我們計算的時候我們只要取一個足夠小的數(比如1e-5)作為h就行了,所以我們要精準計算偏導的話,要盡量取到不會帶來數值計算問題,同時又能很小的h。另外,其實實際計算中,我們用另外一個公式用得更多[f(x+h)?f(x?h)]/2h 下面我們用上面的公式在CIFAR-10數據集上,試一試吧: ~~~ def CIFAR10_loss_fun(W): return L(X_train, Y_train, W) W = np.random.rand(10, 3073) * 0.001 # 隨機權重向量 df = eval_numerical_gradient(CIFAR10_loss_fun, W) # 計算梯度 ~~~ 計算到的梯度(準確地說,梯度的方向是函數增大方向,負梯度才是下降方向)告訴我們,我們應該『下山』的方向是啥,接著我們就沿著它小步邁進: ~~~ loss_original = CIFAR10_loss_fun(W) # 原始點上的損失 print 'original loss: %f' % (loss_original, ) # 多大步伐邁進好呢?我們選一些步長試試 for step_size_log in [-10, -9, -8, -7, -6, -5,-4,-3,-2,-1]: step_size = 10 ** step_size_log W_new = W - step_size * df # 新的權重 loss_new = CIFAR10_loss_fun(W_new) print 'for step size %f new loss: %f' % (step_size, loss_new) # 輸出: # original loss: 2.200718 # for step size 1.000000e-10 new loss: 2.200652 # for step size 1.000000e-09 new loss: 2.200057 # for step size 1.000000e-08 new loss: 2.194116 # for step size 1.000000e-07 new loss: 2.135493 # for step size 1.000000e-06 new loss: 1.647802 # for step size 1.000000e-05 new loss: 2.844355 # for step size 1.000000e-04 new loss: 25.558142 # for step size 1.000000e-03 new loss: 254.086573 # for step size 1.000000e-02 new loss: 2539.370888 # for step size 1.000000e-01 new loss: 25392.214036 ~~~ #### 4.1.2 關于迭代的細節 如果大家仔細看上述代碼的話,會發現我們step_size設的都是負的,確實我們每次update權重W的時候,是用原來的`W`減掉梯度方向的一個較小的值,這樣損失函數才能減小。 #### 4.1.3 關于迭代的步長 我們計算得到梯度之后,就確定了幅度變化最快(負梯度是下降方向)的方向,但是它并沒有告訴我們,我朝著這個方向,應該邁進多遠啊。之后的章節會提到,選擇正確的迭代步長(有時候我們也把它叫做`學習速率`)是訓練過程中最重要(也是最讓人頭疼)的一個待設定參數。就像我想以最快的速度下山,我們能感知到最陡的方向,卻不知道應該邁多大的步子。如果我們小步邁進,那確實每一步都能比上一步下降一些,但是速度太慢了親!!但是如果我們以非常非常大的步伐邁進(假如腿巨長 -_-||),那你猜怎么著,你一不小心可能就邁過山腳邁到另一座山山腰上了… 下圖是對以上情況的一個描述和解釋: ![梯度下降](https://box.kancloud.cn/2016-03-16_56e90ad1ec7fd.jpg "") 圖上紅色的值很大,藍色的值很小,我們想逐步下降至藍色中心。如果邁進的步伐太小,收斂和行進的速度就會很慢,如果邁進的步伐太大,可能直接越過去了。 #### 4.1.4 效率問題 如果你再回過頭去看看上面計算數值梯度的程序,你會發現,這個計算方法的復雜度,基本是和我們的參數個數成線性關系的。這意味著什么呢?在我們的CIFAR-10例子中,我們總共有30730個參數,因此我們單次迭代總共就需要計算30731次損失函數。這個問題在之后會提到的神經網絡中更為嚴重,很可能兩層神經元之間就有百萬級別的參數權重,所以,計算機算起來都很耗時…人也要等結果等到哭瞎… #### 4.2 解析法計算梯度 數值梯度發非常容易實現,但是從公式里面我們就看得出來,梯度實際上是一個近似(畢竟你沒辦法把`h`取到非常小),同時這也是一個計算非常耗時的算法。第二種計算梯度的方法是解析法,它可以讓我們直接得到梯度的一個公式(代入就可以計算,非常快),但是呢,不像數值梯度法,這種方法更容易出現錯誤。so,聰明的同學們,就想了一個辦法,我們可以先計算解析梯度和數值梯度,然后比對結果和校正,在確定我們解析梯度實現正確之后,我們就可以大膽地進行解析法計算了(這個過程叫做`梯度檢查/檢測`) 我們拿一個樣本點的SVM損失函數舉例: Li=∑j≠yi[max(0,wTjxi?wTyixi+Δ)] 我們可以求它對每個權重的偏導數,比如說,我們求它對wyi的偏導,我們得到: ?wyiLi=?????∑j≠yi1(wTjxi?wTyixi+Δ>0)????xi 其中1是一個bool函數,在括號內的條件為真的時候取值為1,否則為0。看起來似乎很嚇人,但實際上要寫代碼完成的話,你只需要計算不滿足指定SVM最小距離的類(對損失函數有貢獻的類)的個數,然后用這個值會對數據向量xi做縮放即可得到梯度。但是要注意只是W中對應正確的類別的列的梯度。對于其他的j≠yi的情況,梯度為: ?wjLi=1(wTjxi?wTyixi+Δ>0)xi 一旦得到梯度的表達式,那計算梯度和調整權重就變得非常直接和簡單。熟練掌握如何在loss expression下計算梯度是非常重要的一個技巧,貫穿整個神經網絡的訓練實現過程,關于這個內容,下次會詳細講到。 ### 5. 梯度下降 在我們有辦法計算得到梯度之后,使用梯度去更新已有權重參數的過程叫做『梯度下降』,偽代碼其實就是如下的樣子: ~~~ while True: weights_grad = evaluate_gradient(loss_fun, data, weights) weights += - step_size * weights_grad # 梯度下降更新參數 ~~~ 這個簡單的循環實質上就是很多神經網絡庫的核心。當然,我們也有其他的方式去實現最優化(比如說L-BFGS),但是梯度下降確實是當前使用最廣泛,也相對最穩定的神經網絡損失函數最優化方法。 #### 5.1 Mini-batch gradient descent 在大型的應用當中(比如ILSVRC),訓練數據可能是百萬千萬級別的。因此,對整個訓練數據集的樣本都算一遍損失函數,以完成參數迭代是一件非常耗時的事情,一個我們通常會用到的替代方法是,采樣出一個子集在其上計算梯度。現在比較前沿的神經網絡結構基本都是這么做的,例如ConvNets是每256張作為一個batch去完成參數的更新。參數更新的代碼如下: ~~~ while True: data_batch = sample_training_data(data, 256) # 抽樣256個樣本作為一個batch weights_grad = evaluate_gradient(loss_fun, data_batch, weights) weights += - step_size * weights_grad # 參數更新 ~~~ 之所以可以這么做,是因為訓練數據之間其實是關聯的。我們簡化一下這個問題,你想想,如果ILSVRC中的120w圖片,如果只是1000張不同的圖片,一直復制1200次得到的。那么其實我們在這1000張圖片上算得的損失函數和120w的平均其實是一致的。當然,當然,在實際場景中,我們肯定很少遇到這種多次重復的情況,但是原數據的一個子集(mini-batch)上的梯度,其實也是對整體數據上梯度的一個很好的近似。因此,只在mini-batch上計算和更新參數,會有快得多的收斂速度。 上述算法的一個極端的情況是,如果我們的一個mini-batch里面只有一張圖片。那這個過程就變成『隨機梯度下降/Stochastic Gradient Descent (SGD)』,說起來,這個其實在實際應用中倒也沒那么常見,原因是向量化之后,一次計算100張圖片,其實比計算一張圖片100次,要快得多。所以即使從定義上來說,SGD表示我們用一張圖片上的梯度近似全局梯度,但是很多時候人們提到SGD的時候,其實他們指的是mini-batch梯度下降,也就是說,我們把一個batch當做1份了。額,還要稍微提一句的是,有些同學可能會問,這個batch size本身不是一個需要實驗的參數嗎,取多大的batch size好啊?但實際應用中,我們倒很少會用cross-validation去選擇這個參數。這么說吧,我們一般是基于我們內存限制去取這個值的,比如設成100左右。 ### 6. 總結 - 把損失函數在各參數上的取值,想象成我們所在山峰的高度。那么我們要最小化損失函數,實際上就是『要想辦法下山』。 - 我們采取的下山策略是,一次邁一小步,只要每次都往下走了,那么最后就會到山底。 - 梯度對應函數變化最快的方向,負梯度的方向就是我們下山,環顧四周之后,發現最陡的下山路方向。 - 我們的步長(也叫學習率),會影響我們的收斂速度(下山速度),如果步伐特別特別大,甚至可能躍過最低點,跑到另外一個高值位置了。 - 我們用mini-batch的方式,用一小部分的樣本子集,計算和更新參數,減少計算量,加快收斂速度。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看