感知器及雙月實驗

一、定義

  感知器是模仿人類神經元的用於線性可分分類的最簡單的神經網絡模型。線性可分分類即可以通過一個線性的超平面將數據分開。其基本模型如下圖所示,其中a1,a2,...ana_{1},a_{2},...a_{n}是輸入數據,w1,w2,...,wnw_{1},w_{2},...,w_{n}是權重可以理解爲可分超平面的斜率,bb是偏執保證超平面不過原點,sumsum是簡單的求和操作,ff在感知器中被稱爲激活函數,實際上是一個限幅器,將輸出映射爲對應的類別的值域中。

在這裏插入圖片描述

  感知器的數學模型如下:
f(x)={1 if wx+b>01 else  f(x)=\left\{\begin{array}{ll}{1} & {\text { if } w \cdot x+b>0} \\ {-1} & {\text { else }}\end{array}\right.
  公式中的-1,1只是表示兩個類別不具備任何具體的含義,可以用其他兩個不同的數字代替,其中i=1nwixi+b=0\sum_{i=1}^{n} w_{i} x_{i}+b=0便是感知器學習到的可分超平面。感知器一般使用梯度下降,最小二乘和有感知器學習對權重w\textbf{w}進行迭代來調整超平面。

二、感知器收斂定理

  對於感知器收斂是可證明的下面就給出相應的收斂定理證明,另外,感知器收斂的前提是數據是線性可分的。

  在感知器中一般bb只會選取一個固定的值比如+1,-1作爲偏置,這裏選擇+1,因此可以將bbww合併,因此相應的輸入xxww變成:

x=[+1,x1,x2,...,xn]Tx=[+1, x_{1}, x_{2},...,x_{n}]^{T}

w=[w0,w1,w2,...,wn]Tw=[w_{0}, w_{1}, w_{2},...,w_{n}]^{T}

  其中xxww分別在第一位添加了一個維度,公式變得更加緊蹙,但是計算結果和過程都不會受到影響。原感知器模型就變成:

f(x)=i=1nwixi+bf(x)=i=1nwixi=wTxf(x)=\sum_{i=1}^{n}w_{i}x_{i}+b \Rightarrow f(x)=\sum_{i=1}^{n}w_{i}x_{i}=\textbf{w}^T\textbf{x}

  感知器更新ww的方式很簡單基本就是懲罰措施,對於正確分類的情況ww不做任何更新,對於錯分的情況才更新ww,可以認爲感知器做錯了就打一巴掌,指導他所有都做對了爲止。基本的更新規則如下(第一個公式就是對於正確分類,第二個公式就是對於錯誤分類,D1D_1D2D_2分別爲兩個類別):
wn+1=wnxnwnT>0xnD1wn+1=wnxnwT0xnD2 \begin{array}{ll}{w_{n+1}=w_{n}} & { x_n w_n^{T}>0 且 x_n\in D_1} \\ {w_{n+1}=w_n} & { x_nw^{T} \leqslant 0且x_n \in D_2}\end{array}

wn+1=wnηnxnxnwnT>0xnD2wn+1=wn+ηnxnxnwT0xnD1 \begin{array}{ll}{w_{n+1}=w_{n}-\eta_n x_n} & { x_n w_n^{T}>0 且 x_n\in D_2} \\ {w_{n+1}=w_n+\eta_n x_n} & { x_nw^{T} \leqslant 0且x_n \in D_1}\end{array}

  η\eta表示學習率,通常學習率只會和學習的快慢相關不會和最總的結果有很大的關係。因此我們現在假定學習率η=1\eta=1且爲常量,而且w0=0w_0=0,因此對於wnw_n的迭代可以得到:wn+1=x1+x2+...+xnw_{n+1}=x_1+x_2+...+x_n,因爲兩個類別是線性可分的因此一定存在一個線性超平面得到最終的解,假定最終的解爲wo\textbf{w}_o,因此可以定義一個常數α\alpha,存在

α=minxnD1woTxnwoTwn+1=woTx1+woTx2++woTxnwoTwn+1nαwo2wn+12n2α2wo2wn+12[woTwn+1]2wn+12n2α2wo2\begin{aligned} &\alpha =\min_{x_n \in D_1} \textbf{w}_o^T \textbf{x}_n \\ &\Rightarrow\mathbf{w}_{o}^{T} \mathbf{w}_{n+1}=\mathbf{w}_{o}^{T} \mathbf{x}_1+\mathbf{w}_{o}^{T} \mathbf{x}_2+\cdots+\mathbf{w}_{o}^{T} \mathbf{x}_n \\ &\Rightarrow \mathbf{w}_{o}^{T} \mathbf{w}_{n+1} \geqslant n \alpha \\ &\Rightarrow\left\|\mathbf{w}_{o}\right\|^{2}\|\mathbf{w}_{n+1}\|^{2} \geqslant n^{2} \alpha^{2} 因爲\left\|\mathbf{w}_{o}\right\|^{2}\|\mathbf{w}_{n+1}\|^{2} \geqslant\left[\mathbf{w}_{o}^{T} \mathbf{w}_{n+1}\right]^{2} \\ &\Rightarrow \|\mathbf{w}_{n+1}\|^{2} \geqslant \frac{n^{2} \alpha^{2}}{\left\|\mathbf{w}_{o}\right\|^{2}} \end{aligned}

  這裏證明了wnw_n的下限,另一方面證明其的上限:

wn+1=wn+xnwn+12=wn2+xn2+2wnTxn,wn+12k=1nxk2nβ,β=maxxnD1xn2\begin{aligned} &\boldsymbol{w}_{n+1}=\boldsymbol{w}_n+\mathbf{x}_n \\ &\Rightarrow \|\mathbf{w}_{n+1}\|^{2}=\|\mathbf{w}_n\|^{2}+\|\mathbf{x}_n\|^{2}+2 \mathbf{w}^{\mathrm{T}}_n \mathbf{x}_n, 對上式兩邊同時平方\\ &\Rightarrow \|\mathbf{w}_{n+1}\|^{2} \leqslant \sum_{k=1}^{n}\|\mathbf{x}_k\|^{2} \leqslant n \beta, 其中\beta=\max _{x_n \in \mathbb{D}_{1}}\|\mathbf{x}_n\|^{2} \end{aligned}

  因此有:

n2α2wo2wn+12k=1nxk2nβ,β=maxxnD1xn2,α=minxnD1woTxnnmax2α2wo2=nmaxβnmaxnmax=βWo2α2\begin{aligned} &\frac{n^{2} \alpha^{2}}{\left\|\mathbf{w}_{o}\right\|^{2}} \leqslant \|\mathbf{w}_{n+1}\|^{2} \leqslant \sum_{k=1}^{n}\|\mathbf{x}_k\|^{2} \leqslant n \beta, 其中\beta=\max _{x_n \in \mathbb{D}_{1}}\|\mathbf{x}_n\|^{2}, \alpha =\min_{x_n \in D_1} \textbf{w}_o^T \textbf{x}_n \\ &\Rightarrow \frac{n_{\max }^{2} \alpha^{2}}{\left\|\mathbf{w}_{\mathrm{o}}\right\|^{2}}=n_{\max } \beta \exist n_{max} \\ &\Rightarrow n_{max}=\frac{\beta||\textbf{W}_o||^2}{\alpha^2} \end{aligned}

  因此對於線性可分的數據集,對於η=0,w0=0\eta=0,w_0=0一定能在一定的迭代次數之後終止。而對於η\eta非固定時,感知器總是能夠在一定步數到達固定的η\eta中的某個狀態,也就是將固定η\eta分解爲多個任務,以不同的η\eta進行迭代,但最終的效果是相同的,唯一不同的是需要迭代訓練的次數增加或者減少。

  感知器算法完整描述:

  • 輸入:數據:x=[+1,x1,x2,...,xn]Tx=[+1,x_1,x_2,...,x_n]^T, 權值w=[b,w1,w2,...,wn]Tw=[b,w_1, w_2,...,w_n]^T,實際響應yny_n期望響應:dnd_n,學習率η\eta

    1. 初始化,對輸入數據和權重進行初始化;
    2. 激活,通過輸入xx和期望響應dnd_n激活感知器;
    3. 計算實際響應,yn=sgn(wnTxn)y_n=sgn(w_n^Tx_n),sgnsgn爲符號函數
    4. 更新權值:wn+1=wn+η(dnyn)xnw_{n+1}=w_n+\eta(d_n-y_n)x_n
    5. n=n+1n=n+1轉2直到驗證的準確率達到閾值爲止。

    其中,dn={+1xD11xD2d_n=\left\{\begin{array}{ll}{+1} & {x \in D_1} \\ {-1} & { { x \in D_2 }}\end{array}\right.

  在學習過程需要注意的是,雖然感知器對線性可分模型一定收斂但是在實際應用中,需要慎重選取η\eta,希望穩定的更新就需要比較小的η\eta,可能速度過慢,希望快速更新就需要比較大的η\eta可能會出現更新過快震盪的情況。

三、貝葉斯分類器

1、高斯環境下的感知器與貝葉斯分類器的關係

  貝葉斯分類器對於二分類問題(兩個類別分別爲D1D_1,D2D_2),其平均風險爲:
R=c11p1D1px(xD1)dx+c22p2D2px(xD2)dx+c21p1D1px(xD1)dx+c12p2D2px(xD2)dx\begin{aligned} \mathcal{R}&=c_{11}p_1\int_{D_1}p_x(x|D_1)dx+c_{22}p_2\int_{D_2}p_x(x|D_2)dx\\&+c_{21}p_1\int_{D_1}p_x(x|D_1)dx+c_{12}p_2\int_{D_2}p_x(x|D_2)dx \end{aligned}
  其中:

  • PiP_i標是變量xx取自子空間DiD_i的先驗概率,i=1,2i=1,2p1+p2=1p_1+p_2=1
  • cijc_{ij}將$類識別爲i類的代價,i=1,2$
  • px(xDi)p_x(x|D_i)標是變量xx的條件概率密度函數,i=1,2i=1,2

  令:D=D1+D2D=D_1+D_2,可以將上式改寫爲:
R=c11p1D1px(xD1)dx+c22p2DD1px(xD2)dx+c21p1D1px(xD1)dx+c12p2DD1px(xD2)dx\begin{aligned} \mathcal{R}&=c_{11}p_1\int_{D_1}p_x(x|D_1)dx+c_{22}p_2\int_{D-D_1}p_x(x|D_2)dx\\&+c_{21}p_1\int_{D_1}p_x(x|D_1)dx+c_{12}p_2\int_{D-D_1}p_x(x|D_2)dx \end{aligned}
  又因c11<c21,c22<c12c_{11}<c_{21},c_{22}<c_{12}且有Dpx(xD1)dx=Dpx(xD2)dx=1\int_Dp_x(x|D_1)dx=\int_Dp_x(x|D_2)dx=1
  則上式簡化爲
R=c21p1+c22p2+D1[p2(c12c22)px(xD2)p1(c21c11)p(x)(xD1)]dx \begin{aligned} \mathcal{R} &=c_{21}p1 + c_{22}p2\\ &+\int_{D_1}[p_2(c_{12}-c_{22})p_x(x|D_2)-p_1(c_{21}-c_{11})p(x)(x|D_1)]dx \end{aligned}
  上式中第一項爲固定項,爲了最小化代價應該最小化第二項,因此最優的分類策列是將使得px(xD2)p_x(x|D_2)越小越好,px(xD1)p_x(x|D_1)越大越好,假設條件
p1(c21c11)px(xD1)>p2(c12c22)px(xD2) \begin{aligned} p_1(c_{21} - c_{11})p_x(x|D_1) \gt p_2(c_{12}-c_{22})p_x(x|D_2) \end{aligned}
  定義
Λ(x)=px(xD1)px(xD2)ξ=p2(c12c22)p1(c21c11) \begin{aligned} \Lambda(x)=\frac{p_x(x|D_1)}{p_x(x|D_2)}和\xi=\frac{p_2(c_{12}-c_{22})}{p_1(c_{21}-c_{11})} \end{aligned}
  其中Λ\Lambda是擬然比,ξ\xi是檢驗閾值,二者恆正,則貝葉斯分類器可以表述爲:
{xD1,Λ(x)>ξxD2,Λ(x)ξ \begin{aligned} \left\{\begin{array}{ll} x \in D_1,\Lambda(x)>\xi\\ x \in D_2,\Lambda(x)\le\xi\end{array}\right. \end{aligned}
  很明顯這個分類器和感知器的分類策略很相似,因此貝葉斯分類器和感知器等價。

2、高斯分佈下的貝葉斯分類器

  對於高斯分佈的情況存在下面的情況:
{E[X]=μ1,E[(Xμ1)(Xμ1)T]=CXD1E[X]=μ2,E[(Xμ2)(Xμ2)T]=CXD2 \begin{aligned} \left\{\begin{array}{ll} \mathbb{E}[X]=\mu_1,\mathbb{E}[(X-\mu_1)(X-\mu_1)^T]=C & X \in D_1\\ \mathbb{E}[X]=\mu_2,\mathbb{E}[(X-\mu_2)(X-\mu_2)^T]=C & X\in D_2\end{array}\right. \end{aligned}
  其中C爲協方差,是非對角,即D1D_1D2D_2是相關的,假設C是非奇異的,即逆矩陣C1C^{-1}存在。
  xx的條件概率密度韓式表示爲多變量高斯分佈爲:
px(xD1)=1(2π)m/2(Δ(C))1/2e12(xμ)TC1(xμ1),i=1,2,mx \begin{aligned} p_x(x|D_1)=\frac{1}{(2\pi)^{m/2}(\Delta(C))^{1/2}}e^{-\frac{1}{2}(x-\mu)^TC^{-1}(x-\mu_1)},i=1,2,m爲x的維數 \end{aligned}
  可以進一步假設,數據時均衡的也就是分類成任何一個類的機會是等價的則有,且假設不同的錯分類的代價相同,即:
p1=p2=12c21=c12,c11=c22=0 \begin{aligned} & p_1=p_2=\frac{1}{2} \\ & c_{21}=c_{12},c_{11}=c_{22}=0 \end{aligned}
  則根據貝葉斯分類器中的情況可以得到Λ\Lambdaξ\xi,並對其進行取對數有:
logΛ(x)=12(xμ1)TC1(xμ1)+(xμ2)TC1(xμ2)=(μ1μ2)TC1x+12(μ2TC1μ2μ1TC1μ1)logξ=0 \begin{aligned} \log{\Lambda(x)}&=-\frac{1}{2}(x-\mu_1)^TC^{-1}(x-\mu_1)+(x-\mu_2)^TC^{-1}(x-\mu_2)\\ &=(\mu_1-\mu_2)^TC^{-1}x+\frac{1}{2}(\mu_{2}^TC^{-1}\mu_2-\mu_{1}^TC^{-1}\mu_1)\\ &\log{\xi} = 0 \end{aligned}
  可以看到上述的表達式完全是一個線性的分類器的模型:
y=wTx+bw=(μ1μ2)TC1b=12(μ2TC1μ2μ1TC1μ1) \begin{aligned} & y=\textbf{w}^T\textbf{x}+b \\ & \textbf{w} = (\mu_1-\mu_2)^TC^{-1} \\ & b=\frac{1}{2}(\mu_{2}^TC^{-1}\mu_2-\mu_{1}^TC^{-1}\mu_1) \end{aligned}

3、總結

  需要注意的是雖然高斯環境下的貝葉斯分類器和感知器類似,但是二者不同:

  1. 感知器對數據的要求是線性可分,否則決策邊界將不斷震盪;而高斯環境下的貝葉斯分類器本身就是假設二者有重疊且相關的;
  2. 感知器是最小化分類誤差概率,和最小化分類誤差還是有區別的;
  3. 感知器收斂算法是分參數的;
  4. 感知器實現簡單。

四、實驗

  雙月分類實驗,目的是通過感知器對雙月數據進行分類,實驗分爲兩部分:第一部分爲雙月數據爲線性可分的情況;第二部分爲雙月數據爲線性不可分,非線性可分的情況:

1、線性可分moon

  如下圖爲線性可分的數據:
在這裏插入圖片描述
  下圖爲分類過程的損失和結果代碼(中間的線爲決策邊界,即wx+b=0wx+b=0),可以看到損失函數下降的很快,基本很快就到0了:

  下面的幾張圖爲感知調整的過程,可以看到調整的很快,第一個決策邊界還不能完全擬合,第二個就基本定型了:
  另外需要注意的是,設定的$b$和最後的決策邊界關係很大,不同的$b$會有不同的邊界,$b$不合適可能無法完全擬合,下圖分別爲不同$b$值的決策邊界,三張圖的$b$值分別爲0.3,0.5,1.0:

2、線性不可分moon

  非線性數據:
在這裏插入圖片描述
  分類結果,可以看到孫然損失函數很快收斂但是後面還是在不斷震蕩:

五、附件

  git鏈接:perceptron
  雙月數據生成代碼:


# -*- coding: utf-8 -*-
#生成半月數據

import numpy as np
import matplotlib.pyplot as plt
 
 
def halfmoon(rad, width, d, n_samp): 
    '''生成半月數據
    @param  rad:    半徑
    @param  width:  寬度
    @param  d:      距離
    @param  n_samp: 數量
    ''' 
    if n_samp%2 != 0:  
        n_samp += 1  
    
    data = np.zeros((3,n_samp))
      
    aa = np.random.random((2,int(n_samp/2)))  
    radius = (rad-width/2) + width*aa[0,:] 
    theta = np.pi*aa[1,:]        
      
    x     = radius*np.cos(theta)  
    y     = radius*np.sin(theta)  
    label = np.ones((1,len(x)))         # label for Class 1  
      
    x1    = radius*np.cos(-theta) + rad  
    y1    = radius*np.sin(-theta) - d  
    label1= -1*np.ones((1,len(x1)))     # label for Class 2  
     
    data[0,:]=np.concatenate([x,x1])
    data[1,:]=np.concatenate([y,y1])
    data[2,:]=np.concatenate([label,label1],axis=1)
    
    return data
 
def halfmoon_shuffle(rad, width, d, n_samp): 
     
    data = halfmoon(rad, width, d, n_samp)      
    shuffle_seq = np.random.permutation(np.arange(n_samp))  
    data_shuffle = data[:,shuffle_seq]
    
    return data_shuffle
 
 
if __name__ == "__main__":
    dataNum = 1000
    data = halfmoon(10,5,5,dataNum)
    pos_data = data[:,0: int(dataNum/2)]
    neg_data = data[:, int(dataNum/2):dataNum]
    
    np.savetxt('halfmoon.txt', data.T,fmt='%4f',delimiter=',')
    
    plt.figure()
    plt.scatter(pos_data[0,:],pos_data[1,:],c="b",s=10)
    plt.scatter(neg_data[0,:],neg_data[1,:],c="r",s=10)
    plt.savefig('./imgs/moon.png')
    plt.show()

  感知器分類實驗代碼:

#通過感知機分類半月數據
import numpy as np
import matplotlib.pyplot as plt

def sgn(y):
    y[y > 0] = 1
    y[y < 0] = -1
    return y

class Perceptron(object):
    '''單層感知機
    '''
    def __init__(self, shape):
        super(Perceptron, self).__init__()

        self.w = np.ones(shape)      #weigth
        self.b = 1.5                                 #the bias
        self.activate_func = sgn

    def update(self,x,y,out,learning_rate):
        self.w += learning_rate * x.T * (y - out)

    def calclate(self, x):
        return self.activate_func(np.dot(self.w, x.T) + self.b)

    def loss_func(self, pre_y, gt_y):
        return (pre_y - gt_y) ** 2

    def train(self, x, y, epochs, learning_rate):
        losses = []
        for epoch in range(epochs):
            loss_tmp = []
            for i in range(x.shape[0]):
                out = self.calclate(x[i])
                loss_tmp.append(self.loss_func(out, y[i]))
                self.update(x[i], y[i], out, learning_rate)

            losses.append(sum(loss_tmp)/len(loss_tmp))
        return losses

    def predict(self, x):
        out = self.calclate(x)
        return out
    
    def test(self, x,y):
        label = self.predict(x)
        gt_count = np.sum(label==y)
        wrong_count = np.sum(label!=y)
        return wrong_count/(wrong_count+gt_count),gt_count/(wrong_count+gt_count)


    def get_params(self):
        return {'weight':self.w, 'bias':self.b}

    def draw(self):
        axis = [i for i in range(1000)]
        out = [self.w * i + self.b for i in axis]
        
        plt.plot(axis, out)
        plt.show()

def load_data(file):
    x = []
    y = []
    with open(file, 'r') as f:
        lines = f.readlines()
        for line in lines:
            line = line.strip().split(',')
            
            x_item = [float(line[0]), float(line[1])]
            y_item = float(line[2])
            
            x.append(x_item)
            y.append(y_item)
    
    return np.array(x), np.array(y)


def split_data(x, y):
    train_x, test_x = x[:int(x.shape[0]*0.7)], x[int(x.shape[0]*0.7):]
    train_y, test_y = y[:int(y.shape[0]*0.7)], y[int(y.shape[0]*0.7):]
    
    return train_x, train_y, test_x, test_y

if __name__ == '__main__':
    #進行非線性數據的分類實驗時,只需要將數據的間隔縮小保證二者重合即可
    desc = 'nonlinear'
    file = './halfmoon.txt'
    x,y = load_data(file)

    train_x, train_y, test_x, test_y = split_data(x, y)

    neur = Perceptron((1,2))
    losses = neur.train(train_x,train_y,100, 0.0001)
    
    err,acc = neur.test(test_x, test_y)
    print('rate of error:', err)
    print('rate of accuracy:', acc)


    #畫損失曲線
    axis = [i for i in range(len(losses))]
    plt.figure()
    plt.plot(axis, losses)
    plt.savefig('../imgs/%s_mse_loss.png' % desc)
    #plt.show()

    #畫決策面   
    x_aixs = x[:,0]
    y_aixs = x[:,1]

    neg_x_axis = x_aixs[y==-1]
    neg_y_axis = y_aixs[y==-1]
    
    pos_x_axis = x_aixs[y==1]
    pos_y_axis = y_aixs[y==1]

    #感知機的參數
    params = neur.get_params()
    w = params['weight']
    b = params['bias']

    k = -1 * w[0][0] / w[0][1]
    b = -1 * b / w[0][1]

    divid_x = [i for i in range(-15,25)]
    divid_y = [k * i + b for i in divid_x]

    plt.figure()
    plt.plot(divid_x, divid_y, c='r')
    plt.scatter(neg_x_axis,neg_y_axis,c="b",s=10)
    plt.scatter(pos_x_axis,pos_y_axis,c="g",s=10)
    plt.savefig('../imgs/%s_divide.png' % desc)   #保存決策面
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章