感知机(perceptron):原理、python实现及sklearn.linear_model.Perceptron参数详解
机器学习之感知机(perceptron)1.感知机模型介绍感知机是一个二分类的线性分类模型,二分类是指输出YYY的分类只有两个值,取+1和-1,线性分类是指模型将训练数据集用一个线性超平面(如果特征空间XXX⊆\sube⊆RnR^nRn,那么该线性超平面就是n-1维)。感知机模型属于判别模型,即通过输入的样本直接学习到fff(xxx),而没有学习到XXX 与YYY的联合分布函数FFF(XXX,YY
文章目录
1.感知机模型介绍
感知机是一个二分类的线性分类模型,二分类是指输出 Y Y Y的分类只有两个值,取+1和-1,线性分类是指模型将训练数据集用一个线性超平面分离(如果特征空间 X X X ⊆ \sube ⊆ R n R^n Rn,那么该线性超平面就是n-1维)。
感知机模型属于判别模型,即通过输入的样本直接学习到 f f f( x x x),而没有学习到 X X X 与 Y Y Y的联合分布 P P P( X X X, Y Y Y)
感知机模型的形式: f ( x ) = s i g n ( w ⋅ x + b ) f(x) = sign(w \centerdot x+b) f(x)=sign(w⋅x+b) sign为符号函数,当 w ⋅ x w \centerdot x w⋅x + + + b >0时取1,<0时取-1。 w ⋅ x w \centerdot x w⋅x + + +b即是该模型要学习的超平面,将实例划分为正负两类。
感知机模型的假设空间:定义在特征空间中的所有线性分类模型,{ f f f| f ( x ) f(x) f(x)= w ⋅ x w \centerdot x w⋅x + + +b}
2.感知机学习策略
假设空间中有无数个模型,那么哪一个才是最好的呢?这就涉及到模型的学习策略
假设数据集是线性可分的,即存在一个超平面
w
⋅
x
w \centerdot x
w⋅x
+
+
+b=0将数据集的正例和反例完全分开。正例在
w
⋅
x
w \centerdot x
w⋅x
+
+
+b>0一侧,反例在另一侧。
即
y
i
=
{
+
1
if
w
⋅
x
+
b
>
0
−
1
if
w
⋅
x
+
b
<
0
y_i =\begin{cases} +1 &\text{if } w \centerdot x+b>0 \\ -1 &\text{if } w \centerdot x+b<0 \end{cases}
yi={+1−1if w⋅x+b>0if w⋅x+b<0
损失函数也叫代价函数,是指模型分类错误时的代价值。这里的模型的损失函数可以是误分类点的比例,但是该函数不是要求参数 w w w 和 b的连续可导函数,不利于求解最优模型。
另一种损失函数是考虑所有误分类点到该超平面
w
⋅
x
w \centerdot x
w⋅x
+
+
+b=0的总距离。特征空间任意一点
x
0
x_0
x0到超平面距离为:
∣
w
⋅
x
0
+
b
∣
∥
w
∥
\LARGE\frac{| w \centerdot x_0+b|}{\|w \|}
∥w∥∣w⋅x0+b∣,
∥
w
∥
\|w \|
∥w∥为向量
w
w
w的模长。我们要计算的就是所有误分类的点到超平面的距离。若所有误分类点属于集合M,则
C
o
s
t
F
u
n
c
t
i
o
n
=
∑
x
i
⊆
M
∣
w
⋅
x
i
+
b
∣
∥
w
∥
CostFunction = \sum\limits_{x_i \sube M}\LARGE\frac{| w \centerdot x_i+b|}{\|w \|}
CostFunction=xi⊆M∑∥w∥∣w⋅xi+b∣
但是有绝对值,也不好对参数求导,于是想到若
(
x
i
,
y
i
)
(x_i, y_i)
(xi,yi) 误分类,即
−
y
i
(
w
⋅
x
i
+
b
)
-y_i(w \centerdot x_i+b)
−yi(w⋅xi+b)>0,所以绝对值可去,不过得加个负号。而且模长始终为正不用考虑,可以去掉,于是得到最终的损失函数:
L
(
w
,
b
)
=
−
∑
x
i
⊆
M
y
i
(
w
⋅
x
+
b
)
\Large L(w,b)=-\sum\limits_{x_i \sube M}y_i(w \centerdot x+b)
L(w,b)=−xi⊆M∑yi(w⋅x+b)
最优模型即是使以上损失函数达最小值时的模型。
3.感知机学习算法
3.1 原始形式
知道了什么样的模型是最优的,可是如何来求解最优模型模型呢?这就是学习算法要做的事。感知机利用 随机梯度下降法(stochastic gradient descent) 来求解出
w
,
b
w,b
w,b从而得出最优模型。
梯度我理解为偏导数组成的向量,损失函数
L
(
w
,
b
)
L(w,b)
L(w,b)有两个参数,梯度也就有两个元素(有两个方向下降),L可以沿着这两个方向下降。
∂
L
(
w
,
b
)
∂
w
=
−
∑
x
i
⊆
M
y
i
x
i
\LARGE\frac{\partial L(w,b)}{\partial w} =-\sum\limits_{x_i \sube M}y_ix_i
∂w∂L(w,b)=−xi⊆M∑yixi
∂
L
(
w
,
b
)
∂
b
=
−
∑
x
i
⊆
M
y
i
\LARGE\frac{\partial L(w,b)}{\partial b} =-\sum\limits_{x_i \sube M}y_i
∂b∂L(w,b)=−xi⊆M∑yi
随机梯度下降法随机选取一个误分类点
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj)(而不是像在线性回归的梯度下降法中使用所有点)对
w
,
b
w,b
w,b进行更新,然而随机一误分类点并不能保证
w
,
b
w,b
w,b是一直是朝着正确的方向改进。
w
←
w
+
η
y
j
x
j
\LARGE w \leftarrow w + \eta y_jx_j
w←w+ηyjxj
b
←
b
+
η
y
j
\LARGE b \leftarrow b +\eta y_j
b←b+ηyj
η
\Large\eta
η 为步长,或叫学习率,决定L下降的快慢。那么我有疑问,这样更新L一定会逐渐下降吗?
更新前:
L
0
(
w
,
b
)
=
−
∑
x
i
⊆
M
y
i
(
w
0
⋅
x
i
+
b
0
)
\Large L_0(w,b)= \Large-\sum\limits_{x_i \sube M}y_i(w_0 \centerdot x_i+b_0)
L0(w,b)=−xi⊆M∑yi(w0⋅xi+b0)
更新:
w
1
=
w
0
+
η
y
j
x
j
;
b
1
=
b
0
+
η
y
j
\LARGE w_1= w_0 + \eta y_jx_j ; \LARGE b_1 = b_0 +\eta y_j
w1=w0+ηyjxj;b1=b0+ηyj
更新后:
L
1
(
w
,
b
)
=
−
∑
x
i
⊆
M
y
i
(
w
1
⋅
x
i
+
b
1
)
=
−
∑
x
i
⊆
M
y
i
(
(
w
0
+
η
y
j
x
j
)
⋅
x
i
+
b
0
+
η
y
j
)
=
−
∑
x
i
⊆
M
y
i
(
w
0
⋅
x
i
+
b
0
)
−
∑
x
i
⊆
M
y
i
(
η
y
j
x
j
⋅
x
i
+
η
y
j
)
=
L
0
(
w
,
b
)
−
∑
x
i
⊆
M
y
i
(
η
y
j
x
j
⋅
x
i
+
η
y
j
)
\Large \begin{aligned} L_1(w,b) &=-\sum\limits_{x_i \sube M}y_i(w_1 \centerdot x_i+b_1)\\ &=-\sum\limits_{x_i \sube M}y_i( (w_0 + \eta y_jx_j)\centerdot x_i+b_0+\eta y_j)\\ &=-\sum\limits_{x_i \sube M}y_i(w_0\centerdot x_i+b_0) -\sum\limits_{x_i \sube M}y_i (\eta y_jx_j \centerdot x_i +\eta y_j)\\ &=L_0(w,b)-\sum\limits_{x_i \sube M}y_i (\eta y_jx_j \centerdot x_i +\eta y_j) \end{aligned}
L1(w,b)=−xi⊆M∑yi(w1⋅xi+b1)=−xi⊆M∑yi((w0+ηyjxj)⋅xi+b0+ηyj)=−xi⊆M∑yi(w0⋅xi+b0)−xi⊆M∑yi(ηyjxj⋅xi+ηyj)=L0(w,b)−xi⊆M∑yi(ηyjxj⋅xi+ηyj)
问题是,减号后面这项真的大于0吗,这个式子里面就一项一定是大于0的(即
i
=
j
i=j
i=j时),但是其他项就无法判断了吧。如果不是大于0就说明L不一定是一直下降的。然而感知机算法好像整个过程并没有计算过这个损失函数,它是所有误分类点导致的误差,这个算法的思想不是直接改进
w
,
b
w,b
w,b使它减小,而是逐步地改进它的每一个部分(也就是每一个误分类点),从而达到整体的改进。也就是说随机梯度下降算法计算的并不是真正的梯度,真正的梯度应该像批梯度计算那样需要所有样本才能计算。随机梯度只是用一个样本进行对真正的梯度进行了一次预估,因此可能会有偏差。即随机梯度下降算法不一定保证每次前进方向都是正确的,所以会带来波动。 在更新
(
w
,
b
)
(w,b)
(w,b)这个过程中,可能会出现总损失函数
L
(
w
,
b
)
L(w,b)
L(w,b)上升的情况, 因为你只针对一个点更新
(
w
,
b
)
(w,b)
(w,b),有可能会使原先分类正确的点又误分类,也有可能使误分类的点距离更远,但是总体是螺旋式下降,所以如果数据集是线性可分,那么损失函数迭代下去就会向0靠近,下面证明为什么该算法虽然有波动,但最终还是会收敛到0。
3.2.1算法收敛性的证明
对于线性可分的数据集,利用批量梯度下降的话,
(
w
,
b
)
(w,b)
(w,b)每一次的更新都必然使损失函数
L
(
w
,
b
)
\ L(w,b)
L(w,b)下降,最终必然会收敛到0,没必要进行收敛性的证明。但是因为使用随机了随机梯度下降,每一次
(
w
,
b
)
(w,b)
(w,b)的更新不是使总的损失函数
L
(
w
,
b
)
\ L(w,b)
L(w,b)下降,而是仅仅使随机误分类点
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj)的损失——即
−
y
i
(
w
⋅
x
+
b
)
-y_i(w \centerdot x+b)
−yi(w⋅x+b) 下降。那么每一次更新都可能使得总损失函数
L
(
w
,
b
)
\ L(w,b)
L(w,b)上升,产生波动。那是否这个算法就是失效了呢?下面证明,即使
L
(
w
,
b
)
\ L(w,b)
L(w,b)不是一直下降,会产生波动,但是经过有限次迭代后,最终还是会收敛到0,可以找到一个将训练数据集完全正确划分的分离超平面。
证明的出发点是,证明误分类的次数k是有上限的,每一次误分类后
(
w
,
b
)
(w,b)
(w,b)都会更新,最多经过有限次(k的上限)次更新后,可以找到训练数据集完全正确划分的分离超平面。证明过程不写了,看其他博主的吧。
3.2对偶形式
对偶形式与原始形式的输入一样,但是输出略有差异,它是将
w
,
b
w,b
w,b表示为
x
i
x_i
xi和
y
i
y_i
yi的线性组合,输出的是
α
\alpha
α和b,而不是直接输出
w
w
w的最终值:
和上面更新相同:
w
1
=
w
0
+
η
y
j
x
j
\LARGE w_1= w_0 + \eta y_jx_j
w1=w0+ηyjxj ;
b
1
=
b
0
+
η
y
j
\LARGE b_1 = b_0 +\eta y_j
b1=b0+ηyj
那么如果整个迭代过程对误分类点
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi)更新了
n
i
n_i
ni次(如果一直分类正确就是0次),这个点对
w
和
b
w和b
w和b的贡献分别为
n
i
∗
η
x
i
y
i
n_i* \eta x_iy_i
ni∗ηxiyi 和
n
i
∗
η
y
i
n_i* \eta y_i
ni∗ηyi,记
α
i
=
n
i
∗
η
\alpha_i=n_i*\eta
αi=ni∗η, 最终
w
,
b
w,b
w,b的结果为:
w
=
∑
i
=
1
N
n
i
∗
η
y
i
x
i
=
∑
i
=
1
N
α
i
y
i
x
i
\Large w =\displaystyle\sum_{i=1}^Nn_i*\eta y_ix_i=\displaystyle\sum_{i=1}^N\alpha _iy_ix_i
w=i=1∑Nni∗ηyixi=i=1∑Nαiyixi
b
=
∑
i
=
1
N
n
i
∗
η
y
i
=
∑
i
=
1
N
α
i
y
i
\Large b =\displaystyle\sum_{i=1}^Nn_i*\eta y_i=\displaystyle\sum_{i=1}^N\alpha_i y_i
b=i=1∑Nni∗ηyi=i=1∑Nαiyi
所以最终输出的模型为:
f
(
x
)
=
s
i
g
n
(
∑
i
=
1
N
α
i
y
i
x
i
⋅
x
+
b
)
f(x)=sign(\displaystyle\sum_{i=1}^N\alpha _iy_ix_i \centerdot x+b)
f(x)=sign(i=1∑Nαiyixi⋅x+b)
4.python实现感知机算法
感知机的基本原理就写完了,但是对于一个机器学习算法,如果只停留在理解但是不会运用这个阶段,那是远远不够的,必须要通过一些实例,多敲代码,在这个过程加深对算法的理解。这里选取sklearn内置数据库的iris(鸢尾属植物)数据集进行实战演练。
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib notebook
将数据存储到dataframe中:
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df
结果如下:
总共有4个特征——sepal花萼的长宽以及petal花瓣的长宽,但是为了更直观的观察数据,这里只取花萼的长宽这两个特征进行这次练习,并且感知机是二分类模型,所以只选取前100行(只包含0、1两类)。
df = df.iloc[:100, [0, 1, -1]]
df.columns = ['sepal_length', 'sepal_width', 'label']
df
结果如下:
画出图形:
plt.figure()
plt.scatter(df.iloc[:50, 0], df.iloc[:50,1], label='0')
plt.scatter(df.iloc[50:100, 0], df.iloc[50:100,1], label='1')
plt.xlabel('sepal_length')
plt.ylabel('sepal_width')
plt.legend()
直观上感觉是线性可分的。提取出特征与输出值,将标签0改为-1。
data = np.array(df)
X, y = data[:,:-1], data[:,-1]
y[y==0] = -1
4.1手写感知机算法
def perceptron(X,y,eta):
w = np.zeros(X.shape[1]) # 初始化w,b
b = 0
def sign_y(x,w,b): # 计算w*x+b的值,以便于乘以真实标记y判断是否误分类
y1 = np.dot(x,w)+b
return y1
while True: # 下面定义了循环结束条件,只要还有误分类点,就会一直循环下去
wrong_count = 0
for i in range(len(X)): # 100个样本
x = X[i]
yi_lable = y[i]
if yi_lable * sign_y(x,w,b)<=0: # 说明误分类了,更新w,b
w = w + eta*yi_lable*x
b = b + eta*yi_lable
wrong_count += 1
if wrong_count == 0: # 定义函数结束条件,一次循环下来,误分类点为0,即所有点都正确分类了
return w,b
w,b=perceptron(X,y,eta=0.6) # 解包
计算得到w=[ 31.6 , -40.28],b=-49.6,画出图形观察是否正确分类:
fig = plt.figure()
x_ticks = np.linspace(4.3,7,10)
ax = plt.subplot(1,1,1)
ax.set_xticks(x_ticks)
ax.set_xlim(4.2,7.1)
ax.set_ylim(1.9,4.5)
ax.set_xlabel('sepal_length')
ax.set_ylabel('sepal_width')
plt.scatter(df.iloc[:50, 0], df.iloc[:50,1], label='0')
plt.scatter(df.iloc[50:100, 0], df.iloc[50:100,1], label='1')
plt.plot(df['sepal_length'], (w[0]*df['sepal_length'] + b)/(-w[1]))
plt.legend(loc = 'best')
得到下图:
可以看到分类正确,那两个看起来在线上的点其实也是分列于两侧,在jupyter notebook图形操作界面用放大镜放大后就能看出来。
4.2 scikit-learn实操
手写只是为了加深对算法的理解,但是一般实操还是调包更快更方便。感知机官方参数介绍可以点击这里
4.2.1sklearn.linear_model.Perceptron参数解释
用于创建感知机模型时传递的参数。
参数名称 | 参数取值 | 参数解释 |
---|---|---|
penalty | 默认=None,即不加惩罚项,‘l2’(L2正则) or ‘l1’(L1正则) or ‘elasticnet’(混合正则) | 惩罚项,加上惩罚项主要为了避免模型过拟合风险 |
alpha | 默认=0.0001,取值为浮点数 | 如果penalty不为None,则正则化项需要乘上这个数 |
l1_ratio | 默认=0.15,取值在[0,1] | 一般只在penalty=elasticnet时用,当l1_ratio =0就是L2正则,当l1_ratio =1就是L1正则,当在(0,1)之间就是混合正则 |
fit_intercept | bool值,默认=True | 是否对参数 截距项b进行估计,若为False则数据应是中心化的 |
max_iter | int整数,默认=1000 | 最大迭代次数,哪怕损失函数依旧大于0 |
tol | float or None,默认=10^(-3) | 迭代停止的标准。如果不为None,那么当loss-pre-loss<tol的时候,就会停止迭代。因为当前迭代造成的损失函数下降太小了,迭代下去对loss影响不大了。 |
shuffle | bool值,默认=True | 每轮训练后是否打乱数据 |
verbose | 取值为整数,默认=0 | verbose = 0 为不在标准输出流输出日志信息,verbose = 1 为输出进度条记录;verbose = 2 为每个epoch输出一行记录 |
eta0 | 取值双精度浮点型double,默认=1 | 学习率,决定梯度下降时每次参数变化的幅度 |
n_jobs | 取值为 int or None,默认=None | 在多分类时使用的CPU数量,默认为None(或1),若为-1则使用所有CPU |
random_state | 取值为int, RandomState instance or None,默认=None | 当 shuffle =True时,用于打乱训练数据 |
n_iter_no_change | 取值int,默认=5 | 在提前停止之前等待验证分数无改进的迭代次数,用于提前停止迭代 |
early_stopping | 取值bool值,默认=False | 当验证得分不再提高时是否设置提前停止来终止训练。若设置此项,当验证得分在n_iter_no_change轮内没有提升时提前停止训练 |
class_weight | 取值为dict, {class_label: weight} 或者 “balanced”或者None,默认=None | 用于拟合参数时,每一类的权重是多少。当为None时,所有类的权重为1,等权重;当为balanced时,某类的权重为该类频数的反比,当为字典时,则key为类的标签,值为对应的权重 |
warm_start | 取值为bool,默认=False | 若为True则调用前一次设置的参数,使用新设置的参数 |
4.2.2sklearn.linear_model.Perceptron属性解释
属性名称 | 属性的类型 | 属性解释 |
---|---|---|
classes_ | array 一维数组,shape=(k,) ,k为y的类别数量 | 放着y所有分类的数组,如感知机是array([-1., 1.]) |
coef_ | array 二维数组 | 输出训练后的模型参数w的数组,不包含截距项b。当为二分类时,该数组shape=(1,n),n为特征数量。当为多分类时shape=(k, n) |
intercept_ | array 一维数组 | 输出训练后的模型截距b的数组。当为二分类时,该数组shape=(1,)。当为多分类时shape=(k, ) |
loss_function_ | 损失函数的类别 | 即用的哪种损失函数来定义模型输出值与真实值之间的差异 |
n_iter_ | 整数 | 即模型停止时共迭代的次数 |
t_ | 整数 | 模型训练时,权重w更新的总次数,等于n_iter_*样本数量 |
4.2.3sklearn.linear_model.Perceptron实战
from sklearn.linear_model import Perceptron
perceptron = Perceptron(fit_intercept=True, max_iter=1000, shuffle=True)
perceptron.fit(X, y) # 默认学习率为1
w = perceptron.coef_[0] # ,注意输出的是二维数组,加上[0]后, w=[ 23.2 -38.7]
b = perceptron.intercept_ # b=-5
用拟合好的模型画出图形
参考:李航博士《统计学习方法》
二分类线性分类模型感知机从原理到实例就讲完了,请大家多多指教,谢谢阅读。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)