筆者邀請您,先思考:
1 您如何學習機器學習算法?
2 您如何應用機器學習算法?
從頭開始編寫機器學習算法是一種非常有益的學習體驗。 我們在此過程中強調了6個步驟。
有些算法比其他算法更復雜,所以從一些簡單的算法開始,從一些非常簡單的算法開始,比如單層感知器。
我將以感知器爲例,帶您經歷以下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/
版權聲明:作者保留權利,嚴禁修改,轉載請註明原文鏈接。