深度學習背景中的基礎算法代碼和故事

深度學習發(fā)展到如今的地位,離不開下面這幾段代碼。本文介紹了這些代碼的創(chuàng)作者及其完成這些突破性成就的故事背景。每個故事都有簡單的代碼示例

1. 最小二乘法

  • 1805 年由 Adrien-Marie Legendre 首次提出(1805, Legendre),這位巴黎數(shù)學家也以測量儀器聞名。他極其癡迷于預測彗星的方位,堅持不懈地尋找一種可以基于彗星方位歷史數(shù)據(jù)計算其軌跡的算法。

  • 他嘗試了許多種算法,一遍遍試錯,終于找到了一個算法與結(jié)果相符。Legendre 的算法是首先預測彗星未來的方位,然后計算誤差的平方,最終目的是通過修改預測值以減少誤差平方和。而這也正是線性回歸的基本思想。

運行上述代碼來加深對這個算法的理解。m 是系數(shù),b 是預測的常數(shù)項,coordinates 是彗星的位置。目標是找到合適的 m 和 b 使其誤差盡可能小。

這是深度學習的核心思想:給定輸入值和期望的輸出值,然后尋找兩者之間的相關性。

# 最小二乘法
# y = mx + b
# m is slope, b is y-intercept

def compute_error_for_line_given_points(b, m, coordinates):
    totalError = 0
    for i in range(0, len(coordinates)):
        x = coordinates[i][0]
        y = coordinates[i][1]
        totalError += (y - (m * x + b)) ** 2
    return totalError / float(len(coordinates))
# example 
compute_error_for_line_given_points(1, 2, [[3,6],[6,9],[12,18]])
22.0

2.梯度下降

Legendre 這種通過手動嘗試來降低錯誤率的方法非常耗時。荷蘭的諾貝爾獎得主 Peter Debye 在一個世紀后(1909 年)正式提出了一種簡化這個過程的方法。

假設 Legendre 的算法需要考慮一個參數(shù) —— 我們稱之為 X 。Y 軸表示每個 X 的誤差值。Legendre 的算法是找到使得誤差最小的 X。在下圖中,我們可以看到當 X = 1.1 時,誤差 Y 取到最小值。



Peter Debye 注意到最低點左邊的斜率是負的,而另一邊則是正的。因此,如果知道了任意給定 X 的斜率值,就可以找到 Y 的最小值點。

這便是梯度下降算法的基本思想。幾乎所有的深度學習模型都會用到梯度下降算法。

要實現(xiàn)這個算法,我們假設誤差函數(shù)是 Error = x ^ 5 -2x ^ 3-2。要得到任意給定 X 的斜率,我們需要對其求導,即 5x^4 – 6x^2:

這里的竅門在于 learning_rate。我們通過沿斜率的相反方向行進來逼近最低點。此外,越接近最低點,斜率越小。因此當斜率接近零時,每一步下降的幅度會越來越小。

num_iterations 是你預計到達最小值之前所需的迭代次數(shù)??梢酝ㄟ^調(diào)試該參數(shù)訓練自己關于梯度下降算法的直覺。

current_x = 0.5 # the algorithm starts at x=0.5
learning_rate = 0.01 # step size multiplier
num_iterations = 60 # the number of times to train the function
 
#the derivative of the error function (x**4 = the power of 4 or x^4) 
def slope_at_given_x_value(x): 
   return 5 * x**4 - 6 * x**2
 
# Move X to the right or left depending on the slope of the error function
for i in range(num_iterations):
   previous_x = current_x
   current_x += -learning_rate * slope_at_given_x_value(previous_x)
   print(previous_x)
 
print("The local minimum occurs at %f" % current_x)

0.5
0.511875
0.5241633413153
0.5368738723924315
0.5500139565765964
0.5635891007995755
0.5776025354752059
0.5920547451593525
0.6069429517083986
0.6222605546198048
0.6379965370663893
0.6541348509073133
0.6706537996630098
0.6875254449508584
0.7047150688998199
0.7221807320875879
0.7398729728132125
0.7577346980096246
0.7757013175681643
0.7937011709295161
0.8116562862093865
0.8294834969490578
0.8470959195861032
0.8644047667410041
0.881321439496782
0.8977598093924315
0.9136385722426767
0.9288835358868889
0.943429696757781
0.9572229683912065
0.9702214489531998
0.9823961521131122
0.9937311713023107
1.004223295220484
1.0138811358280073
1.0227238635418048
1.0307796645701868
1.0380840414117025
1.044678070931505
1.0506067181771692
1.05591728202966
1.060658024670622
1.06487701379143
1.0686211866128092
1.0719356292600397
1.0748630541233568
1.0774434511849047
1.0797138862095075
1.0817084183291976
1.0834581110655934
1.0849911135011239
1.0863327915502436
1.0875058926711327
1.0885307306128311
1.0894253797422326
1.0902058710556728
1.0908863841268206
1.0914794309907059
1.0919960293497617
1.0924458635591308
The local minimum occurs at 1.092837

線性回歸

最小二乘法配合梯度下降算法,就是一個完整的線性回歸過程。在 20 世紀 50 年代和 60 年代,一批實驗經(jīng)濟學家在早期的計算機上實現(xiàn)了這些想法。這個過程是通過實體打卡 —— 真正的手工軟件程序?qū)崿F(xiàn)的。準備這些打孔卡就需要幾天的時間,而通過計算機進行一次回歸分析最多需要 24 小時。

#Price of wheat/kg and the average price of bread
wheat_and_bread = [[0.5,5],[0.6,5.5],[0.8,6],[1.1,6.8],[1.4,7]]
 
def step_gradient(b_current, m_current, points, learningRate):
    b_gradient = 0
    m_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i][0]
        y = points[i][1]
        b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
        m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current))
    new_b = b_current - (learningRate * b_gradient)
    new_m = m_current - (learningRate * m_gradient)
    return [new_b, new_m]
 
def gradient_descent_runner(points, starting_b, starting_m, learning_rate, num_iterations):
    b = starting_b
    m = starting_m
    for i in range(num_iterations):
        b, m = step_gradient(b, m, points, learning_rate)
    return [b, m]
 
gradient_descent_runner(wheat_and_bread, 1, 1, 0.01, 100)
[3.30446237250121, 2.966062687410304]

感知機

接下來讓我們來認識一下 Frank Rosenblatt。這是一個白天解剖老鼠大腦,晚上尋找外星生命跡象的家伙。1958年,他發(fā)明了一個模仿神經(jīng)元的機器(1958, Rosenblatt),并因此登上《紐約時報》的頭條:“New Navy Device Learns By Doing”。

如果向 Rosenblatt 的機器展示 50 組分別在左右兩側(cè)有標記的圖像,它可以在沒有預先編程的情況下分辨出兩張圖像(標記的位置)。大眾被這個可能真正擁有學習能力的機器震驚了。

如上圖所示,每個訓練周期都是從左側(cè)輸入數(shù)據(jù)開始。給所有輸入數(shù)據(jù)添加一個初始的隨機權重。然后將它們相加。如果總和為負,將其輸出為 0,否則輸出為 1。

如果預測結(jié)果是正確的,就不改變循環(huán)中的權重。如果預測結(jié)果是錯誤的,可以用誤差乘以學習率來相應地調(diào)整權重。

我們用經(jīng)典的“或”邏輯來運行感知機。

輸入 輸出
0 0 = 0
0 1 = 1
1 0 = 1
1 1 = 1

經(jīng)過最初的炒作一年之后,Marvin Minsky 和 Seymour Papert 擊碎了這個想法(1969, Minsky & Papert)。當時,Minsky 和 Papert 都在麻省理工學院的 AI 實驗室工作。他們寫了一本書,證明感知機只能解決線性問題。他們還批判了關于多層感知機的想法??杀氖?,F(xiàn)rank Rosenblatt 兩年后因船難去世。

在 Minsky 和 Papert 的書籍出版一年之后,一位芬蘭碩士研究生提出了用多層感知機解決非線性問題的理論(Linnainmaa, 1970)。由于業(yè)內(nèi)主流對感知機普遍不看好,十多年來 AI 的研究資金也非常短缺。這是 AI 首次遇冷。

from random import choice 
from numpy import array, dot, random 
v1_or_0 = lambda x: 0 if x < 0 else 1 
training_data = [ (array([0,0,1]), 0), 
                    (array([0,1,1]), 1), 
                    (array([1,0,1]), 1), 
                    (array([1,1,1]), 1), ] 
weights = random.rand(3) 
errors = [] 
learning_rate = 0.2 
num_iterations = 100 

for i in range(num_iterations): 
    input, truth = choice(training_data) 
    result = dot(weights, input) 
    error = truth - v1_or_0(result) 
    errors.append(error) 
    weights += learning_rate * error * input 

w = weights    
for x, _ in training_data: 
    result = dot(x, w) 
    print("{}: {} -> {}".format(input[:2], result, v1_or_0(result)))

[1 1]: -0.05961306140476624 -> 0
[1 1]: 0.4990767670409854 -> 1
[1 1]: 0.3576917409623607 -> 1
[1 1]: 0.9163815694081123 -> 1

人工神經(jīng)網(wǎng)絡

到 1986 年,幾項實驗證明,神經(jīng)網(wǎng)絡可以解決復雜的非線性問題(Rumelhart et al., 1986)。 當時計算機的運算速度比該理論提出的時候快了一萬倍。Rumelhart 等人是這樣介紹他們赫赫有名的論文的:

我們描述了一種新的類神經(jīng)元網(wǎng)絡學習過程——反向傳播。該過程通過反復調(diào)整網(wǎng)絡中的連接權重,最小化網(wǎng)絡的實際輸出向量與期望輸出向量之間的差異。調(diào)整權重的結(jié)果就是,不屬于輸入或輸出的內(nèi)部“隱藏”單元成為了描述任務域的重要特征,并且這些單元的交互項還捕獲了任務中的正則條件。 相較于早期更簡單的方法,如“感知機收斂過程” Nature 323, 533 – 536 (09 October 1986),反向傳播可以創(chuàng)造出有用的新特征。
為了理解這篇文章的核心內(nèi)容,我會在下面重現(xiàn) DeepMind 團隊 Andrew Trask 的代碼。這不是一段普通的代碼。它曾被用于斯坦福大學 Andrew Karpathy 的深度學習課程,以及 Siraj Raval 的 Udacity 課程。最重要的是,它解決了“異或”問題,也結(jié)束了 AI 遇冷的時代。


學習這段代碼之前,我們首先通過這個模擬器 交互學習一到兩個小時來掌握神經(jīng)網(wǎng)絡的核心邏輯。需要注意到,X_XOR 數(shù)據(jù)中添加的參數(shù) [1] 是偏置神經(jīng)元,它們等價于線性函數(shù)中的常數(shù)項。

反向傳播,矩陣乘法和梯度下降放在一起會讓人很難理解。這個過程的可視化通常是對其背后原理的簡化。專注于理解其背后的邏輯,但不要過多地考慮直覺上的理解。

在這個可視化網(wǎng)站交互學習

import numpy as np
 
X_XOR = np.array([[0,0,1], [0,1,1], [1,0,1],[1,1,1]]) 
y_truth = np.array([[0],[1],[1],[0]])
 
np.random.seed(1)
syn_0 = 2*np.random.random((3,4)) - 1
syn_1 = 2*np.random.random((4,1)) - 1
 
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output
def sigmoid_output_to_derivative(output):
    return output*(1-output) 
 
for j in range(60000):
    layer_1 = sigmoid(np.dot(X_XOR, syn_0))
    layer_2 = sigmoid(np.dot(layer_1, syn_1))
    error = layer_2 - y_truth
    layer_2_delta = error * sigmoid_output_to_derivative(layer_2)
    layer_1_error = layer_2_delta.dot(syn_1.T)
    layer_1_delta = layer_1_error * sigmoid_output_to_derivative(layer_1)
    syn_1 -= layer_1.T.dot(layer_2_delta)
    syn_0 -= X_XOR.T.dot(layer_1_delta)
    
print("Output After Training: n", layer_2)
Output After Training: n [[ 0.00260572]
 [ 0.99672209]
 [ 0.99701711]
 [ 0.00386759]]
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容