感知机(perceptron)是二分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取+1和-1二值。感知机旨在求出将训练数据进行线性划分的分离超平面,因此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。感知机的基本原理就是逐点修正,首先在超平面上随意取一条分类面,统计分类错误的点;然后随机对某个错误点就行修正,即变换直线的位置,使该错误点得以修正;接着再随机选择一个错误点进行纠正,分类面不断变化,直到所有的点都完全分类正确了,就得到了最佳的分类面。
1、感知机学习策略
为了找到这样的超平面,即确定感知机模型参数w,b,需要定义(经验)损失函数并将损失函数极小化。
首先在输入空间中任一点x0到超平面S的距离为:
对于模型而言,在分类错误的情况下,若,则实际的yi应该等于-1,而当时,yi等于1,因此由这个特性我们可以去掉上面的绝对值符号,将公式转化为
因此,误分类点xi到超平面S的距离为
由此得到所有误分类点到超平面S的总距离为
基于此,当给定训练数据集时,感知机学习的损失函数定义为
优化策略选择随机梯度下降法。
示例代码
import numpy as np
import matplotlib.pyplot as plt
import random as rd
# generate train data
class GenerateData:
def __init__(self, num_data, w_t, b_t, ratio):
self.numData = num_data
self.w_t = w_t
self.b_t = b_t
def generateData(self):
# generate pos and neg samples
x_t = np.array(range(self.numData))
y_t = x_t * self.w_t + self.b_t
rd.seed(10)
y_pos = []
y_neg = []
for y_i in y_t:
limit = rd.randint(10, max(y_t))
rand_pos = rd.randint(1, limit)
y_pos.append(y_i + rand_pos)
rand_neg = rd.randint(1, limit)
y_neg.append(y_i - rand_neg)
x = list(x_t) * 2
y = y_pos + y_neg
label = [1] * len(y_pos) + [-1] * len(y_neg)
data = []
for i in range(len(x)):
data.append([x[i], y[i]])
# shuffle the train data
tempData = list(zip(data, label))
rd.shuffle(tempData)
data, label = zip(*tempData)
data = np.array(list(data))
label = np.array(list(label))
train_data, train_label, test_data, test_label = self.splitData(data, label)
return train_data, train_label, test_data, test_label, x_t, y_t, y_pos, y_neg
def splitData(self, data, label):
numData = len(data)
# split train data and test data 8:2
train_data = data[:round(numData*0.8), :]
test_data = data[round(numData*0.8):, :]
train_label = label[:round(numData*0.8)]
test_label = label[round(numData*0.8):]
return train_data, train_label, test_data, test_label
class PLA:
def __init__(self, train_data = [], real_result = [], eta = 1):
self.train_data = train_data
self.real_result = real_result
self.w = np.zeros([1, len(train_data.T)], int)
self.b = 0
self.eta = eta
def sign(self, x):
if x > 0:
return 1
else:
return -1
def model(self, x):
y = np.dot(x, self.w.T) + self.b
y_pred = self.sign(y) # predicted output
return y_pred, y
# compute loss
def loss(self, fx, y):
return int(fx) * y
# update weight and bias
def update(self, x, y):
self.w = self.w + self.eta * y * x
self.b = self.b + self.eta * y
def train(self, count):
update_count = 0
num_data = len(self.train_data)
while count > 0:
count = count -1
if num_data < 0:
print('Exception exit')
break
# random pick train samples
index = rd.randint(0, num_data - 1)
x = self.train_data[index]
y = self.real_result[index]
pred_y, linear_y = self.model(x)
loss_v = self.loss(y, linear_y)
if loss_v > 0:
continue
update_count = update_count + 1
self.update(x, y)
print('loss = {}'.format(loss_v))
print("update count: ", update_count)
def verify(self, verify_data, verify_result):
size = len(verify_data)
failed_count = 0
if size <= 0:
pass
for i in range(size):
x = verify_data[i]
y = verify_result.T[i]
if self.loss(y, self.model(x)[1]) > 0:
continue
failed_count = failed_count + 1
success_rate = (1.0 - (float(failed_count)/size))*100
print("Success Rate: ", success_rate, "%")
print("All input: ", size, " failed_count: ", failed_count)
def predict(self, predict_data):
size = len(predict_data)
result = []
if size <= 0:
pass
for i in range(size):
x = verify_data[i]
y = verify_result.T[i]
result.append(self.model(x)[0])
return result
if __name__ == '__main__':
num_data = 300 # number of samples
w_t = 2
b_t = 5
ratio = 0.8 # train:test=4:1
generater = GenerateData(num_data, w_t, b_t, ratio)
train_data, train_label, test_data, test_label, x_t, y_t, y_pos, y_neg = generater.generateData()
# train
num_iter = 5000 # number of iterations
perceptron = PLA(train_data, train_label)
perceptron.train(num_iter)
# test
perceptron.verify(test_data, test_label)
print("T1: w:", perceptron.w," b:", perceptron.b)
# 1. draw the (x,y) points
plt.figure(figsize = (10, 8))
plt.scatter(x_t, y_pos, c='y')
plt.scatter(x_t, y_neg, c='b')
# 2. draw y=gradient*x+offset line
plt.plot(x_t, y_t, color="g")
# 3. draw the line w_1*x_1 + w_2*x_2 + b = 0
plt.plot(x_t, -(np.array(x_t).dot(float(perceptron.w.T[0]))+float(perceptron.b))/float(perceptron.w.T[1]),
color='r')
plt.show()
得到的结果如下:
测试准确率为98.3%
代码参考自: https://blog.csdn.net/yxhlfx/article/details/79093456
2、感知机学习算法的对偶形式
对偶形式的基本想法是,将w和b表示为实例xi和标记yi的线性组合的形式,通过求解其系数而求得w和b,不是一般性,在算法2.1中可假设初始值w0,b0均为0,对误分类点(xi,yi)通过
逐步修改w,b,设修改n次,则w,b关于(xi,yi)的增量分别是,这里。因此,最后学习到的w,b可以分别表示为
经过如上变换后,每次训练迭代更新就需要更新α与b值。更新策略改变为如下:
同时观察模型可以发现xi与xj以内积的形式出现,通过这个热证可以想到Gram矩阵的定义
而在对偶形式当中,α与损失函数进行梯度下降的次数是紧密联系的,随着α的不断增加,损失函数执行梯度下降的次数不断增加,模型也越接近真实情况。
感知机的限制与推广
限制:感知机的限制主要是只能处理线性的情况,不能解决线性不可分的问题。
推广:可以应用于多维空间的多分类问题
示例:应用感知机解决MNIST分类问题