看懂深度學習與Tensorflow,這幾篇文章就夠(1前饋神經網路)

看懂深度學習與Tensorflow,這幾篇文章就夠(1前饋神經網路)

本文知乎鏈接:zhuanlan.zhihu.com/p/33

這一節我們開始接觸最簡單最樸素的神經網路,叫做前饋神經網路(feedforward neural network)。在這種神經網路中,各神經元從輸入層開始,接收前一級輸入,並輸入到下一級,直至輸出層。整個網路中無反饋,可用一個有向無環圖(directed acyclic graph,DAG)表示。

通常我們說的前饋神經網路有兩種,一種叫Back Propagation Networks——反向傳播網路(以下簡稱BP網路),一種叫做RBF Network——徑向基函數神經網路。Propagation的含義是傳播,所以也叫作反向傳播神經網路。

1網路結構

BP網路是所有神經網路中結構最為單純的一種。

一般習慣上我們喜歡把網路化成「左邊輸入,右邊輸出」的結構,一個向量從左邊進入,經過網路的運算從右邊產生一個輸出結果。就像上面這樣,當然前饋神經網路的結構不是一種固定的,上面這兩個圖知識隨意列出來了兩種。

第一個神經網路有兩層,每層4個節點。第二個神經網路和它相比也是大同小異,區別是層數不同:多了一個隱藏層;另外,每一層的神經元數量也不同——一層5個,一層3個,而且最後的輸出層只有一個神經元,這些都是與第一個神經網路的不同之處,但它們也都是前饋神經網路。你別看節點數目不一樣而且不對稱——反正沒人規定過這種網路必須對稱。這些並不是「問題」,神經網路本身就有很多種設計模式,並且會在不同的模式下產生不同的訓練效果和運用特點。

神經網路有一個不太好理解的地方就是它的組成結構太複雜,「元件」太多——一層一層的神經元,會使得模型看上去很不直觀。那好,我們就創造一個最簡單的BP網路結構。把這個網路結構研究明白了,再複雜的網路也就不在話下了。

就2層,我們說過輸入層不算,隱藏層就算1層,輸出層算1層,一共2層。x我們也讓它最簡單化,就一個維度——一個實數。

隱藏層h和輸出層o這兩層都是 z=wx+b f(z)=1/(1+e^{-z}) 的組合。那麼這個「網路」一旦輸入了x和y之後,它就可以開始訓練過程了。

2線性回歸的訓練

BP神經網路的訓練其實跟我們以前接觸過的基於統計的機器學習模型很接近,如果熟悉線性回歸的朋友就會覺得這個過程非常簡單了。「機器學習」就是機器通過對觀測到的失誤進行歸納,進而總結出它們之間的規律、關係一類。在整個訓練過程中,我們要看看一個線性回歸的模型究竟學到了些什麼,怎麼學到的。作為鋪墊,線性回歸的訓練過程如果能看明白,那麼下面在看這兩層的神經網路也會非常清晰簡單。

2.1牛頓法

牛頓法是一種通過迭代法來解方程的思路。雖然在神經網路和線性回歸的訓練中這個方法沒有辦法直接使用,但是對我們開闊思路是十分有用的。

迭代法的核心思想就是用步步逼近的方式來接近理論上的精確值,只要發現當前的試探值已經收斂到一個滿足場景要求的誤差精度就可以判斷迭代結束,用這個試探值來充當求解的目標值。這種方法可以使很多「直接法」無法求解的問題得到一個足夠精確的近似解。例如,我們都知道一元二次方程 y=ax^{2}+bx+c 通過配方和一項可以得到它求根公式為:

這種得到的以待定係數的函數作為表達式的解就是我們說的解析解。

迭代法與此不同,是通過多次「試探性」的計算並比對與這個真實值之間的差距是否縮小來得到解。這種以有限成本的「次優」取代無限成本的「最優」的哲學思想是每一個工程人員都可以借鑒的思維方式。而牛頓法就是一種經典的迭代方法。

例如有一個一元方程: f(x)=0

先不管f(x)具體的表達式是什麼,或者複雜或者簡單,假設f(x)=0真的有解,而r是滿足f(x)=0的解,我們怎麼找到這個r呢。要知道f(x)的表達式可能真的千奇百怪,還真不見得通過人的手算、移項、配方......各種方法快速得到解。那就不妨用剛剛提到的這種迭代法的思路。

設置一個初始值 x_{0} ,帶入函數,代入函數 y=f(x) ,則平面直角坐標繫上會有點( x_{0},f(x_{0}) )在這個點落在曲線 y=f(x) 上。

過點( x_{0},f(x_{0}) )做 y=f(x) 的切線 L_{0} L_{0} 的方程就應該是:

y=f(x_{0})+f』(x_{0})(x-(x_{0})) ,其中 f』(x) 就是 f(x) 的一階導數。

y=x^{2} 為例,對圖形上的每個點都做切線,都取切線斜率的話會得到一系列的x和斜率值,這新的對應關係x和斜率值y就是我們剛剛說的 f』(x) 函數了。這同樣是一個函數,而輸出值y的意義表示原函數 f(x) 的斜率。有經驗的朋友可能一下子就看出答案, y=x^{2} 這個函數的導數是 y=2x

回來看剛剛說過的方程 y=f(x_{0})+f』(x_{0})(x-(x_{0})) ,如果你看不明白這個表達式是怎麼出來的,那就做個代還,以我們最容易接受的 y=kx+b 的斜率與截距的方式來表示,那 f』(x_{0}) 就是 k y=f(x_{0})+f』(x_{0})(x-(x_{0})) 就可以改寫為 y=kx+(f(x_{0})-kx_{0}) ,從圖上來看截距就是 f(x_{0})-kx_{0}

這樣就可以求助直線 L_{0} X 軸的交點的橫坐標

得到的 x_{1} r 的一次近似點。

然後照葫蘆畫瓢在曲線 y=f(x) 上以相同方式過點( x_{1},f(x_{1}) )做 y=f(x) 的切線 L_{1} ,得到 L_{1} X 軸的交點橫坐標

得到的 x_{2} 為r的二次近似點。

以此種方式進行迭代,通項表達式即為:

x_{n} 就稱為r的n次近似點的值,這個公式就是牛頓迭代公式。整個迭代的收斂過程就像下圖這樣,通過n次的逼近最後得到r的近似值。

剛剛這種就是使用一次一次迭代來逼近最優解(局部最優解)的過程,而且牛頓迭代公式是可以推廣到高維去使用的,比如二維、三維且各維度可導情況。不管導數多麼複雜,這種方法都是通用的。

2.2開始訓練

剛剛插入了迭代法的介紹,其實主要是為了鋪墊這一節的內容。

還是接著這裡來講,我們想要求得y=wx+b中的w和b中的w和b,我們有眾多的x和y,比如就是剛才說的10個。假設在擬合的過程中有這樣一個參數e代表error,表示誤差的含義,即

當我取定任何一個w和b的時候,只要代入一個x和對應的y就一定產生一個e來表示一個全局的誤差總量,看看錶達式是什麼樣子。

這裡的下標i表示第i個樣本,每一個的表達式都應該是這樣。加和則變成了:

也就是表示是這10個e相加的和。這裡請注意,e表示的是誤差,也就是說e是正數是誤差,e是負數也是誤差,這種誤差我們稱之為殘差——就理解為我們建模之後進行擬合然後殘留的差距就可以了。既然e本身是正是負都應該算作殘差,那麼讓其內部正負低消顯然不合適,這種情況下應該把每個e都做一個非負的處理,或者取絕對值或者取平方,都能達到類似的效果。我們在這裡就取平方看看會有什麼效果。殘差

各樣本產生的殘差平方後會得到Σ裡面的這麼一個多項式,有 w^{2}、b^{2}、wb 項、 w 項、 b 項,以及後面的常數項 y_{i}^{2} ——別看它這裡寫著個y,其是我們在實驗開始的時候獲得的樣本標籤,一個已知數。在Σ加和完全展開後, w^{2}、b^{2}、wb 項、 b 項和常數項都會各自提取公因式合併,變成這種形式:

其中的A、B、C、D、E、F全部都是常數係數。

好了,現在我們得到一個全局性的誤差函數,其中的未知數是w和b。現在要做一件事,那就是找到一個比較好的w和一個比較好的b,使得整個Loss儘可能小,越接近0越好,說明擬合的誤差越小。用白話說也就是畫一條線從眾多的點中穿過去,讓它儘可能距離這些樣本點比較近,看上去靠譜一些。雖然我們不知道這些值具體是什麼但是可以先畫一個它的「近親」——函數 z=f(x,y)=x^{2}+y^{2}+xy+x+y+1 出來看看函數在三維空間里是什麼情況:

整個圖形在xyz三維直角坐標系中看上去像一個「碗」。看到這個圖像我們頓時心就放鬆下來,這就和平面直角坐標系中的拋物線一樣,存在極值。那下面就是求極值的問題了,也就是求解這個「碗底」的坐標(x,y),也就是我們剛才說的(w,b)的取值點,這裡的(x,y)就相當於(w,b),符號字母不同但是意義相同——這個點就是保證整個全局誤差Loss的解。

2.3梯度下降法

前面我們已經說過用迭代法——牛頓法來解方程的根,這種方法同樣適用於剛剛的線性回歸的學習過程。雖然我們沒有辦法得到一個解析解來找到這個極值的位置,但通過迭代法不斷學習,可以逼近這個模型設置中待定係數w和b的最佳值位置。

首先我們初始化一個 w_{0} 和一個 b_{0} ,隨便是什麼實數都可以,反正代入到

中都是可以輸出某一個 Loss_{0} 值的。這個時候( w_{0},b_{0},Loss_{0} )就會出現在整個「碗壁」上的某個位置,而且這個位置很可能離我們要找的碗底還差得很遠很遠。

我們的前人也都知道計算機不靠譜,自己也沒有任何解題思路,還是的告訴計算機怎麼來找到這個極值點的位置。這個方法現在應用很普遍,例如梯度下降法(gradient descent)就是其中一種,這也是用來解決凸優化問題的通用方法。例如 y=x^{2}+2 這種函數就是典型的一元凸函數,剛才說的這個 z=f(x,y)=x^{2}+y^{2}+xy+x+y+1 則是二元凸函數,三元及以上的高維凸函數也是有的,只是沒有辦法畫出圖像而已。

f(x)=x^{2}+2 這種函數要求其極小值那是有固定套路的,我們可以用一種「挪挪看」的方法來找。我們先來看看這種一元二次函數的極值是怎麼找到的。

2.4一元凸函數

就以 f(x)=x^{2}+2 為例,比如我們確實不知道 f(x)=x^{2}+2 的極值在什麼位置,也不想用解析解來表達,那麼就先在函數曲線上隨便取一個點( x_{0},x_{0}^{2}+2 )的極值在什麼位置,因為是隨意取的點所以十有八九是不會恰好在極值點上的。假如我開始取的是(3,11)這個點,這個點顯然不是我們要求的極值點。不過沒有關係,在取了這個點之後,我讓計算機往它的兩邊「看」,看看那邊更低一些,比如x=2.8和x=3.2這兩個點。這兩個值分別對應的點就是(2.8,9.84)和(3.2,12.24)。這兩個點相比很容易就比出來,x=2.8這邊這個方向要更低一些,就應該往這邊挪。

下次就是比較x=2.6和x=3.0這兩個點了,一看x=2.6這個點的函數值更低,那就接著挪。按照這種方式就可以在十幾次以後挪到極值點x=0的位置了。你看看,我們不用傳統手動解方程的方式,還是有辦法用循環加減乘除這麼Low的辦法解極值問題的是不是?

不過在這裡我每次挪0.2是給大家做個示範,真實的情況下其實通常是不確定挪多少合適的。一般來說,我們是特別希望這種情況下這個挪動能夠來個「自適應」,該多挪的時候多挪,該少挪的時候少挪,挪到位了就別挪了——很理想。

梯度下降法就是為了解決這種問題的。還用剛才這個例子來說,能不能讓我們每次更新不要都是0.2,離極值點遠的地方我們讓它諾得快一些,近的時候挪動慢一些,挪到位就不動了。這個方法寫出來是這麼個形式:

這表示的是一個更新的邏輯過程, x_{n+1} x_{n} 分別表示兩個鄰近迭代中的x值, x_{n+1} x_{n} 更新後的下一次迭代的值,每次更新的時候 x_{n}-ηdf(x)/dx 的值賦給 x_{n+1}其中希臘字母η稱為「學習率」,也就是一個挪動步長的基數,所以也可以叫做「步長」,設得大就挪動得多,設得小就挪動得少,在學習伊始有編程人員給賦值就OK了。 df(x)/dx f(x) 的導函數或稱為導數,也可以記做f(x),這個導數的概念就是函數曲線上的切線斜率的概念。

以函數 f(x)=x^{2}+2 為例子,x=3這一點的導數大小就是(3,11)這一點的斜率,在函數這一點做切線求斜率是可以的。那就直接把f』(x)的表達式求出來,f(x)=2x,然後把x=3代入到f(x)中去,可以得到一樣的結果—— f(x)=x^{2}+2 中(3,11)這一點的斜率大小。

這一點的導數看上去還是蠻大的,我替大家直接求解了f(3)=6,假如η我們設定位0.1的話,這時 -ηdf(x)/dx 就應該等於-0.6了,由於 x_{n} =3,所以更新後 x_{n+1} =2.4。

而在進行下一次迭代的時候,即 x_{n} =2.4的時候,f(2.4)=4.8,那麼 -ηdf(x)/dx =-0.48,更新後 x_{n+1} =1.92。

大家都能看出來,這每一次移動的步長是在逐步減小,原因就是鄰近整個函數圓乎乎的地步的時候斜率降低,導致 -ηdf(x)/dx 的絕對值減小,更新的時候改變的量也就相應減小。這個更新原則 x_{n+1}=x_{n}-ηdf(x)/dx 還有一個優勢不知道細心的朋友發現了沒有,這裡面沒有往兩邊試的這個過程,直接就做更新了。這是為什麼呢?就用剛剛的這個函數 f(x)=x^{2}+2 來說,當 x_{n} =3的時候,這一點的切線的斜率是個正數, -ηdf(x)/dx 這一項一定是個負數,更新後 x_{n+1} 會變小,朝著底部的方向前進;如果 x_{n} =-3的時候,這一點的斜率是一個負數,而 -ηdf(x)/dx 這一項一定是一個正數,更新後 x_{n+1} 會變大,也朝著底部的方向前進。這兩點都是保證這種演算法收斂的基本因素,而這種底部圓乎乎的函數其實就是凸函數。

從教科書上的定義來看,凸函數有著複雜和嚴格的定義,不過我們可以記住它最簡單最基礎的性質——也是一個必要條件:在函數f(x)上有任意兩個變數 x_{1} x_{2} ,函數滿足:

我們感覺一下,這種函數比f(x)=wx+b那種直勾勾的函數,具備了一種特性,那就是要麼是一條筆直的直線,也就是恰好滿足如下公式:

要麼就是向一側發生了彎曲,而且還是向「下方」發生了彎曲,也就是滿足了如下公式:

我們說它叫「凸」函數,其實就相當於我們在很熟圖像的下方看它,函數向我們觀察者所在的方向吐出來,所以由此得名。

如果在一個問題的求解中,最後我們把這個問題化簡成在凸函數上求極值的問題就算破解了,在這種情況下就需要使用梯度下降法來求極值,而這種方法剛剛我們已經找到辦法了。核心思路就是在函數的曲線(曲面)上初始化一個點,然後讓他沿著梯度下降的方向移動,指導移動到函數值極值的位置,這個位置視具體位置而定,可能是極小值也可能是極大值——因為如果是凹函數那就是梯度上升的方向了。

2.5二元(多元)凸函數

別著急,我們快摸著門了,現在一維凸函數的極值我們可以用編程序的方法解決了。那麼我們看二維凸函數怎麼辦。有招兒沒有。我們來看這樣一個函數:

這個函數圖像如下:

在這個函數上我們要想從開始給的一個( x_{0},y_{0} )的點通過一次一次迭代挪到極值點上去恐怕跟原來思路會不大一樣,和一元凸函數不同,最起碼我們有4個方向可以試,一個凸函數只有2個方向,這一點剛才我們說過了。不過在更新方程 x_{n+1}=x_{n}-ηdf(x)/dx 里,我們可是發現一個竅門,那就是不用真的去兩邊都試,這個更新方程本身就能在一個維度上「識別」這個方向,這個剛剛我們也說過了。那麼在兩個維度上簡化後應該有這樣兩個方程:

注意這裡有一個新的符號出現了——?,即表示偏導的符號。以剛剛說的函數 z=f(x,y)=x^{2}+8y^{2} 為例, ?f(x,y)/?x 讀作「偏f偏x」,它表示的含義是 z=f(x,y) 這個曲面上的點上在沿著平行於x軸的方向做切線, ?f(x,y)/?x 就表示點(x,y)處的沿著平行於x軸方向的切線斜率。

同理 ?f(x,y)/?y 表示的含義是 z=f(x,y) 這個曲面上的點上沿著平行於y軸的方向做切線, ?f(x,y)/?y 就表示點(x,y)上的沿著平行於y軸方向的切線斜率。

從數學的角度來說, ?f(x,y)/?x ?f(x,y)/?y 的求法與 df(x,y)/dx df(x,y)/dy 的求法一樣。以 df(x,y)/dx 為例就是在這個表達式 z=f(x,y) 中,把y直接當成一個已知數或者說係數來看待,然後對x求導數, df(x,y)/dy 也是同理。這裡用 df(x,y)/dx df(x,y)/dy 這種寫法在數學層面上顯得太不專業了,只是為了表達這個過程與一元函數求導沒有差別。通過求導可以分別得到 z=f(x,y)=x^{2}+8y^{2} 這個二元函數中,

那麼給我任何一個點(x,y),我都知道這一點上的 ?f(x,y)/?x ?f(x,y)/?y 分別是多少了,例如(3,4)這個點, ?f(x,y)/?x ?f(x,y)/?y 分別是(6,64)。這個時候,這個更新方程其實就可以開始工作了:

不過對於計算機有限的運算能力來說計算資源永遠是不足的,在實際的深度學習網路中,極可能幾千萬甚至上億個維度、幾十億個維度需要更新,所以從效率層面來考慮也是希望這種更新能夠產生最高效的收斂效果。所謂「收斂」就是通過多次迭代逐步逼近想要求的值的過程,那顯然在準確度相當的情況下收斂快的方法會更受歡迎一些。對於梯度下降法中有這麼多維度的選擇,例如z=f(x,y)就有兩個維度,什麼更新原則收斂速度回最快呢,有什麼方法可循嗎?

有的,在一個三維空間中,在山頂上往山腳下前進,如果想要最快的話,那就沿著最陡峭的方向去走了。這裡有一個名詞叫做梯度,記做:

形式上是兩個方向上的偏導數,每次如果進行更新的時候就用η去乘兩個方向上的偏導數各自完成自己的更新量:

如果變數很多,比如不是只有x和y,而是有1000個變數,例如1000個w怎麼辦?也是一樣的,把他們表示成為:

再去各自乘以η就可以了,得到:

w的上標i表示第幾個w,w的下標n和n+1表示迭代的次數,在這個例子里也就是一次迭代對1000個w分別更新的含義。

到這裡我們基本理解了梯度下降法(最速梯度下降法)在多元凸函數上更新所經歷的步驟和原理。那麼如何解決訓練問題呢?我想你已經心裡明白了八九分了,只要能夠把殘差Loss函數描述成待定的若干個w所描述的凸函數——Loss(w),那麼就可以用梯度下降法,用最快的方法更新w的各個維度,最後滿足Loss(w)找到極值點的位置就算是大功告成了。

2.6損失函數

我們管這種函數叫Cost或者Loss都可以,還是外國人起名字比較講究,不管是Cost還是Loss,你聽著就是那麼讓人心疼,這是要麼費錢要麼有損失的感覺。是的,這東西就叫做損失函數。

損失函數這個叫法確實非常形象,你想啊,你廢了半天勁兒做了個擬合,本身是為了讓它和你想要得到的那個真實結果一致,結果中間有差距了,那還不是損失啊?問題是怎麼讓損失變小,最好是沒有損失,只要損失能消滅了那就算是圓滿了。

在深度學習中的損失函數其實是不一而足的,每種損失函數在當初誕生的時候都是由一些客觀環境和理由的。但是不管哪種損失函數都是有以下幾個特點。

特點一:恆非負。都說是損失了,最圓滿的情況就是沒損失,或者說損失是0,但凡有一點擬合的偏差那就會讓損失增加。所以損失函數都是恆非負的,否則無法出現合理的解釋了。

特點二:誤差越小函數值越小。這個性質也是非常重要的,如果函數定義的不好,優化起來沒有方向或者邏輯過於複雜,那對於問題處理顯然是不利的。誰願意沒事情給自己找個邏輯解釋繞脖子的方法來解決問題啊,是不?

特點三:收斂快。這個性質沒有那麼關鍵。收斂快的意思就是指在我們優化這個損失函數Loss的迭代過程中需要讓它比較快地逼近極小值,逼近函數值的地點。同等情況下一個鐘頭能得到解那絕對沒有必要花三個鐘頭,好的損失函數的定義會讓這個訓練時間在一定程度上縮短的。不過這個條件不能算是必要條件,因為它只要不影響正確性,慢一點其實也不能算作「錯誤」。這是個錦上添花的屬性,大家心裡有個數就行了。

2.7導數怎麼求

最後一個問題,導數怎麼求。這一小節很關鍵哦,我們來看實現用梯度下降法更新Loss(w)的一段程序。

注意一個問題,讓不靠譜的計算機用加減乘除來做更新的過程中,還有一個問題我們在前面沒有提到,那就是怎麼求導數的問題。學過高等數學或者數學分析的讀者朋友也別高興得太早,損失函數Loss(w)一般情況下都呈現出一種看上去非常「不規則」的樣子,沒辦法通過查表得到導數的表達式(也就是我們所說的解析解)。那也就更不能通過代入當時這一點的x向量(n個維度)值來求得各個方向上的偏導數的數值了。

不過我們還是有辦法來求一個偏導數在某一個點的大概值的。首先我們先想想看偏導數的幾何定義,就是切線斜率。那我們能不能用別的方法求出切線斜率呢?

我們還是以一個一元二次曲線為例,還是 f(x)=x^{2}+2 吧。還是給一個初始化的點,給(4,18)好了。在這裡使用 x_{n+1}=x_{n}-ηdf(x)/dx 的時候 df(x)/x 應該取值多少呢?

既然是求切線,切線的定義是

其實這也是 df(x)/x 的定義,分子上是一個f(x+Δ)-f(x),從含義上來看,就是在任何一個x的取值上在想其叛變挪動一個Δ大小,在數學和物理上專門用來表示一個很小的差值,注意這個Δ可是沒有說正負值。所以分子上f(x+Δ)-f(x)的含義就是x叛變挪動一個Δ後再看看這個f(x+Δ)與當前f(x)的差值。好,我們記住它的定義,再來看看分母。

分母上直接就是一個Δ,這個Δ是一個很小的差值,沒啥好說的。再來看整個表達式:

整體的含義就是讓這個比值中的Δ無限接近於0,但不能等於0——0作為除數對於目前的高等數學中還沒有解釋意義。

我們看圖上,這個Δ在無限趨近於0的情況下你會發現,無論它是一個正數還是一個負數,最後都趨近於一個值,就是這個點的斜率值。f(x+Δ)-f(x)是這個三角形的直邊高,Δ是三角形的底邊長。

如果是這樣,我們就可以用這樣的表達式來近似替代導函數的斜率值了,還是以(4,18)為例,這一點的斜率我們用(f(x+0.001)-f(x))/0.001來試試看。

得到的這個值還是相對比較精確的,既然有了這種辦法,那麼在任何一個維度上,只要它可導我們都能求出 -ηdf(x)/dx 的近似值,並更新 x_{n+1}=x_{n}-ηdf(x)/dx 。不用擔心8.001≈8的誤差會產生疊加交過,從而讓求解出問題,因為它最後在應用的過程中僅僅是用來確定一個更新的量。更新的時候還是用精確的方式更新了n維度向量x的某個維讀的值而已,在剛剛這個示例中最多就是步長上和理想的 ηdf(x)/dx 可能差了0.001η而已,影響非常有限,完全不必太糾結。

2.8訓練過程

回過頭來再看看我們初始化( w_{0},b_{0},Loss_{0} )後下一步怎麼做,一切都水到渠成。

再次強調一下,不求梯度(偏導數)的情況下,通過改變w和b的值是一定能夠比較出來移動的方向的,但是問題是不知道移動多少比較便宜。而有了偏導數與學習率η的乘積後,當這個點逐步接近「碗底」的時候,偏導數也隨之降低,移動的步伐也慢慢減小,收斂更為平緩,不會輕易出現「步子太大」而越過最低點的情況。

一輪一輪進行迭代,直到每次更新的值非常小,損失值不再明顯減少就可以判斷為訓練結束。此時得到的(w,b)值就是我們要求的模型y=wx+b中最合適的w和b——也就是這次機器學習所學到的具體內容。

2.9模型工作

但訓練結束後,模型就可以開始工作了。

這個工作的過程是非常簡單的,那就是把一個輸入的x代入到訓練好的y=wx+b中去,使它輸出一個y。

這個過程比起剛剛的訓練過程時間要短得多,必經訓練過程需要迭代法去一次一次計算逼近要求的解,而這個工作過程完全是一輪普通的加減乘除運算。

可以說,幾乎所有的機器學習演算法模型都會體現出這樣一個特點,那就是「訓練時間很長,而一次工作的時間很短」。

3神經網路的訓練

在看罷了一個線性回歸的訓練過程之後,我們回頭看看剛在擱置一旁很久的兩層網路。

這個網路用函數表達式去寫的話會是這個樣子:

看到這樣的情形,我們應該不會感到緊張了。剛才的線性回歸已經被我們徹底征服了。那麼即便沒有人告訴我們,我們也會知道接下來就是一套俗不可耐的「初始化之後挪啊挪」的過程——把整個網路里所有的特定係數 w_{h}、b_{h}、w_{o} b_{o} 都初始化一個值,然後照貓畫虎地按照剛才的套路,定義一個描述誤差的損失函數,然後將 w_{h}、b_{h}、w_{o} b_{o} 逐步變化,知道損失函數減小到足夠小就OK了。好,我們先按照這個套路走一遍看看。

3.1準備樣本

在一個複雜的網路中,我們準備一定數量的用來訓練的x向量,可以是文本,可以是圖片,可以是音頻,甚至可以是音視頻結合的更為複雜的訓練樣本,當然只要是輸入到網路中作為訓練樣本的一定是向量化的。在這個簡單的網路里為了看的清晰一些還是舉普通的數字為例。

這裡我們造10個x值1到10,10個y值0.1到1.0。看上去非常像一個線性回歸的關係,對不對?只不過在這我們可以完全裝作不知道這件事,讓計算機通過網路自己去學習,學出 w_{h}、b_{h}、w_{o} b_{o} 這四個待定參數,學出來什麼關係就是什麼關係。

如果你是做圖片分類的話,那你還要給每個樣本打上標籤,

x_{1} y_{1}

x_{2} y_{2}

一直往下標,直到我們準備的最後一個向本:

x_{n} y_{n}

比如,給我一張照片 x_{1} ,我標記一個 y_{1} ——貓,給我一張圖片 x_{2} ,我標記一個 y_{2} ,一直這樣下去,直到標記完畢所有的照片樣本。

3.2清洗數據

其實清洗處理這個過程是比較複雜的,也是整個神經網路和深度學習比較難的地方,我們後面會具體針對場景進行討論,現在我們只要了解到在放入網路之前進行訓練之前,需要進行一定處理,處理的目的是為了幫助網路更加高效、更準確地做好分類,這樣就可以了。

3.3正式訓練

在前面的工作基本做好的情況下,我們就可以開始訓練模型了。

我們現在要做的事情就是把剛才這兩列丟進去

根據網路中兩個神經元的表達式描述

x_{1} 一旦代入之後,就會是這樣一個映射關係了:

那麼由 x_{1} y_{1} 帶來的誤差值也可以定義了,也就是:

由10個訓練數據共同帶來的誤差值就變成了:

不斷初始化 w_{h}、b_{h}、w_{o} b_{o} 是什麼值,這個損失函數Loss都是恆為非負數的,現在就開始「挪動」 w_{h}、b_{h}、w_{o} b_{o} 這四個待定係數逐步減小Loss的過程了。這個似曾相識的過程又出現了,也就是需要有這樣四個表達式來做更新:

其他都好理解,無非是一個「5維空間里的碗」找碗底的過程。問題是 ?Loss/?w_{h}、?Loss/?b_{h}、?Loss/?w_{o} ?Loss/?b_{o} 這四個值怎麼求。別的倒不怕,求不出來那就要面臨著凸優化梯度下降過程中挪得過大越過碗底的問題。

辦法還是有的,求導數中有兩種特殊的導函數求解方法,一種是「連乘型」,一種是「嵌套型」。首先用個技巧先把整個函數做個變形:

給函數前面配一個1/2來,主要是為了一會兒削起來方便。然後用「嵌套型」函數的求導特點來求導——整個「嵌套型」的學名叫做鏈式法則(chain rule)。先求離輸出端最近的Loss對 w_{o}、b_{o} 的偏導數。

求導有幾個相對比較固定的技巧,其中一個就是線性疊加,也就是說如果 h(x)=f(x)+g(x) ,那麼 dh(x)/dx=df(x)/dx+dg(x)/dx 。現在這個Σ就可以放心地做展開了,就變成:

兩級就結束了,前面 y_{oi} 其實是 y_{oi}(z_{o})=1/(1+e^{-z_{o}}) 這樣一個函數;後面是 z_{o}(b_{o})=w_{o}y_{h}+b_{o} ,細心的讀者朋友已將看出來了是常數1。同理可以求出 ?Loss/?w_{o}

同時可以求出距離輸出端更遠一些的參數的偏導數:

好了,到此為止我們已經把每次更新的這4個表達式的具體值都可以求出來了

接下來只要按照表達式去更新就OK了。其他結構的網路中,可能會有這樣一些小的區別。

區別1:在訓練樣本很多的情況下Σ加和的次數會更多一些,有1000個樣本就是1000次,有10000個樣本就是10000次。

區別2:如果網路層數更深,則後面偏導數連乘的項就很長,在這我們看到2層就出現4個,而如果是10層,那就是20個連乘。

區別3:如果網路更加複雜,比如一層網路不知一個節點,那麼其中一個節點上係數的偏導數則會從多個路徑傳播出去,因為這個節點的後面會不止一個節點把它的輸出當成自己的輸入,從而形成多個「嵌套型關係」。

綜上,我們已經了解了最簡單的神經網路的訓練過程和原理了。以後我們還會學到更為複雜的神經網路構建方式,但是從本質上來講都跟以上內容一樣,是不斷調整各個神經元中待定係數使得損失函數向不斷降低的方向移動。

請注意,在這裡必須強調一個非常重要的觀點。剛才給大家看到的這個推導過程是一個非常簡化並且容易理解的推導過程,通過凸優化的方式能夠順利求出損失函數的極值。然而我們在真正的生產環境遇到的各種神經網路中包含著非常多的線性核非線性分類器函數組合,也就意味著,在這種非常複雜的網路環境中,損失函數極有可能甚至可以說幾乎一定不是凸函數——在這個函數空間中會呈現出「層巒疊嶂」、「坑坑窪窪」的不規則形狀,而非前面我們畫出來的一個大碗。因此,在真正的商用框架中——比如我們下幾節要講的Tensorflow會用很多技巧來尋找整個向量孔家中擁有極小值點的參數向量。對於這種不規則形狀的非凸函數來說,當然也是可以通過遺傳演算法、隨機梯度下降等多種方式相結合的方法來不斷試探找到極小值點位置的。

閑話少說,我們下一節就開始一個小實驗,用Tensorflow小試牛刀,來做一個手寫識別功能的實現。

參考文獻:《白話深度學習與Tensorflow》,有增刪改減。

本文知乎鏈接:zhuanlan.zhihu.com/p/33


推薦閱讀:

支持向量機(SVM)——SMO演算法
最小二乘法(Least squares)的前世今生
DIY發現新行星操作指南 | 谷歌開源了行星發現代碼
Character人物之線條表現
人工智慧——狀態機

TAG:人工智慧 | 人工智慧演算法 | 機器學習 |