介紹最先以算法方式描述的分類機器學習算法:感知器(perceptron)和自適應線性神經元(adaptive linear neuron).咱們將使用python按部就班地實現一個感知器,而且經過訓練使其具有對鳶尾花數據集中數據進行分類的能力python
主要知識點:算法
爲了瞭解大腦的工做原理以設計人工智能系統,沃倫.麥卡洛可(Warren McCullock)與沃爾特.皮茨(Walter Pitts)在1943年提出來第一個腦神經元的抽象模型,也稱爲麥卡洛可–皮茨神經元(MCP),神經元是大腦相互鏈接的神經細胞,它能夠處理和傳遞化學信號和電信號編程
from IPython.display import Image
麥卡洛可和皮茨將神經細胞描述爲一個具有二進制輸出的邏輯門.樹突接收多個輸入信號,若是累加的信號超過某一閾值,經細胞體的整合就會生成一個輸出信號,並經過軸突進行傳遞網絡
幾年後,弗蘭克.羅森布拉特(Frank Rossenblatt)基於MCP神經元模型提出了第一個感知器學習法則.在此感知器規則中,羅森布拉特提出了一個自學習算法,此算法能夠本身經過優化獲得權重係數,此係數與輸入值的乘積決定了神經元是否被激活app
關於線性代數的知識可經過以下連接免費獲取:http://www.cs.cmu.edu/~zkolter/course/linalg/linalg_notes.pdf
linalg_notes.pdf
下圖中,左圖說明了感知器模型的激勵函數如何將輸入z=w^Tx轉換到二值輸出(-1或1),右圖說明了感知器模型如何將兩個可區分類別進行線性區分dom
MCP神經元和羅森布拉特閾值感知器的理念就是,經過模擬的方式還原大腦中單個神經元的工做方式:它是否被激活.這樣羅森布拉特感知器最初的規則很是簡單,可總結爲以下幾步:機器學習
須要注意的是:感知器收斂的前提是兩個類別必須是線性可分的,且學習速率足夠小.若是兩個類別沒法經過一個線性決策邊界進行劃分,能夠爲模型在訓練數據集上的學習迭代次數設置一個最大值,或者設置一個容許錯誤分類樣本數量的閾值—不然,感知器訓練算法將永遠不停地更新權值函數
總結一下感知器的基本概念:學習
上圖說明了感知器如何接收樣本x的輸入,並將其與權值w進行加權以計算淨輸入(net input).進而淨輸入被傳遞到激勵函數(在此爲單位階躍函數),而後生成值爲+1或-1的二值輸出,並以其做爲樣本的預測類標.在學習階段,此輸出用來計算預測的偏差並更新權重測試
經過使用面向對象編程的方式在一個python類中定義感知器的接口,使得咱們能夠初始化新的感知器對象,並使用類中定義的fit方法從數據中進行學習,使用predict方法進行預測.按照python開發的慣例,對於那些並不是在初始化對象時建立可是又被對象中其餘方法調用的屬性,能夠在後面添加一個下劃線,例如:self.w_
import numpy as np class Perceptron(object): def __init__(self,eta=0.01,n_iter=10): """ Parameters ---------- eta:float Learning rate(between 0.0 and 1.0) n_iter:int Passes over the training dataset Attributes ---------- w_:1d-array Weights after fitting errors_:list Number of misclassifications in every epoch """ self.eta=eta self.n_iter=n_iter def fit(self,X,y): """ Parameters ---------- X:{array-like},shape=[n_samples,n_features] y:array-like,shape=[n_samples] Target values """ self.w_=np.zeros(1+X.shape[1]) self.errors_=[] for _ in range(self.n_iter): errors=0 for xi,target in zip(X,y): update=self.eta*(target-self.predict(xi)) self.w_[1:]+=update*xi self.w_[0]+=update errors+=int(update!=0.0) self.errors_.append(errors) return self def net_input(self,X): """Calculate net input""" return np.dot(X,self.w_[1:])+self.w_[0] def predict(self,X): """Return class label after unit step""" return np.where(self.net_input(X)>=0.0,1,-1)
在感知器實現過程當中,咱們實例化一個Perceptron對象時,給出了一個學習速率eta和在訓練數據集上進行迭代的次數n_iter.經過fit方法,咱們將self.w_中的權值初始化爲一個零向量R^m+1,其中m是數據集中維度(特徵)的數量,咱們在此基礎上增長了一個0權重列(也就是閾值)
挑選鳶尾花數據集中山鳶尾(Setosa)和變色鳶尾(Versicolor)兩種花的信息做爲測試數據.雖然感知器並不將數據樣本特徵的數量限定爲兩個,但出於可視化方面的緣由,咱們只考慮數據集中萼片長度(sepal length)和花瓣長度(petal-length)這兩個特徵.不過,感知器算法能夠擴展到多類別的分類器應用中,好比經過一對多(One-vs.-all,OvA)
首先咱們使用pandas庫直接從UCI機器學習庫中將鳶尾花數據集轉換爲DataFrame對象並加載到內存中,並使用tail方法顯示數據的最後5行以確保數據正確加載
import pandas as pd df=pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data",header=None) df.tail()
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
145 | 6.7 | 3.0 | 5.2 | 2.3 | Iris-virginica |
146 | 6.3 | 2.5 | 5.0 | 1.9 | Iris-virginica |
147 | 6.5 | 3.0 | 5.2 | 2.0 | Iris-virginica |
148 | 6.2 | 3.4 | 5.4 | 2.3 | Iris-virginica |
149 | 5.9 | 3.0 | 5.1 | 1.8 | Iris-virginica |
接下來,咱們從中提取前100個類標,其中分別包含50個山鳶尾類標和50個變色鳶尾類標,並將這些類標用兩個整數值來替代:1代替變色鳶尾,-1表明山鳶尾.同時把pandas DataFrame產生的對應的整數類標賦給Numpy的向量y,提取這100個訓練樣本的第一個特徵列(萼片長度)和第三個特徵(花瓣長度),並賦給屬性矩陣X
import matplotlib.pyplot as plt import numpy as np y=df.iloc[0:100,4].values y=np.where(y=='Iris-setosa',-1,1) X=df.iloc[0:100,[0,2]].values plt.scatter(X[:50,0],X[:50,1],color='red',marker='o',label='setosa') plt.scatter(X[50:100,0],X[50:100,1],color='blue',marker='x',label='versicolor') plt.xlabel('petal length') plt.ylabel('sepal length') plt.legend(loc='upper left') plt.show()
如今,咱們可使用抽取出的鳶尾化數據集來訓練感知器.同時將繪製每次迭代的錯誤分類數量的折線圖,以檢驗算法是否收斂並找到能夠分開兩種類型鳶尾花的決策邊界
ppn=Perceptron(eta=0.1,n_iter=10) ppn.fit(X,y) plt.plot(range(1,len(ppn.errors_)+1),ppn.errors_,marker='o') plt.xlabel('Epochs') plt.ylabel('Number of misclassifications') plt.show()
經過一個簡單的函數來實現隊二維數據集決策邊界的可視化
from matplotlib.colors import ListedColormap def plot_decision_regions(X,y,classifier,resolution=0.02): markers=('s','x','o','^','v') colors=('red','blue','lightgreen','gray','cyan') cmap=ListedColormap(colors[:len(np.unique(y))]) x1_min,x1_max=X[:,0].min()-1,X[:,0].max()+1 x2_min,x2_max=X[:,1].min()-1,X[:,1].max()+1 xx1,xx2=np.meshgrid(np.arange(x1_min,x1_max,resolution),np.arange(x2_min,x2_max,resolution)) Z=classifier.predict(np.array([xx1.ravel(),xx2.ravel()]).T) Z=Z.reshape(xx1.shape) plt.contourf(xx1,xx2,Z,alpha=0.4,cmap=cmap) plt.xlim(xx1.min(),xx1.max()) plt.ylim(xx2.min(),xx2.max()) for idx,cl in enumerate(np.unique(y)): plt.scatter(x=X[y==cl,0],y=X[y==cl,1],alpha=0.8,c=cmap(idx),marker=markers[idx],label=cl)
plot_decision_regions(X,y,classifier=ppn) plt.xlabel('sepal length [cm]') plt.ylabel('petal length [cm]') plt.legend(loc='upper left') plt.show()
注意:感知器所面臨的最大問題是算法的收斂.Frank Rosenblatt從數學上證實了若是兩個類別能夠經過線性超平面進行劃分,則感知器算法必定會收斂.可是若是兩個類別沒法經過線性斷定邊界徹底正確地劃分,則權重會不斷更新.爲防止發生此類事件,一般事先設置權重更新的最大迭代次數
自適應線性神經網絡(Adaptive Linear Neuron,Adaline)算法至關有趣,它闡明瞭代價函數的核心概念,而且對其作了最小化優化,這是理解邏輯斯諦迴歸(logistic regression),支持向量機(support vector machine)的高級機器學習分類算法的基礎
基於Adeline規則的權重更新是經過一個連續的線性激勵函數來完成的,而不像Rosenblatt感知器那樣使用單位階躍函數,這是兩者的主要區別
線性激勵函數在更新權重的同時,咱們使用量化器(quantizer)對類標進行預測,量化器與前面提到的單單位階躍函數相似
機器學習中監督學習算法的一個核心組成在於:在學習階段定義一個待優化的目標函數.這個目標函數一般是須要咱們作最小化處理的代價函數
咱們將在前面實現的感知器代碼的基礎上修改fit方法,將其權重的更新改成經過梯度降低最小化代價函數來實現Adaline算法
class AdalineGD(object): def __init__(self,eta=0.01,n_iter=50): self.eta=eta self.n_iter=n_iter def fit(self,X,y): self.w_=np.zeros(1+X.shape[1]) self.cost_=[] for i in range(self.n_iter): output=self.net_input(X) errors=(y-output) self.w_[1:]+=self.eta*X.T.dot(errors) self.w_[0]+=self.eta*errors.sum() cost=(errors**2).sum()/2.0 self.cost_.append(cost) return self def net_input(self,X): return np.dot(X,self.w_[1:])+self.w_[0] def activation(self,X): return self.net_input(X) def predict(self,X): return np.where(self.activation(X)>=0.0,1,-1)
咱們分別使用eta=0.01和eta=0.0001兩個學習速率來描繪迭代次數與代價函數的圖像
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(8,4)) ada1=AdalineGD(n_iter=10,eta=0.01).fit(X,y) ax[0].plot(range(1,len(ada1.cost_)+1),np.log10(ada1.cost_),marker='o') ax[0].set_xlabel('Epochs') ax[0].set_ylabel('log(Sum-squared-error)') ax[0].set_title('Adaline-learning rate 0.01') ada2=AdalineGD(n_iter=10,eta=0.0001).fit(X,y) ax[1].plot(range(1,len(ada2.cost_)+1),np.log10(ada2.cost_),marker='o') ax[1].set_xlabel('Epochs') ax[1].set_ylabel('log(Sum-squared-error)') ax[1].set_title('Adaline-learning rate 0.0001') plt.show()
左邊的圖像顯示了學習速率過大可能會出現的問題—並無使代價函數的值儘量的低,反而由於算法跳過了全局最優解,致使偏差隨着迭代次數增長而增大
右邊的圖像代價函數逐漸減少,可是選擇的學習速率eta=0.0001的值過小,以至爲了達到算法收斂的目標,須要更多的迭代次數
下圖說明了咱們如何經過更改特定的權重參數值來最小化代價函數J(左子圖).右子圖展現了若是學習速率選擇過大會發生什麼狀況:算法跳過全局最優解(全局最小值)
梯度降低就是經過特徵縮放而受益的衆多算法之一.在此,採用一種稱做標準化的特徵縮放方法,此方法可使數據具有標準正態分佈的特性:各特徵值的均值爲0,標準差爲1
標準化能夠簡單地經過Numpy的mean和std方法來完成
X_std=np.copy(X) X_std[:,0]=(X[:,0]-X[:,0].mean())/X[:,0].std() X_std[:,1]=(X[:,1]-X[:,1].mean())/X[:,1].std()
在進行標準化操做後,咱們以學習速率eta=0.01再次對Adaline進行訓練,看看它是不是收斂的
ada=AdalineGD(n_iter=15,eta=0.01) ada.fit(X_std,y) plot_decision_regions(X_std,y,classifier=ada) plt.title('Adaline-Gradient Descent') plt.xlabel('sepal length [standardized]') plt.ylabel('petal length [standardized]') plt.legend(loc='upper left') plt.show() plt.plot(range(1,len(ada.cost_)+1),ada.cost_,marker='o') plt.xlabel('Epochs') plt.ylabel('Sum-squared-error') plt.show()
在上一節中,咱們學習瞭如何使用整個訓練數據集沿着梯度相反的方向進行優化,以最小化代價函數;這也是此方法有時稱做批量梯度降低的緣由.假定如今有一個包含幾百萬條數據的巨大數據集,這個量非同尋常的.因爲向全局最優勢移動的每一步都須要使用整個數據集來進行評估,所以這種狀況下使用批量梯度降低的計算成本很是高
一個經常使用的替代批量梯度降低的優化算法是隨機梯度降低(stochastic gradient descent),有時也稱做迭代梯度降低(iterative gradient descent)或者在線梯度降低(on-line gradient descent)
爲了經過隨機梯度降低獲得更加準確的結果,讓數據以隨機的方式提供給算法是很是重要的,這也是咱們每次迭代都要打亂訓練集以防止進入循環的緣由
因爲咱們已經使用梯度降低實現了Adaline學習規則,所以只需在此基礎上將學習算法中的權重更新改成經過隨機梯度降低來實現便可
from numpy.random import seed class AdalineSGD(object): def __init__(self,eta=0.01,n_iter=10,shuffle=True,random_state=None): self.eta=eta self.n_iter=n_iter self.w_initialized=False self.shuffle=shuffle if random_state: seed(random_state) def fit(self,X,y): self._initialize_weights(X.shape[1]) self.cost_=[] for i in range(self.n_iter): if self.shuffle: X,y=self._shuffle(X,y) cost=[] for xi,target in zip(X,y): cost.append(self._update_weights(xi,target)) avg_cost=sum(cost)/len(y) self.cost_.append(avg_cost) return self def partial_fit(self,X,y): if not self.w_initialized: self._initialize_weights(X.shape[1]) if y.ravel().shape[0]>1: for xi,target in zip(X,y): self._update_weights(xi.target) else: self._update_weights(X,y) return self def _shuffle(self,X,y): r=np.random.permutation(len(y)) return X[r],y[r] def _initialize_weights(self,m): self.w_=np.zeros(1+m) self.w_initialized=True def _update_weights(self,xi,target): output=self.net_input(xi) error=(target-output) self.w_[1:]+=self.eta*xi.dot(error) self.w_[0]+=self.eta*error cost=0.5*error**2 return cost def net_input(self,X): return np.dot(X,self.w_[1:]+self.w_[0]) def activation(self,X): return self.net_input(X) def predict(self,X): return np.where(self.activation(X)>=0.0,1,-1)
分類器AdalineSGD中_shuffle方法的工做原理以下:經過numpy.random的permutation函數,咱們生成一個包含0-100的不重複的隨機序列.這些數字能夠做爲索引幫助打亂咱們的特徵矩陣和類標向量
接下來,咱們就能夠經過fit方法訓練AdalineSGD分類器,並應用plot_decision_regions方法繪製訓練結果
ada=AdalineSGD(n_iter=15,eta=0.01,random_state=1) ada.fit(X_std,y) plot_decision_regions(X_std,y,classifier=ada) plt.title('Adaline-Stochastic Gradient Descent') plt.xlabel('sepal length [standardized]') plt.ylabel('petal length [standardized]') plt.legend(loc='upper left') plt.show() plt.plot(range(1,len(ada.cost_)+1),ada.cost_,marker='o') plt.xlabel('Epochs') plt.ylabel('Average Cost') plt.show()