统计学习——感知机
一、什么是感知机
感知机1957年由Rosenblatt提出,是神经网络的起源算法。
什么是感知机呢?对神经网络来说,感知机就是最基础,最普遍的一种逻辑模型,为什么说是最基础的逻辑模型呢,就是得解决网络中基本逻辑,就比如说与或这种,感知机刚好就能解决与或这样基本问题,但唯独不能解决异或问题,也算是很大缺陷吧。用书上话来讲感知机(perceptron)是一种解决二分类的线性分类模型,将特征空间的实例特征向量划分为正负两类,本质是一种分离超平面,是一种判别模型。
二、感知机详解
1.函数模型
假设输入空间(特征空间) 是\(x\subseteq R^n\),输出空间是\(y=\{+1,-1\}\).由输入空间到输出空间的如下函数:
其中,\(w\)和\(b\)为感知机模型参数,\(w\in R^n\)叫作权值(weight)或权值向量(weight vector),\(b\in R\)叫作偏置(bias),\(w\cdot x\)表示\(w\)和\(x\)的内积。\(sign\)是符号函数,即
感知机模型的假设空间是定义在特征空间中的所有线性分类模型(linear classificatio model)或线性分类器(linear classifier),即函数集合\(\{f|f(x) = w \cdot x+b\}\).
2.几何解释
感知机有如下几何解释:线性方程
对应于特征空间\(R^n\)中的一个超平面\(S\),其中\(w\)是超平面的法向量,\(b\)是超平面的截距。这个超平面将特征空间划分为两个部分。位于两部分的点(特征向量)分别被分为正、负两类。因此,超平面\(S\)称为分离超平面(separating hyperplane).
感知机学习,由训练数据集(实例的特征向量及类别)
感知机预测,通过学习得到的感知机模型,对于新的输入实例给出其对应的输出类别。
3.感知机学习策略
3.1数据集的线性可分性
定义:给定一个数据集\(T = \{(x_1,y_1),(x_2,y_2),···,(x_n,y_n)\}\),如果存在某个超平面\(S\)
能够将数据集的正实例点和负实例点完全正确地划分到超平面的两侧,即对所有\(y_i = +1\)的实例\(i\),有\(w \cdot x_i +b \geq 0\),对所有\(y_i = -1\)的实例\(i\),有\(w \cdot x_i +b \lt 0\),则称数据集\(T\)为线性可分数据集(linear separab data set);否则,为线性不可分。
3.2感知机学习策略
假设训练数据集是线性可分的,感知机学习的目标是求得一个能够将训练集正实例点和负实例点完全正确分开的分离超平面。为了找出这样的超平面,即确定感知机模型参数w,b,需要确定一个学习策略,即定义(经验)损失函数并将损失函数极小化。
首先损失函数容易想到采用误分类点的数量作为损失函数,但是是离散的,\(w,b\)不是连续可导的,所以我们采取误分类点到超平面的总距离作为损失函数,我们希望总距离越小越好,当损失函数为\(0\)时,不存在误分类点。
3.2.1损失函数
首先引入输入空间\(R^n\)中任一点\(x_i\)到超平面\(S\)的距离:
这里,\(||w||\)是\(w\)的\(L_2\)范数。
对于误分类的数据\((x_i,y_i)\)来说,
显然成立。
因此,一个误分类点到超平面\(S\)的距离为:
假设误分类点集合为\(M\),那么总距离为:
最后,不考虑\(\frac{1}{||w||}\),求得最终的感知机损失函数即经验风险函数:
思考:为什么不考虑\(\frac{1}{||w||}?\)
1.\(\frac{1}{||w||}\)不影响正负判断。
2.感知机算法的终止条件是所有输入输出都被正确分类,即不存在误分类点,最终损失为0,让分子为0即可。
4.感知机学习算法
采用随机梯度下降(SGD),目标函数如下:
4.1原始形式算法
求函数极小化问题的解,在随机梯度下降过程中,每一次仅用一个误分类点样本来使其梯度下降。
首先,求解梯度,分别对\(w,b\)求偏导:
然后,随机选取一个误分类点对\(w,b\)进行更新:
其中,\(\eta\) 为学习率,通过这样迭代使损失函数\(L(w,b)\)不断减小,直至最小化。
算法步骤
输入:训练数据\(T = \{(x_1,y_1),(x_2,y_2),···,(x_n,y_n)\},y_i \in \{-1,+1\}\),学习率\(\eta(0<\eta <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)<0\),则更新
(4) 转到(2),直到训练集中没有误分类点
这种学习算法直观上有如下解释:当一个实例点被误分类,即位于分离超平面的错误一侧时,则调整\(w,b\)的值,使分离超平面向该误分类点的一侧移动,以减少该误分类点与超平面间的距离,直至超平面越过该误分类点使其被正确分类。
代码实战:
拿出iris数据集中两个数据[sepal length,sepal width]作为输入特征,进行分类:
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
iris = load_iris()
df = pd.DataFrame(iris.data,columns=iris.feature_names)
df['label'] = iris.target
df.columns = [
'sepal length', 'sepal width', 'petal length', 'petal width', 'label'
]
df.label.value_counts()
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
data = np.array(df.iloc[:100, [0, 1, -1]])
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model:
def __init__(self):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32)
self.b = 0
self.l_rate = 0.1
# self.data = data
def sign(self, x, w, b):
y = np.dot(x, w) + b
return y
# 随机梯度下降法
def fit(self, X_train, y_train):
is_wrong = False
while not is_wrong:
wrong_count = 0
for d in range(len(X_train)):
X = X_train[d]
y = y_train[d]
if y * self.sign(X, self.w, self.b) <= 0:
self.w = self.w + self.l_rate * np.dot(y, X)
self.b = self.b + self.l_rate * y
wrong_count += 1
if wrong_count == 0:
is_wrong = True
return 'Perceptron Model!'
perceptron = Model()
perceptron.fit(X, y)
x_points = np.linspace(4, 7, 10)
y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1]
plt.plot(x_points, y_)
plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0')
plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
将蓝点,橙点进行分类
可见感知机模型完美地将两类实例点进行二分。
4.2算法的收敛性
现在证明,对于线性可分数据集感知机学习算法原始形式收敛,即经过有限次迭代可以得到一个将训练数据集完全正确划分的分离超平面及感知机模型。
为了方便叙述与推导,将偏置\(b\)并入权重向量\(w\),记作\(\hat w\) = \((w^T,b)^T\),同样也将输入向量加以扩充,加进常数\(1\),记作\(\hat x\) = \((x^T,1)^T\),\({\hat w}\)\(\cdot\)\(\hat x\) = \(w\cdot x+b\).
Novikoff定理:设训练数据集\(T= \{(x_1,y_1),(x_2,y_2),···,(x_n,y_n)\}\)是线性可分的,其中\(x_i \in X=R^n,y_i \in Y = \{-1,+1\},i=1,2,3,···n\),则
(1) 存在满足条件\(||\hat w_{opt}| |=1\)的超平面\(\hat w_{opt}\)\(\cdot\)\(\hat x =0\)将训练数据集完全正确分开;且存在 \(\gamma \geq 0\),对所有\(i=1,2,···,n\)
(2) 令\(R=\max\limits_{1\leq i\leq n}||\hat x_i||\),则感知机算法在训练数据集上的误分类次数\(k\)满足不等式
Proof:
(1)由于训练数据集是线性可分的,按照定义,存在超平面可将训练数据集完全正确分开,取此超平面为\(\hat w_{opt} \cdot \hat x\) =\(w_{opt} \cdot x +b_{opt} = 0\),因为\(w_{opt}\)是该超平面的法向量,是可以任意伸缩的,我们让这个法向量除以它的范数,就可以使\(||\hat w_{opt}|| =1\),就像\(2x+2y+1=0\)与\(\frac{2}{3}x+\frac{2}{3}y+\frac{1}{3}=0\)表示同一条直线。接下来又因为所有分类点都是正确分类的,所以对于所有分类点来说,存在
那么自然能在上述非负数集合中找到一个最小的非负数\(\gamma \geq0\),
使
(2)感知机算法从\(\hat w_0 =0\)开始,如果实例被误分类,则更新权重,令\(\hat w_{k-1}\)是第\(k\)个误分类实例之前的扩充权重向量,即\(\hat w_{k-1} = (w_{k-1}^T,b_{k-1})^T\),则第\(k\)个误分类实例的条件是
若\((x_i,y_i)\)是被\(\hat w_{k-1}\)误分类的数据,则\(w,b\)的更新是
即
下面推导两个不等式:
①
证明:
由此递推可得不等式
②
证明:
所以有不等式:
于是
定理表明,误分类的次数\(k\)是有上界的,经过有限次搜索可以找到将训练数据完全正确分开的分离超平面。也就是说,感知机学习算法原始形式是收敛的。
4.3感知机算法的对偶形式
原始形式感知机每次梯度下降都是随机选取一个误分类点数据来更新\(w,b\),最终迭代若干次得到最终结果。对于从来都没有误分类的点来说,它被选择参与迭代的次数为0,假设样本点\((x_i,y_i) \in T\)在迭代更新\(w,b\)时被使用了\(k_i\)次,令\(\alpha _i = \eta k_i\),因此在原始感知机算法中,算法最后收敛时的\(w,b\)为:
注意,为什么是\(x_i\in T\),而不是\(x_i \in M\),因为此时对于非误分类点来说\(\alpha_i=0\),对权重\(w\)不会造成影响,把上述\(w,b\)回代到原始感知机算法的假设函数中可得
此时的参数不再是\(w,b\),而是\(\alpha_i\)。因此原始问题由求解参数\(w,b\)转换为如何求解参数\(\alpha_i\)。综上可以给出对偶形式下的参数迭代格式:
这很容易理解,当该数据点误分类,那么让它继续分类次数\(k_i\)加1。
其中\((x_j,x_i)\)表示做内积运算,引入\(Gram\)矩阵来存储内积。在迭代更新时可以发现当某实例数据点在算法更新时使用次数越多,意味着它距离分离超平面越近,也就越难以正确分类,换言之,这样的数据点对感知机算法学习的效果影响最大。
\(Gram\)矩阵定义如下:
其实就是对偶形式算法本质就是将\(w\)表示成数据点\(y_ix_i\)的线性组合,\(b\)表示成\(y_i\)的线性组合。
算法步骤
输入:训练数据\(T = \{(x_1,y_1),(x_2,y_2),···,(x_n,y_n)\},y_i \in \{-1,+1\}\),学习率\(\eta(0<\eta <1)\)
输出:\(\alpha,b\);感知机模型\(f(x) =sign(\sum\limits_{j=1}^n\alpha_jy_jx_j\cdot x+b)\),其中\(\alpha =(\alpha_1,\alpha_2,···,\alpha_n)^T\)
(1) 赋初值\(\alpha_0,b_0\)
(2) 选取数据点\((x_i,y_i)\)
(3) 判断该数据点是否为当前模型的误分类点,即判断若\(y_i(\sum\limits_{j=1}^n\alpha_jy_jx_j\cdot x_i+b)\leq0\)则更新
(4) 转(2),直到训练集中没有误分类点
def fit( self,X,y):
#权重和偏置初始化
self.alpha = np.zeros ( X.shape[0])
self.b =0
train_complete_flag = False
Gram = np.dot(X, X.T) #存放样本两两内积的Gram矩阵(特点)
while not train_complete_flag:
error_count = 0
for i in range ( X.shape[0]):
x_, y_ = X[i],y[i]
#有—个点当分类错误时,更新alpha_i和偏置
tmp= np.dot(np.multiply(self.alpha,y),Gram[ :, i]) +self.b
#进行误分类点判断
if y_* tmp <= 0:
self.alpha[i] += self.lr
self.b += self.lr * y_
error_count += 1
if not error_count:
train_complete_flag = True#训练完成后计算权重
self.w = np.dot(np.multiply (self.alpha,y),X)
4.4原始形式与对偶形式
在向量维数(特征数)过高时(特征数n \(>>\) 样本数N),需要计算输入特征\(x_i\)与权值向量\(w\)的内积,效率低,应选择对偶算法
在向量个数(样本数)过多时(特征数n \(<<\) 样本数N),需要计算每次累加和,效率低,应选择原始算法
5.例子
如图所示,正实例点是\(x_1 = ( 3 , 3 )^ T , x_2 = ( 4 , 3 )^T,\)负实例点是\(x_3 = (1,1)^T\),使用感知机模型求解\(f(x)=sign(w\cdot x+b)\)。这里\(w=(w^{(1)},w^{(2)}),x=(x^{(1)},x^{(2)})\)
这里我们取初值\(w_0=0,b_0=0\),取\(\eta = 1\)
原始形式代码如下:
train = [[(3, 3), 1], [(4, 3), 1], [(1, 1), -1]]
w = [0, 0]
b = 0
# 使用梯度下降法更新权重
def update(data):
global w, b
w[0] = w[0] + 1 * data[1] * data[0][0]
w[1] = w[1] + 1 * data[1] * data[0][1]
b = b + 1 * data[1]
print(w, b)
# 计算到超平面的距离
def cal(data):
global w, b
res = 0
for i in range(len(data[0])):
res += data[0][i] * w[i]
res += b
res *= data[1]
return res
# 检查是否可以正确分类
def check():
flag = False
for data in train:
if cal(data) <= 0:
flag = True
update(data)
if not flag:
print("The result: w: " + str(w) + ", b: "+ str(b))
return False
flag = False
for i in range(1000):
check()
if check() == False:
break
[3, 3] 1
[2, 2] 0
[1, 1] -1
[0, 0] -2
[3, 3] -1
[2, 2] -2
[1, 1] -3
The result: w: [1, 1], b: -3
对偶形式代码如下:
import numpy as np
train = np.array([[[3, 3], 1], [[4, 3], 1], [[1, 1], -1]])
a = np.array([0, 0, 0])##为什么是三维的,三维对应三个数据点,这里代表每一个数据点误分类次数,实际是ki*学习率
b = 0
lr = 1
Gram = np.array([])
y = np.array(range(len(train))).reshape(1, 3)# 标签
x = np.array(range(len(train) * 2)).reshape(3, 2)# 特征
# 计算Gram矩阵
def gram():
g = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
for i in range(len(train)):
for j in range(len(train)):
g[i][j] = np.dot(train[i][0], train[j][0])
return g
# 更新权重
def update(i):##更新第i个误分类点
global a, b
a[i] = a[i] + lr
b = b + lr * train[i][1]
print(a, b)
# 计算到超平面的距离
def cal(key):
global a, b, x, y
i = 0
for data in train:
y[0][i] = data[1]
i = i + 1
temp = a * y
res = np.dot(temp, Gram[key])
res = (res + b) * train[key][1]
return res
# 检查是否可以正确分类
def check():
global a, b, x, y
flag = False
for i in range(len(train)):
if cal(i) <= 0:
flag = True
update(i)
if not flag:
i = 0
for data in train:
y[0][i] = data[1]
x[i] = data[0]
i = i + 1
temp = a * y
w = np.dot(temp, x)
print("The result: w: " + str(w) + ", b: "+ str(b))
return False
flag = False
Gram = gram()# 初始化Gram矩阵
for i in range(1000):
check()
if check() == False:
break
[1 0 0] 1
[1 0 1] 0
[1 0 2] -1
[1 0 3] -2
[2 0 3] -1
[2 0 4] -2
[2 0 5] -3
The result: w: [[1 1]], b: -3
6.总结
本章介绍了统计学习中最简单的一种算法——感知机,对现在的机器学习理论来说,这个算法的确是太简单了,但这样简单的东西却是很多现在流行算法的基础,比如神经网络,比如支持向量机,需要好好掌握。