【算法】從頭開始編寫任何機器學習算法的6個步驟:感知器案例研究

筆者邀請您,先思考:

1 您如何學習機器學習算法?

2 您如何應用機器學習算法?

從頭開始編寫機器學習算法是一種非常有益的學習體驗。 我們在此過程中強調了6個步驟。

有些算法比其他算法更復雜,所以從一些簡單的算法開始,從一些非常簡單的算法開始,比如單層感知器。

我將以感知器爲例,帶您經歷以下6步過程,從頭開始編寫算法:

  1. 對算法有一個基本的瞭解
  2. 找到一些不同的學習來源
  3. 將算法分解成塊
  4. 從一個簡單的例子開始
  5. 使用可信的實現進行驗證
  6. 寫下你的過程

獲得基本瞭解

這又回到了我最初所說的。如果你不瞭解基礎知識,不能從頭開始處理算法。

至少,你應該能夠回答以下問題:

  • 它是什麼?
  • 它的典型用途是什麼?
  • 我什麼時候不能用這個?

對於感知器,讓我們繼續回答這些問題:

  • 單層感知器是最基本的神經網絡。它通常用於二進制分類問題(1或0,“是”或“否”)。
  • 一些簡單的用法可能是情感分析(正面或負面反應)或貸款違約預測(“將違約”,“將不違約”)。對於這兩種情況,決策邊界都必須是線性的。
  • 如果決策邊界是非線性的,你就不能用感知器。對於這些問題,您需要使用不同的方法。

使用不同的學習資源

在你對模型有了基本的瞭解之後,是時候開始你的研究了。 有些人用教科書學得更好,有些人用視頻學得更好。 就我個人而言,我喜歡到處轉轉,使用各種各樣的資源。

對於數學細節,教科書做得很好,但對於更實際的例子,我更喜歡博客帖子和YouTube視頻。 對於感知器,這裏有一些很好的來源:

教科書

  • The Elements of Statistical Learning 4.5.1節
  • Understanding Machine Learning: From Theory To Algorithms 21.4 節

博客

  • How To Implement The Perceptron Algorithm From Scratch In Python, by Jason Brownlee
  • Single-Layer Neural Networks and Gradient Descent, by Sebastian Raschka

視頻

  • Perceptron Training How the Perceptron Algorithm Works

將算法分解成塊

現在我們已經收集了資料,是時候開始學習了。 與其從頭到尾讀一章或一篇博客文章,不如先瀏覽一下章節標題和其他重要信息。 寫下要點,並試着概述算法

在瀏覽了這些資料之後,我將感知器分爲以下5個部分:

  • 初始化權重
  • 將權重乘以輸入,然後求和
  • 將結果與閾值進行比較以計算輸出(1或0)
  • 更新權重
  • 重複

讓我們詳細討論每一個問題。

1 初始化權重 首先,我們將初始化權向量。 權重的數量需要與特徵的數量匹配。假設我們有三個特徵,這是權重向量的樣子

權重向量通常是用零初始化的,所以我將在這個例子中繼續使用它。

2 將權重乘以輸入,然後求和

接下來,我們將權重乘以輸入,然後求和。 爲了更容易理解,我在第一行中對權重及其對應的特徵進行了着色

在我們把權重乘以特徵之後,我們把它們加起來。這也被稱爲點積。

最後的結果是0。我將把這個臨時結果稱爲“f”。

3 比較閾值

在計算出點積之後,我們需要將它與閾值進行比較。 我選擇用0作爲我的閾值,但是你可以試着用一些不同的數字。

由於我們計算出來的點積f不大於我們的閾值(0)我們的估計值等於0。 我將估計值表示爲帶帽的y(又名“y帽”),下標爲0以對應第一行。你可以在第一行用1,這無所謂。我選擇從0開始。 如果我們將這個結果與實際值進行比較,我們可以看到我們當前的權重沒有正確地預測實際輸出。

由於我們的預測是錯誤的,我們需要更新權重,這將我們帶到下一步。

4 更新權重

接下來,我們要更新權重。下面是我們要用到的方程:

基本思想是,我們在迭代“n”處調整當前權重,以便在下一個迭代中得到一個新的權重“n+1”。 爲了調整權重,我們需要設置一個“學習率”。這是用希臘字母“eta”表示的。 我選擇用0.1表示學習速率,但是你可以用不同的數字,就像用臨界值一樣。 以下是我們到目前爲止的總結:

現在讓我們繼續計算迭代n=2的新權重。

我們已經成功地完成了感知器算法的第一次迭代。

5 重複

由於我們的算法沒有計算正確的輸出,我們需要繼續。 通常我們需要多次迭代。遍歷數據集中的每一行,我們將每次更新權重。 對數據集的一次完整掃描稱爲“epoch”。 因爲我們的數據集有3行,我們需要3次迭代才能完成1epoch。

我們可以設置總的迭代次數或epoch以繼續執行算法。也許我們想指定30次迭代(或10次epochs)。 與閾值和學習率一樣,epoch的數量是一個可以隨意使用的參數。 在下一個迭代中,我們將繼續討論第二行特徵。

我不會重複每一步,但這是下一個點積的計算:

接下來,我們將比較點積和閾值,以計算新的估計值,更新權值,然後繼續。如果數據是線性可分的,感知器就會收斂。

從一個簡單的例子開始

現在我們已經手工將算法分解成塊,現在是開始在代碼中實現它的時候了。 爲了簡單起見,我總是喜歡從一個非常小的“玩具數據集”開始。

對於這種類型的問題,一個漂亮的小的線性可分離數據集是NAND門。這是數字電子學中常用的邏輯門。

由於這是一個相當小的數據集,我們可以手動將其輸入到Python中。 我要添加一個虛擬的特徵“x0”它是一列1。我這樣做是爲了讓我們的模型計算偏差項。 您可以將偏差看作是截距項,它正確地允許我們的模型分離這兩個類。 以下是輸入數據的代碼:

 1# Importing libraries
 2# NAND Gate
 3# Note: x0 is a dummy variable for the bias term
 4#     x0  x1  x2
 5x = [[1., 0., 0.],
 6     [1., 0., 1.],
 7     [1., 1., 0.],
 8     [1., 1., 1.]]
 9
10y =[1.,
11    1.,
12    1.,
13    0.]

與前一節一樣,我將逐步詳細介紹算法,編寫代碼並測試它。

1 初始化權重 第一步是初始化權重。

1# Initialize the weights
2import numpy as np
3w = np.zeros(len(x[0]))
1Out:
2[ 0.  0.  0.]

請記住,權重向量的長度需要與特徵的數量匹配。對於這個NAND門的例子,長度是3。

2 將權重乘以輸入,然後求和

接下來,我們將權重乘以輸入,然後求和。 它的另一個名字是“點積” 同樣,我們可以使用Numpy輕鬆地執行此操作。我們將使用的方法是.dot()。

我們從權向量和第一行特徵的點積開始。

1# Dot Product
2f = np.dot(w, x[0])
3print f

正如預期的那樣,結果是0。 爲了與上一節的筆記保持一致,我將點積賦給變量f。

3 與閾值比較

在計算了點積之後,我們準備將結果與閾值進行比較,從而對輸出進行預測。 同樣,我將保持與上一節的筆記一致。 我要讓臨界值z等於0。如果點積f大於0,我們的預測是1。否則,它就是零。 記住,這個預測通常是頂部一橫來表示的,也被稱爲“帽子”。我將把預測賦給的變量是yhat。

1# Activation Function
2z = 0.0
3if f > z:
4    yhat = 1.
5else:
6    yhat = 0.
7
8print yhat

正如預期的那樣,預測爲0。 您會注意到,在上面的註釋中,我將其稱爲“激活函數”。這是對我們正在做的更正式的描述。 查看NAND輸出的第一行,我們可以看到實際值是1。由於我們的預測是錯誤的,我們需要繼續更新權重。

4 更新權重

現在我們已經做出了預測,我們準備更新權重。 我們需要設定一個學習速度才能做到這一點。爲了與前面的示例一致,我將學習速率“eta”賦值爲0.1。 我將對每個權重的更新進行硬編碼,使其更易於閱讀。

1eta = 0.1
2w[0] = w[0] + eta*(y[0] - yhat)*x[0][0]
3w[1] = w[1] + eta*(y[0] - yhat)*x[0][1]
4w[2] = w[2] + eta*(y[0] - yhat)*x[0][2]
5
6print w

我們可以看到我們的權重現在已經更新了,所以我們準備繼續。

5 重複

現在我們已經完成了每一個步驟,現在是時候把所有的東西放在一起了。 最後一個我們沒有討論的是我們的損失函數。這是我們要最小化的函數,在我們的例子中,這將是平方和(SSE)誤差。

這就是我們用來計算誤差的方法,看看模型是如何運行的。 把所有這些都聯繫起來,完整的函數是這樣的:

 1import numpy as np
 2
 3
 4# Perceptron function
 5def perceptron(x, y, z, eta, t):
 6    '''
 7    Input Parameters:
 8        x: data set of input features
 9        y: actual outputs
10        z: activation function threshold
11        eta: learning rate
12        t: number of iterations
13    '''
14
15    # initializing the weights
16    w = np.zeros(len(x[0]))      
17    n = 0                        
18
19    # initializing additional parameters to compute sum-of-squared errors
20    yhat_vec = np.ones(len(y))     # vector for predictions
21    errors = np.ones(len(y))       # vector for errors (actual - predictions)
22    J = []                         # vector for the SSE cost function
23
24    while n < t: for i in xrange(0, len(x)): # dot product f = np.dot(x[i], w) # activation function if f >= z:                               
25                yhat = 1.                               
26            else:                                   
27                yhat = 0.
28            yhat_vec[i] = yhat
29
30            # updating the weights
31            for j in xrange(0, len(w)):             
32                w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
33
34        n += 1
35        # computing the sum-of-squared errors
36        for i in xrange(0,len(y)):     
37           errors[i] = (y[i]-yhat_vec[i])**2
38        J.append(0.5*np.sum(errors))
39
40    return w, J

現在我們已經編寫了完整感知器的代碼,讓我們繼續運行它:

 1#     x0  x1  x2
 2x = [[1., 0., 0.],
 3     [1., 0., 1.],
 4     [1., 1., 0.],
 5     [1., 1., 1.]]
 6
 7y =[1.,
 8    1.,
 9    1.,
10    0.]
11
12z = 0.0
13eta = 0.1
14t = 50
15
16print "The weights are:"
17print perceptron(x, y, z, eta, t)[0]
18
19print "The errors are:"
20print perceptron(x, y, z, eta, t)[0]

看一看錯誤,我們可以看到錯誤在第6次迭代時趨於0。對於迭代的其餘部分,它保持在0。 當誤差趨於0時,我們知道模型收斂了。這告訴我們,我們的模型已經正確地“學習”了適當的權重。 在下一節中,我們將使用對較大數據集的計算權重來進行預測。

使用可信的實現進行驗證

到目前爲止,我們已經找到了不同的學習資源,手工完成了算法,並通過一個簡單的例子在代碼中測試了它。 現在是時候將我們的結果與可信的實現進行比較了。爲了比較,我們將使用scikit-learn中的感知器。 我們將使用以下步驟進行比較:

  • 導入數據
  • 將數據分成訓練集/測試集
  • 訓練我們的感知器
  • 測試感知器
  • 和scikit-learn的感知器相比

1 導入數據

讓我們從導入數據開始。您可以在這裏獲得數據集的副本。 這是一個我創建的線性可分離數據集以確保感知器能夠工作。爲了確認,讓我們繼續對數據畫圖。

1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4
5df = pd.read_csv("dataset.csv")
6plt.scatter(df.values[:,1], df.values[:,2], c = df['3'], alpha=0.8)

看看這個圖,很容易看出我們可以用一條直線將這些數據分開。 在繼續之前,我將在上面解釋我的繪圖代碼。 我使用panda導入csv,它自動將數據放入dataframe中。

爲了繪製數據,我必須從dataframe中提取值,所以我使用了.values方法。 特徵在第1和第2列中,所以我在散點圖函數中使用了這些特徵。第0列是我包含的1的虛擬特徵,這樣就能計算出截距。這應該與我們在前一節中對NAND gate所做的事情一樣。

最後,我在scatterplot函數中使用c = df['3']和alpha = 0.8爲兩個類着色。輸出是第3列(0或1)中的數據,因此我告訴函數使用第3列爲兩個類着色。 你可以在這裏找到關於Matplotlib的散點圖函數的更多信息。

2 將數據分成訓練集/測試集

既然我們已經確定了數據可以線性分離,那麼現在就該分割數據了。 在單獨的數據集上訓練模型和另一個數據上測試模型是很好的實踐。這有助於避免過度擬合。 做這個有不同的方法,但爲了簡單起見,我將使用一個訓練集和一個測試集。 我擾亂一下我們的數據。如果您查看原始文件,您會看到數據是按輸出(第三列)中0的行進行分組的,然後是所有的1。我想要改變一下,增加一些隨機性,所以我要洗牌。

1df = df.values  
2
3np.random.seed(5)
4np.random.shuffle(df)

我首先將數據從dataframe改爲numpy數組。這將使我更容易地使用許多numpy函數,例如.shuffle。 爲了讓結果重現,我設置了一個隨機種子(5)。完成後,嘗試改變隨機種子,看看結果如何變化。 接下來我將把70%的數據分成訓練集,30%分成測試集。

1train = df[0:int(0.7*len(df))]
2test = df[int(0.7*len(df)):int(len(df))]

最後一步是分離訓練和測試集的特徵和輸出。

1x_train = train[:, 0:3]
2y_train = train[:, 3]
3
4x_test = test[:, 0:3]
5y_test = test[:, 3]

我選擇了70%/30%作爲訓練集/測試集,只是爲了這個示例,但我鼓勵您研究其他方法,比如k-fold交叉驗證。

3 訓練我們的感知器 接下來,我們要訓練感知器。 這非常簡單,我們將重用在前一節中構建的代碼。

 1def perceptron_train(x, y, z, eta, t):
 2    '''
 3    Input Parameters:
 4        x: data set of input features
 5        y: actual outputs
 6        z: activation function threshold
 7        eta: learning rate
 8        t: number of iterations
 9    '''
10
11    # initializing the weights
12    w = np.zeros(len(x[0]))      
13    n = 0                        
14
15    # initializing additional parameters to compute sum-of-squared errors
16    yhat_vec = np.ones(len(y))     # vector for predictions
17    errors = np.ones(len(y))       # vector for errors (actual - predictions)
18    J = []                         # vector for the SSE cost function
19
20    while n < t:          for i in xrange(0, len(x)):                                           # dot product             f = np.dot(x[i], w)                                   # activation function             if f >= z:                               
21                yhat = 1.                               
22            else:                                   
23                yhat = 0.
24            yhat_vec[i] = yhat
25
26            # updating the weights
27            for j in xrange(0, len(w)):             
28                w[j] = w[j] + eta*(y[i]-yhat)*x[i][j]
29
30        n += 1
31        # computing the sum-of-squared errors
32        for i in xrange(0,len(y)):     
33           errors[i] = (y[i]-yhat_vec[i])**2
34        J.append(0.5*np.sum(errors))
35
36    return w, J
37
38z = 0.0
39eta = 0.1
40t = 50
41
42perceptron_train(x_train, y_train, z, eta, t)

讓我們來看看權重和平方和誤差。

1w = perceptron_train(x_train, y_train, z, eta, t)[0]
2J = perceptron_train(x_train, y_train, z, eta, t)[1]
3
4print w
5print J

權值現在對我們來說意義不大,但我們將在下一節中使用這些數字來測試感知器。我們還將使用權重來比較我們的模型和scikit-learn模型。 看一下平方求和誤差,我們可以看到感知器已經收斂,這是我們期望的,因爲數據是線性可分離的。

4測試我們的感知器

現在是測試感知器的時候了。爲此,我們將構建一個小型的perceptron_test函數。 這和我們已經看到的很相似。這個函數取我們使用perceptron_train函數計算的權值的點積,以及特徵,以及激活函數,來進行預測。 我們唯一沒有看到的是accuracy_score。這是一個來自scikitlearn的評價度量函數。你可以在這裏瞭解更多。 把所有這些放在一起,下面是代碼的樣子:

 1from sklearn.metrics import accuracy_score
 2
 3w = perceptron_train(x_train, y_train, z, eta, t)[0]
 4
 5def perceptron_test(x, w, z, eta, t):
 6    y_pred = []
 7    for i in xrange(0, len(x-1)):
 8        f = np.dot(x[i], w)   
 9
10        # activation function
11        if f > z:                               
12            yhat = 1                               
13        else:                                   
14            yhat = 0
15        y_pred.append(yhat)
16    return y_pred
17
18y_pred = perceptron_test(x_test, w, z, eta, t)
19
20print "The accuracy score is:"
21print accuracy_score(y_test, y_pred)

得分爲1.0表明我們的模型正確地預測了所有的測試數據。這個數據集顯然是可分離的,所以我們期望這個結果。 5 和學過的感知器相比 最後一步是將我們的結果與scikit-learn的感知器進行比較。下面是這個模型的代碼:

1from sklearn.linear_model import Perceptron
2
3# training the sklearn Perceptron
4clf = Perceptron(random_state=None, eta0=0.1, shuffle=False, fit_intercept=False)
5clf.fit(x_train, y_train)
6y_predict = clf.predict(x_test)

現在我們已經訓練了模型,讓我們將權重與模型計算的權重進行比較。

scikit-learn模型中的權重與我們的相同。這意味着我們的模型工作正常,這是個好消息。 在我們結束之前,有幾個小問題需要複習一下。在scikit-learn模型中,我們必須將隨機狀態設置爲“None”並關閉變換。我們已經設置了一個隨機種子並打亂了數據,所以我們不需要再這樣做了。 我們還必須將學習速率“eta0”設置爲0.1,以與我們的模型相同。

最後一點是截距。因爲我們已經包含了一個虛擬的特徵列1s,我們正在自動擬合截距,所以我們不需要在scikit-learn感知器中打開它。 這些看起來都是次要的細節,但如果我們不設置這些,我們就無法複製與我們的模型相同的結果。 這一點很重要。在使用模型之前,閱讀文檔並理解所有不同設置的作用是非常重要的

寫下你的過程

這個過程中的最後一步可能是最重要的。 您已經完成了所有的工作,包括學習、記筆記、從頭開始編寫算法,並將其與可信的實現進行比較。不要讓所有的好工作白白浪費掉! 寫下這個過程很重要,原因有二:

  • 你會得到更深的理解,因爲你正在教導別人你剛剛學到的東西。
  • 你可以向潛在僱主展示它。

證明你可以從機器學習庫中實現一個算法是一回事,但如果你可以自己從頭實現它,那就更令人印象深刻了。 一個展示你作品的好方法是使用GitHub頁面組合。

總結

在這篇文章中,我們學習瞭如何從零開始實現感知器。 更重要的是,我們學習瞭如何找到有用的學習資源,以及如何將算法分解成塊。 然後,我們學習瞭如何使用一個玩具數據集在代碼中實現和測試算法。 最後,我們通過比較我們的模型和可信實現的結果來結束本文。

這是在更深層次上學習算法的一個很好的方法,這樣您就可以自己實現它了。 大多數情況下,您將使用可信的實現,但如果您真的想深入瞭解底層的情況,從頭實現它是一個很好的練習。 請務必在下面留下您的評論,如果您在學習過程中還有其他的幫助您的技巧,請告訴我!

作者:John Sullivan 原文鏈接:https://www.dataoptimal.com/machine-learning-from-scratch/

版權聲明:作者保留權利,嚴禁修改,轉載請註明原文鏈接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章