《統計學習方法》第 2 章 感知機 可視化

slmethod_perceprton.gif

原理

假設輸入空間(特徵空間)是 \mathrm{x} \subseteq \mathrm{R}^{n},輸出空間是y=\{+1,-1\}html

模型

f(x)=\operatorname{sign}(w \cdot x+b)

稱爲 感知機python

  • \mathrm{w}\mathrm{b} 爲感知機模型參數
  • \mathrm{w} \subseteq \mathrm{R}^{n}叫做權重/權值(weight)或權值向量(weight vector)
  • \mathrm{b} \in \mathrm{R} 叫做偏置(bias)
  • \mathbf{w} \cdot \mathbf{x} 表示 \mathbf{w}\mathbf{x} 的內積
  • sign 是符號函數
\operatorname{sign}(x)=\left\{\begin{array}{ll}{+1,} & {x \geqslant 0} \\ {-1,} & {x<0}\end{array}\right.

策略

假設訓練數據集是線性可分的 感知機學習的目標是求得一個可以將訓練集正實例點和負實例點徹底正確分開的分離超平面。爲了找出這樣的超平面,即肯定感知機模型參數\mathrm{w}\mathrm{b} ,須要肯定一個學習策略,即定義(經驗)損失函數並將損失函數極小化。git

損失函數的一個天然選擇是誤分類點的總數。 可是,這樣的損失函數不是參數w,b的連續可導函數,不易優化。 損失函數的另外一個選擇是誤分類點到超平面S的總距離,這是感知機所採用的。github

全部誤分類點到超平面S的總距離爲算法

-\frac{1}{\|w\|} \sum_{x_{i} \in M} y_{i}\left(w \cdot x_{i}+b\right)

不考慮\frac{1}{\|w\|},就獲得感知機學習的損失函數。api

算法

原始形式

輸入:訓練數據集 \mathrm{T}=\left\{\left(\mathrm{x}_{1}, \mathrm{y}_{1}\right),\left(\mathrm{x}_{2}, \mathrm{y}_{2}\right), \ldots,\left(\mathrm{x}_{\mathrm{N}}, \mathrm{y}_{\mathrm{N}}\right)\right\},其中 \mathrm{x}_{\mathrm{i}} \in \mathrm{x}=\mathrm{R}_{\mathrm{n}}, \quad \mathrm{y}_{\mathrm{i}} \in \mathcal{Y}=\{-1,+1\},  \quad  \mathrm{i}=1,2\ldots, \mathbf{N};學習率\eta(0<\eta \leq 1)bash

輸出:\mathrm{w}, \mathrm{b};感知機模型 f(x)=\operatorname{sign}(w \cdot x+b)app

  1. 選取初值 \mathbf{w}_{0}, \mathbf{b}_{0}
  2. 在訓練集中選取數據 \left(\mathrm{x}_{\mathrm{i}}, \mathrm{y}_{\mathrm{i}}\right)
  3. 若是 \mathrm{y}_{\mathrm{i}}\left(\mathrm{y} \cdot \mathrm{x}_{\mathrm{i}}+\mathrm{b}\right) \leq 0
\begin{array}{l}{w \leftarrow w+\eta y_{i} x_{i}} \\ {b \leftarrow b+\eta y_{i}}\end{array}
  1. 轉至 2,直至訓練集中沒有誤分類點。
# 原始
    def _fit(self):
        n_samples, n_features = self.X.shape
        # 選取初值 w0,b0
        self.w = np.zeros(n_features, dtype=np.float64)
        self.b = 0.0

        is_finished = False
        # 一直循環
        while not is_finished:
            count = 0  # 記錄誤分類點的數目
            for i in range(n_samples):
                # 若是 yi(w·xi+b)≤0
                if self.y[i] * self.sign(self.w, self.X[i], self.b) <= 0:
                    self.w += self.l_rate * np.dot(self.y[i], self.X[i])
                    self.b += self.l_rate * self.y[i]
                    self._wbs.append((i, self.w, self.b))
                    count += 1

                # 直至訓練集中沒有誤分類點
                if count == 0:
                    is_finished = True
複製代碼

這種學習算法直觀上有以下解釋:dom

當一個實例點被誤分類,即位於分離超平面的錯誤一側時,則調整w,b的值,使分離超平面向該誤分類點的一側移動,以減小該誤分類點與超平面間的距離,直至超平面越過該誤分類點使其被正確分類。函數

算法是感知機學習的基本算法,對應於後面的對偶形式,稱爲原始形式。

感知機學習算法簡單且易於實現。

感知機學習算法的對偶形式

  1. a \leftarrow 0, \quad b \leftarrow 0
  2. 在訓練集中選取數據 \left(x_{i}, y_{i}\right)
  3. 若是 y_{i}\left(\sum_{j=1}^{N} \alpha_{j} y_{j} x_{j} \cdot x_{i}+b\right) \leqslant 0
\begin{array}{l}{\alpha_{i} \leftarrow \alpha_{i}+\eta} \\ {b \leftarrow b+\eta y_{i}}\end{array}
  1. 轉至 2 直到沒有誤分類數據。
# 對偶形式
    def _fit_dual(self):
        """ 對偶形式中訓練實例僅之內積的形式出現 先求出 Gram 矩陣,能大大減小計算量 """
        n_samples, n_features = self.X.shape
        self._alpha = np.zeros(n_samples, dtype=np.float64)
        self.w = np.zeros(n_features, dtype=np.float64)
        self.b = 0.0

        self._cal_gram_matrix()

        i = 0
        while i < n_samples:
            if self._dual_judge(i) <= 0:
                self._alpha[i] += self.l_rate
                self.b += self.l_rate * self.y[i]
                i = 0
            else:
                i += 1

        for i in range(n_samples):
            self.w += self._alpha[i] * self.X[i] * self.y[i]
複製代碼

對偶形式中訓練實例僅之內積的形式出現。爲了方便,能夠預先將訓練集中實例間的內積計算出來並以矩陣的形式存儲,這個矩陣就是所謂的Gram矩陣(Gram matrix)。

測試

pytest fixture 中的 pytest.mark.parametrize 裝飾器能夠實現用例參數化。 這裏直接傳進 5 組參數。

import pytest
from slmethod.perceptron import Perceptron
import numpy as np


# 
@pytest.mark.parametrize("dual, l_rate", [(True, None), (False, None),
                                          (False, 1), (False, 0.1),
                                          (False, 0.01)])
def test_perceptron(dual, l_rate):
    train_X = np.array([ ... ])
    train_y = np.array([ ... ])

    clf = Perceptron(dual=dual, l_rate=l_rate)
    clf.fit(train_X, train_y)
    test_X = np.array([[10, 3], [-29, 5]])
    test_y = np.array([1, -1])
    predict_y = clf.predict(test_X)
    assert np.array_equal(test_y, predict_y)
複製代碼

pytest.png

具體代碼可查看 GitHub: github.com/iOSDevLog/s…

動畫

matplotlib.org/api/animati…

只展現 2D 數據

def show2d(self, name=None):
        if (self.X.shape[1] != 2):
            raise ValueError("X must have 2d array.")
複製代碼

取 X 最小值與最大值用於畫直線

minX = np.min(self.X[:, 0])
        maxX = np.max(self.X[:, 0])
        x_points = np.array([minX, maxX])
複製代碼

導入相關庫

from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
複製代碼

靜態圖

fig, ax = plt.subplots()
        ax.scatter(self.X[:, 0], self.X[:, 1], c=self.y, s=1, marker="o")
        line, = ax.plot(x_points,
                        np.zeros(len(x_points)),
                        "r-",
                        linewidth=2,
                        label="slmethod perceptron")
複製代碼

更新動畫

接着,構造自定義動畫函數 update,用來更新每一幀上各個 x 對應的 y 座標值,參數表示第 i 幀:

def update(iter):
            (index, w, b) = self._wbs[iter]
            # title
            title = "iter: {}, index: {}".format(iter, index)
            plt.title(title)
            # show w and b
            wb = "w0: {}, w1: {}, b: {}".format(w[0], w[1], b)
            ax.set_xlabel(wb)
            # update y
            y_points = -(w[0] * x_points + b) / w[1]
            line.set_ydata(y_points)

            return line, ax
複製代碼

初始化

而後,構造開始幀函數 init

def init():
            line.set_ydata(np.zeros(len(x_points)))
            return line,
複製代碼

生成動畫

接下來,咱們調用FuncAnimation函數生成動畫。

參數說明:

  • fig 進行動畫繪製的 figure
  • func 自定義動畫函數,即傳入剛定義的函數 update
  • frames 動畫長度,一次循環包含的幀數
  • init_func 自定義開始幀,即傳入剛定義的函數 init
  • interval 更新頻率 ms
anim = FuncAnimation(fig,
                             update,
                             init_func=init,
                             frames=len(self._wbs),
                             interval=200)
複製代碼

顯示動畫

plt.show()
複製代碼

保存 gif

anim.save(name, writer="imagemagick")
複製代碼

保存和顯示不能同時,不知道爲何?

具體代碼請查看:github.com/iOSDevLog/s…

使用

pip 安裝

pip install slmethod
複製代碼

使用方法和 sklearn 很是類似,如下步驟可省略部分。

  1. 獲取數據
  2. 數據預處理
  3. 劃分測試集與訓練集
  4. 估計器擬合
  5. 可視化
  6. 預測測試集
import numpy as np
from sklearn.datasets import make_blobs
from slmethod.perceptron import Perceptron

X, y = make_blobs(n_samples=500,
                  n_features=2,
                  centers=2,
                  cluster_std=0.2,
                  random_state=59)

y = np.where(y == 1, 1, -1)

# 原始感知機
origin_cls = Perceptron(dual=False)
origin_cls.fit(X, y)
origin_cls.show_anim()

# 對偶形式
dual_cls = Perceptron(dual=True)
dual_cls.fit(X, y)
dual_cls.show2d()
複製代碼

代碼:github.com/iOSDevLog/s…

相關文章
相關標籤/搜索