Python之ML--機器學習分類算法


Python之ML–機器學習分類算法

介紹最早以算法方式描述的分類機器學習算法:感知器(perceptron)和自適應線性神經元(adaptive linear neuron).我們將使用python循序漸進地實現一個感知器,並且通過訓練使其具備對鳶尾花數據集中數據進行分類的能力

主要知識點:

  • 機器學習算法的直觀知識
  • 使用numpy,pandas和matplotlib讀取,處理和可視化數據
  • python實現線性分類算法

一.人造神經元

為了了解大腦的工作原理以設計人工智能系統,沃倫.麥卡洛可(Warren McCullock)與沃爾特.皮茨(Walter Pitts)在1943年提出來第一個腦神經元的抽象模型,也稱為麥卡洛可–皮茨神經元(MCP),神經元是大腦相互連接的神經細胞,它可以處理和傳遞化學信號和電信號

from IPython.display import Image

output_6_0.png

麥卡洛可和皮茨將神經細胞描述為一個具備二進制輸出的邏輯門.樹突接收多個輸入信號,如果累加的信號超過某一閾值,經細胞體的整合就會生成一個輸出信號,並通過軸突進行傳遞

幾年后,弗蘭克.羅森布拉特(Frank Rossenblatt)基於MCP神經元模型提出了第一個感知器學習法則.在此感知器規則中,羅森布拉特提出了一個自學習算法,此算法可以自己通過優化得到權重系數,此系數與輸入值的乘積決定了神經元是否被激活

關於線性代數的知識可通過如下鏈接免費獲取:http://www.cs.cmu.edu/~zkolter/course/linalg/linalg_notes.pdf
linalg_notes.pdf
下圖中,左圖說明了感知器模型的激勵函數如何將輸入z=w^Tx轉換到二值輸出(-1或1),右圖說明了感知器模型如何將兩個可區分類別進行線性區分

output_11_0.png

MCP神經元和羅森布拉特閾值感知器的理念就是,通過模擬的方式還原大腦中單個神經元的工作方式:它是否被激活.這樣羅森布拉特感知器最初的規則非常簡單,可總結為如下幾步:

  • 將權重初始化為零或一個極小的隨機數
  • 迭代所有訓練樣本x^i,執行如下操作
    • 計算輸出值y^
    • 更新權重

需要注意的是:感知器收斂的前提是兩個類別必須是線性可分的,且學習速率足夠小.如果兩個類別無法通過一個線性決策邊界進行划分,可以為模型在訓練數據集上的學習迭代次數設置一個最大值,或者設置一個允許錯誤分類樣本數量的閾值—否則,感知器訓練算法將永遠不停地更新權值

output_14_0.png

總結一下感知器的基本概念:

output_16_0.png

上圖說明了感知器如何接收樣本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權重列(也就是閾值)

2.1基於鳶尾花數據集訓練感知器模型

挑選鳶尾花數據集中山鳶尾(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()

output_27_0.png

現在,我們可以使用抽取出的鳶尾化數據集來訓練感知器.同時將繪制每次迭代的錯誤分類數量的折線圖,以檢驗算法是否收斂並找到可以分開兩種類型鳶尾花的決策邊界

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()

output_29_0.png

通過一個簡單的函數來實現隊二維數據集決策邊界的可視化

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()

output_32_1.png

注意:感知器所面臨的最大問題是算法的收斂.Frank Rosenblatt從數學上證明了如果兩個類別可以通過線性超平面進行划分,則感知器算法一定會收斂.但是如果兩個類別無法通過線性判定邊界完全正確地划分,則權重會不斷更新.為防止發生此類事件,通常事先設置權重更新的最大迭代次數

三.自適應線性神經元及其學習的收斂性

自適應線性神經網絡(Adaptive Linear Neuron,Adaline)算法相當有趣,它闡明了代價函數的核心概念,並且對其做了最小化優化,這是理解邏輯斯諦回歸(logistic regression),支持向量機(support vector machine)的高級機器學習分類算法的基礎

基於Adeline規則的權重更新是通過一個連續的線性激勵函數來完成的,而不像Rosenblatt感知器那樣使用單位階躍函數,這是二者的主要區別

線性激勵函數在更新權重的同時,我們使用量化器(quantizer)對類標進行預測,量化器與前面提到的單單位階躍函數類似

output_38_0.png

1.通過梯度下降最小化代價函數

機器學習中監督學習算法的一個核心組成在於:在學習階段定義一個待優化的目標函數.這個目標函數通常是需要我們做最小化處理的代價函數

output_41_0.png

2.實現自適應線性神經元

我們將在前面實現的感知器代碼的基礎上修改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()

output_46_0.png

左邊的圖像顯示了學習速率過大可能會出現的問題—並沒有使代價函數的值盡可能的低,反而因為算法跳過了全局最優解,導致誤差隨着迭代次數增加而增大

右邊的圖像代價函數逐漸減小,但是選擇的學習速率eta=0.0001的值太小,以致為了達到算法收斂的目標,需要更多的迭代次數

下圖說明了我們如何通過更改特定的權重參數值來最小化代價函數J(左子圖).右子圖展示了如果學習速率選擇過大會發生什么情況:算法跳過全局最優解(全局最小值)

output_49_0.png

梯度下降就是通過特征縮放而受益的眾多算法之一.在此,采用一種稱作標准化的特征縮放方法,此方法可以使數據具備標准正態分布的特性:各特征值的均值為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()

output_54_1.png

output_54_2.png

3.大規模機器學習與隨機梯度下降

在上一節中,我們學習了如何使用整個訓練數據集沿着梯度相反的方向進行優化,以最小化代價函數;這也是此方法有時稱作批量梯度下降的原因.假定現在有一個包含幾百萬條數據的巨大數據集,這個量非同尋常的.由於向全局最優點移動的每一步都需要使用整個數據集來進行評估,因此這種情況下使用批量梯度下降的計算成本非常高

一個常用的替代批量梯度下降的優化算法是隨機梯度下降(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()

output_63_1.png

output_63_2.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM