統計學習方法 學習筆記(二):感知機
在這一篇筆記中,來了解一下感知機模型(perceptron),我們將要了解感知機的模型,感知機的學習策略和感知機的學習算法,並且使用Python編程來實現書中的例子。
感知機:
感知機是神經網絡與支持向量機的基礎,是二類分類的線性分類模型,其輸入為實列的特征向量,輸出為實列的類別,取+1和-1二值(這裏為啥取+1和-1呢?為啥不取+1和0呢?下面會解釋,稍安勿躁)。感知機對應於特征空間中將數據進行線性劃分的分離超平面,屬於判別模型。感知機學習旨在求出將訓練數據進行線性劃分的分離超平面,為此,導入基於誤分類的損失函數,利用梯度下降法對損失函數進行極小化,求得感知機模型。
感知機模型:
定義:假設輸入空間(特征空間)是$\mathcal{X}\subseteq R^n$,輸出空間是$\mathcal{Y} = \{+1,-1\}$。輸入$x\in\mathcal{X}$表示實列的特征向量,對應於輸入空間(特征空間)的點;輸出$y\in\mathcal{Y}$表示實例的類別。由輸入空間到輸出空間的如下函數:
$$f(x) = sign(w\cdot x + b)$$
稱為感知機。其中,$w$和$b$為感知機模型參數,$w\in R^n$叫作權值(weight)或權值向量(weight vector),$b\in R$叫做偏置(bias),$w\cdot x$表示$w$和$x$的內積。$sign$是符號函數,即
$$sign(x) = \left\{\begin{matrix}
+1, & x\geqslant 0\\
-1, & x<0
\end{matrix}\right.$$
感知機有如下幾何解釋:線性方程
$$w\cdot x + b = 0$$
對應於特征空間$R^n$中的一個超平面$S$,其中$w$是超平面的法向量,$b$是超平面的截距。這個超平面將特征空間劃分為兩個部分。位於兩部分的點(特征向量)分別被分為正、負兩類。因此,超平面$S$稱為分離超平面:如圖,
由中學知識得知:點到直線的距離為:$d = \frac{Ax_0 + By_0 + C}{\sqrt{A^2 + B^2}}$,所以,坐標原點到$w\cdot x + b = 0$的距離為$\frac{|b|}{||w||}$,圖中直線可以寫為:$w^{(1)}x^{(1)}+ w^{(2)}x^{(2)} + b = 0$,所以:$x^{(2)} = -\frac{w^{(1)}}{w^{(2)}}x^{(1)} - b$,由於圖中的直線與$x^{(2)}$的正軸相交,所以:$-b>0$
感知機學習策略:
首先介紹一下數據集的線性可分性定義(為啥要特別介紹這個呢?因為感知機模型處理的數據集只能是線性可分的,這是感知機模型的弊端,那如何解決這個弊端呢?在本篇中不作介紹,請參考支持向量機):
數據集的線性可分性:給定一個數據集
$$T = \{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\}$$
其中,$x_i \in \mathcal{X} = R^n, y_i \in \mathcal{Y} = {+1,-1},i=1,2,...,N$。如果存在某個超平面$S$
$$w\cdot x + b = 0$$
能夠將數據集的正實例點和負實例點完全正確地劃分到超平面的兩側,即對所有的$y_i = +1$的實例$i$,有$w\cdot x_i + b >0$,對所有$y_i = -1$的實例$i$,有$w\cdot x_i + b<0$,則稱數據集$T$為線性可分數據集;否則,稱數據集$T$線性不可分。
損失函數:損失函數的一個自然選擇是誤分類點的總數。但是,這樣的損失函數不是參數$w,b$的連續可導函數,不易優化。損失函數的另一個選擇是誤分類點到超平面$S$的總距離,這是感知機所采用的損失函數。為此,輸入空間$R^n$中任一點$x_0$到超平面$S$的距離:
$$\frac{1}{||w||}|w\cdot x_0 + b|$$
這裏,$||w||$是$w$的$L_2$範數,$L_p$範數公式為:
$$||x|| = (\sum_{i}|x_i|^{p})^{\frac{1}{p}}$$
我們把$||w||_2$簡寫為$||w||$。在這裏補充一點:
一個非零向量除以它的模的結果是一個與原向量同方向的單位向量:$\frac{\vec{a}}{|\vec{a}|} = \vec{e_a}$
如上述圖:
$$x_0 = x_1 + s\cdot \frac{w}{||w||}$$
求得,點$x_0$到直線的距離$s$為:
$$s = \frac{1}{||w||}|w\cdot x_0 + b|$$
對於誤分類的數據$(x_i,y_i)$來說,$-y_i(w\cdot x_i + b) > 0$成立,因為當$w\cdot x_i + b > 0$時,$y_i = -1$,因此,誤分類點$x_i$到超平面$S$的距離是:
$$-\frac{1}{||w||}y_i(w\cdot x_i + b)$$
這樣,假設超平面$S$的誤分類點集合為$M$,那麽所有誤分類點到超平面$S$的總距離為(看到這裏,就能明白為啥標簽取+1和-1了吧,為了能夠得到下面這個統一優美的損失函數):
$$-\frac{1}{||w||}\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
不考慮$\frac{1}{||w||}$,就得到感知機學習的損失函數為:
$$L(w,b) = -\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
這個損失函數就是感知機學習的經驗風險函數。如果沒有誤分類點,損失函數值是0
感知機學習算法:
感知機學習問題轉化為求解損失函數的最優化問題,最優化的方法是隨機梯度下降法.
感知機學習算法的原始形式:
感知機學習算法的目標函數為:
$$\min_{w,b}L(w,b) = -\sum_{x_i\in M}y_i(w\cdot x_i + b)$$
其中$M$為誤分類點的集合。感知機學習算法是誤分類驅動的,具體采用隨機梯度下降法。首先,任意選取一個超平面$w_0,b_0$,然後用梯度下降法不斷地極小化目標函數。極小化過程中不是一次使$M$中所有誤分類點的梯度下降,而是一次隨機選取一個誤分類點使其梯度下降。
假設誤分類點集合$M$是固定的,那麽損失函數$L(w,b)$的梯度由:
$$\bigtriangledown _wL(w,b) = - \sum_{x_i\in M}y_ix_i\\\bigtriangledown _bL(w,b) = - \sum_{x_i\in M}y_i$$
給出。
隨機選取一個誤分類點$(x_i,y_i)$,對$w,b$進行更新:
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
式中$\eta(0<\eta\leq 1)$是步長,又稱為學習率。這樣,通過叠代可以期待損失函數$L(w,b)$不斷減小,直到為0。
所以,感知機學習算法的原始形式:
輸入:訓練數據集$T = {(x_1,y_1),(x_2,y_2),...,(x_N,y_N)}$,其中$x_i\in\mathcal{X} = R^n,y_i \in \mathcal{Y}=\{-1,+1\},i=1,2,...,N$;學習率$\eta(0<\eta\leq 1)$;
輸出: $w,b$;感知機模型$f(x)=sign(w\cdot x + b)$
(1)選取初值$w_0,b_0$
(2)在訓練集中選取數據$(x_i,y_i)$
(3)如果$y_i(w\cdot x_i + b)\leq 0$
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
(4)轉至(2),直至訓練集中沒有誤分類點。
感知機學習算法的對偶形式:
感知機學習算法的原始形式和對偶形式與支持向量機學習算法的原始形式和對偶形式想對應。對偶形式的基本想法是,將$w$和$b$表示為實例$x_i$和標記$y_i$的線性組合的形式,通過求解其系數而求得$w$和$b$。對誤分類點$(x_i,y_i)$通過
$$w\leftarrow w + \eta y_ix_i\\b\leftarrow b + \eta y_i$$
逐步修改$w,b$,設修改$n$次,則$w,b$關於$(x_i,y_i)$的增量分別是$\alpha_iy_ix_i$和$\alpha_iy_i$,這裏,$\alpha_i = n_i\eta$。這樣,最後學習到的$w,b$可以分別表示為
$$w = \sum_{i=1}^{N}\alpha_iy_ix_i\\b = \sum_{i=1}^{N}\alpha_iy_i$$
這裏,$\alpha_i\geq 0,i=1,2,...,N$,當$\eta = 1$時,表示第$i$個實例點由於誤分而進行更新的次數。實例點更新次數越多,意味著它距離分離超平面越近,也就越難正確分類。
感知機學習算法的對偶形式:
輸入:訓練數據集$T = {(x_1,y_1),(x_2,y_2),...,(x_N,y_N)}$,其中$x_i\in\mathcal{X} = R^n,y_i \in \mathcal{Y}=\{-1,+1\},i=1,2,...,N$;學習率$\eta(0<\eta\leq 1)$;
輸出:$\alpha,b$;感知機模型$f(x) = sign(\sum_{j=1}^{N}\alpha_{i}y_jx_j\cdot x + b)$,其中$\alpha = (\alpha_1,\alpha_2,...,\alpha_N)^T$
(1)$\alpha \leftarrow 0,b\leftarrow 0$
(2)在訓練集中選取數據$(x_i,y_i)$
(3)如果$y_i(\sum_{j=1}^{N}\alpha_jy_jx_j\cdot x_i + b) \leq 0$
$$\alpha_i \leftarrow \alpha_i + \eta\\b \leftarrow b + \eta y_i$$
(4)轉至(2)直到沒有誤分類數據。
對偶形式中訓練實例僅以內積的形式出現,為了方便,可以預先將訓練集中實例間的內積計算出來並以矩陣的形式存儲,這個矩陣就是所謂的Gram矩陣。
*******************************************哈哈我是分割線****************************************
感知機學習算法原始形式python代碼示例:
我通過下面五個階段來介紹代碼:
(1)收集數據:提供文本文件
(2)準備數據:使用python解析文本文件
(3)分析數據:使用Matplotlib畫出二維散點圖
(4)訓練算法:通過學習算法學習到參數
(5)觀察結果:使用Matplotlib畫出二維散點圖及學到的模型
1、收集數據:使用書中例2.1所示的訓練數據集:構造一個文件名為“train.txt”文本文件,數據之間使用一個tab鍵來隔開,一個訓練樣本作為一行,第三列是標簽,看下圖所示:
2、準備數據:通過解析“train.txt”文件,將訓練樣本的信息放入到數組train_feature中,將訓練樣本的標簽放入到列表train_label中(和train_feature按順序對應)
def file2array(filename): with open(filename) as f: lines = f.readlines() # lines is list type, every element in lines is a str type num_rows = len(lines) num_columns = len(lines[0].strip().split()) train_feature = np.zeros((num_rows,num_columns-1)) # every train sample have 2 dimensions train_label = [] index = 0 for line in lines: line = line.strip().split() # for every line, first remove Head and tail spaces and newline characters(‘\n‘), then use ‘\t‘ split to get a list, train_feature[index,:] = line[0:2] train_label.append(int(line[-1])) # every element in line is a str type, so, use int() index += 1 return train_feature,train_label
3、分析數據:畫散點圖來觀察訓練樣本分布,紅點表示正樣本
def plot_scatter(train_feature,train_label): color_list = [] for label in train_label: if label == 1: color_list.append(‘r‘) else: color_list.append(‘k‘) plt.scatter(train_feature[:,0],train_feature[:,1],15.0,color_list,‘o‘) plt.axis([0, 6, 0, 6]) plt.show()
4、訓練算法:通過下面代碼,學習到參數$w$和$b$
def algorithm(feature,label,learn_rate): rows,columns = feature.shape w = np.zeros(columns) b = 0 #pdb.set_trace() while(1): count = 0 for i in range(0,rows): if label[i] * (np.dot(feature[i,:],w)+b)<= 0: w = w + learn_rate*label[i]*feature[i,:] b = b + learn_rate*label[i] print(w,b) else: count += 1 if count == len(feature): return w,b
5、觀察結果:畫出訓練樣本散點圖和學到的參數組成的直線
def draw_result(w,b,train_feature,train_label): x = np.arange(-10,10) y = - w[0]/w[1]*x - b color_list = [] for label in train_label: if label == 1: color_list.append(‘r‘) else: color_list.append(‘k‘) plt.plot(x,y) plt.scatter(train_feature[:,0],train_feature[:,1],15.0,color_list,‘o‘) plt.axis([0, 6, 0, 6]) plt.show()
6、運行主程序
import numpy as np import matplotlib.pyplot as plt import argparse def main(filename): ‘‘‘ (1): 加載訓練數據 (2):分析數據 (3):學習參數 (4): 畫出結果 ‘‘‘ train_feature,train_label = file2array(filename) plot_scatter(train_feature,train_label) w,b = algorithm(train_feature,train_label,1) print(w,b) draw_result(w,b,train_feature,train_label) if __name__ == ‘__main__‘: parser = argparse.ArgumentParser(‘load filename‘) parser.add_argument("--filename",default=‘train.txt‘) args = parser.parse_args() main(args.filename)
感知機學習算法對偶形式python代碼示例:
和原始形式的代碼比較有以下幾點不同:
(1)新添加了一個求gram矩陣的代碼
(2)修改了訓練算法
(3)修改了運行主程序
1、求gram矩陣:
def gram_matrix(feature): rows,_ = feature.shape gram = np.zeros((rows,rows)) for i in range(0,rows): for j in range(0,rows): gram[i,j] = np.dot(feature[i],feature[j]) return gram
2、訓練算法
def algorithm(feature,label,learn_rate,gram): rows,columns = feature.shape alpha = np.zeros(rows) b = 0 #pdb.set_trace() while(1): count = 0 for i in range(0,rows): sum = 0 for j in range(0,rows): sum+=alpha[j]*label[j]*gram[i,j] if label[i] * (sum+b)<= 0: alpha[i]= alpha[i] + learn_rate b = b + learn_rate*label[i] print(alpha,b) else: count += 1 if count == len(feature): return alpha,b
3、運行主程序
import numpy as np import matplotlib.pyplot as plt import argparse def main(filename,learn_rate): ‘‘‘ (1): 加載訓練數據 (2):分析數據 (3):學習參數 (4): 畫出結果 ‘‘‘ train_feature,train_label = file2array(filename) plot_scatter(train_feature,train_label) gram = gram_matrix(train_feature) alpha,b = algorithm(train_feature,train_label,learn_rate,gram) rows,colums = train_feature.shape w = np.zeros(colums) for i in range(0,rows): w+=alpha[i]*train_label[i]*train_feature[i,:] print(w,b) draw_result(w,b,train_feature,train_label) if __name__ == ‘__main__‘: parser = argparse.ArgumentParser(‘load filename‘) parser.add_argument("--filename",default=‘train.txt‘) parser.add_argument("--learn_rate",default=1) args = parser.parse_args() main(args.filename,args.learn_rate)
統計學習方法 學習筆記(二):感知機