感知机(Perceptron) 由Rosenblatt在1957年提出,是神经网络和支持向量机的基础。
方法 | 适用问题 | 模型特点 | 模型类型 | 学习策略 | 学习的损失函数 | 学习算法 |
---|---|---|---|---|---|---|
感知机 | 二类分类 | 分离超平面 | 判别模型 | 极小化误分点到超平面距离 | 误分点到超平面距离 | 随机梯度下降算法 |
感知机是《统计学习方法》第二章的内容,也是机器学习众多方法中的第一个。在大三下学期备考《统计学习方法》这门课程时,给同学讲过这书的一些方法,其中就有感知机,当时通过课本公式结合案例进行推到,没有涉及编程。本博客添加了感知机模型的程序。
目录
1 感知机的原理
感知机是二分类的线性模型。假设训练数据集是线性可分的,感知机学习的目标是求得一个能够将训练数据集正实例点和负实例点完全正确分开的分离超平面。感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式。
感知机的输入是实例的特征向量,输出的是事例的类别,分别是+1和-1,属于判别模型。可以用神经网络中的感知机模型来描述一下,下图是感知机的模型图。
图中,n 维向量[a1,a2,…,an]的转置作为感知机的输入,[w1,w2,…,wn]的转置为输入分量连接到感知机的权重(weifht),b为偏置(bias),f(.)为激活函数,t 为感知机的输出(即类别)。t 的数学表示为:
t
=
f
(
∑
i
=
1
n
w
i
x
i
+
b
)
=
f
(
w
T
x
)
t = f\left( {\sum\limits_{i = 1}^n {{w_i}{x_i} + b} } \right) = f\left( {{w^T}x} \right)
t=f(i=1∑nwixi+b)=f(wTx)
这里的 f(.) 用的是符号函数:
f
(
n
)
=
{
+
1
x
≥
0
−
1
x
≤
0
f\left( n \right) = \left\{ \begin{array}{l}+1{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} x \ge 0\\-1{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} x \le 0 \end{array} \right.
f(n)={+1x≥0−1x≤0
符号函数是非连续的,不光滑的,符号函数只是激活函数的一种。
1.1 点到线的距离
设直线方程为
A
x
+
B
y
+
C
=
0
Ax + By + C = 0
Ax+By+C=0,点
P
P
P的坐标为
(
x
0
,
y
0
)
\left( {{x_0},{y_0}} \right)
(x0,y0),点到直线的距离:
d
=
A
x
+
B
y
+
C
A
2
+
B
2
d = \frac{{Ax + By + C}}{{\sqrt {{A^2} + {B^2}} }}
d=A2+B2Ax+By+C
1.2 样本到超平面距离
在感知机中,一般把超平面方程写为:
w
x
+
b
=
0
wx + b = 0
wx+b=0。w 为超平面的法向量,b 是超平面的截距,超平面把数据分为两类。样本点到超平面的距离可以写成:
d
=
w
⋅
x
‘
+
b
∥
w
∥
d = \frac{{w \cdot {x^‘} + b}}{{\left\| w \right\|}}
d=∥w∥w⋅x‘+b
1.3 超平面(Hyperplanes)
对于超平面方程:
w
x
+
b
=
0
wx + b = 0
wx+b=0 可以将类别分为正、负两类。因此,超平面S也称为分离超平面(separating byperplane),如下图所示。
感知机学习,由训练数据集(实例的特征向量及类别)
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
x
n
,
y
n
)
}
T = \left\{ {\left( {{x_1},{y_1}} \right),\left( {{x_2},{y_2}} \right),...,\left( {{x_n},{y_n}} \right)} \right\}
T={(x1,y1),(x2,y2),...,(xn,yn)}
其中,x∈
R
n
R^n
Rn, y∈{+1,-1},i=1,2,…,N, 求得感知机模型,即求得模型参数w,b。感知机预测,通过学习得到的感知机模型,对于新的输入实例给出其对应的输出类别。
2 感知机模型
引入一个超平面方程后,需要确定一个学习策略,一个模型目标函数的评判标准,即定义 (经验)损失函数并将损失函数极小化作为目标函数的学习策略。
2.1 感知机的损失函数
我们首先定义对于任何样本 ( x i , y i ) \left( {{x_i},{y_i}} \right) (xi,yi),如果 w ⋅ x i + b > 0 w \cdot {x_i} + b > 0 w⋅xi+b>0,则记 y i y_i yi=+1,如果 w ⋅ x i + b < 0 w \cdot {x_i} + b < 0 w⋅xi+b<0则记 y i y_i yi=−1。
可以通过每个样本点 y i y_i yi与 w ⋅ x i + b w \cdot {x_i} + b w⋅xi+b的乘积值判断误分类点。
因为正确分类的样本满足
y
i
(
w
⋅
x
i
+
b
)
>
0
{y_i}(w \cdot {x_i} + b) > 0
yi(w⋅xi+b)>0,而错误分类的样本满足
y
i
(
w
⋅
x
i
+
b
)
<
0
{y_i}(w \cdot {x_i} + b) < 0
yi(w⋅xi+b)<0。我们损失函数的优化目标,就是期望使误分类的所有样本,到超平面的距离之和最小。所以损失函数定义如下:
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
(
w
⋅
x
i
+
b
)
∥
w
∥
L\left( {w,b} \right) = - \frac{{\sum\limits_{{x_i} \in M} {{y_i}(w \cdot {x_i} + b)} }}{{\left\| w \right\|}}
L(w,b)=−∥w∥xi∈M∑yi(w⋅xi+b)
其中M为超平面S的误分类点的集合。
在就不考虑前面的
1
∥
w
∥
\frac{1}{{\left\| w \right\|}}
∥w∥1后,得到感知机模型的损失函数:
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
(
w
⋅
x
i
+
b
)
L\left( {w,b} \right) = - \sum\limits_{{x_i} \in M} {{y_i}(w \cdot {x_i} + b)}
L(w,b)=−xi∈M∑yi(w⋅xi+b)
2.2 为什么不考虑 1 ∥ w ∥ \frac{1}{{\left\| w \right\|}} ∥w∥1?
不考虑
1
∥
w
∥
\frac{1}{{\left\| w \right\|}}
∥w∥1参数是因为:
1.
1
∥
w
∥
\frac{1}{{\left\| w \right\|}}
∥w∥1不影响误分类点的判断,只考虑损失函数的分子就可以完成对误分类点的判断。
2. 忽略
1
∥
w
∥
\frac{1}{{\left\| w \right\|}}
∥w∥1,不影响感知机学习算法的最终结果,也不会对感知机学习算法的执行过程产生任何影响。反而还能简化运算,提高算法执行效率。
3 感知机学习算法
感知机学习算法是对上述损失函数进行极小化,求得参数w和b。
在损失函数公式中,可以看出只有误分类的M集合里面的样本才能参与损失函数的优化。即需要遍历整个数据集,只随机选取一个误分类点进行参数更新,这就是随机梯度下降(SGD);基于所有样本的梯度和的均值的批量梯度下降法(BGD)是行不通的,原因在于我们的损失函数里面有限定,M集合里面的样本才能进行参数w和b的更新。
这里梯度是指损失函数在参数w和b上的偏导。例如:
设二元函数
z
=
f
(
x
,
y
)
z = f\left( {x,y} \right)
z=f(x,y)在平面区域D上具有一阶连续偏导数,则函数
f
f
f对于每一个点P(x,y)的梯度为:
g
r
a
d
f
(
x
,
y
)
=
∇
f
(
x
,
y
)
=
{
∂
f
∂
x
,
∂
f
∂
y
}
=
f
x
(
x
,
y
)
i
−
+
f
y
(
x
,
y
)
j
—
gradf\left( {x,y} \right) = \nabla f\left( {x,y} \right) = \left\{ {\frac{{\partial f}}{{\partial x}},\frac{{\partial f}}{{\partial y}}} \right\}= {f_x}\left( {x,y} \right)\mathop i\limits^ - + {f_y}\left( {x,y} \right)\mathop j\limits^ —
gradf(x,y)=∇f(x,y)={∂x∂f,∂y∂f}=fx(x,y)i−+fy(x,y)j—
梯度下降方向就是梯度的反方向,最小化损失函数 L(w,b) 就是先求函数在 w 和 b 两个变量轴上的偏导:
∇
w
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
x
i
∇
b
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
\begin{array}{l} \nabla_w L\left( {w,b} \right) = - \sum\limits_{{x_i} \in M} {{y_i}{x_i}} \\ \nabla_b L\left( {w,b} \right) = - \sum\limits_{{x_i} \in M} {{y_i}} \end{array}
∇wL(w,b)=−xi∈M∑yixi∇bL(w,b)=−xi∈M∑yi
当出现误分类点(x,y)时,对w,b进行更新:
w
←
w
+
η
y
i
x
i
b
←
b
+
η
y
i
\begin{array}{l} w \leftarrow w + \eta {y_i}{x_i}\\ b \leftarrow b + \eta {y_i} \end{array}
w←w+ηyixib←b+ηyi
式中
η
η
η(0<η≤1)是步长,在统计学习中又称为学习率(lerning rate)。这样,通过迭代可以修正参数w和b,期待损失函数L(w,b)不断减小,直到为0,得到超平面S。
3.1 原始形式算法及实现
在上述迭代过程中,当没有误分类点的时候,停止参数更新,所得的参数就是感知机学习的结果,这就是感知机的原始形式。原始形式算法也就是上述算法的过程,也是感知机基本的算法。
算法步骤:
(1)预先设定一个
w
0
w_0
w0和
b
0
b_0
b0,即 w 和 b 的初值。
(2)在训练集中选取数据(xi,yi)。
(3)当样本点是误分类点时,即
y
i
(
w
⋅
x
i
+
b
)
≤
0
{y_i}(w \cdot {x_i} + b) ≤ 0
yi(w⋅xi+b)≤0时,利用随机梯度下降算法进行参数更新。
(4)转至(2),直至训练集中没有误分类点。
算法实现过程:
import numpy as np
class Perceptron:
def __init__(self):
self.weights = None
self.bias = None
def sign(self, value): # 返回标签函数
return 1 if value >= 0 else -1
def train(self, data_set, labels): # 感知机训练
lr = 1 # 学习率
data_set = np.array(data_set)
n = data_set.shape[0] # 行:n
m = data_set.shape[1] # 列:m
weights = np.zeros(m) # 这里选择初始化权重(0,0)
bias = 0
i = 0
while i < n:
if (labels[i] * self.sign(np.dot(weights, data_set[i]) + bias) == -1): # 误分类点
weights = weights + lr * labels[i] * data_set[i] # 更新权重w
bias = bias + lr * labels[i] # 更新权重b
i = 0
else:
i += 1
self.weights = weights
self.bias = bias
def predict(self, data): # 用来预测,即计算:wx+b
if (self.weights is not None and self.bias is not None):
return self.sign(np.dot(self.weights, data) + self.bias)
else:
return 0
if __name__ == "__main__":
data_set = [[3, 3],
[4, 3],
[1, 1]]
labels = [1, 1, -1]
perceptron = Perceptron()
perceptron.train(data_set, labels)
print("权重w:", perceptron.weights)
print("偏量b:", perceptron.bias)
result = perceptron.predict([3, 3])
print("预测分类:", result)
测试结果:
权重w: [1. 1.]
偏量b: -3
预测分类: 1
初试w = (0, 0) ,b = 0超平面方程为:
x
(
1
)
+
x
(
2
)
−
3
=
0
{x^{\left( 1 \right)}} + {x^{\left( 2 \right)}} - 3 = 0
x(1)+x(2)−3=0
初试w = (0, 1), b = 0时测试结果为:
权重w: [0.5 0.5]
偏量b: -2
预测分类: 1
超平面方程为:
1
/
2
x
(
1
)
+
1
/
2
x
(
2
)
−
2
=
0
{1/2 x^{\left( 1 \right)}} + {1/2x^{\left( 2 \right)}} - 2= 0
1/2x(1)+1/2x(2)−2=0
可以看出,不同的初值会影响超平面的函数方程,但是不影响对预测点[3,3]的分类。
3.2 对偶形式算法及实现
对偶形式的基本想法是:将w和b表示为实例x和标记y的线性组合的形式,通过求解其系数而求得w和b,不失一般性。
由于w,b的梯度更新公式:
w
←
w
+
η
y
i
x
i
b
←
b
+
η
y
i
\begin{array}{l} w \leftarrow w + \eta {y_i}{x_i}\\ b \leftarrow b + \eta {y_i} \end{array}
w←w+ηyixib←b+ηyi
参数w,b经过了n次修改后的,参数可以变化为下公式,其中
α
i
=
n
i
η
\alpha_i = n_i\eta
αi=niη,则w, b关于(x,y)的增量分别是
α
i
y
i
x
i
\alpha_iy_ix_i
αiyixi和
α
i
y
i
\alpha_iy_i
αiyi,最后学习到的w,b为实例x和标记y的线性组合的形式,可以写成:
w
=
∑
i
=
1
N
α
i
y
i
x
i
b
=
∑
i
=
1
N
α
i
y
i
\begin{array}{l} w = \sum\limits_{i = 1}^N {{\alpha _i}} {y_i}{x_i}\\ b = \sum\limits_{i = 1}^N {{\alpha _i}} {y_i} \end{array}
w=i=1∑Nαiyixib=i=1∑Nαiyi
输入:训练数据集T=(
x
1
x_1
x1,
y
1
y_1
y1),(
x
2
x_2
x2,
y
2
y_2
y2),…,(
x
N
x_N
xN,
y
N
y_N
yN),
y
i
y_i
yi∈{−1,+1},学习率η(0<η<1)
输出:α,b;感知机模型
f
(
x
)
=
s
i
g
n
(
∑
j
=
1
n
α
i
y
i
x
i
⋅
x
i
+
b
)
f\left( x \right) = sign\left( {\sum\nolimits_{j = 1}^n {{\alpha _i}{y_i}{x_i} \cdot {x_i} + b} } \right)
f(x)=sign(∑j=1nαiyixi⋅xi+b)
算法步骤:
(1)赋初值
α
0
α_0
α0,
b
0
b_0
b0
(2)选取某个数据点(
x
1
x_1
x1,
y
1
y_1
y1)
(3)判断该数据点是否为当前模型的误分类点,即判断若
f
(
x
)
=
(
∑
j
=
1
n
α
i
y
i
x
i
⋅
x
i
+
b
)
≤
0
f\left( x \right) = \left( {\sum\nolimits_{j = 1}^n {{\alpha _i}{y_i}{x_i} \cdot {x_i} + b} } \right)≤0
f(x)=(∑j=1nαiyixi⋅xi+b)≤0则更新:
a
i
=
a
i
+
η
b
=
b
+
η
y
i
\begin{array}{l} {a_i} = {a_i} + \eta \\ b = b + \eta {y_i} \end{array}
ai=ai+ηb=b+ηyi
(4)转到(2),直到训练集中没有误分类点。
为了减少计算量,我们可以预先计算式中的内积,得到Gram矩阵:
G
=
[
x
i
,
x
j
]
N
×
N
G = {\left[ {{x_i},{x_j}} \right]_{N \times N}}
G=[xi,xj]N×N
算法实现过程:
import numpy as np
class PerceptronDual:
def __init__(self):
self.weights = None
self.bias = None
def sign(self, x): # 符号函数
return 1 if x >= 0 else -1
def train(self, data_set, labels):
lr = 1
n = np.array(data_set).shape[0] #列:行
data_set = np.mat(data_set)
alpha = np.zeros(n) # 错误点的总迭代次数是小于n的数
bias = 0
i = 0
while i < n:
#in this step, we elide gram matrix
if (labels[i] * self.sign(sum(alpha * labels * data_set * data_set[i].T)+bias) == -1): # 误分类点
alpha[i] = alpha[i] + lr # 更新权重a
bias = bias + lr * labels[i] # 更新权重b
i = 0
else:
i += 1
self.weights = sum(alpha * labels * data_set)
self.bias = bias
def predict(self, data):
data = np.array([data])
if (self.weights is not None and self.bias is not None):
return self.sign((self.weights * data.T) + self.bias)
else:
return 0
if __name__ == '__main__':
data_set = [[3, 3],
[4, 3],
[1, 1]]
labels = [1, 1, -1]
perceptron = PerceptronDual()
perceptron.train(data_set, labels)
print("权重a:", perceptron.weights)
print("偏量b:", perceptron.bias)
result = perceptron.predict([3, 3])
print("预测分类:", result)
测试结果:
权重a: [[1. 1.]]
偏量b: -3
预测分类: 1
初试w = (0, 0) ,b = 0超平面方程为:
x
(
1
)
+
x
(
2
)
−
3
=
0
{x^{\left( 1 \right)}} + {x^{\left( 2 \right)}} - 3 = 0
x(1)+x(2)−3=0
同时,样本[3, 3]分类的标签为:1
总结:
1 在向量维数(特征数)过高时,计算内积非常耗时,应选择对偶形式算法。
2 在向量个数(样本数)过多时,每次计算累计和就没有必要,应选择原始算法。
参考文献
[1] 周志华 《机器学习》
[2] 李航《统计学方法》