1. 程式人生 > >(轉載)深度學習(2)——線性單元和梯度下降

(轉載)深度學習(2)——線性單元和梯度下降

原文地址:https://www.zybuluo.com/hanbingtao/note/448086

轉載在此的目的是自己做個筆記,日後好複習,如侵權請聯絡我!!

  在上一篇文章中,我們已經學會了編寫一個簡單的感知器,並用它來實現一個線性分類器。你應該還記得用來訓練感知器的『感知器規則』。然而,我們並沒有關心這個規則是怎麼得到的。本文通過介紹另外一種『感知器』,也就是『線性單元』,來說明關於機器學習一些基本的概念,比如模型、目標函式、優化演算法等等。這些概念對於所有的機器學習演算法來說都是通用的,掌握了這些概念,就掌握了機器學習的基本套路。

線性單元是什麼?

  感知器有一個問題,當面對的資料集不是線性可分

的時候,『感知器規則』可能無法收斂,這意味著我們永遠也無法完成一個感知器的訓練。為了解決這個問題,我們使用一個可導線性函式來替代感知器的階躍函式,這種感知器就叫做線性單元。線性單元在面對線性不可分的資料集時,會收斂到一個最佳的近似上。

  這樣的線性單元如下圖所示:

對比此前我們講過的感知器。

  這樣替換了啟用函式f之後,線性單元將返回一個實數值而不是0,1分類。因此線性單元用來解決迴歸問題而不是分類問題。

線性單元的模型

  你也許會說,這個模型太不靠譜了,是這樣的,因為我們考慮的因素太少了,僅僅包含了工作年限。如果要考慮更多的因素,比如所處的行業,公司,職級等等,可能預測就會靠譜的多,我們把工作年限,行業,公司,職級這些資訊稱之為特徵。對於一個工作5年,在IT行業,百度工作,職位T6這樣的人,我們可以用這樣的一個特徵向量表示他

監督學習和無監督學習

  接下來,我們需要關心的是這個模型如何訓練,也就是引數w取什麼值最合適。

  機器學習有一類學習方法叫做監督學習,它是說為了訓練一個模型,我們要提供這樣一堆訓練樣本:每個訓練樣本即包括輸入特徵x,也包括對應的輸出y(y也叫作標記label)。也就是說,我們要找到很多人,我們即知道他們的特徵(工作年限,行業...),也知道他們的收入,我們用這樣的樣本去訓練模型,讓模型即看到我們提出的每個問題(輸入特徵x),也看到對應問題的答案(標記y)。當模型看到足夠多的樣本之後,它就能總結出其中的一些規律。然後,就可以預測那些它沒看過的輸入所對應的答案了。

  另外一類學習方法叫做無監督學習,這種方法的訓練樣本中只有x而沒有y。模型可以總結出特徵x的一些規律,但是無法知道其對應的答案y。

  很多時候,即有x又有y的訓練樣本是很少的,大部分都只有x,比如在語音到文字(STT)的識別任務中,x是語音,y是這段語音對應的文字。我們很容易獲取大量的語音錄音,然而把語音一段一段切分好並標註上對應文字則是非常費力氣的事情。這種情況下,為了彌補帶標註樣本的不足,我們可以用無監督學習方法先做一些聚類,讓模型總結出那些音節是相似的,然後再用少量的帶標註的訓練樣本,告訴模型其中一些音節對應的文字。這樣模型就可以把相似的音節都對應到相應文字上,完成模型的訓練。

線性單元的目標函式

  現在我們只考慮監督學習。

  在監督學習下,對於一個樣本,我們知道它的特徵x,以及標記y。同時,我們還可以根據模型H(x)計算得到輸出Yhat。注意這裡面我們用y表示訓練樣本里面的標記,也就是實際值;用帶上劃線的Yhat表示計算模型計算的出來的預測值,我們當然希望模型計算出來的Yhat和Y越接近越好。

  我們還可以把上面的公司寫成和式的形式。使用和式,不光書寫起來簡單,逼格也跟著暴漲,一舉兩得,所以一定要寫成下面這樣

梯度下降優化演算法

  大學時候我們學過怎麼樣求函式的極值。函式y=f(x)的極值點,就是它的導數f'(x) = 0 的那個點,因此我們可以通過解方程f'(x) = 0 ,求得函式的極值點(X0,Y0)。

  不過對於計算機來說,它可不會解方程。但是它可以憑藉強大的計算能力,一步一步的去把函式的極值點試出來,如下圖所示:

   首先,我們隨便選擇一個點開始,比如上圖的X0點,接下來,每次迭代修改x的值為X1,X2,X3,.....經過數次迭代後最終達到函式最小值點。

  你可能要問了,為啥每次修改x的值,都能往函式最小值那個方向前進呢?這裡的奧祕在於,我們每次都是向函式y =f(x)的梯度的相反方向來修改x,什麼是梯度呢?梯度是一個向量,它指向函式值上升最快的方向。顯然,梯度的反方向當然是函式值下降最快的方向了。我們每次沿著梯度相反方向去修改x的值,當然就能走到函式的最小值附近。之所以是最小值附近而不是最小值那個點,是因為我們每次移動的步長不會那麼恰到好處,有可能最後一次迭代走遠了越過了最小值那個點。步長的選擇是門手藝,如果選擇小了,那麼就會迭代很多輪才能走到最小值附近;如果選擇大了,那麼可能就會越過最小值很遠,收斂不到一個好的點上。

  按照上面的討論,我們就可以寫出梯度下降演算法的公式:

  下面,請先做幾次深呼吸,保持大腦清醒,我們來求取deltaE(w),然後帶入上式,就能得到線性單元的引數修改規則。

  關於deltaE(w)的推導過程,請下去再瞭解。現在我們關注的是目標函式E(w)的梯度:

  因此,線性單元的引數修改規則最後是這個樣子:

隨機梯度下降演算法(Stochastic Gradient Descent,SGD)

  如果我們根據式(3)來訓練模型,那麼我們每次更新w的迭代,要遍歷訓練資料中所有的樣本進行計算,我們稱這種演算法叫做批梯度下降(Batch Gradient Descent)。如果我們的樣本非常大,比如數百萬到數億,那麼計算量異常巨大。因此,實用的演算法是SGD演算法。在SGD演算法中,每次更新w的迭代,只計算一個樣本。這樣對於一個具有數百萬樣本的訓練資料,完成一次遍歷就會對w更新數百萬次,效率大大提升。由於樣本的噪聲和隨機性,每次更新w並不一定按照減少E的方向。然而,雖然存在一定隨機性,大量的更新總體上沿著減少E的方向前進的,因此最後也能收斂到最小值附近。

  下圖展示了SGD和BGD的區別

  如上圖,橢圓表示的是函式值的等高線,橢圓中心是函式的最小值點。紅色是BGD的逼近曲線,而紫色是SGD的逼近曲線。我們可以看到BGD是一直向著最低點前進的,而SGD明顯躁動了許多,但總體上仍然是向最低點逼近的。

  最後需要說明的是,SGD不僅僅效率高,而且隨機性有時候反而是好事。今天的目標函式是一個『凸函式』,沿著梯度反方向就能找到全域性唯一的最小值。然而對於非凸函式來說,存在許多區域性最小值。隨機性有助於我們逃離某些很糟糕的區域性最小值,從而獲得一個更好的模型。

實現線性單元

完整程式碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from perceptron import Perceptron


#定義啟用函式f
f = lambda x: x

class LinearUnit(Perceptron):
    def __init__(self, input_num):
        '''初始化線性單元,設定輸入引數的個數'''
        Perceptron.__init__(self, input_num, f)


def get_training_dataset():
    '''
    捏造5個人的收入資料
    '''
    # 構建訓練資料
    # 輸入向量列表,每一項是工作年限
    input_vecs = [[5], [3], [8], [1.4], [10.1]]
    # 期望的輸出列表,月薪,注意要與輸入一一對應
    labels = [5500, 2300, 7600, 1800, 11400]
    return input_vecs, labels    


def train_linear_unit():
    '''
    使用資料訓練線性單元
    '''
    # 建立感知器,輸入引數的特徵數為1(工作年限)
    lu = LinearUnit(1)
    # 訓練,迭代10輪, 學習速率為0.01
    input_vecs, labels = get_training_dataset()
    lu.train(input_vecs, labels, 10, 0.01)
    #返回訓練好的線性單元
    return lu


def plot(linear_unit):
    import matplotlib.pyplot as plt
    input_vecs, labels = get_training_dataset()
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(map(lambda x: x[0], input_vecs), labels)
    weights = linear_unit.weights
    bias = linear_unit.bias
    x = range(0,12,1)
    y = map(lambda x:weights[0] * x + bias, x)
    ax.plot(x, y)
    plt.show()


if __name__ == '__main__': 
    '''訓練線性單元'''
    linear_unit = train_linear_unit()
    # 列印訓練獲得的權重
    print linear_unit
    # 測試
    print 'Work 3.4 years, monthly salary = %.2f' % linear_unit.predict([3.4])
    print 'Work 15 years, monthly salary = %.2f' % linear_unit.predict([15])
    print 'Work 1.5 years, monthly salary = %.2f' % linear_unit.predict([1.5])
    print 'Work 6.3 years, monthly salary = %.2f' % linear_unit.predict([6.3])
    plot(linear_unit)

  接下來,讓我們擼一把程式碼。

  因為我們已經寫了感知器的程式碼,因此我們先比較一下感知器模型和線性單元模型,看看哪些程式碼能夠複用。

  結果就是,原來除了啟用函式f 不同之外,兩者的模型和訓練規則是一樣的(在上表中,線性單元的優化演算法是SGD演算法)。那麼,我們只需要把感知器的啟用函式進行替換即可。感知器的程式碼請參考上一篇文章。

from perceptron import Perceptron
#定義啟用函式f
f = lambda x: x
class LinearUnit(Perceptron):
    def __init__(self, input_num):
        '''初始化線性單元,設定輸入引數的個數'''
        Perceptron.__init__(self, input_num, f)

  通過繼承Perceptron,我們僅用幾行程式碼就實現了線性單元。這再次證明了面向物件程式設計正規化的強大。

  接下來,我們用簡單的資料進行一下測試。

def get_training_dataset():
    '''
    捏造5個人的收入資料
    '''
    # 構建訓練資料
    # 輸入向量列表,每一項是工作年限
    input_vecs = [[5], [3], [8], [1.4], [10.1]]
    # 期望的輸出列表,月薪,注意要與輸入一一對應
    labels = [5500, 2300, 7600, 1800, 11400]
    return input_vecs, labels    
def train_linear_unit():
    '''
    使用資料訓練線性單元
    '''
    # 建立感知器,輸入引數的特徵數為1(工作年限)
    lu = LinearUnit(1)
    # 訓練,迭代10輪, 學習速率為0.01
    input_vecs, labels = get_training_dataset()
    lu.train(input_vecs, labels, 10, 0.01)
    #返回訓練好的線性單元
    return lu
if __name__ == '__main__': 
    '''訓練線性單元'''
    linear_unit = train_linear_unit()
    # 列印訓練獲得的權重
    print linear_unit
    # 測試
    print 'Work 3.4 years, monthly salary = %.2f' % linear_unit.predict([3.4])
    print 'Work 15 years, monthly salary = %.2f' % linear_unit.predict([15])
    print 'Work 1.5 years, monthly salary = %.2f' % linear_unit.predict([1.5])
    print 'Work 6.3 years, monthly salary = %.2f' % linear_unit.predict([6.3])

 程式執行結果如下:

擬合的直線如下圖

  

小結

事實上,一個機器學習演算法其實只有兩部分

  • 模型 從輸入特徵預測輸入的那個函式
  • 目標函式 目標函式取最小(最大)值時所對應的引數值,就是模型的引數的最優值。很多時候我們只能獲得目標函式的區域性最小(最大)值,因此也只能得到模型引數的區域性最優值

因此,如果你想最簡潔的介紹一個演算法,列出這兩個函式就行了。

接下來,你會用優化演算法去求取目標函式的最小(最大)值。[隨機]梯度{下降|上升}演算法就是一個優化演算法。針對同一個目標函式,不同的優化演算法會推匯出不同的訓練規則。我們後面還會講其它的優化演算法。

其實在機器學習中,演算法往往並不是關鍵,真正的關鍵之處在於選取特徵。選取特徵需要我們人類對問題的深刻理解,經驗、以及思考。而神經網路演算法的一個優勢,就在於它能夠自動學習到應該提取什麼特徵,從而使演算法不再那麼依賴人類,而這也是神經網路之所以吸引人的一個方面。

現在,經過漫長的燒腦,你已經具備了學習神經網路的必備知識。下一篇文章,我們將介紹本系列文章的主角:神經網路,以及用來訓練神經網路的大名鼎鼎的演算法:反向傳播演算法。