机器学习:感知机模型(perceptron)
简介
感知机是二类分类问题的线性分类模型,输入为实例的特征向量,输出为实例的类别,取+1和-1值
定义
假设输入空间(特征空间)是
X
∈
R
n
X \in 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 ∗ x + b = 0 w*x + b = 0 w∗x+b=0对应于特征空间 R n R^n Rn的一个超平面S(关于超平面),这个平面将特征空间分为了两个部分,将处于特征空间中的点分为了正,负两类。其中 w w w是这个超平面的“斜率”, b b b是“截距”(尽管 w w w和 b b 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∈Myi(w∗xi+b),这个损失函数就是感知机学习的经验风险函数,直观上看,当一个点 x i x_i xi是负分类的,那么 y i ( w ∗ x i + b ) < 0 y_i(w*x_i + b)<0 yi(w∗xi+b)<0,因为误分类点的 w ∗ x i + b w*x_i + b w∗xi+b和 y i y_i yi的符号一定是相反的。因此损失函数是非负的,误分类点越少,损失函数的值便越小。感知机的学习策略便是在训练过程中使用随机梯度下降法不断更新 w w w和 b b b的值,使得损失函数最小
感知机学习算法
原始形式
整个学习过程是对以下最优化问题(极小化问题)的求解:
m
i
n
w
,
b
L
(
w
,
b
)
=
m
i
n
w
,
b
∑
x
i
∈
M
y
i
(
w
∗
x
i
+
b
)
min_{w, b}L(w, b) = min_{w, b}\sum_{x_i \in M}y_i(w*x_i + b)
minw,bL(w,b)=minw,b∑xi∈Myi(w∗xi+b)
先求损失函数的梯度: (倒三角的markdown代码是\nabla)
∇
w
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
x
i
∇
b
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
\nabla_wL(w, b) = -\sum_{x_i\in M}y_ix_i \\ \nabla_bL(w, b) = -\sum_{x_i \in M}y_i
∇wL(w,b)=−xi∈M∑yixi∇bL(w,b)=−xi∈M∑yi
则整个学习的步骤是:
- 选取初值 w 0 , b 0 w_0, b_0 w0,b0
- 在训练集中选取误分类点 ( 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
,
b
w, b
w,b:
w ← w + η y i x i b ← b + η y i w\leftarrow w + \eta y_ix_i \\ b\leftarrow b + \eta y_i w←w+ηyixib←b+ηyi
其中 η \eta η称为学习率(learning rate),决定学习的快慢/精确与否。 - 直到训练集中没有误分类点(不是所有情况都满足,算法提升在支持向量机)
对偶形式
在原始形式中,我们假设初始参数为
w
0
=
0
,
b
0
=
0
w_0 = 0, b_0 = 0
w0=0,b0=0,然后通过:
w
←
w
+
η
y
i
x
i
b
←
+
η
y
i
w \leftarrow w + \eta y_ix_i \\ b \leftarrow + \eta y_i
w←w+ηyixib←+ηyi
逐步修改
w
,
b
w, b
w,b,假设修改了n次,则最后学习到的
w
,
b
w,b
w,b 可以分别表示为:
w
=
∑
i
=
1
N
α
i
y
i
x
i
b
=
∑
i
=
1
N
α
i
y
i
w = \sum_{i=1}^N \alpha_i y_i x_i b = \sum_{i=1}^N \alpha_i y_i
w=i=1∑Nαiyixib=i=1∑Nαiyi
则对偶形式的感知机模型可以表示为:
f
(
x
)
=
s
i
g
n
(
∑
j
=
1
N
α
j
y
j
x
j
∗
x
+
b
)
f(x) = sign(\sum_{j=1}^N \alpha_j y_j x_j * x + b)
f(x)=sign(j=1∑Nαjyjxj∗x+b)
则判断分类错误的条件变为:
y
i
(
∑
j
=
1
N
α
j
y
j
x
j
∗
x
i
+
b
)
<
=
0
y_i(\sum_{j=1}^N \alpha_j y_j x_j * x_i + b) <= 0
yi(j=1∑Nαjyjxj∗xi+b)<=0
更新参数的方法变为:
α
i
←
α
i
+
η
\alpha_i \leftarrow \alpha_i + \eta
αi←αi+η
b
←
b
+
η
y
i
b \leftarrow b + \eta y_i
b←b+ηyi
这里预先将
x
x
x的内积
x
j
x
i
x_jx_i
xjxi计算出来,用一个矩阵(Gram矩阵)表示,便可以加速训练过程,不用每次判断误分类的时候都计算一次内积。这里也是对偶形式的算法比原始形式更加优化的地方。
G
i
,
j
=
x
i
∗
x
j
G_{i, j} = x_i * x_j
Gi,j=xi∗xj
代码实现
核心代码perceptron.py
- 原始形式
import numpy as np
import pandas as pd
class Perceptron:
def __init__(self, dimension):
self.w = np.zeros((1, dimension))
self.b = 0
self.step = 0.5
def model(self, x):
# t_x = np.transpose(x)
# print(t_x)
return 1 if np.dot(self.w, x) + self.b >= 0 else -1
def classify_fault(self, node):
# t_x = np.transpose(node[0:2])
return (np.dot(self.w, node[0:2]) + self.b) * node[2] <= 0
def update(self, x, y):
self.w = self.w + self.step * y * x
self.b = self.b + self.step * y
def learn(self, data_set):
fault_nodes = []
while True:
for node in fault_nodes:
self.update(node[0:2], node[2])
fault_nodes[:] = []
for node in data_set:
if self.classify_fault(node):
fault_nodes.append(node)
if not fault_nodes:
break;
def get_parameter(self):
return self.w[0], self.b
- 对偶形式
class Perceptron_antithesis:
def __init__(self, data_set):
self.b = 0
self.step = 0.5
self.data_set = data_set
self.nums = data_set.shape[0]
self.alpha = np.zeros(self.nums)
self.G = np.zeros((self.nums, self.nums))
for i in range(self.nums):
x_i = self.data_set[i][0:2]
for j in range(self.nums):
x_j = self.data_set[j][0:2]
self.G[i][j] = np.dot(x_i, x_j)
self.x = self.data_set[:, 0:2]
self.y = self.data_set[:, 2]
def modle(self, x):
l = [np.dot(self.x[i], x)*self.y[i]*self.alpha[i] for i in range(self.nums)]
f_x = np.sum(l) + self.b
return 1 if f_x >= 0 else -1
def classify_fault(self, i):
l = [self.alpha[j]*self.y[j]*self.G[j, i] for j in range(self.nums)]
f_x = np.sum(l) + self.b
return self.y[i] * f_x <= 0
def update(self, i):
self.alpha[i] = self.alpha[i] + self.step
self.b = self.b + self.step*self.y[i]
def learn(self):
fault_nodes = []
while True:
for i in fault_nodes:
self.update(i)
fault_nodes[:] = []
for i in range(self.nums):
if self.classify_fault(i):
fault_nodes.append(i)
if not fault_nodes:
break;
def get_parameter(self):
l_w = [self.alpha[i]*self.y[i]*self.x[i] for i in range(self.nums)]
w = np.sum(l_w, axis=0)
return w, self.b
数据可视化`data_view.py
import matplotlib.pyplot as plt
def draw(data_set, parameter):
x_1 = data_set[:, 0]
x_2 = data_set[:, 1]
color = data_set[:, 2]
fig = plt.figure()
plt.scatter(x_1, x_2, c=color)
w, b = parameter[0], parameter[1]
#draw a classifying line
#x_1 = 0
t_x_2 = (-b) / w[1]
#x_2 = 0
t_x_1 = (-b) / w[0]
plt.plot([0, t_x_1], [t_x_2, 0])
plt.show()
实验效果
由于对测试的数据由限制,分类效果还算不错,生成数据的代码如下:
def get_data_set_random(num_node):
data_set = np.random.random((num_node, 3)) * 10
for i in range(num_node):
data_set[i,2] = 1 if data_set[i, 0] ** 2 + data_set[i, 1] ** 2 <= 50 else -1
return data_set
实验效果图:
MD搞了一晚上,最后发现是图画错了…