1. 程式人生 > >使用python實現深度神經網路 1

使用python實現深度神經網路 1

深度學習基本概念

一、實驗介紹

1.1 實驗內容

深度學習並沒有你想象的那麼難,本課程將會一邊講解深度學習中的基本理論,一邊通過動手使用python實現一個簡單的深度神經網路去驗證這些理論,讓你從原理上真正入門深度學習。

本次實驗將會帶大家學習深度學習中的一些最基本的概念,本次實驗很重要,理解這些概念是繼續深入學習的基礎。

1.2 實驗知識點

  • 如何讓機器“學習”
  • 神經網路的概念
  • 有監督與無監督學習的區別
  • 迴歸與分類的區別
  • 損失函式的概念
  • 梯度下降演算法介紹
  • 超引數的概念

1.3 實驗要求

本實驗要求你在實驗前具備以下基本能力:

  1. 熟練使用python
  2. 具備高中畢業生的數學水平
  3. 對深度學習的興趣

以下能力不做要求,會在實驗過程中儘量講解,但你最好能夠先提前學習這些內容:

  1. 函式導數的概念及求導鏈式法則 學習資料
  2. 矩陣的概念及其基本運算規則 學習資料
  3. python numpy科學計算庫的使用 學習資料

1.4 實驗環境

  • python 2.7
  • numpy 1.12.1

二、實驗步驟

2.1 什麼是讓機器“學習”

如果你只是聽說過機器學習或者深度學習,你可能會被他們的名字嚇到,機器真的已經能像人一樣“學習”了嗎?其實,這裡的“學習”和人的學習的確有一些相似的地方,但又有很大不同。為了理解這裡的“學習”究竟是什麼含義,讓我們先來回想一下以前我們是如何讓機器工作的,我們會以程式設計計算圓的面積為例。

2.1.1 計算圓的面積–手工程式設計的方法

計算圓的面積顯然是一個很簡單的問題,任何程式設計師應該都能立馬寫出類似以下的函式來進行計算:

  1. # 程式碼片段 1
  2. # 計算圓面積的舊辦法
  3. defcalArea(radius):
  4. area=3.14*radius*radius
  5. return area

在編寫這段程式碼的過程中,其實我們做的事情很簡單。因為我們事先知道輸入和輸出之間的關係(輸出等於3.14乘以輸入的平方,高中畢業生應該都能輕易得出這個結論吧),所以我們直接將這種關係通過area=3.14*radius*radisu這個式子表達出來就行了。注意我們設定了一個引數:圓周率3.14。

2.1.2 計算圓的面積–讓機器“學習”

現在讓你看看如何讓機器通過“學習”來計算圓的面積:

  1. # 程式碼片段 2
  2. deflearn(model,data)
    :
  3. ... # 通過資料data訓練模型model的程式碼
  4. return model # 已經訓練好引數的模型
  5. deftest(model,x):
  6. ... # 將x輸入模型model中得出結果y的程式碼
  7. return y # 返回計算結果--圓的面積
  8. trained_model=learn(init_model,data) # 通過訓練得到模型trained_model
  9. y=test(trained_model,x=100) # 得到半徑為100的圓的面積

其實讓機器“學習”很簡單不是嗎(至少上面的程式碼看上去是如此^^),我們提供給機器足夠的訓練資料data,比如下面這樣:

x y
1 3.14
1.4 6.15
2 12.56
10 314
20 1256

然後learn函式(我們暫時不知道如何實現這個函式,之後的實驗我們會逐步實現這個函式)使用這些資料去訓練模型model(我們接下來會介紹如何構建這個model),當我們需要計算一個新的圓的面積時,就呼叫test函式,根據訓練好的模型去計算其面積。 注意這裡我們不需要事先知道輸入和輸出之間的關係,也不需要手動設定任何引數,只要把資料“餵給”學習演算法learn,它就會自動得出一個能夠解決問題的模型。 也就是說,在訓練過程中,模型model其實是“學習”到了輸入和輸出之前的關係以及相關的引數。就像是一個數學家通過對圓的觀察,得出了圓的面積和半徑的關係,並且求出了圓周率。

2.1.3 機器學習–激動人心的未來

這太令人激動了不是嗎,如果我們能找到一個完美的學習演算法learn和合適的模型model,並且提供給它足夠的學習資料data,那這個世界上幾乎所有問題都可以用這個學習演算法來解決。遺憾的是,目前我們的學習演算法、學習模型還不夠完美,我們的學習資料仍然不夠,能夠通過機器學習(一般認為深度學習包含在機器學習當中)解決的問題還很有限。不過目前取得的很多成果已經非常激動人心了,比如讓人類“重新認識圍棋”的AlphaGO、準確率已經接近甚至達到人類水準的語音識別和機器翻譯、已經能夠上路的自動駕駛汽車。 總之,機器學習將會徹底改變機器為我們服務的方式,我們將越來越少的通過程式設計明確地告訴計算機如何去解決一個問題,而是讓計算機自己學習如何解決問題。

2.1.4 讓機器“學習”的必要性 & 實驗專案的引入

上面的例子太過簡單,無法體現出讓機器學習的必要性,要計算圓的面積,更快的方法顯然是直接手工編寫程式碼。 但是現實世界中的很多問題是手工編寫程式碼幾乎無法解決的。比如將會貫穿本課程的一個小專案:機器實現圖片英文字母識別,你幾乎無法找到一個能手工編寫出的演算法去讓程式能夠從下面這樣的圖片中識別出英文字母:

此處輸入圖片的描述

為什麼這個問題難以手工程式設計解決?一種思考的角度是,對於上圖這樣22*55尺寸的圖片,如果將它看成是一個向量,向量中的每一個數值對應圖片中的一個畫素灰度值,那這個向量的維度(長度)就達到了22*55=1210,相對於計算圓面積問題中長度為1的輸入向量,這個向量維度非常高,存在於1210維的向量空間當中。高維度帶來的高複雜度使得實際上無法使用手工程式設計解決這個問題。只有通過讓機器自己“學習”,才有可能找到解決辦法。

2.2 幾個基本概念

為了接下來的講解方便,這裡先告訴大家幾個基本概念。

2.2.1迴歸問題與分類問題

迴歸(regression)分類(classification)是機器學習中的兩大類問題。上面我們舉的計算圓形面積的例子就屬於迴歸問題,即我們的目的是對於一個輸入x,預測其輸出值y,且這個y值是根據x連續變化的值。分類問題則是事先給定若干個類別,對於一個輸入x,判斷其屬於哪個類別,即輸出一般是離散的,比如本課程將會帶大家實踐的圖片英文字母識別就屬於分類問題(判斷一個圖片中包含的字元屬於26個類別中的哪一個)。

本課程主要介紹分類問題,迴歸問題可能會在後續的課程中向大家介紹。

2.2.2 有監督學習和無監督學習

有監督無監督是機器學習方法的兩大分類。上面我們舉的計算圓形面積的例子就屬於有監督學習,因為我們的輸入data既包含輸入x,又包含x對應的y,即學習資料已經事先給出了正確答案。 無監督學習則是隻有輸入x。你可能會感到不可思議,正確答案都不告訴我,我要怎麼學習呢?確實,無監督學習要更難。無監督學習目前一般用於聚類(cluster)問題,即給定一批資料,根據這批資料的特點,將其分為多個類別,雖然我並不知道這每個類別所代表的具體含義。比如網路商城的商品推薦演算法可能會根據使用者的使用習慣,以往的瀏覽歷史等,將使用者分為多個類別,同一類別的使用者在行為模式上可能比較相似。而事先並不知道最終會劃分出多少個類別,每個類別有哪些共同特點。

本課程主要介紹有監督學習,無監督學習可能會在後續課程中向大家介紹。

2.3 模型的構建–神經網路

上面我們提到過要讓機器“學習”,一般需要:

  1. 用來解決問題的模型model
  2. 學習資料(或者說訓練資料)data
  3. 讓模型model通過資料data學會解決特定問題的學習演算法learn

我們先來學習深度學習中一種非常重要的模型–神經網路(neural network)

2.3.1 與生物大腦的類比

一些人喜歡將生物大腦神經元網路的結構看做是神經網路的靈感來源。大腦神經元的結構如下:

此處輸入圖片的描述

我們主要關注細胞體、樹突、軸突。我們看到,樹突作為接收端,接收從其他神經元傳導過來的神經衝動,而軸突再將經過細胞體“處理”的神經衝動傳遞給其他神經元。 而神經網路的結構如下:

此處輸入圖片的描述

你會發現這張圖片和上圖大腦神經元的圖片有很多相似之處,只不過神經衝動在這裡變成了具體的數值。 圖中的圓圈代表一個個神經元(neuron),其中網路層1(輸入層)中的每個神經元都與網路層2中的每個神經元相連。同時我們注意到,神經元間相連的線上有該條連線線的權重w,神經網路工作時,將前一層網路中的每個神經元的值,與連線線上的權重w相乘,傳遞給下一層網路中的神經元。同時,每個神經元還會接收一個偏移量(bias)。即在該圖中,有:

  1. w11*a1+w12*a2+w13*a3 + bias1=b1
  2. w21*a1+w22*a2+w23*a3 + bias2=b2

如果寫成矩陣的形式(如果你不知道矩陣運演算法則,可以先跳過這裡,之後的實驗會有講解),就是:

此處輸入圖片的描述

如果你對線性代數比較熟悉,你會發現這就是一個線性方程組而已,沒錯,線性代數是神經網路的重要理論基礎之一,線性代數與神經網路的聯絡更密切直接。 但是神經網路中不止有線性運算。對於網路層2中的神經元b,它的值在傳給輸出之前,會先經過一次非線性運算g(圖中沒有畫出來)。即在該圖中,有:

  1. g(b1)=Y1
  2. g(b2)=Y2

注:如果你不知道線性運算非線性運算的區別,請自行查閱資料瞭解。

2.3.2 非線性啟用函式

神經網路中,將上面提到的非線性運算g稱為啟用函式

實際運用當中,有多種啟用函式可以選擇,這裡我們介紹最經典的一種啟用函式:sigmoid啟用函式,它的數學形式為:此處輸入圖片的描述

sigmoid函式的影象是這樣的:

此處輸入圖片的描述

即輸入值x越大,函式值越接近1,輸入值越小,函式值越接近0,當輸入為0的時候,函式值為0.5。

也許你會覺得奇怪,為什麼我們需要這樣一個複雜(但以後你會發現其實它的形式很簡單優美)的函式來對網路層2的輸出值再進行一遍運算呢?

有多種理由要求我們必須向神經網路中引入非線性運算部分,最簡單的原因之一就是,對於如下圖所示的多層神經網路(即深度神經網路),如果沒有非線性運算部分,那麼由於矩陣乘法的結合性(這裡又一次涉及到了矩陣的運算性質,如果你不熟悉又想搞清楚這裡,請先看看第二次實驗),多個線性運算層直接相連的效果實際上相當於只進行了一次線性運算,多層次的網路結構失去了意義。

此處輸入圖片的描述

* 深度神經網路示例,圖中非關鍵部分被略去。

* 如果沒有非線性啟用函式,則其中網路層1 與 網路層2 之間的尺寸為2x3的矩陣和網路層2 與 網路層3 之間的尺寸為3x2的變換矩陣由於矩陣乘法的結合性實際上會合併成為單獨的一個3x3的矩陣(請思考這裡為什麼是3x3而不是2x2)。

第二個原因解釋起來可能複雜一點,在解釋第二個原因之前,讓我們先看一看如果只有線性矩陣運算部分,我們的神經網路實際上在做些什麼。 以神經網路示例圖中的b1為例,假如我們將其用於分類問題,同時只考慮有兩個輸入變數的情況,即:w11*a1+w12*a2 + bias1=b1 對於輸入a,當最終得到的b1大於0時,我們認為a屬於b1所代表的的類別,否則不屬於b1所代表的的類別。

對於式子w11*a1+w12*a2 + bias1=b1,其實你對他非常熟悉,如果在座標軸上畫出他的函式圖形,就是這樣的一條直線:此處輸入圖片的描述

對於圖中紅色的點,其對應的b1值要大於0,可以認為其屬於b1所代表的類別,而綠色的點對應的b1值小於0,故它們不屬於b1所代表的類別。 當輸入資料的維度為3時,單純的線性運算就相當於是使用一個平面對空間進行劃分,當資料維度高於3維時類似,只是我們已經無法直觀的觀察大於3維的空間了。 也許你馬上就會發現一個問題,如果我們要分類的資料分佈是下面這樣的該怎麼辦:

此處輸入圖片的描述

不同類別之間的分界線不是直線而是曲線,此時無論如何也無法用線性分類器去準確的進行分類。 而非線性部分的引入,在一定程度上可以使原本的直線變成曲線,如下圖:

此處輸入圖片的描述

這樣原本不可分的問題就變得可分了。

非線性啟用函式還有其他作用,比如sigmoid將輸出值的範圍限制在0到1之間,使其剛好可以被作為概率來看待。

2.4 評價神經網路的效果

2.4.1 使用隨機值初始化神經網路引數

上一節中,我們只向大家介紹了神經網路的結構,我們現在還不知道如何設定神經網路中的網路引數值(包括連線線上的權重w和偏置值bias)。一種最簡單的方法是隨機設定網路引數。如果神經網路中的網路引數是隨機設定的,那麼我們得到的“網路效能”應該就是隨機情況下的平均值。比如我們要判斷一些圖片中的字母屬於26個字母中的哪一個,如果網路引數值是隨機設定的話,那我們得到的識別準確率理論上應該在1/26左右。 隨機設定引數值當然不可能是我們解決問題的辦法,但要學習如何設定“好”的網路引數值來使神經網路正確的工作,我們要先研究一下上面提到的“網路效能”的問題。 我們如何判定一個神經網路是“好”的還是“不好”的呢,即我們用什麼來評價一個神經網路的效果呢?

2.4.2 損失函式的概念

對於有監督學習,由於我們的學習資料data中包含輸入資料的預期正確輸出值,一個簡單的辦法就是比較神經網路的輸出h與預期的正確輸出y之間的差異。比如計算(h-y)^2,當得到的值比較大時,就說明我們的神經網路的輸出與預期的正確輸出偏差較大,反之,如果得到的值很小甚至等於0,就說明我們的模型工作的不錯,能夠正確的預測輸出值。

這就引出了損失函式(cost function)的概念,實際當中同樣有多種損失函式可供選擇,這裡我們來介紹最經典的一種損失函式:二次損失函式(quadratic loss function)

2.4.3 二次損失函式 quadratic loss

就像它的名字所暗示的那樣,quadratic loss function通過計算hy之間差值的二次方,來表達一個神經網路的效果。具體形式如下:

此處輸入圖片的描述

這個公式幾乎就是我們在上面提到的對模型的誤差求平方,只是稍微再複雜一點。其中的希臘字母theta代表網路中的所有引數,X代表向神經網路輸入的資料,Y代表輸入資料對應的預期正確輸出值。

不知你是否注意到,這個公式有些奇怪,因為一般我們都是把變數名寫入函式名後面的括號裡,代表這個函式的自變數有哪些,可這裡的網路引數theta怎麼跑進去了,而訓練資料XY卻不在裡面。

這個地方有些微妙,而且非常重要。實際上,在這裡theta才是自變數,因為我們一開始不知道讓網路能夠正確的工作的引數值是多少。我們的學習演算法learn按照某種策略,通過不斷的更新引數值來使損失函式J(theta,X,Y)的值減小。在這裡,theta是不斷變化的量。

XY就不是自變量了嗎?對,它們確實不是自變數!因為對於我們要解決的一個具體的問題(比如圖片字母識別),其訓練資料中包含的資訊其實代表的是這個問題本身的性質,我們的學習演算法learn其實最終就是要從訓練資料data中學習到這些性質,並通過網路模型model的結構和其中的引數把這些性質表達出來。對於一個特定的問題來說,可以認為它們是常量!

這裡比較容易搞混,請仔細思考一下thetaX Y在這裡所代表的意義。

2.5 梯度下降演算法

上一節我們介紹了衡量神經網路效能的辦法:損失函式,那為了讓我們的神經網路能夠準確的識別圖片中的字母,就必須想辦法讓損失函式的值儘量小,最好是讓損失函式值為0。

那麼如何實現這個目標呢?這裡就要佩服數學家的智慧(但是放心,這裡不會涉及很高深的數學知識)。很久以前就有數學家發明了梯度下降演算法來幫助我們減小損失函式的值。

假設我們的損失函式圖形是這樣的:

此處輸入圖片的描述

我們隨機初始化的網路引數在A這個位置。顯然,我們的目標是使A儘量靠近最低點B,最好是與B重合,這樣才能最小化損失函式。這時,就必須介紹一下梯度的概念,如果你還記得高等數學中梯度的概念,可以直接跳過下一段。

上圖這種,函式圖形向下突出的函式被稱為“凸函式”(注意這裡的叫法,“凸”是指向下凸)。

2.5.1 什麼是“梯度”

梯度本身是一個向量,由損失函式對每個自變數(圖中的theta0和theta1)分別求偏導(如果你不知道什麼是偏導,可以先看看第二次試驗中的相關講解)得到。

形象的理解,梯度指向損失函式變化最快的那個方向(且該方向讓函式值變大),對於上圖中三維的情形,就是曲面上在A點最“陡峭”的方向。對於二維的情形,其實就是斜率方向。

2.5.2 梯度下降演算法

那為了使A點向B點移動,就可以對A的值減去(請思考這裡為什麼是減去)該點的梯度。通過公式來表達,就是:

此處輸入圖片的描述 注意這裡多了一個希臘字母alpha,因為梯度只為我們指明瞭更新引數theta的方向,而我們具體朝著這個方向“走多遠”則由alpha控制。

當走到下一個位置之後,再求該點的梯度,用梯度更新引數位置,如此重複,直到逼近B點。這就是梯度下降演算法的原理

這裡你可能會有疑問,如果我們的損失函式不是凸函式怎麼辦,那樣我們的梯度下降演算法就可能無法達到最低點。或者我們可能被“區域性最低點”(梯度為0)欺騙而無法達到“全域性最低點”。的確,這看起來是一個嚴重的問題,但實際當中,這個問題並沒有那麼突出,我們有一些辦法來防止被“區域性最低點”欺騙,後面的課程可能會講到。

注:alpha 在這裡是一個 超引數(hyperparameter)

2.6 引數(parameter)與超引數(hyper parameter)

2.6.1 區別

如果你足夠仔細,你會發現在上面的描述中,我們將模型model中的引數theta稱為引數,而學習演算法(即梯度下降演算法)裡的引數alhpa被稱為超引數。這兩個地方的“引數”為什麼叫法不一樣呢?

叫法不同,是因為它們的作用及我們設定它們值的方式不一樣。theta被稱為“引數”,是因為theta決定了我們的模型model的性質,並且theta的最終值是由我們的學習演算法learn學習得到的,不需要我們手工設定。 而alpha則不同,在這裡alpha並不是模型model中的引數,而是在學習演算法learn中決定我們的梯度下降演算法每一步走多遠的引數,它需要我們手工設定,且決定了得到最優theta的過程。即超引數決定如何得到最優引數

看到這裡你也許會說,這不還是要手動設定引數嗎?雖然這裡的超引數仍然需要我們手工設定,但模型中的數量巨大(對於複雜問題,模型中可能有上億個引數!)的引數已經不需要我們來設定。這已經大大的減輕了手動設定引數的負擔。所以說機器學習還是非常非常強大的。

2.6.1 超引數alpha–學習速率

機器學習中,我們將上面提到的超引數alpha稱為學習速率(learning rate)。這很好理解,因為alpha越大時,我們的引數theta更新的幅度越大,我們可能會更快的到達最低點。但是alpha不能設定的太大,否則有可能一次變化的太大導致“步子太長”,會直接越過最低點,甚至導致損失函式值不降反升。如下圖:此處輸入圖片的描述

所以,設定合理的學習速率alpha值非常重要。實際當中的做法一般是先設定alpha為一個較小的值,比如0.001,觀察每一次引數theta更新後損失函式J的值如何變化,如果J變大了,就將alpha的值除以10,變成0.0001,直到損失函式值開始變小。如果J變小了,則可以將alpha再乘以10,使得學習的速率更快。最終使alpha停留在一個能夠使J變小,又不會因為值過小導致學得太慢的“臨界值”。

深度學習中還可能有其他幾個超引數,我們會在後面的課程繼續介紹

2.7 遺留的問題

我們已經介紹了深度學習中的網路模型model、學習演算法learn,還順便提了一下有監督學習下的訓練(學習)資料data。可以說,到這裡,深度學習中幾個最基本最重要的概念你已經清楚了。

但是這裡還留有一個嚴重的問題,上面我們提到梯度下降演算法根據損失函式的梯度來更新模型引數,那麼我們該如何求梯度呢?這個問題將留到我們第三次實驗課程來解決,我門同時會開始編寫程式碼(講了這麼多理論,終於可以寫程式碼了)來實現梯度的求解。

第二次實驗我們會講解深度學習必須要用到的一些基本數學知識以及numpy科學計算庫的用法。

三、實驗總結

作為第一次實驗,我們講到的概念和基礎理論比較多,你可能需要較多的時間才能消化。如果你暫時無法理解其中的一些概念,不要氣餒,請回去重新看一下,思考一下。當後面再用到這些東西而你覺得映像模糊時,也請重新回過頭來看一下。只要你能理解這些基本的東西,可以說,後面的學習都會勢如破竹。你將從此走進深度學習的世界!

本次實驗,我們學到的知識有:

  1. 機器學習(包括深度學習)利用訓練資料data,使用學習演算法learn對模型model中包含的引數進行更新。使得評價模型效果的損失函式不斷減小並達到最優值。
  2. 深度學習中的模型一般是指(深度)神經網路, 神經網路由多個網路層構成,層與層之間通過連線來傳遞資訊。
  3. 神經網路中一般包含線性運算和非線性運算(啟用函式)部分,兩個線性運算部分之間必須存在非線性部分,否則這兩個線性部分會合併成一個線性部分。
  4. 損失函式用來描述一個神經網路模型的效能,損失函式值越小模型效能越好。
  5. 梯度下降演算法可以通過多次迭代更新模型引數值來使損失函式值減小。梯度下降演算法的關鍵是求損失函式對於網路引數的梯度。
  6. 機器學習(包括深度學習)分為有監督學習和無監督學習,前者的訓練資料既包括輸入,也包括輸入對應的正確輸出(標籤label)。後者的訓練資料不包含正確輸出。
  7. 機器學習解決的問題一般可以分為迴歸問題、分類問題、聚類問題這三種(實際上還有別的種類),本課程主要研究分類問題。
  8. 超引數是在訓練模型的過程中,為了得到最優模型引數而手工設定的一類引數。設定合理的超引數值,對模型引數的優化十分重要。

四、課後作業

快來做下面的作業檢驗一下你理解的如何吧,所有的作業都能根據上面的知識點推測出答案。

  1. “手工程式設計計算圓的面積”中,3.14是“引數”還是“超引數”?
  2. 一個如本次實驗所描述的神經網路有兩層,第一層有10個神經元,第二層有20個神經元,請問這兩層神經元之間有多少條連線?第一層到第二層之間由這些連線所表示的線性變換矩陣尺寸是多少?
  3. “使用深度學習預測股票走勢曲線”是分類問題還是迴歸問題?
  4. “根據人臉圖片識別人的性別”是分類問題還是迴歸問題?
  5. 如果說,有監督學習的訓練資料data由輸入X和正確答案Y組成,那無監督學習的訓練資料應該是什麼樣的?
  6. [選做題]sigmoid函式對x求導結果是什麼?
  7. [選做題]2.6.1小節中的圖片裡,為什麼“學習速率適中”圖中每次更新的“步子”越來越短,而“學習速率過大”圖中每次更新的“步子”越來越長?
  8. [選做題]你能自己想出一種求損失函式梯度的方法嗎?