感知機的 python 實現
本文的主要內容是感知機的python實現。在閱讀程式之前,如果對感知機的原理不瞭解,可以參考我的上一篇文章:感知機演算法原理(PLA原理)及 Python 實現
建立一些用於測試的線性可分資料
機器學習是資料驅動的學科,如果您在網路上很難找到線性可分的資料的話,不妨自己來“捏造一些”。順便提一下,因為我沒有給我的 ubuntu
上的 sublime
新增中文支援,無法輸入中文,所以註釋使用英文寫的。我會在程式碼中解釋。
首先,新建一個名為 pla.py
的檔案,將下面的程式碼新增進去,以匯入 numpy
科學計算模組。
from numpy import *
然後,我們來定義一個函式makeLinearSeparableData
pla.py
中:
def makeLinearSeparableData(weights, numLines):
''' (list, int) -> array
Return a linear Separable data set.
Randomly generate numLines points on both sides of
the hyperplane weights * x = 0.
Notice: weights and x are vectors.
>>> data = pla.makeLinearSeparableData([2,3],5)
>>> data
array([[ 0.54686091, 3.60017244, 1. ],
[ 2.0201362 , 7.5046425 , 1. ],
[-3.14522458, -7.19333582, -1. ],
[ 9.72172678, -7.99611918, -1. ],
[ 9.68903615, 2.10184495, 1. ]])
>>> data = pla.makeLinearSeparableData([4,3,2],10)
>>> data
array([[ -4.74893955e+00, -5.38593555e+00, 1.22988454e+00, -1.00000000e+00],
[ 4.13768071e-01, -2.64984892e+00, -5.45073234e-03, -1.00000000e+00],
[ -2.17918583e+00, -6.48560310e+00, -3.96546373e+00, -1.00000000e+00],
[ -4.34244286e+00, 4.24327022e+00, -5.32551053e+00, -1.00000000e+00],
[ -2.55826469e+00, 2.65490732e+00, -6.38022703e+00, -1.00000000e+00],
[ -9.08136968e+00, 2.68875119e+00, -9.09804786e+00, -1.00000000e+00],
[ -3.80332893e+00, 7.21070373e+00, -8.70106682e+00, -1.00000000e+00],
[ -6.49790176e+00, -2.34409845e+00, 4.69422613e+00, -1.00000000e+00],
[ -2.57471371e+00, -4.64746879e+00, -2.44909463e+00, -1.00000000e+00],
[ -5.80930468e+00, -9.34624147e+00, 6.54159660e+00, -1.00000000e+00]])
'''
w = array(weights)
numFeatures = len(weights)
dataSet = zeros((numLines, numFeatures + 1))
for i in range(numLines):
x = random.rand(1, numFeatures) * 20 - 10
innerProduct = sum(w * x)
if innerProduct <= 0:
dataSet[i] = append(x, -1)
else:
dataSet[i] = append(x, 1)
return dataSet
程式碼解釋如下:
weights
是一個列表,裡面儲存的是我們用來產生隨機資料的那條直線的法向量。numLines
是一個正整數,表示需要建立多少個數據點。numFeatures
是一個正整數,代表特徵的數量dataSet = zeros((numLines, numFeatures + 1))
用於建立一個規模為numLines x (numFeatures + 1)
的陣列,且內容全為 0。注意:numFeatures + 1
是為了在最後一列可以儲存該資料點的分類(+1或者-1)。- 然後我們在
for
迴圈裡面填充dataSet
的每一行。 x = random.rand(1, numFeatures) * 20 - 10
產生一個數組,規模為一行,numFeatures
列, 每個數都是 -10 到 10 的隨機數。innerProduct = sum(w * x)
計算內積- 接下來的
if
語句判斷如果內積小於等於 0,則是負例,否則是正例 numpy
提供的append
函式可以擴充一維陣列,可以自己實驗一下。- 最後返回資料集合。
函式的 docstring
裡面提供了使用例子,可以自己試一下,因為是隨機數,所以結果不會相同。
我們可以實驗一下。在Linux中,首先在pla.py
所在目錄開啟 python
命令提示符,輸入如下圖所示命令:
Windows中只需直接執行我們編寫的模組調,然後用函式
makeLinearSeparableData
即可。
將資料集視覺化
得到了隨機產生的資料集,當然要畫畫圖看看是不是真的是線性可分的。下面是繪製散點圖的程式碼。我們需要用到 matplotlib
模組。將如下程式碼新增到 pla.py
中:
def plotData(dataSet):
''' (array) -> figure
Plot a figure of dataSet
'''
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Linear separable data set')
plt.xlabel('X')
plt.ylabel('Y')
labels = array(dataSet[:,2])
idx_1 = where(dataSet[:,2]==1)
p1 = ax.scatter(dataSet[idx_1,0], dataSet[idx_1,1], marker='o', color='g', label=1, s=20)
idx_2 = where(dataSet[:,2]==-1)
p2 = ax.scatter(dataSet[idx_2,0], dataSet[idx_2,1], marker='x', color='r', label=2, s=20)
plt.legend(loc = 'upper right')
plt.show()
matplotlib
用起來比較複雜,但是能夠精確控制影象的顯示。注意程式碼中的 where
函式是用來找出正例的行的下標,然後我們可以把正例和反例用不同的顏色和形狀表示出來。如果有其他函式使用的問題,可以自行百度解決,很容易就能找到函式的用法。
下面我們來測試一下(注意,我們編寫的函式 plotData
只能繪製二維影象,所以我們需要產生只有兩個特徵的資料集。如果你想繪製三維影象,可以自己摸索一下)。因為在 pla.py
中添加了新程式碼,所以首先要重新載入我們的模組:
>>> reload(pla)
輸入如下命令以產生 100 個數據點:
>>> data = pla.makeLinearSeparableData([4,3],100)
然後輸入如下命令繪製散點圖(引數為我們產生的資料集):
>>> pla.plotData(data)
如圖所示:
繪製的散點圖如下圖所示:
可以看到,我們產生的隨機的資料集合是沒有問題的,是線性可分的。
訓練感知機,視覺化分類器及其法向量
已經有了線性可分的資料,接下來,我們就可訓練感知機了。將如下程式碼新增到 pla.py
中:
def train(dataSet, plot = False):
''' (array, boolean) -> list
Use dataSet to train a perceptron
dataSet has at least 2 lines.
'''
numLines = dataSet.shape[0]
numFeatures = dataSet.shape[1]
w = zeros((1, numFeatures - 1)) # initialize weights
separated = False
i = 0;
while not separated and i < numLines:
if dataSet[i][-1] * sum(w * dataSet[i,0:-1]) <= 0:
w = w + dataSet[i][-1] * dataSet[i,0:-1]
separated = False
i = 0;
else:
i += 1
if plot == True:
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Linear separable data set')
plt.xlabel('X')
plt.ylabel('Y')
labels = array(dataSet[:,2])
idx_1 = where(dataSet[:,2]==1)
p1 = ax.scatter(dataSet[idx_1,0], dataSet[idx_1,1],
marker='o', color='g', label=1, s=20)
idx_2 = where(dataSet[:,2]==-1)
p2 = ax.scatter(dataSet[idx_2,0], dataSet[idx_2,1],
marker='x', color='r', label=2, s=20)
x = w[0][0] / abs(w[0][0]) * 10
y = w[0][1] / abs(w[0][0]) * 10
ann = ax.annotate(u"",xy=(x,y),
xytext=(0,0),size=20, arrowprops=dict(arrowstyle="-|>"))
ys = (-12 * (-w[0][0]) / w[0][1], 12 * (-w[0][0]) / w[0][1])
ax.add_line(Line2D((-12, 12), ys, linewidth=1, color='blue'))
plt.legend(loc = 'upper right')
plt.show()
return w
程式碼解釋:
該函式有兩個引數,地一個是資料集
dataSet
,第二個是plot
,如果不提供值,有預設值False
,意思是隻返回最後的結果,不繪製散點圖。我這樣設計這個訓練函式,是為了方便檢視訓練完成後的結果。首先獲得資料集的行數
numLines
和特徵的數目numFeatures
,減一是因為最後一列是資料點的分類標籤,分類標籤並不是特徵。建立一個數組
w
儲存權重向量。while
迴圈只要滿足任何一個條件就結束:已經完全將正例和負例分開,或者i
的值超過樣本的數量。其實第二個條件是不會發生的,因為感知機的訓練演算法是收斂的,所以一定會將資料完全分開,證明可見我的另一篇文章:感知機演算法原理(PLA原理)及 Python 實現,但前提是資料集必須是線性可分的。下面的程式碼是訓練演算法的核心,即隨機梯度下降:
if dataSet[i][-1] * sum(w * dataSet[i,0:-1]) <= 0: # 如果分類錯誤
w = w + dataSet[i][-1] * dataSet[i,0:-1] # 更新權重向量
separated = False # 設定為未完全分開
i = 0; # 重新開始便利每個資料點
else:
i += 1 # 如果分類正確,檢查下一個資料點
簡單解釋一下繪圖部分的程式碼:
- 接下來的
if plot == True:
程式碼塊內的程式碼使用來繪製分類器及其法向量(權重向量)的。 - 需要解釋的地方是
w
是一個二位陣列,所以w
的第一個元素是w[0][0]
,第二個元素是w[0][1]
。 x = w[0][0] / abs(w[0][0]) * 10
和y = w[0][1] / abs(w[0][0]) * 10
是為了避免求得的權重向量長度過大在散點圖中無法顯示,所以將它按比例縮小了。- 如下的程式碼用來產生兩個點的 y 值,以繪製一條直線(感知機):
ys = (-12 * (-w[0][0]) / w[0][1], 12 * (-w[0][0]) / w[0][1])
annotate
函式用於繪製法向量(帶箭頭的直線)
下面我們來測試一下:
>>> data = pla.makeLinearSeparableData([4,3],100)
>>> w = pla.train(data)
>>> w
array([[ 16.32172416, 11.54429628]])
函式 train
正確返回了一個權重向量。在條用 train
函式時我並沒有給第二個引數 plot
賦值,所以預設不會繪製散點圖以及感知機。下面再來測試一下,並使第二個引數為 True
:
w = pla.train(data, True)
首先會顯示散點圖,關閉散點圖後,才會返回權重向量 w
:
注意,如果看不見法向量(權重向量),可以使用左下角第四個按鈕拖動散點圖,如果發現法向量和分類器不是垂直的,是因為橫縱座標的比例不同,改變圖片視窗的尺寸調整為正方形即可。
由散點圖可以看出,我們的程式是正確的。另外需要注意的是我們產生資料用的權重向量
以上就是 python 實現感知機的全部內容,如有錯誤,請批評指正,謝謝。
宣告: