[筆記-統計學習方法]感知機模型(perceptron) 原理與實現

前幾天認把感知機這一章讀完了,順帶作了點筆記 如今把筆記作第三次的整理 (不得不說博客園的LaTex公式和markdown排版真的不太舒服,該考慮在服務器上建一個博客了)python

零、總結

  1. 適用於具備線性可分的數據集的二分類問題,能夠說是很侷限了
  2. 感知機本質上是一個分離超平面
  3. 在向量維數(特徵數)太高時,選擇對偶形式算法 在向量個數(樣本數)過多時,應選擇原始算法
  4. 批量梯度降低和隨機梯度降低的區別和優點 參考連接:隨機梯度降低(Stochastic gradient descent)和 批量梯度降低(Batch gradient descent )的公式對比、實現對比
  • 批量梯度降低(BGD, Batch Gradient Descent) $ \theta \leftarrow \theta + \eta \sum \frac{\partial L}{\partial \theta}$ 即屢次作全局樣本的參數更新 缺點:計算耗時 優勢:能夠趨向全局最優,受數據噪音影響少
  • 隨機梯度降低(SGD, Srochastic Gradient Descent) $ \theta \leftarrow \theta + \eta \frac{\partial L}{\partial \theta}$ 即屢次作單個樣本的參數更新 缺點:訓練耗時較短 優勢:不必定趨向全局最優(每每是最優/較優,單峯問題除外),受數據噪音影響大

1、模型

輸入空間 $ \mathcal{X} \subseteq R^n $ 輸出空間 $ \mathcal{Y} \subseteq {-1, +1} $ 假設空間 $ \mathcal{F} \subseteq {f|f(x) = \omega \cdot x + b} $ 參數 $ \omega \in R^n, b \in R $ 模型 $ f(x) = sign(\omega \cdot x + b) $算法

其中 符號函數爲 $$ sign(x)=\left{\begin{matrix} +1 , x \geqslant 0\ -1 , x \geqslant 0 \end{matrix}\right. $$服務器

線性方程 $ \omega \cdot x + b $ 能夠表示爲特徵空間 $ R^n $中的一個分離超平面markdown

2、策略

(定義的損失函數,並極小化損失函數) (注意損失函數非負的性質)app

爲了使損失函數更容易優化,咱們選擇誤分類點到超平面的距離做爲損失函數 任意向量$x \in R^n$距分離超平面的距離爲 $ S=\frac{1}{|\omega|}|\omega \cdot x + b| $函數

接下來優化一下這個距離,讓它更好的成爲一個損失函數學習

  1. 爲了連續可導,去絕對值 $ S=-\frac{1}{|\omega|} y_i(\omega \cdot x + b) $
  2. 去掉不相關的係數(避免浪費計算),獲得 $ L(\omega, b)=-\sum_{x_i \in M} y_i(\omega \cdot x + b) $ 其中$ M $爲誤分類點集合

3、算法

(如何實現最優化問題) 注意最終訓練出的模型參數的值取決於初值和誤分類點的選取,因此通常值不一樣優化

爲了極小化損失函數,咱們採用梯度降低的方法spa

  1. 原始形式算法
  • 賦初值 $ \omega \leftarrow 0 , b \leftarrow 0 $
  • 選取數據點 $ (x_i, y_i) $
  • 判斷該數據點是否爲當前模型的誤分類點,即判斷若$ y_i(\omega \cdot x + b) <=0 $ 則更新 $$ \begin{matrix} \omega &\leftarrow \omega + \eta n_ix_iy_i \ b &\leftarrow b + \eta n_iy_i \end{matrix}$$
  1. 對偶形式算法 注意到原始形式算法中,最終訓練好的模型參數是這樣的,其中$ n_i $表示在第i個數據點上更新過幾回 $$ \begin{matrix} \omega &= \eta \sum_i n_ix_iy_i \ b &= \eta \sum_i n_iy_i \end{matrix} $$ 因而咱們能夠做出如下簡化
  • 賦初值 $ n \leftarrow 0, b \leftarrow 0 $
  • 選取數據點 $ (x_i, y_i) $
  • 判斷該數據點是否爲當前模型的誤分類點,即判斷若$ y_i(\eta \sum n_iy_ix_i \cdot x + b) <=0 $ 則更新 $$ \begin{matrix} n_i &\leftarrow n_i + 1 \ b &\leftarrow b + \eta y_i \end{matrix}$$ 爲了減小計算量,咱們能夠預先計算式中的內積,獲得Gram矩陣 $ G=[x_i, x_j]_{N \times N} $
  1. 原始形式和對偶形式的選擇 相見知乎如何理解感知機學習算法的對偶形式? 在向量維數(特徵數)太高時,計算內積很是耗時,應選擇對偶形式算法加速 在向量個數(樣本數)過多時,每次計算累計和(對偶形式中的$\omega$)就沒有必要,應選擇原始算法

4、代碼實現

由於感知機對數據要求很嚴格,爲了實現這個模型,我用到了iris的數據集,用來給鳶尾花分類 又由於感知機只能作二分類,因此仍是要把原數據的兩個類別合併.net

爲了學習numpy,仍是用了python實現

import numpy as np
from matplotlib import pyplot as plt

class Perceptron:
    # use the primitive algorithm
    arguments={
        "item_class":{
            "Iris-setosa": -1,
            "Iris-versicolor": 1,
            "Iris-virginica": 1,
        },
        "epoch": 800,
        "colors": ['blue', 'red'],
        "draw_start_x": 4,
        "draw_end_x": 7.5,
        "epsilon": 0.0,
        "learning_rate": 0.25,
    }

    def __init__(self, vec_dim, learning_rate=None, epsilon=None):
        # self.data=np.empty(dim)
        # self.counter=np.zeros(dim)
        self.data=None
        self.vec_dim=vec_dim
        self.lr=learning_rate
        if epsilon:
            self.epsilon=epsilon
        else:
            self.epsilon=self.arguments["epsilon"]
        if learning_rate:
            self.lr=learning_rate
        else:
            self.lr=self.arguments["learning_rate"]

        self.weight=np.zeros((self.vec_dim-1, 1))
        self.bias=0

    def read_data(self, filepath):
        raw_data=[]
        with open(filepath, "r") as file:
            for line in file.readlines():
                if line=='\n':
                    break
                item=line.replace('\n', '').split(',')
                itemc=self.arguments["item_class"][item[-1]]
                vec=[float(x) for x in item[0:2]]+[itemc]

                raw_data.append(vec)
        self.data=np.array(raw_data).T

    def process(self):
        # it is dual form
        vec=self.data[:, 0:2]
        self.gram=np.dot(vec, vec.T)

    def train(self):
        self.bias=0
        self.weight=np.zeros((self.vec_dim-1, 1))
        # self.counter=np.zeros(dim)
        for epoch in range(1, self.arguments["epoch"]+1):
            error_counter=0
            for idx in range(self.data.shape[1]):
                vec=self.data[:, idx]
                x, y=vec[0:-1, np.newaxis], vec[-1]
                if y*(np.dot(self.weight.T, x)+self.bias)<=self.epsilon:
                    self.weight+=self.lr*y*x
                    self.bias+=self.lr*y
                    error_counter+=1
            print("epoch #%03d: error:%03d total:%03d"%(
                epoch, error_counter, self.data.shape[1]))
            print("weight:", self.weight.ravel())
            print("bias:", self.bias, "\n")

            if error_counter==0:
                print("train done!")
                break

    def show(self):
        for idx in range(self.data.shape[1]):
            color=self.arguments["colors"][0]
            if self.data[2, idx]<0:
                color=self.arguments["colors"][1]
            plt.scatter(self.data[0, idx], self.data[1, idx], color=color)
        y=[-(self.weight[0, 0]*self.arguments["draw_start_x"] + self.bias)/self.weight[1, 0],
           -(self.weight[0, 0]*self.arguments["draw_end_x"] + self.bias)/self.weight[1, 0]]
        plt.plot([self.arguments["draw_start_x"], self.arguments["draw_end_x"]], y)
        plt.show()

更新了代碼實現部分

相關文章
相關標籤/搜索