機器學習回顧篇(10):感知機模型

 

注:本系列全部博客將持續更新併發布在githubgitee上,您能夠經過github、gitee下載本系列全部文章筆記文件。javascript

 

1 引言

感知機是一種簡單且易於實現的二分類判別模型,主要思想是經過誤分類驅動的損失函數結合梯度降低發求解一個超平面將線性可分的數據集劃分爲兩個不一樣的類別(+1類和-1類)。
在神經網絡、支持向量機等算法盛行的當下,感知機模型應用得並很少,但必須認可,感知機倒是神經網絡和支持向量機的基礎,因此仍是頗有必要學習一下的,本文接下來的內容將從感知機數學描述、損失函數、兩種不一樣學習形式等方面詳細介紹感知機,最後使用Python實現感知機兩種學習形式。css

 

2 感知機模型及損失函數

2.1 數學描述

對於給定訓練樣本數據集$D = \{ ({x_i},{y_i})\} _{i = 1}^m$,${x_i} \in X \subseteq {R^n}$表示訓練樣本的特徵向量,${y_i} \in Y = \{ + 1, - 1\} $表示樣本類別。$x$與$y$之間的以下函數關係: $$y = f(x) = sign(w \cdot x + b)$$ 稱爲感知機。其中,$w \in {R^n}$稱爲感知機的權值係數或者權值向量,$b \in R$稱爲偏置,$sign$是符號函數,有: $$sign = \left\{ {_{ - 1, x < 0}^{ + 1, x \geqslant 0}} \right.$$ 從定義上能夠看出,感知機最終目標就是求解出$w$和$b$。咱們能夠從幾何上對感知機進行理解,若是以$w$爲法向量,以$b$爲截距,能夠肯定一超平面: $$w \cdot x + b = 0$$ 經過這一超平面,能夠順利將對數據集進行劃分。以二維數據爲例,以下圖所示,當樣本點$x$恰好落在超平面上時,有$w \cdot x + b = 0$,當$x$落在超平面下方時,有$w \cdot x + b < 0$,經過$sign$函數後輸出爲$-1$,也就是標記爲$-1$類;當$x$落在超平面上方時,有$w \cdot x + b > 0$,經過$sign$函數後輸出爲$+1$,也就是標記爲$+1$類。注意,這樣的超平面通常不惟一,也就是說感知機最終解能夠有不少個,受參數初始值、訓練樣本輸入順序等因素的影響,每次訓練時所得到的超平面均可能不同。 imagehtml

 

2.2 損失函數

爲了求解參數$w$和$b$,肯定最終的分割超平面,咱們須要定義一個目標函數或者說損失函數,經過最小化損失函數來達到目的。在感知機模型中,以誤分類的樣本對象與分割超平面間的距離之和最爲損失函數。咱們高中時學過,對於點$({x_0},{y_0})$,到平面$A \cdot x + B \cdot y + C = 0$的距離爲: $$dist = \frac{{|A \cdot {x_0} + B \cdot {y_0} + C|}}{{\sqrt {{A^2} + {B^2}} }} $$ 將這一公式擴展到超平面中,對於超平面$w \cdot x + b = 0$,誤分類點$x_i$到超平面的距離爲: $$dist = \frac{{|w \cdot xi + b|}}{{\left\| w \right\|}} $$ 式中,${\left\| w \right\|}$是$w$的$L_2$範數,等同於上面的${\sqrt {{A^2} + {B^2}} }$。 爲了方便計算,咱們須要將分子中的絕對值去掉,怎麼去掉呢?由於$({x_i},{y_i})$是誤分類樣本點,$y_i$與${w \cdot xi + b}$必定是異號的,因此: $$ - {y_i} \cdot (w \cdot x_i + b) > 0 $$ 且由於$|{y_i}| = 1$,因此: $$ - {y_i} \cdot (w \cdot x_i + b) = |w \cdot x_i + b| $$ 因而,$({x_i},{y_i})$到超平面的距離能夠表示爲: $$\frac{{ - {y_i} \cdot (w \cdot x_i + b)}}{{\left\| w \right\|}} $$ 假設$M$是全部誤分類點組成的集合,那麼全部誤分類點到超平面距離總和爲: $$\sum\limits_{{x_i} \in M} {\frac{{ - {y_i} \cdot (w \cdot {x_i} + b)}}{{\left\| w \right\|}}} \tag{1}$$ 這就是咱們須要的損失函數的雛形了,之因此說是雛形,是由於咱們還能夠經過令$\left\| w \right\|{\text{ = }}1$對上式進一步化簡,因而有: $$L(w,b) = \sum\limits_{{x_i} \in M} { - {y_i} \cdot (w \cdot {x_i} + b)} \tag{2}$$ $L(w,b)$就是咱們最終須要的損失函數。 爲何能夠直接令$\left\| w \right\|{\text{ = }}1$來化簡式(1)呢?html5

咱們能夠在權值向量$w$中添加一個$w_0$元素,在特徵向量$x_i$中添加一個第0緯度${x^{(0)}} = 1$,令偏置$b = {w_0} \cdot {x^{(0)}}$,這樣,咱們就把偏置$b$也放進了權值向量$w$中,那式(1)就變爲: $$\sum\limits_{{x_i} \in M} {\frac{{ - {y_i} \cdot w \cdot {x_i}}}{{\left\| w \right\|}}} $$ 此時,分子和分母都含有$w$,當分子的$w$擴大$N$倍時,分母的$L_2$範數也會擴大N倍。也就是說,分子和分母有固定的倍數關係。那麼咱們能夠固定分子或者分母爲1,而後求分母的倒數或者分子本身的最小化做爲損失函數,這樣能夠簡化咱們的損失函數。在感知機模型中,採用的是保留分子的策略。
另外一種解釋,當把偏置$b$包含進$w$後,超平面表達式也簡化成$w \cdot {x_i} = 0$,不管是把$w$擴大多少倍、縮小多少倍,都對超平面沒有影響(就像$x + y - 1 = 0$與$2x + 2y - 2 = 0$始終表示同一條直線),那麼咱們總能找到一個倍數,將$w$縮小到知足${\left\| w \right\|}=1$,但並不影響咱們得到最終的超平面,可是令${\left\| w \right\|}=1$後卻有助於咱們化簡和求解。java

 

3 優化方法

上一節中,咱們介紹了感知機模型損失函數$L(w,b)$的由來,接下來就要說說怎麼經過優化損失函數來得到最終的超平面。在感知機模型中,有兩種優化方式:原始形式和對偶形式。node

3.1 原始形式

原始形式採用的是梯度降低法進行求解,若是對梯度降低法不瞭解,能夠參看前面寫過的一篇博客。這裏須要注意的是,在上一小節中說過,感知機是基於誤分類驅動的一種模型,因此不能使用整個數據集進行梯度降低優化,只能對誤分類樣本集合$M$採用隨機梯度降低法或者小批量梯度降低法進行優化。 對損失函數$L(w,b)$求偏導: $$\frac{{\partial L(w,b)}}{{\partial w}} = - \sum\limits_{{x_i} \in M} {{y_i} \cdot {x_i}} $$ $$\frac{{\partial L(w,b)}}{{\partial b}} = - \sum\limits_{{x_i} \in M} {{y_i}} $$ 那麼,$w$的梯度降低迭代公式爲: $$w = w + \alpha \cdot \sum\limits_{{x_i} \in M} {{y_i} \cdot {x_i}} $$ 偏置$b$的梯度降低迭代公式爲: $$b = b + \alpha \cdot \sum\limits_{{x_i} \in M} {{y_i}} $$ 式中,$\alpha $是學習率。 感知機模型中,通常採用隨機梯度降低法進行優化,每次使用一個誤分類樣本點進行梯度更新。假設$(x_i,y_i)$是$M$中的一個誤分類點,進行梯度更新: $$w = w + \alpha \cdot {y_i}{x_i} \tag{3}$$ $$b = b + \alpha \cdot {y_i} \tag{4}$$ 總結一下原始形式優化步驟。 輸入:訓練樣本數據集$D = \{ ({x_i},{y_i})\} _{i = 1}^m$,${x_i} \in X \subseteq {R^n}$,${y_i} \in Y = \{ + 1, - 1\} $,學習率% $\alpha \in (0,1)$
輸出:$w$,$b$;感知機模型$f(x) = sign(w \cdot x + b) $
(1)初始化$w_0$,$b_0$;
(2)在$D$中選取任意點$(x_i,y_i)$;
(3)經過${y_i} \cdot (w \cdot {x_i} + b)$的值判斷是不是誤分類點,若是是,使用式(3)、(4)更新參數;
(4)回到步驟(2)直到準確率知足條件。python

3.2 對偶形式

對偶形式時原始形式在執行效率上的優化。經過3.1小節中,咱們知道,每當一個樣本點$x_i$被錯誤分類一次時,都會使用式(3)(4)更新一次參數,那麼,若是樣本點$x_i$在迭代過程當中被錯誤分類屢次(假設$n_i$次),那麼就回有$n_i$次參與到參數更新中,咱們假設參數$w$和$b$的初始值都爲0向量,那麼,最終得到的參數$w$和$b$爲: $$w = \sum\limits_{i = 1}^N {{\beta _i} {y_i}{x_i}} \tag{5}$$ $$b = \sum\limits_{i = 1}^N {{\beta _i} {y_i}} \tag{6}$$ 這是在對偶形式中的參數更新方式,式中,${\beta _i} ={n_i}\alpha$。另外,在原始形式中,咱們使用${y_i}(w \cdot {x_i} + b) \leqslant 0$來判斷樣本點$x_i$是否被錯誤分類,將式(5)(6)代入這一判別式中,得: $${y_i}(\sum\limits_{i = 1}^N {{\beta _i} {y_i}{x_i}} \cdot {x_j} + \sum\limits_{i = 1}^N {{\beta _i} {y_i}}) \leqslant 0 \tag{7}$$ 在對偶形式中,採用式(7)判斷樣本點是否正確分類,觀察後能夠發現,式(7)中有兩個樣本點$x_i$和$x_j$內積計算,這個內積計算的結果在下面的迭代過程當中須要屢次被重複使用,若是咱們事先用矩陣運算計算出全部的樣本之間的內積,那麼在算法迭代過程當中, 僅僅一次的矩陣內積運算比原始形式中每遍歷一個樣本點都要計算$w$與$x_i$的內積要省時得多,這也是對偶形式的感知機模型比原始形式優的緣由。
在感知機模型中,樣本的內積矩陣稱爲Gram矩陣,它是一個對稱矩陣,記爲$G = {[{x_i},{x_j}]_{m \times m}}$。
總結一下對偶形式的步驟。
輸入:訓練樣本數據集$D = \{ ({x_i},{y_i})\} _{i = 1}^m$,${x_i} \in X \subseteq {R^n}$,${y_i} \in Y = \{ + 1, - 1\} $,學習率% $\alpha \in (0,1)$
輸出:$w$,$b$;感知機模型$f(x) = sign(w \cdot x + b) $
(1)初始化全部$n_i$值爲0;
(2)計算Gram矩陣;
(3)在$D$中選取任意點$(x_i,y_i)$;
(4)若是${y_i}(\sum\limits_{i = 1}^N {{\beta _i} {y_i}{x_i}} \cdot {x_j} + \sum\limits_{i = 1}^N {{\beta _i} {y_i}}) \leqslant 0 $,令${\beta _i} = {\beta _i} + \alpha $;
(5)檢查是否還有誤分類樣本點,若是有,回到步驟(2);若是沒有,(5)(6)計算$w$、$b$最終值。jquery

 

4 算法實現

In [14]:
import numpy as np
import matplotlib.pyplot as plt 
import copy
In [15]:
# 先來製造一批數據
a = np.random.normal(20,5,300)
b = np.random.normal(15,5,300)
cluster1 = np.array([[x, y, -1] for x, y in zip(a,b)])
In [16]:
a = np.random.normal(45,5,300)
b = np.random.normal(40,5,300)
cluster2 = np.array([[x, y, 1] for x, y in zip(a,b)])
In [17]:
dataset = np.append(cluster1,cluster2, axis=0)
In [95]:
for i in dataset:
    plt.scatter(i[0], i[1],c='black',s=6)
plt.show()
 
In [93]:
len(dataset)
Out[93]:
600
In [175]:
class Perception(object):

    def __init__(self):
        """
        感知機模型
        """
        self.w = 0
        self.b = 0
    
    def fit_raw_mod(self, train_data, lr=1, max_epoch=None, min_error_rate=0):
        """
        原始模式的感知機訓練方法,當達到最大迭代次數或錯誤率降到最小範圍退出
        train_data:訓練數據集
        lr:學習率
        max_epoch:最大迭代次數
        min_error_rate:最小錯誤率
        """
        self.w = np.zeros(train_data.shape[1]-1)  # 根據訓練集維度初始化權重係數
        epoch = 1  # 記錄迭代次數
        while True:
            error_count = 0  # 記錄錯誤分類樣本數
            for sample in train_data:
                xi = sample[0:-1]
                yi = sample[-1]
                distance = yi * (self.w @ xi + self.b)  # yi*(w⋅xi*+b)
                if distance <= 0: # 對於判斷錯誤的樣本點
                    self.w += lr * sample[-1] * sample[0:-1]
                    self.b += lr * sample[-1]
                    error_count += 1
            # 每完成一次迭代以後,驗證一次準確率,準確率達標則退出
            current_error_rate = float(error_count) / train_data.shape[0]
            # print('epoch {0},current_error_rate: {1}'.format(epoch+1, current_error_rate))
            # print('w:{0}, b:{1}'.format(self.w, self.b))
            # self.show_graph(train_data)  # 每一次迭代都展現一次圖像
            if current_error_rate <= min_error_rate:
                break
            if isinstance(max_epoch, int) and epoch >= maxepoch:
                break
            epoch += 1
        print('w:{0}, b:{1}'.format(self.w, self.b))
        self.show_graph(train_data)
        
    def fit_dual_mod(self,train_data,lr=1):
        """
        對偶模式的感知機訓練方法
        train_data:訓練數據集
        lr:學習率
        """
        x_train = train_data[:,:-1]
        y_train = train_data[:,-1]
        num_samples, num_features = x_train.shape
        beta = np.zeros((num_samples,))
        self.b = 0

        # 計算 Gram 矩陣
        gram = np.dot(x_train, x_train.T)

        while True:
            error_count = 0
            for i in range(num_samples):
                inner_product = gram[i]
                y_i = y_train[i]
                
                distance = y_i * (np.sum(beta * y_train * inner_product) + self.b)
                # 對於誤分類點,修正 beta 和 偏置b,跳出本層循環,從新遍歷數據計算,開始新的循環
                if distance <= 0:
                    error_count += 1
                    beta[i] = beta[i] + lr
                    self.b = self.b + lr * y_i
                    break  
            # 數據沒有誤分類點,跳出 while 循環
            if error_count == 0:
                break
        self.w = np.sum(beta * y_train * x_train.T, axis=1)  # 計算w參數最終值
        print('w:{0}, b:{1}'.format(self.w, self.b))
        self.show_graph(train_data)  # 展現圖像

        
    def predict(self, sample):
        """
        輸入一個樣本點,判斷是-1類仍是+1類
        sample:樣本點
        """
        output = self.w @ sample + self.b
        return 1 if output >= 0 else -1
    
    def show_graph(self, train_data):
        """
        把訓練出來的超平面圖像展現出來

        """
        for sample in train_data:
            if sample[-1] == 1:
                plt.scatter(sample[0], sample[1],c='black',s=6)
            else:
                plt.scatter(sample[0], sample[1],c='red',s=6)
        x = np.linspace(0.,60.,200)
        y = -(self.w[0]*x + self.b) / self.w[1]
        plt.plot(x,y)
        plt.show()
In [176]:
model = Perception()
model.fit_raw_mod(dataset, lr=1)
 
w:[8.54367705 9.34962314], b:-542.0
 
In [177]:
model = Perception()
model.fit_dual_mod(dataset, lr=1)
 
w:[ 6.06428531 19.42194872], b:-749.0
 
In [178]:
model.predict(np.array([20,30]))
Out[178]:
-1
In [179]:
model.predict(np.array([50,50]))
Out[179]:
1
相關文章
相關標籤/搜索