感知机是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取+1和-1二值。感知机对应于输入空间(特征空间)中将实例划分为正负两类的分类超平面,属于判别模型。
感知机学习旨在求出将训练数据进行线性划分的分类超平面,为此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。
感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式。感知机预测是用学习得到的感知机模型对于新输入实例进行分类。
假设输入空间(特征空间)是 X ⊆ R n X\subseteq R^n X⊆Rn,输出空间是 Y = { + 1 , − 1 } Y=\{+1,-1\} Y={+1,−1}。输入 x ∈ X x\in X x∈X表示实例的特征向量,对应于输入空间(特征空间)的点;输出 y ∈ Y y\in Y y∈Y表示实例的类别。由输入空间到输出空间的如下函数: f ( x ) = s i g n ( w ⋅ x + b ) f(x)=sign(w·x+b) f(x)=sign(w⋅x+b)称为感知机。其中,w和b为感知机参数, w ∈ R n w\in R^n w∈Rn叫做权值(weight)或权值向量(weight vector), b ∈ R b\in R b∈R叫做偏置, w ⋅ x w·x w⋅x表示 w w w和 x x x的內积,sign是符号函数,即: s i g n ( x ) = { + 1 , x ≥ 0 − 1 , x < 0 sign(x)=\left\{\begin{matrix} +1,x\geq 0\\ -1,x< 0 \end{matrix}\right. sign(x)={+1,x≥0−1,x<0
感知机是一种线性分类模型,属于判别模型。感知机模型的假设空间是定义在特征空间中的所有线性分类模型或线性分类器,即函数集合 { f ∣ f ( x ) = w ⋅ x + b } \{f|f(x)=w·x+b\} {f∣f(x)=w⋅x+b}。
感知机有以下几何解析,线性方程: w ⋅ x + b = 0 w·x+b=0 w⋅x+b=0对于特征空间 R n R^n Rn中的一个超平面 S S S,其中 w w w是超平面的法向量,b是超平面的截距。这个超平面将特征空间划分为两个部分。位于两部分的点(特征向量)分别被分为正、负两类。因此,超平面 S S S称为分离超平面,如下所示。
下面证明一下 w w w是超平面的法向量:
超平面为 w ⋅ x + b = 0 w·x+b=0 w⋅x+b=0对于超平面上的两个点 x 1 x_1 x1和 x 2 x_2 x2,有: w ⋅ x 1 + b = 0 w·x_1+b=0 w⋅x1+b=0 w ⋅ x 2 + b = 0 w·x_2+b=0 w⋅x2+b=0将两个公式相减得到: w ⋅ ( x 1 − x 2 ) = 0 w·(x_1-x_2)=0 w⋅(x1−x2)=0因为 x 1 − x 2 x_1-x_2 x1−x2为超平面上的一条直线,因此 w w w与超平面垂直,所以 w w w为超平面的法向量。
感知机学习,由训练数据集(实例的特征向量及类别): T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,求得感知机模型,即求得模型参数 w w w, b b b。
感知机预测,通过学习得到的感知机魔心,对于新的输入实例给定对应的输出类别。
给定一个数据集: T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,如果存在某个超平面 S S S: w ⋅ x + b = 0 w·x+b=0 w⋅x+b=0能够将数据集的正实例点和和负实例点完全正确地划分到超平面的两侧,即对所有 y i = + 1 y_i=+1 yi=+1的实例 i i i,有 w ⋅ x i + b > 0 w·x_i+b>0 w⋅xi+b>0,对所有 y i = − 1 y_i=-1 yi=−1的实例 i i i,有 w ⋅ x i + b < 0 w·x_i+b<0 w⋅xi+b<0,则称数据集T为线性可分数据集;否则,称数据集 T T T线性不可分。
假设训练数据及线性可分,感知机学习的目的是求得一个能够将训练集正实例点和负实例点完全正确分开的分离超平面。为了能够找出这样的超平面,即确定感知机模型参数 w w w, b b b,需要确定一个学习策略,即定义(经验)损失函数并将损失函数极小化。
损失函数的一个自然选择是误分类点的总数。但是,这样的损失函数不是参数 w w w, b b b的连续可导函数,不易优化。
损失函数的另一个选择是误分类点到超平面 S S S的总距离,这是感知机所采用的。
为此,首先写出输入空间 R n R^n Rn中任一点 x 0 x_0 x0到超平面 S S S的距离: 1 ∣ ∣ w ∣ ∣ ∣ w ⋅ x 0 + b ∣ \frac{1}{||w||}|w·x_0+b| ∣∣w∣∣1∣w⋅x0+b∣公式中的 ∣ ∣ w ∣ ∣ ||w|| ∣∣w∣∣是 w w w的 L 2 L_2 L2范数。
其次,对于误分类的数据 ( x i , y i ) (x_i,y_i) (xi,yi)来说 − y i ( w ⋅ x i + b ) > 0 -y_i(w·x_i+b)>0 −yi(w⋅xi+b)>0成立。因为当 w ⋅ x i + b > 0 w·x_i+b>0 w⋅xi+b>0时, y i = − 1 y_i=-1 yi=−1;而当 w ⋅ x i + b < 0 w·x_i+b<0 w⋅xi+b<0时, y i = + 1 y_i=+1 yi=+1。因此,误分类点 x i x_i xi到超平面 S S S的距离是: − 1 ∣ ∣ w ∣ ∣ y i ∣ w ⋅ x 0 + b ∣ -\frac{1}{||w||}y_i|w·x_0+b| −∣∣w∣∣1yi∣w⋅x0+b∣这样,假设超平面 S S S的误分类点集合为M,那么所有误分类点到超平面 S S S的总距离为 − 1 ∣ ∣ w ∣ ∣ ∑ x i ∈ M y i ∣ w ⋅ x 0 + b ∣ -\frac{1}{||w||}\sum_{x_i\in M}y_i|w·x_0+b| −∣∣w∣∣1xi∈M∑yi∣w⋅x0+b∣不考虑 1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} ∣∣w∣∣1就得到感知机学习的损失函数。
为什么不用考虑 1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} ∣∣w∣∣1,个人认为对于感知机模型来说,当数据集线性可分时,最后的损失函数为0,这种情况下 1 ∣ ∣ w ∣ ∣ \frac{1}{||w||} ∣∣w∣∣1的存在与否对于优化过程没有任何影响。
给定训练数据集: T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N。感知机 s i g h ( w ⋅ x + b ) sigh(w·x+b) sigh(w⋅x+b)学习的损失函数定义为: L ( w , b ) = − ∑ x i ∈ M y i ( w ⋅ x i + b ) L(w,b)=-\sum_{x_i \in M}y_i(w·x_i+b) L(w,b)=−xi∈M∑yi(w⋅xi+b)公式中的M为误分类点的集合,这个损失函数就是感知机学习的经验风险函数。
显然,损失函数 L ( w , b ) L(w,b) L(w,b)是非负的。如果没有误分类点,损失函数值为0.而且,误分类点越少,误分类点离超平面越近,损失函数值越小。
一个特定的样本点的损失函数:在误分类时是参数 w , b w,b w,b的线性函数,在正确分类时是0.因此,给定训练数据集T,损失函数 L ( w , b ) L(w,b) L(w,b)是 w , b w,b w,b的连续可导函数。
感知机学习的策略是在假设空间中选取使损失函数式 L ( w , b ) L(w,b) L(w,b)最小的模型参数 w , b w,b w,b,即感知机模型。
感知机学习问题可以转化为求解损失函数式 L ( w , b ) L(w,b) L(w,b)的最优化问题,最优化的方法是随机梯度下降法。这里叙述感知机学习的具体算法,包括原始形式和对偶形式。
感知机学习算法是对以下最优化问题的算法,给定一个训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,求参数 w , b w,b w,b,使其为以下损失函数极小化问题的解: m i n w , b L ( w , b ) = − ∑ x i ∈ M y i ( w ⋅ x i + b ) min_{w,b}L(w,b)=-\sum_{x_i\in M}y_i(w·x_i+b) minw,bL(w,b)=−xi∈M∑yi(w⋅xi+b)其中M为误分类点的集合。
感知机学习算法是误分类驱动的,具体采用随机梯度下降法。
这里简单分析一下为什么不同批量梯度下降法,个人认为,在感知机模型中,当数据被正确分类时,其损失函数值为0,只有被误分类的点才会存在损失函数值,当我们用批量数据进行训练的时候,其中可能只有一小部分数据存在损失函数值,很大一部分的损失函数值是为0的,所以在求得损失函数进行梯度更新的时候比较麻烦。
正如上面所说,感知机学习算法是误分类驱动的,既然批量梯度下降存在以上问题,那我们就用随机梯度下降,随机遍历训练数据,当遇到误分类点时,进行梯度更新。
在使用梯度下降算法时,首先任意选取一个超平面 w 0 , b 0 w_0,b_0 w0,b0,然后使用梯度下降法不断地极小化目标函数 L ( w , b ) L(w,b) L(w,b)。极小化过程中不是一次使M中所有的误分类点的梯度下降,而是一次随机选取一个误分类点使其梯度下降。
假设误分类点集合M是固定的,那么损失函数 L ( w , b ) L(w,b) L(w,b)的梯度由: ▽ w L ( w , b ) = − ∑ x i ∈ M y i x i \triangledown _wL(w,b)=-\sum_{x_i\in M}y_ix_i ▽wL(w,b)=−xi∈M∑yixi ▽ b L ( w , b ) = − ∑ x i ∈ M y i \triangledown _bL(w,b)=-\sum_{x_i\in M}y_i ▽bL(w,b)=−xi∈M∑yi给出。
随机选取一个误分类点 ( x i , y i ) (x_i,y_i) (xi,yi),对 w , b w,b w,b进行更新: w ← w + η y i x i w\leftarrow w+\eta y_ix_i w←w+ηyixi b ← b + η y i b\leftarrow b+\eta y_i b←b+ηyi公式中的 η ( 0 < η < 1 ) \eta(0<\eta <1) η(0<η<1)是学习步长,在统计学习中又称为学习率。这样,通过迭代可以期待损失函数 L ( w , b ) L(w,b) L(w,b)不断减小,直到为0。综上所述,得到以下算法:
感知机学习算法的原始形式
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)},其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,学习率 η ( 0 < η < 1 ) \eta(0<\eta <1) η(0<η<1);
输出: w , b w,b w,b;感知机模型 f ( x ) = s i g n ( w ⋅ x + b ) f(x)=sign(w·x+b) f(x)=sign(w⋅x+b);
这种学习算法直观上有如下解释,当一个实例点被误分类,即位于分类超平面的错误一侧时,则调整 w , b w,b w,b的值,使分离超平面向该误分类点的一侧移动,以减少该误分类点与超平面的距离,直至超平面越过该误分类点使其被正确分类。
上述算法是感知机学习的基本算法,对应于后面的对偶形式,称为原始形式,对应于后面的对偶形式,称为原始形式。感知机学习算法简单且易于实现。
其Python代码实现如下:
# 代码来源:https://github.com/fengdu78/lihang-code
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
# 此代码并非原创
class Model:
def __init__(self,data):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32) # 初始化w
self.b = 0 # 初始化偏置bias
self.l_rate = 0.1 # 初始化学习率
# self.data = data
def sign(self, x, w, b): # sign()函数
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*(sum(X*self.w)+self.b) <= 0: # 当发现误分类点
self.w = self.w + self.l_rate * np.dot(y, X) # 更新参数w
self.b = self.b + self.l_rate * y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
return 'Perceptron Model!'
def score(self):
pass
将感知机应用于实例:
# 代码来源:https://github.com/fengdu78/lihang-code
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# load data
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.show()
data = np.array(df.iloc[:100, [0, 1, -1]]) # 获得数据的'sepal length'和'sepal width'和'label'
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model:
def __init__(self,data):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32) # 初始化w
self.b = 0 # 初始化偏置bias
self.l_rate = 0.1 # 初始化学习率
# self.data = data
def sign(self, x, w, b): # sign()函数
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*(sum(X*self.w)+self.b) <= 0: # 当发现误分类点
self.w = self.w + self.l_rate * np.dot(y, X) # 更新参数w
self.b = self.b + self.l_rate * y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
return 'Perceptron Model!'
def score(self):
pass
perceptron = Model(data)
perceptron.fit(X, y)
x_points = np.linspace(4, 7, 10) # 获得X轴的取值范围
y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1] # w_1*x_1 + w_2*x_2 + b = 0
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.show()
感知机学习算法的原始形式和对偶实行与支持向量机学习算法的原始形式与对偶形式相对应。
对偶形式的基本想法是,将 w w w和 b b b表示为实例 x i x_i xi和标记 y i y_i yi的线性组合的形式,通过求解其系数而求得 w w w和 b b b。不失一般性,在原始算形式中可假设初始值 w 0 , b 0 w_0,b_0 w0,b0均为0。对误分类点 ( x i , y i ) (x_i,y_i) (xi,yi)通过 w ← w + η y i x i w\leftarrow w+\eta y_ix_i w←w+ηyixi b ← b + η y i b\leftarrow b+\eta y_i b←b+ηyi逐步修改 w , b w,b w,b,设对于误分类点 ( x i , y i ) (x_i,y_i) (xi,yi)共修改n次,则 w , b w,b w,b关于 ( x i , y i ) (x_i,y_i) (xi,yi)的增量分别是 α i y i x i \alpha_iy_ix_i αiyixi和 α i y i \alpha_iy_i αiyi,这里的 α i = n i η \alpha_i=n_i\eta αi=niη。这样,从学习过程中不难看出,最后学习到的 w , b w,b w,b可以分别表示为: w = ∑ i = 1 N α i y i x i w=\sum_{i=1}^N\alpha_iy_ix_i w=i=1∑Nαiyixi b = ∑ i = 1 N α i y i b=\sum_{i=1}^N\alpha_iy_i b=i=1∑Nαiyi公式中的 α i ≥ 0 , i = 1 , 2 , . . . , N \alpha_i\geq0,i=1,2,...,N αi≥0,i=1,2,...,N,当 η = 1 \eta=1 η=1时,表示第 i i i个实例点犹豫误分类而进行更新的次数,实例点更新次数越多,意味着它距离分类超平面越近,也就越难正确分类。换句话说,这样的实例对学习结果影响最大。
简单理解,如果有N个训练数据,则上述公式中的 α \alpha α的维度为N维, α i \alpha_i αi表示表示第i个数据的因为误分类而更新的次数,如果 α i = 0 \alpha_i=0 αi=0,则表示该数据没有被误分类过,同时其对 w , b w,b w,b的更新没有影响;
下面叙述感知机学习的对偶形式:
感知机学习算法的对偶形式
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)},其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,学习率 η ( 0 < η < 1 ) \eta(0<\eta <1) η(0<η<1);
输出: a , b a,b a,b;感知机模型 f ( x ) = s i g n ( ∑ j = 1 N α j y j x j ⋅ x + b ) f(x)=sign(\sum_{j=1}^N\alpha_jy_jx_j·x+b) f(x)=sign(∑j=1Nαjyjxj⋅x+b),其中 α = ( α 1 , . . . , α n ) T \alpha=(\alpha_1,...,\alpha_n)^T α=(α1,...,αn)T;
对偶形式训练实例仅以內积的形式出现,为了方便,可以预先将训练集中实例间的內积计算出来并以矩阵的形式存储,这个矩阵就是所谓的Gram矩阵: G = [ x i ⋅ x j ] N ∗ N G=[x_i·x_j]_{N*N} G=[xi⋅xj]N∗N
感知机学习算法的对偶形式的Python实现如下所示:
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model_Gram:
def __init__(self,data):
self.a = np.zeros(len(data), dtype=np.float32) # 初始化a`
self.b = 0 # 初始化偏置bias
self.l_rate = 1 # 初始化学习率
self.w = np.zeros(len(data[0])-1, dtype=np.float32)
def gram_matrix(self,data): # 计算gram_matrix矩阵,维度为[len(data),len(data)]
return np.dot(data,data.T)
# 随机梯度下降法
def fit(self, X_train, y_train):
is_wrong = False
X_gram = self.gram_matrix(X_train) # gram_matrix
while not is_wrong:
wrong_count = 0
for d in range(len(X_train)):
X = X_gram[d]
y = y_train[d]
if y*(np.sum((self.a * y_train)*X)+self.b)<=0:
self.a[d] += 1 # 更新参数a
self.b = self.b + y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
self.w = np.dot((self.a * y_train).T,X_train) # 计算w
return 'Perceptron Model!'
def score(self):
pass
将对偶形式的感知机应用于实例有:
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# load data
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.show()
data = np.array(df.iloc[:100, [0, 1, -1]]) # 获得数据的'sepal length'和'sepal width'和'label'
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model_Gram:
def __init__(self,data):
self.a = np.zeros(len(data), dtype=np.float32) # 初始化a`
self.b = 0 # 初始化偏置bias
self.l_rate = 1 # 初始化学习率
self.w = np.zeros(len(data[0]) - 1, dtype=np.float32)
def gram_matrix(self, data): # 计算gram_matrix矩阵,维度为[len(data),len(data)]
return np.dot(data, data.T)
# 随机梯度下降法
def fit(self, X_train, y_train):
is_wrong = False
X_gram = self.gram_matrix(X_train) # gram_matrix
while not is_wrong:
wrong_count = 0
for d in range(len(X_train)):
X = X_gram[d]
y = y_train[d]
if y * (np.sum((self.a * y_train) * X) + self.b) <= 0:
self.a[d] += 1 # 更新参数a
self.b = self.b + y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
self.w = np.dot((self.a * y_train).T, X_train) # 计算w
return 'Perceptron Model!'
def score(self):
pass
perceptron = Model_Gram(data)
perceptron.fit(X, y)
x_points = np.linspace(4, 7, 10) # 获得X轴的取值范围
y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1] # w_1*x_1 + w_2*x_2 + b = 0
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.show()
既然有了原始形式的算法,为什么要提出对偶形式的算法呢?
个人感觉是原始形式中,计算过程中需要进行数据特征维度的乘法运算,当特征维度比较大时,运算时间比较长;当转换为对偶形式后,计算过程为数据个数的乘法运算,同时两个数据间的乘法结果可以通过事先计算好的Gram矩阵查询获得。这样对比之下,对偶算法的计算效率更高。
如果训练数据是线性可分的,那么感知机可以找到一个判别函数来分割不同类的数据。但是感知机不能保证找到的判别函数是最优的(比如泛化能力高),这样可能导致过拟合。
感知机学习到的权重向量和训练样本的顺序相关。在迭代次序上排在后面的错误样本比前面的错误样本对最终的权重向量影响更大。比如有1000个训练样本,在迭代100个样本后,感知器已经学习到一个很好的权重向量。在接下来的899个样本都预测正确,也没有更新权重向量。但是,在最后第1000个样本时预测错误,并更新了权重,这次更新可能反而使得权重向量变差。
为了提高感知机的鲁棒性和泛化能力,我们可以将感知机学习过程中的所有K个权重向量保存起来(K为感知机在训练中权重向量的总更新次数),并赋予每个权重向量 w k w_k wk一个置信系数 c k ( 1 ≤ k ≤ K ) c_k(1\leq k\leq K) ck(1≤k≤K)。最终的分类结果通过这K个不同权重的感知机投票决定,这个模型也称为投票感知机。
令 τ k \tau_k τk为第 k k k次更新权重 w k w_k wk时的迭代次数(即训练过的样本数量), τ k + 1 \tau_{k+1} τk+1为下次权重更新时的迭代次数,则权重 w k w_k wk的置信系数 c k c_k ck设置为从 τ k \tau_k τk到 τ k + 1 \tau_{k+1} τk+1之间间隔的迭代次数,即 c k = τ k + 1 − τ k c_k=\tau_{k+1}-\tau_k ck=τk+1−τk。置信系数 c k c_k ck越大,说明权重 w k w_k wk在之后的训练过程中正确分类样本的数量越多,越值得信赖。
这样,投票感知机的形式为 y = s i g n ( ∑ k = 1 K c k s i g n ( w k T x + b ) ) y=sign(\sum_{k=1}^Kc_ksign(w_k^Tx+b)) y=sign(k=1∑Kcksign(wkTx+b))
投票感知机虽然提高了感知机的泛化能力,但是需要保存K个权重向量。在实际操作中会带来额外的开销。因此,人们经常会使用一个简化的版本,通过使用“参数平均”的策略来减少投票感知机的参数数量,这也叫做平均感知机。平均感知机的形式为: y = s i g n ( 1 T ∑ k = 1 K c k ( w k T x + b ) ) y = sign(\frac{1}{T}\sum_{k=1}^Kc_k(w_k^Tx+b)) y=sign(T1k=1∑Kck(wkTx+b)) = s i g n ( 1 T ( ∑ k = 1 K c k w k ) T x + b ) =sign(\frac{1}{T}(\sum_{k=1}^Kc_kw_k)^Tx+b) =sign(T1(k=1∑Kckwk)Tx+b) = s i g n ( ( 1 T ∑ t = 1 T w t ) T x + b ) =sign((\frac{1}{T}\sum_{t=1}^Tw_t)^Tx+b) =sign((T1t=1∑Twt)Tx+b) = s i g n ( w ˉ T x + b ) =sign(\bar{w}^Tx+b) =sign(wˉTx+b)
公式中的T为迭代总次数, w w w为T次迭代的平均权重向量,这个方法非常简单,只需要在原始形式的算法中增加一个 w ˉ \bar w wˉ,并且在每次迭代时都更新 w ˉ \bar w wˉ: w ˉ ← w ˉ + w t \bar w\leftarrow \bar w + w_t wˉ←wˉ+wt但是这个方法需要在处理每个样本时都要更新 w ˉ \bar w wˉ。因为 w ˉ \bar w wˉ和 w t , n w_{t,n} wt,n都是稠密向量。所以更新操作比较费时。为了提高迭代速度,有很多改进方法,让这个更新只需要在错误预测时才进行更新。
平均感知机算法:
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)},其中, x i ∈ X = R n x_i\in X=R^n xi∈X=Rn, y i ∈ Y = { + 1 , − 1 } y_i\in Y=\{+1,-1\} yi∈Y={+1,−1}, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,学习率 η ( 0 < η < 1 ) \eta(0<\eta <1) η(0<η<1);
输出: w , b w,b w,b;感知机模型 f ( x ) = s i g n ( w ⋅ x + b ) f(x)=sign(w·x+b) f(x)=sign(w⋅x+b);
其Python代码实现如下所示:
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model_mean:
def __init__(self,data):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32) # 初始化w
self.b = 0 # 初始化偏置bias
self.sum_w = np.zeros(len(data[0])-1, dtype=np.float32)
self.sum_b = 0
self.time = 0
self.t = 0
self.l_rate = 1 # 初始化学习率
def sign(self, x, w, b): # sign()函数
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)):
self.t += 1
X = X_train[d]
y = y_train[d]
if y*(sum(X*self.w)+self.b) <= 0:
self.sum_w += (self.t - self.time)*self.w
self.sum_b += (self.t - self.time)*self.b
self.time = self.t
self.w = self.w + self.l_rate * np.dot(y, X) # 更新参数w
self.b = self.b + self.l_rate * y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
self.w = self.sum_w/self.t
self.b = self.sum_b/self.t
return 'Perceptron Model!'
def score(self):
pass
在具体例子中运行该平均感知机模型:
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# load data
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.show()
data = np.array(df.iloc[:100, [0, 1, -1]]) # 获得数据的'sepal length'和'sepal width'和'label'
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model_Mean:
def __init__(self,data):
self.w = np.ones(len(data[0]) - 1, dtype=np.float32) # 初始化w
self.b = 0 # 初始化偏置bias
self.sum_w = np.zeros(len(data[0])-1, dtype=np.float32)
self.sum_b = 0
self.time = 0
self.t = 0
self.l_rate = 1 # 初始化学习率
def sign(self, x, w, b): # sign()函数
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)):
self.t += 1
X = X_train[d]
y = y_train[d]
if y*(sum(X*self.w)+self.b) <= 0:
self.sum_w += (self.t - self.time)*self.w
self.sum_b += (self.t - self.time)*self.b
self.time = self.t
self.w = self.w + self.l_rate * np.dot(y, X) # 更新参数w
self.b = self.b + self.l_rate * y # 更新参数b
wrong_count += 1 # 统计误分类点个数
if wrong_count == 0:
is_wrong = True # 当is_wrong为True则表示数据集data中没有误分类点,停止迭代
self.w = self.sum_w/self.t
self.b = self.sum_b/self.t
return 'Perceptron Model!'
def score(self):
pass
perceptron = Model_Mean(data)
perceptron.fit(X, y)
x_points = np.linspace(4, 7, 10) # 获得X轴的取值范围
y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1] # w_1*x_1 + w_2*x_2 + b = 0
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.show()
体验scikit-learn中感知机模型的使用过程:
# 代码来源:https://github.com/fengdu78/lihang-code
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.linear_model import Perceptron
# load data
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.show()
data = np.array(df.iloc[:100, [0, 1, -1]]) # 获得数据的'sepal length'和'sepal width'和'label'
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
clf = Perceptron(fit_intercept=True,max_iter=1000, shuffle=True)
clf.fit(X, y)
# 画布大小
plt.figure(figsize=(10,10))
# 中文标题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title('鸢尾花线性数据示例')
plt.scatter(data[:50, 0], data[:50, 1], c='b', label='Iris-setosa',)
plt.scatter(data[50:100, 0], data[50:100, 1], c='orange', label='Iris-versicolor')
# 画感知机的线
x_ponits = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ponits + clf.intercept_)/clf.coef_[0][1]
plt.plot(x_ponits, y_)
# 其他部分
plt.legend() # 显示图例
plt.grid(False) # 不显示网格
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()
其分类结果如下所示:
在上图中,有一个位于左下角的蓝点没有被正确分类,这是因为 SKlearn 的 Perceptron 实例中有一个tol参数。
tol 参数规定了如果本次迭代的损失和上次迭代的损失之差小于一个特定值时,停止迭代。所以我们需要设置 tol=None 使之可以继续迭代:
# 代码来源:https://github.com/fengdu78/lihang-code
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.linear_model import Perceptron
# load data
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.show()
data = np.array(df.iloc[:100, [0, 1, -1]]) # 获得数据的'sepal length'和'sepal width'和'label'
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
clf = Perceptron(fit_intercept=True,
max_iter=1000,
tol=None,
shuffle=True)
clf.fit(X, y)
# 画布大小
plt.figure(figsize=(10,10))
# 中文标题
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title('鸢尾花线性数据示例')
plt.scatter(data[:50, 0], data[:50, 1], c='b', label='Iris-setosa',)
plt.scatter(data[50:100, 0], data[50:100, 1], c='orange', label='Iris-versicolor')
# 画感知机的线
x_ponits = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ponits + clf.intercept_)/clf.coef_[0][1]
plt.plot(x_ponits, y_)
# 其他部分
plt.legend() # 显示图例
plt.grid(False) # 不显示网格
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()