基於感知機Perceptron的鳶尾花分類實踐


本文將使用感知機模型,對鳶尾花進行分類,並調整參數,對比分類效率。

1. 感知機簡介

感知機(perceptron)是二類分類的線性分類模型

  • 輸入:實例的特徵向量
  • 輸出:實例的類別,取 +1 和 -1 二值
  • 感知機對應於輸入空間(特徵空間)中將實例劃分爲正負兩類的分離超平面,屬於判別模型
  • 旨在求出將訓練數據進行線性劃分的分離超平面,爲此,導入基於誤分類的損失函數,利用梯度下降法對損失函數進行極小化,求得感知機模型。
  • 感知機學習算法具有簡單而易於實現的優點,分爲原始形式和對偶形式。
  • 預測:對新的輸入進行分類

具體內容見李航《統計學習方法》第二章,感知機 讀書筆記

2. 編寫感知機實踐

本文代碼參考了此處:fengdu78,本人添加了感知機算法的對偶形式,並對不同的參數下的迭代次數進行比較。

2.1 數據處理

# 讀取鳶尾花數據
iris = load_iris()
# 將鳶尾花4個特徵,以4列存入pandas的數據框架
df = pd.DataFrame(iris.data, columns=iris.feature_names)
# 在最後一列追加 加入標籤(分類)列數據
df['lab'] = iris.target

# df.columns=[iris.feature_names[0], iris.feature_names[1], iris.feature_names[2], iris.feature_names[3], 'lab']
# df['lab'].value_counts()
# 選取前兩種花進行劃分(每種數據50組)
plt.scatter(df[:50][iris.feature_names[0]], df[:50][iris.feature_names[1]], label=iris.target_names[0])
plt.scatter(df[50:100][iris.feature_names[0]], df[50:100][iris.feature_names[1]], label=iris.target_names[1])
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])

# 選取數據,前100行,前兩個特徵,最後一列標籤
data = np.array(df.iloc[:100, [0, 1, -1]])
# X是除最後一列外的所有列,y是最後一列
X, y = data[:, :-1], data[:, -1]
# 生成感知機的標籤值,+1, -1, 第一種-1,第二種+1
y = np.array([1 if i == 1 else -1 for i in y])

2.2 編寫感知機類

class PerceptronModel():
    def __init__(self, X, y, eta):
        self.w = np.zeros(len(X[0]), dtype=np.float)  # 權重
        self.b = 0  # 偏置
        self.eta = eta  # 學習率
        self.dataX = X  # 數據
        self.datay = y  # 標籤
        self.iterTimes = 0  # 迭代次數

        # 對偶形式的參數
        self.a = np.zeros(len(X), dtype=np.float)  # alpha
        self.Gmatrix = np.zeros((len(X), len(X)), dtype=np.float)
        self.calculateGmatrix()  # 計算Gram矩陣

    def sign0(self, x, w, b):  # 原始形式sign函數
        y = np.dot(w, x) + b
        return y

    def sign1(self, a, G_j, Y, b):  # 對偶形式sign函數
        y = np.dot(np.multiply(a, Y), G_j) + b
        return y

    def OriginClassifier(self):  # 原始形式的分類算法
        self.iterTimes = 0
        self.b = 0
        stop = False
        while not stop:
            wrong_count = 0
            for i in range(len(self.dataX)):
                X = self.dataX[i]
                y = self.datay[i]
                if (y * self.sign0(X, self.w, self.b)) <= 0:
                    self.w += self.eta * np.dot(X, y)
                    self.b += self.eta * y
                    wrong_count += 1
                    self.iterTimes += 1
            if wrong_count == 0:
                stop = True
        print("原始形式,分類完成!步長:%.4f, 共迭代 %d 次" % (self.eta, self.iterTimes))

    def calculateGmatrix(self):  # 計算Gram矩陣
        for i in range(len(self.dataX)):
            for j in range(0, i + 1):  # 對稱的計算一半就行
                self.Gmatrix[i][j] = np.dot(self.dataX[i], self.dataX[j])
                self.Gmatrix[j][i] = self.Gmatrix[i][j]

    def DualFormClassifier(self):  # 對偶形式分類算法
        self.iterTimes = 0
        self.b = 0
        stop = False
        while not stop:
            wrong_count = 0
            for i in range(len(self.dataX)):
                y = self.datay[i]
                G_i = self.Gmatrix[i]
                if (y * self.sign1(self.a, G_i, self.datay, self.b)) <= 0:
                    self.a[i] += self.eta
                    self.b += self.eta * y
                    wrong_count += 1
                    self.iterTimes += 1
            if wrong_count == 0:
                stop = True
        print("對偶形式,分類完成!步長:%.4f, 共迭代 %d 次" % (self.eta, self.iterTimes))

2.3 多參數組合運行

# 調用感知機進行分類,學習率eta
 perceptron = PerceptronModel(X, y, eta=0.3)
 perceptron.OriginClassifier()  # 原始形式分類

 # 繪製原始算法分類超平面
 x_points = np.linspace(4, 7, 10)
 y0 = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1]
 plt.plot(x_points, y0, 'r', label='原始算法分類線')

 perceptron.DualFormClassifier()  # 對偶形式分類
 # 由alpha,b 計算omega向量
 omega0 = sum(perceptron.a[i] * y[i] * X[i][0] for i in range(len(X)))
 omega1 = sum(perceptron.a[i] * y[i] * X[i][1] for i in range(len(X)))
 y1 = -(omega0 * x_points + perceptron.b) / omega1

 # 繪製對偶算法分類超平面
 plt.plot(x_points, y1, 'b', label='對偶算法分類線')

 plt.rcParams['font.sans-serif'] = 'SimHei'  # 消除中文亂碼
 plt.legend()
 plt.show()

在這裏插入圖片描述

原始算法 對偶算法
η=0.1\eta=0.1 初值全0,迭代1518次,初值全1,迭代1473次 初值全0,迭代1488次,初值全1,迭代2378次
η=0.5\eta=0.5 初值全0,迭代1562次,初值全1,迭代1472次 初值全0,迭代1518次,初值全1,迭代1325次
η=1\eta=1 初值全0,迭代1562次,初值全1,迭代1486次 初值全0,迭代1518次,初值全1,迭代1367次
# ------------------學習率不同,查看迭代次數----------------------------
n = 100
i = 0
eta_iterTime = np.zeros((n, 3), dtype=float)
for eta in np.linspace(0.01, 1.01, n):
    eta_iterTime[i][0] = eta    # 第一列,學習率
    perceptron = PerceptronModel(X, y, eta)
    perceptron.OriginClassifier()
    eta_iterTime[i][1] = perceptron.iterTimes # 第二列,原始算法迭代次數
    perceptron.DualFormClassifier()
    eta_iterTime[i][2] = perceptron.iterTimes # 第三列,對偶算法迭代次數
    i += 1
x = eta_iterTime[:, 0]  # 數據切片
y0 = eta_iterTime[:, 1]
y1 = eta_iterTime[:, 2]
plt.scatter(x, y0, c='r', marker='o', label='原始算法')
plt.scatter(x, y1, c='b', marker='x', label='對偶算法')
plt.xlabel('步長(學習率)')
plt.ylabel('迭代次數')
plt.title("不同步長,不同算法形式下,迭代次數")
plt.legend()
plt.show()

在這裏插入圖片描述
結論:

  • 感知機的兩種算法形式均會因爲初值和學習率的不同,而造成的多種迭代路徑
  • 從上面圖標也印證了,對於線性可分的數據,感知機學習算法迭代是收斂

3. sklearn 感知機實踐

sklearn.linear_model.Perceptron 官網參數介紹

class sklearn.linear_model.Perceptron(penalty=None, alpha=0.0001, 
fit_intercept=True, max_iter=1000, tol=0.001, shuffle=True, 
verbose=0, eta0=1.0, n_jobs=None, random_state=0,
early_stopping=False,validation_fraction=0.1, 
n_iter_no_change=5, class_weight=None, warm_start=False)
classify = Perceptron(fit_intercept=True, max_iter=1000, shuffle=True, eta0=0.1, tol=None)
classify.fit(X, y)
print("特徵權重:", classify.coef_)  # 特徵權重 w
print("截距(偏置):", classify.intercept_)  # 截距 b

# 可視化
plt.scatter(df[:50][iris.feature_names[0]], df[:50][iris.feature_names[1]], label=iris.target_names[0])
plt.scatter(df[50:100][iris.feature_names[0]], df[50:100][iris.feature_names[1]], label=iris.target_names[1])
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])

# 繪製分類超平面
x_points = np.linspace(4, 7, 10)
y = -(classify.coef_[0][0] * x_points + classify.intercept_) / classify.coef_[0][1]
plt.plot(x_points, y, 'r', label='sklearn Perceptron分類線')

plt.title("sklearn內置感知機分類")
plt.legend()
plt.show()

運行結果:

特徵權重: [[ 6.95 -8.73]]
截距(偏置): [-11.2]

在這裏插入圖片描述

  • 可以看出在這兩個特徵下,兩種花線性可分,感知機將兩類花分類成功

我們稍微更改下數據爲後兩種花,再次運行

在這裏插入圖片描述

  • 可以看出,後兩種花在這2個特徵下線性不可分,感知機做出了錯誤分類線

4. 附完整代碼

# -*- coding:utf-8 -*-
# @Python 3.7
# @Time: 2020/2/28 22:07
# @Author: Michael Ming
# @Website: https://michael.blog.csdn.net/
# @File: 2.perceptron.py
# @Reference: https://github.com/fengdu78/lihang-code

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import Perceptron
import matplotlib.pyplot as plt


class PerceptronModel():
    def __init__(self, X, y, eta):
        self.w = np.zeros(len(X[0]), dtype=np.float)  # 權重
        self.b = 0  # 偏置
        self.eta = eta  # 學習率
        self.dataX = X  # 數據
        self.datay = y  # 標籤
        self.iterTimes = 0  # 迭代次數

        # 對偶形式的參數
        self.a = np.zeros(len(X), dtype=np.float)  # alpha
        self.Gmatrix = np.zeros((len(X), len(X)), dtype=np.float)
        self.calculateGmatrix()  # 計算Gram矩陣

    def sign0(self, x, w, b):  # 原始形式sign函數
        y = np.dot(w, x) + b
        return y

    def sign1(self, a, G_j, Y, b):  # 對偶形式sign函數
        y = np.dot(np.multiply(a, Y), G_j) + b
        return y

    def OriginClassifier(self):  # 原始形式的分類算法
        self.iterTimes = 0
        self.b = 0
        stop = False
        while not stop:
            wrong_count = 0
            for i in range(len(self.dataX)):
                X = self.dataX[i]
                y = self.datay[i]
                if (y * self.sign0(X, self.w, self.b)) <= 0:
                    self.w += self.eta * np.dot(X, y)
                    self.b += self.eta * y
                    wrong_count += 1
                    self.iterTimes += 1
            if wrong_count == 0:
                stop = True
        print("原始形式,分類完成!步長:%.4f, 共迭代 %d 次" % (self.eta, self.iterTimes))

    def calculateGmatrix(self):  # 計算Gram矩陣
        for i in range(len(self.dataX)):
            for j in range(0, i + 1):  # 對稱的計算一半就行
                self.Gmatrix[i][j] = np.dot(self.dataX[i], self.dataX[j])
                self.Gmatrix[j][i] = self.Gmatrix[i][j]

    def DualFormClassifier(self):  # 對偶形式分類算法
        self.iterTimes = 0
        self.b = 0
        stop = False
        while not stop:
            wrong_count = 0
            for i in range(len(self.dataX)):
                y = self.datay[i]
                G_i = self.Gmatrix[i]
                if (y * self.sign1(self.a, G_i, self.datay, self.b)) <= 0:
                    self.a[i] += self.eta
                    self.b += self.eta * y
                    wrong_count += 1
                    self.iterTimes += 1
            if wrong_count == 0:
                stop = True
        print("對偶形式,分類完成!步長:%.4f, 共迭代 %d 次" % (self.eta, self.iterTimes))


if __name__ == '__main__':
    # 讀取鳶尾花數據
    iris = load_iris()
    # 將鳶尾花4個特徵,以4列存入pandas的數據框架
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    # 在最後一列追加 加入標籤(分類)列數據
    df['lab'] = iris.target

    # df.columns=[iris.feature_names[0], iris.feature_names[1], iris.feature_names[2], iris.feature_names[3], 'lab']
    # df['lab'].value_counts()
    # 選取前兩種花進行劃分(每種數據50組)
    plt.scatter(df[:50][iris.feature_names[0]], df[:50][iris.feature_names[1]], label=iris.target_names[0])
    plt.scatter(df[50:100][iris.feature_names[0]], df[50:100][iris.feature_names[1]], label=iris.target_names[1])
    plt.xlabel(iris.feature_names[0])
    plt.ylabel(iris.feature_names[1])

    # 選取數據,前100行,前兩個特徵,最後一列標籤
    data = np.array(df.iloc[:100, [0, 1, -1]])
    # X是除最後一列外的所有列,y是最後一列
    X, y = data[:, :-1], data[:, -1]
    # 生成感知機的標籤值,+1, -1, 第一種-1,第二種+1
    y = np.array([1 if i == 1 else -1 for i in y])

    # 調用感知機進行分類,學習率eta
    perceptron = PerceptronModel(X, y, eta=0.1)
    perceptron.OriginClassifier()  # 原始形式分類

    # 繪製原始算法分類超平面
    x_points = np.linspace(4, 7, 10)
    y0 = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1]
    plt.plot(x_points, y0, 'r', label='原始算法分類線')

    perceptron.DualFormClassifier()  # 對偶形式分類
    # 由alpha,b 計算omega向量
    omega0 = sum(perceptron.a[i] * y[i] * X[i][0] for i in range(len(X)))
    omega1 = sum(perceptron.a[i] * y[i] * X[i][1] for i in range(len(X)))
    y1 = -(omega0 * x_points + perceptron.b) / omega1

    # 繪製對偶算法分類超平面
    plt.plot(x_points, y1, 'b', label='對偶算法分類線')

    plt.rcParams['font.sans-serif'] = 'SimHei'  # 消除中文亂碼
    plt.legend()
    plt.show()
    # ------------------學習率不同,查看迭代次數----------------------------
    n = 5
    i = 0
    eta_iterTime = np.zeros((n, 3), dtype=float)
    for eta in np.linspace(0.01, 1.01, n):
        eta_iterTime[i][0] = eta  # 第一列,學習率
        perceptron = PerceptronModel(X, y, eta)
        perceptron.OriginClassifier()
        eta_iterTime[i][1] = perceptron.iterTimes  # 第二列,原始算法迭代次數
        perceptron.DualFormClassifier()
        eta_iterTime[i][2] = perceptron.iterTimes  # 第三列,對偶算法迭代次數
        i += 1
    x = eta_iterTime[:, 0]  # 數據切片
    y0 = eta_iterTime[:, 1]
    y1 = eta_iterTime[:, 2]
    plt.scatter(x, y0, c='r', marker='o', label='原始算法')
    plt.scatter(x, y1, c='b', marker='x', label='對偶算法')
    plt.xlabel('步長(學習率)')
    plt.ylabel('迭代次數')
    plt.title("不同步長,不同算法形式下,迭代次數")
    plt.legend()
    plt.show()
    # ------------------sklearn實現----------------------------
    classify = Perceptron(fit_intercept=True, max_iter=10000, shuffle=False, eta0=0.5, tol=None)
    classify.fit(X, y)
    print("特徵權重:", classify.coef_)  # 特徵權重 w
    print("截距(偏置):", classify.intercept_)  # 截距 b

    # 可視化
    plt.scatter(df[:50][iris.feature_names[0]], df[:50][iris.feature_names[1]], label=iris.target_names[0])
    plt.scatter(df[50:100][iris.feature_names[0]], df[50:100][iris.feature_names[1]], label=iris.target_names[1])
    plt.xlabel(iris.feature_names[0])
    plt.ylabel(iris.feature_names[1])

    # 繪製分類超平面
    x_points = np.linspace(4, 7, 10)
    y = -(classify.coef_[0][0] * x_points + classify.intercept_) / classify.coef_[0][1]
    plt.plot(x_points, y, 'r', label='sklearn Perceptron分類線')

    plt.title("sklearn內置感知機分類")
    plt.legend()
    plt.show()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章