前幾天認把感知機這一章讀完了,順帶做了點筆記
現在把筆記做第三次的整理
(不得不說博客園的LaTex公式和markdown排版真的不太舒服,該考慮在服務器上建一個博客了)
零、總結
- 適用於具有線性可分的數據集的二分類問題,可以說是很局限了
- 感知機本質上是一個分離超平面
- 在向量維數(特征數)過高時,選擇對偶形式算法
在向量個數(樣本數)過多時,應選擇原始算法 - 批量梯度下降和隨機梯度下降的區別和優勢
參考鏈接:隨機梯度下降(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}$
即多次做單個樣本的參數更新
缺點:訓練耗時較短
優點:不一定趨向全局最優(往往是最優/較優,單峰問題除外),受數據噪音影響大
一、模型
輸入空間 $ \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) $
其中
符號函數為
線性方程
$ \omega \cdot x + b $
可以表示為特征空間 $ R^n $中的一個分離超平面
二、策略
(定義的損失函數,並極小化損失函數)
(注意損失函數非負的性質)
為了使損失函數更容易優化,我們選擇誤分類點到超平面的距離作為損失函數
任意向量\(x \in R^n\)距分離超平面的距離為
$ S=\frac{1}{|\omega|}|\omega \cdot x + b| $
接下來優化一下這個距離,讓它更好的成為一個損失函數
- 為了連續可導,去絕對值
$ S=-\frac{1}{|\omega|} y_i(\omega \cdot x + b) $ - 去掉不相關的系數(避免浪費計算),得到
$ L(\omega, b)=-\sum_{x_i \in M} y_i(\omega \cdot x + b) \( 其中\) M $為誤分類點集合
三、算法
(如何實現最優化問題)
注意最終訓練出的模型參數的值取決於初值和誤分類點的選取,所以一般值不同
為了極小化損失函數,我們采用梯度下降的方法
- 原始形式算法
- 賦初值 $ \omega \leftarrow 0 , b \leftarrow 0 $
- 選取數據點 $ (x_i, y_i) $
- 判斷該數據點是否為當前模型的誤分類點,即判斷若$ y_i(\omega \cdot x + b) <=0 $
則更新
- 對偶形式算法
注意到原始形式算法中,最終訓練好的模型參數是這樣的,其中$ n_i $表示在第i個數據點上更新過幾次
於是我們可以作出以下簡化
- 賦初值 $ n \leftarrow 0, b \leftarrow 0 $
- 選取數據點 $ (x_i, y_i) $
- 判斷該數據點是否為當前模型的誤分類點,即判斷若$ y_i(\eta \sum n_iy_ix_i \cdot x + b) <=0 $
則更新
為了減少計算量,我們可以預先計算式中的內積,得到Gram矩陣
$ G=[x_i, x_j]_{N \times N} \( 3. **原始形式和對偶形式的選擇** 相見知乎[如何理解感知機學習算法的對偶形式?](https://www.zhihu.com/question/26526858) 在向量維數(特征數)過高時,計算內積非常耗時,應選擇對偶形式算法加速 在向量個數(樣本數)過多時,每次計算累計和(對偶形式中的\)\omega$)就沒有必要,應選擇原始算法
四、代碼實現
因為感知機對數據要求很嚴格,為了實現這個模型,我用到了iris的數據集,用來給鳶尾花分類
又因為感知機只能做二分類,所以還是要把原數據的兩個類別合並
為了學習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()
更新了代碼實現部分