機器學習---用python實現感知機算法和口袋算法(Machine Learning PLA Pocket Algorithm Application)


之前在《機器學習---感知機(Machine Learning Perceptron)》一文中介紹了感知機算法的理論知識,現在讓我們來實踐一下。

 

有兩個數據文件:data1和data2,分別用於PLA和Pocket Algorithm。可在以下地址下載:https://github.com/RedstoneWill/MachineLearningInAction/tree/master/Perceptron%20Linear%20Algorithm/data

 

先回顧一下感知機算法:

1,初始化w

2,找出一個分類錯誤點

3,修正錯誤,假設迭代次數為t次(t=1,2,...),那么修正公式為:

4,直至沒有分類錯誤點,返回最終的w

 

接下來讓我們按照算法步驟,一步一步進行。

 

首先導入需要用到的庫,其中pandas用於讀取數據文件,matplotlib用於畫圖,numpy用於數組運算:

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots()
import numpy as np

 

讀取數據文件:

data=pd.read_csv(r"...\data1.csv",header=None)

 

提取特征和目標:

X=data.iloc[:,[0,1]]  #提取特征
y=data[2]   #提取目標

 

提取不同類別的數據,用於畫圖:

x_positive=X[y==1]
x_negative=X[y==-1]

 

畫圖:

ax.scatter(x_positive[0],x_positive[1],marker="o",label="y=+1")
ax.scatter(x_negative[0],x_negative[1],marker="x",label="y=-1")
ax.legend()
ax.set_xlabel("x1")
ax.set_ylabel("x2")
ax.set_title("Original Data")

 

從上圖可以看到數據是線性可分的,接下來我們先把數據歸一化:

mean=X.mean(axis=0)
sigma=X.std(axis=0)
X=(X-mean)/sigma

 

再畫圖看看:

 

設置好特征和權重,用於數組運算:

X[2]=np.ones((X.shape[0],1))   #給特征增加一列常數項
X=X.values    #把特征轉換成ndarray格式

###初始化w###
w=X[0].copy()  #選取原點到第一個點的向量作為w的初始值
w[2]=0  #增加一項---閾值,閾值初始化為0
w=w.reshape(3,1)

 

畫出初始法向量和初始分類直線(因為是在二維空間,所以是直線):

###畫出初始法向量###
ax.scatter(w[0],w[1],color="red")
ax.plot([0,w[0]],[0,w[1]])  

###畫出初始分類直線###
line_x=np.linspace(-3,3,10)
line_y=(-w[2]-w[0]*line_x)/w[1]
ax.plot(line_x,line_y)

注:因為w1x1+w2x2+b=0,現在我們已經有了w和b的值,因此只需要設置x1的值,就可以計算出x2的值。

注:注意要適當調整一下圖像比例,否則顯示出來不對,具體請見完整代碼。

 

現在我們已經完成初始化w的工作,接下去就是要找出分類錯誤點。現在的思路是:先計算出在當前參數w下的預測目標,然后把其和目標y進行比較,這樣就可以知道分類錯誤的地方了。

scores=np.dot(X,w)  #把特征和權重點乘,得到目前參數下預測出的目標分數

y_pred=np.ones((scores.shape[0],1))  #設置預測目標,初始化值全為1,形狀和目標分數相同
y=y.values.reshape((y_pred.shape[0],1))  #把目標轉換成ndarray格式,形狀和預測目標相同
    
loc_negative=np.where(scores<0)[0]  #標記分數為負數的地方
y_pred[loc_negative]=-1  #使標記為負數的地方預測目標變為-1

loc_wrong=np.where(y_pred!=y)[0]  #標記分類錯誤的地方

 

找出分類錯誤點后,我們對w進行修正(這里選取第一個分類錯誤點進行更新):

w=w+y[loc_wrong][0]*X[loc_wrong,:][0].reshape(3,1)

 

最后進行迭代就可以找出最終的w:

for i in range(100):
    scores=np.dot(X,w)  #把特征和權重點乘,得到此參數下預測出的目標分數

    y_pred=np.ones((scores.shape[0],1))  #設置預測目標,初始化值全為1,形狀和目標分數相同
    
    loc_negative=np.where(scores<0)[0]  #標記分數為負數的地方
    y_pred[loc_negative]=-1  #使標記為負數的地方預測目標變為-1

    loc_wrong=np.where(y_pred!=y)[0]  #標記分類錯誤的地方
    print("錯誤分類點有{}個。".format(len(loc_wrong)))
    if len(loc_wrong)>0:
        w=w+y[loc_wrong][0]*X[loc_wrong,:][0].reshape(3,1)
    else:
        break

print("參數w:{}".format(w))
print("分類直線:{}x1+{}x2+{}=0".format(w[0][0],w[1][0],w[2][0]))
line_x=np.linspace(-3,3,10)
line_y=(-w[2]-w[0]*line_x)/w[1]
ax.plot(line_x,line_y)

 

運行結果:

錯誤分類點有5個。
錯誤分類點有2個。
錯誤分類點有3個。
錯誤分類點有0個。
參數w:[[0.24622161]
 [2.81328976]
 [1.        ]]
分類直線:0.2462216100520832x1+2.8132897563595076x2+1.0=0

 

畫出的分類直線:

 

最后的最后,將上述代碼整理一下。感知機算法完整代碼如下(除去用於畫圖的代碼,核心代碼不到20行):

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots(figsize=(6,6))
import numpy as np

data=pd.read_csv(r"...\data1.csv",header=None)

X=data.iloc[:,[0,1]]  #提取特征
y=data[2]   #提取目標

###把數據歸一化###
mean=X.mean(axis=0)
sigma=X.std(axis=0)
X=(X-mean)/sigma

###提取不同類別的數據,用於畫圖###
x_positive=X[y==1]
x_negative=X[y==-1]

ax.scatter(x_positive[0],x_positive[1],marker="o",label="y=+1")
ax.scatter(x_negative[0],x_negative[1],marker="x",label="y=-1")
ax.legend()
ax.set_xlabel("x1")
ax.set_ylabel("x2")
ax.set_title("Standardized Data")
ax.set_xlim(-2,2.6)
ax.set_ylim(-2,2.6)

X[2]=np.ones((X.shape[0],1))   #給特征增加一列常數項
X=X.values    #把特征轉換成ndarray格式

###初始化w###
w=X[0].copy()  #選取原點到第一個點的向量作為w的初始值
w[2]=0  #增加一項---閾值,閾值初始化為0
w=w.reshape(3,1)

y=y.values.reshape(100,1)  #把目標轉換成ndarray格式,形狀和預測目標相同
    
def compare(X,w,y):
    ###用於比較預測目標y_pred和實際目標y是否相符,返回分類錯誤的地方loc_wrong###
    ###輸入特征,權重,目標###
    scores=np.dot(X,w)  #把特征和權重點乘,得到此參數下預測出的目標分數
    
    y_pred=np.ones((scores.shape[0],1))  #設置預測目標,初始化值全為1,形狀和目標分數相同
    
    loc_negative=np.where(scores<0)[0]  #標記分數為負數的地方
    y_pred[loc_negative]=-1  #使標記為負數的地方預測目標變為-1
    
    loc_wrong=np.where(y_pred!=y)[0]  #標記分類錯誤的地方
    
    return loc_wrong

def update(X,w,y):
    ###用於更新權重w,返回更新后的權重w###
    ###輸入特征,權重,目標###
    w=w+y[compare(X,w,y)][0]*X[compare(X,w,y),:][0].reshape(3,1)
    return w

def perceptron(X,w,y):
    ###感知機算法,顯示最終的權重和分類直線,並畫出分類直線###
    ###輸入特征,初始權重,目標###
    while len(compare(X,w,y))>0:
        print("錯誤分類點有{}個。".format(len(compare(X,w,y))))
        w=update(X,w,y)

    print("參數w:{}".format(w))
    print("分類直線:{}x1+{}x2+{}=0".format(w[0][0],w[1][0],w[2][0]))
    line_x=np.linspace(-3,3,10)
    line_y=(-w[2]-w[0]*line_x)/w[1]
    ax.plot(line_x,line_y)

plt.show()

 


 

接下來看一下data2文件和口袋算法的應用。首先回顧一下Pocket Algorithm:

1,初始化w,把w作為最好的解放入口袋

2,隨機找出一個分類錯誤點

3,修正錯誤,假設迭代次數為t次(t=1,2,...),那么修正公式為:

4,如果wt+1比w犯的錯誤少,那么用wt+1替代w,放入口袋

5,經過t次迭代后停止,返回口袋里最終的結果

 

口袋算法和PLA差不多,因此代碼還是用上述感知機算法的框架,只需要局部修改一下即可。

 

首先畫出原始數據圖像和歸一化后的數據圖像:(代碼和之前類似,故在此不再贅述)

     

 

可以看到數據是線性不可分的。接下來修改一下perceptron函數和update函數。

def perceptron_pocket(X,w,y):
    ###感知機口袋算法,顯示n次迭代后最好的權重和分類直線,並畫出分類直線###
    ###輸入特征,初始權重,目標###
    best_len=len(compare(X,w,y))  #初始化最少的分類錯誤點個數
    best_w=w  #初始化口袋里最好的參數w
    for i in range(100):
        print("錯誤分類點有{}個。".format(len(compare(X,w,y))))
        w=update(X,w,y)
        #如果當前參數下分類錯誤點個數小於最少的分類錯誤點個數,那么更新最少的分類錯誤點個數和口袋里最好的參數w
        if len(compare(X,w,y))<best_len:
            best_len=len(compare(X,w,y))
            best_w=w

    print("參數best_w:{}".format(best_w))
    print("分類直線:{}x1+{}x2+{}=0".format(best_w[0][0],best_w[1][0],best_w[2][0]))
    print("最少分類錯誤點的個數:{}個".format(best_len))
    line_x=np.linspace(-3,3,10)
    line_y=(-best_w[2]-best_w[0]*line_x)/best_w[1]
    ax.plot(line_x,line_y)

perceptron函數主要增加了一個口袋,用於存放最好的解,函數名稱改為perceptron_pocket。

 

def update(X,w,y):
    ###用於更新權重w,返回更新后的權重w###
    ###輸入特征,權重,目標###
    num=len(compare(X,w,y)) #分類錯誤點的個數
    w=w+y[compare(X,w,y)][np.random.choice(num)]*X[compare(X,w,y),:][np.random.choice(num)].reshape(3,1)
    return w

update函數將“選取第一個分類錯誤點進行更新”修改為“隨機選取分類錯誤點進行更新”。

 

運行結果如下(由於帶有隨機性,每次運行結果都不同):

錯誤分類點有9個。
錯誤分類點有30個。
錯誤分類點有11個。
錯誤分類點有24個。
錯誤分類點有5個。
錯誤分類點有22個。
錯誤分類點有16個。
錯誤分類點有17個。
錯誤分類點有5個。
錯誤分類點有15個。
錯誤分類點有5個。
錯誤分類點有15個。
錯誤分類點有6個。
錯誤分類點有13個。
錯誤分類點有7個。
錯誤分類點有12個。
錯誤分類點有9個。
錯誤分類點有14個。
錯誤分類點有12個。
錯誤分類點有16個。
錯誤分類點有9個。
錯誤分類點有10個。
錯誤分類點有12個。
錯誤分類點有12個。
錯誤分類點有7個。
錯誤分類點有16個。
錯誤分類點有5個。
錯誤分類點有7個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有9個。
錯誤分類點有11個。
錯誤分類點有7個。
錯誤分類點有5個。
錯誤分類點有11個。
錯誤分類點有6個。
錯誤分類點有8個。
錯誤分類點有6個。
錯誤分類點有12個。
錯誤分類點有6個。
錯誤分類點有11個。
錯誤分類點有14個。
錯誤分類點有10個。
錯誤分類點有5個。
錯誤分類點有5個。
錯誤分類點有5個。
錯誤分類點有4個。
錯誤分類點有6個。
錯誤分類點有6個。
錯誤分類點有6個。
錯誤分類點有9個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有7個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有5個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有8個。
錯誤分類點有6個。
錯誤分類點有5個。
錯誤分類點有6個。
錯誤分類點有7個。
錯誤分類點有9個。
錯誤分類點有7個。
錯誤分類點有6個。
錯誤分類點有7個。
錯誤分類點有6個。
錯誤分類點有4個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有11個。
錯誤分類點有15個。
錯誤分類點有10個。
錯誤分類點有5個。
錯誤分類點有5個。
錯誤分類點有6個。
錯誤分類點有9個。
錯誤分類點有6個。
錯誤分類點有8個。
錯誤分類點有5個。
錯誤分類點有10個。
錯誤分類點有5個。
錯誤分類點有10個。
錯誤分類點有6個。
錯誤分類點有10個。
錯誤分類點有18個。
錯誤分類點有9個。
錯誤分類點有10個。
錯誤分類點有10個。
錯誤分類點有5個。
錯誤分類點有5個。
錯誤分類點有6個。
錯誤分類點有8個。
參數best_w:[[-1.09227879]
 [ 5.19393394]
 [ 1.        ]]
分類直線:-1.0922787897353627x1+5.193933943326238x2+1.0=0
最少分類錯誤點的個數:4個

 

分類直線畫圖如下:

 

口袋算法完整代碼如下:

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots()
import numpy as np

data=pd.read_csv(r"...\data2.csv",header=None)

X=data.iloc[:,[0,1]]  #提取特征
y=data[2]   #提取目標

###把數據歸一化###
mean=X.mean(axis=0)
sigma=X.std(axis=0)
X=(X-mean)/sigma

###提取不同類別的數據,用於畫圖###
x_positive=X[y==1]
x_negative=X[y==-1]

ax.scatter(x_positive[0],x_positive[1],marker="o",label="y=+1")
ax.scatter(x_negative[0],x_negative[1],marker="x",label="y=-1")
ax.legend()
ax.set_xlabel("x1")
ax.set_ylabel("x2")
ax.set_title("standardized Data")

X[2]=np.ones((X.shape[0],1))   #增加一列常數項
X=X.values    #把特征轉換成ndarray格式

###初始化w###
w=X[0].copy()  #選取原點到第一個點的向量作為w的初始值
w[2]=0  #增加一項---閾值,閾值初始化為0
w=w.reshape(3,1)

y=y.values.reshape(100,1)  #把目標轉換成ndarray格式,形狀和預測目標相同
    
def compare(X,w,y):
    ###用於比較預測目標y_pred和實際目標y是否相符,返回分類錯誤的地方loc_wrong###
    ###輸入特征,權重,目標###
    scores=np.dot(X,w)  #把特征和權重點乘,得到此參數下預測出的目標分數
    
    y_pred=np.ones((scores.shape[0],1))  #設置預測目標,初始化值全為1,形狀和目標分數相同
    
    loc_negative=np.where(scores<0)[0]  #標記分數為負數的地方
    y_pred[loc_negative]=-1  #使標記為負數的地方預測目標變為-1
    
    loc_wrong=np.where(y_pred!=y)[0]  #標記分類錯誤的地方
    
    return loc_wrong

def update(X,w,y):
    ###用於更新權重w,返回更新后的權重w###
    ###輸入特征,權重,目標###
    num=len(compare(X,w,y)) #分類錯誤點的個數
    w=w+y[compare(X,w,y)][np.random.choice(num)]*X[compare(X,w,y),:][np.random.choice(num)].reshape(3,1)
    return w

def perceptron_pocket(X,w,y):
    ###感知機口袋算法,顯示n次迭代后最好的權重和分類直線,並畫出分類直線###
    ###輸入特征,初始權重,目標###
    best_len=len(compare(X,w,y))  #初始化最少的分類錯誤點個數
    best_w=w  #初始化口袋里最好的參數w
    for i in range(100):
        print("錯誤分類點有{}個。".format(len(compare(X,w,y))))
        w=update(X,w,y)
        #如果當前參數下分類錯誤點個數小於最少的分類錯誤點個數,那么更新最少的分類錯誤點個數和口袋里最好的參數w
        if len(compare(X,w,y))<best_len:
            best_len=len(compare(X,w,y))
            best_w=w

    print("參數best_w:{}".format(best_w))
    print("分類直線:{}x1+{}x2+{}=0".format(best_w[0][0],best_w[1][0],best_w[2][0]))
    print("最少分類錯誤點的個數:{}個".format(best_len))
    line_x=np.linspace(-3,3,10)
    line_y=(-best_w[2]-best_w[0]*line_x)/best_w[1]
    ax.plot(line_x,line_y)

plt.show()

 

另外附上感知機算法用損失函數和梯度下降法實現的代碼(和上述感知機算法的不同之處在於w每次是用離當前分類直線最遠的那個分類錯誤點進行更新的):

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots()
import numpy as np

data=pd.read_csv(r"...\data1.csv",header=None)

X=data.iloc[:,[0,1]]  #提取特征
y=data[2]   #提取目標

class Perceptron:
    def __init__(self):
        self._w = self._b = None
    
    def standardization(self,X):
        #將輸入的X歸一化
        mean=X.mean(axis=0)
        sigma=X.std(axis=0)
        X=(X-mean)/sigma
        
        return X
        
    def fit(self, X, y, lr=1, epoch=100):
        #訓練數據
        #將輸入的X,y轉換為numpy數組
        X, y = np.asarray(X, np.float32), np.asarray(y, np.float32)
        #初始化w,b
        self._w = np.zeros(X.shape[1])
        self._b = 0

        for _ in range(epoch):    
            # 計算 w·x+b
            y_pred = np.dot(X,self._w) + self._b    
            # 標記使損失函數最大的樣本
            idx = np.argmax(np.maximum(0, -y_pred * y))    
            # 若該樣本被正確分類,則結束訓練
            if y[idx] * y_pred[idx] > 0:  
                break   
            # 否則,讓參數沿着負梯度方向走一步
            else:
                delta = lr * y[idx]
                self._w += delta * X[idx]
                self._b += delta
        
        return self._w,self._b
    
    def print_results(self,w,b):
        print("參數w:{}".format(w))
        print("參數b:{}".format(b))
        print("分類直線:{}x1+{}x2+{}=0".format(w[0],w[1],b))
    
    def draw_pics(self,X,w,b):
        #提取不同分類的數據
        x_positive=X[y==1]
        x_negative=X[y==-1]
        
        #畫出歸一化后的數據
        ax.scatter(x_positive[0],x_positive[1],marker="o",label="y=+1")
        ax.scatter(x_negative[0],x_negative[1],marker="x",label="y=-1")
        ax.legend()
        ax.set_xlabel("x1")
        ax.set_ylabel("x2")
        ax.set_title("Standardized Data")
        
        #畫出分類直線
        line_x=np.linspace(-3,3,10)
        line_y=(-b-w[0]*line_x)/w[1]
        ax.plot(line_x,line_y)
        
    def predict(self,X):
        return np.where(np.dot(X,self._w)-self._b>0,1,-1)

if __name__=="__main__":
    PLA=Perceptron()
    X=PLA.standardization(X)
    w,b=PLA.fit(X,y,lr=1,epoch=100)
    PLA.print_results(w,b)
    PLA.draw_pics(X,w,b)

 


免責聲明!

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



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