一、定義
感知器是模仿人類神經元的用於線性可分分類的最簡單的神經網絡模型。線性可分分類即可以通過一個線性的超平面將數據分開。其基本模型如下圖所示,其中是輸入數據,是權重可以理解爲可分超平面的斜率,是偏執保證超平面不過原點,是簡單的求和操作,在感知器中被稱爲激活函數,實際上是一個限幅器,將輸出映射爲對應的類別的值域中。
感知器的數學模型如下:
公式中的-1,1只是表示兩個類別不具備任何具體的含義,可以用其他兩個不同的數字代替,其中便是感知器學習到的可分超平面。感知器一般使用梯度下降,最小二乘和有感知器學習對權重進行迭代來調整超平面。
二、感知器收斂定理
對於感知器收斂是可證明的下面就給出相應的收斂定理證明,另外,感知器收斂的前提是數據是線性可分的。
在感知器中一般只會選取一個固定的值比如+1,-1作爲偏置,這裏選擇+1,因此可以將和合併,因此相應的輸入和變成:
其中和分別在第一位添加了一個維度,公式變得更加緊蹙,但是計算結果和過程都不會受到影響。原感知器模型就變成:
感知器更新的方式很簡單基本就是懲罰措施,對於正確分類的情況不做任何更新,對於錯分的情況才更新,可以認爲感知器做錯了就打一巴掌,指導他所有都做對了爲止。基本的更新規則如下(第一個公式就是對於正確分類,第二個公式就是對於錯誤分類,和分別爲兩個類別):
表示學習率,通常學習率只會和學習的快慢相關不會和最總的結果有很大的關係。因此我們現在假定學習率且爲常量,而且,因此對於的迭代可以得到:,因爲兩個類別是線性可分的因此一定存在一個線性超平面得到最終的解,假定最終的解爲,因此可以定義一個常數,存在
這裏證明了的下限,另一方面證明其的上限:
因此有:
因此對於線性可分的數據集,對於一定能在一定的迭代次數之後終止。而對於非固定時,感知器總是能夠在一定步數到達固定的中的某個狀態,也就是將固定分解爲多個任務,以不同的進行迭代,但最終的效果是相同的,唯一不同的是需要迭代訓練的次數增加或者減少。
感知器算法完整描述:
-
輸入:數據:, 權值,實際響應期望響應:,學習率
-
- 初始化,對輸入數據和權重進行初始化;
- 激活,通過輸入和期望響應激活感知器;
- 計算實際響應,,爲符號函數
- 更新權值:
- 轉2直到驗證的準確率達到閾值爲止。
其中,
在學習過程需要注意的是,雖然感知器對線性可分模型一定收斂但是在實際應用中,需要慎重選取,希望穩定的更新就需要比較小的,可能速度過慢,希望快速更新就需要比較大的可能會出現更新過快震盪的情況。
三、貝葉斯分類器
1、高斯環境下的感知器與貝葉斯分類器的關係
貝葉斯分類器對於二分類問題(兩個類別分別爲,),其平均風險爲:
其中:
- 標是變量取自子空間的先驗概率,且
- 將$ii=1,2$
- 標是變量的條件概率密度函數,
令:,可以將上式改寫爲:
又因且有
則上式簡化爲
上式中第一項爲固定項,爲了最小化代價應該最小化第二項,因此最優的分類策列是將使得越小越好,越大越好,假設條件
定義
其中是擬然比,是檢驗閾值,二者恆正,則貝葉斯分類器可以表述爲:
很明顯這個分類器和感知器的分類策略很相似,因此貝葉斯分類器和感知器等價。
2、高斯分佈下的貝葉斯分類器
對於高斯分佈的情況存在下面的情況:
其中C爲協方差,是非對角,即是相關的,假設C是非奇異的,即逆矩陣存在。
的條件概率密度韓式表示爲多變量高斯分佈爲:
可以進一步假設,數據時均衡的也就是分類成任何一個類的機會是等價的則有,且假設不同的錯分類的代價相同,即:
則根據貝葉斯分類器中的情況可以得到和,並對其進行取對數有:
可以看到上述的表達式完全是一個線性的分類器的模型:
3、總結
需要注意的是雖然高斯環境下的貝葉斯分類器和感知器類似,但是二者不同:
- 感知器對數據的要求是線性可分,否則決策邊界將不斷震盪;而高斯環境下的貝葉斯分類器本身就是假設二者有重疊且相關的;
- 感知器是最小化分類誤差概率,和最小化分類誤差還是有區別的;
- 感知器收斂算法是分參數的;
- 感知器實現簡單。
四、實驗
雙月分類實驗,目的是通過感知器對雙月數據進行分類,實驗分爲兩部分:第一部分爲雙月數據爲線性可分的情況;第二部分爲雙月數據爲線性不可分,非線性可分的情況:
1、線性可分moon
如下圖爲線性可分的數據:
下圖爲分類過程的損失和結果代碼(中間的線爲決策邊界,即),可以看到損失函數下降的很快,基本很快就到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) #保存決策面