感知机是神经网络的前身,学习神经网络,当燃不可避免的要先从感知机开始啦!网络对感知机原理进行讲解的博文数不胜数。有的文章图文结合,旁征博引,引经据典。令人赞叹。笔者也不认为能够写得比别人好。因此,本文仅介绍简单的原理过程和转化为代码实现的过程。如果想看详细、严谨的引入推导等等,请移步其他博文。
我们的目标当燃是要找到一个适合直线 w x + b = 0 wx+b=0 wx+b=0,来使下面的两堆数据能够正确分类开来啦
在定义损失函数之前,先来看一下y的定义:
y i = { 1 , w x i + b ≥ 0 − 1 , w x i + b < 0 (1) y_i = \left\{ \begin{matrix} {1,~~wx_i + b \geq 0} \\ {- 1,~~wx_i + b < 0} \\ \end{matrix} \right.\tag{1} yi={1, wxi+b≥0−1, wxi+b<0(1)
公式(1)中代表y的标签值可以取值为两类,其对应的 w x i + b wx_i+b wxi+b如果大于0,则对应的y值为1。反之则为-1。
显而易见,对于样本值和标签值中,下面的式子无论y是1还是-1。下面的式子始终成立:
y i ( w x i + b ) > 0 (2) y_i(wx_i+b)>0\tag{2} yi(wxi+b)>0(2)
但在训练的时候,一开始模型没有收敛,那么就无法保证式子(2)就一定成立,会出现 y i ( w x i + b ) < 0 y_i(wx_i+b)<0 yi(wxi+b)<0的情况,我们将这种样本标记为误分类样本。so?
如何找到一条合适的直线能够正确地将其分类开来,最朴素的想法当燃是使每一个被错误分类的点到该直线的距离总和越小越好。当所有的点都能被正确分类时,那么该距离就为0。因此,我们利用点到直线的距离公式就可以便可:
d = ∣ w x + b ∣ ∣ ∣ w ∣ ∣ 2 (3) d=\frac{|wx+b|}{||w||_2}\tag{3} d=∣∣w∣∣2∣wx+b∣(3)
对于误分类点 y i ( w x i + b ) < 0 y_i(wx_i+b)<0 yi(wxi+b)<0,所以
− y i ( w x i + b ) = ∣ w x i + b ∣ > 0 (4) -y_i(wx_i+b)=|wx_i+b|>0\tag{4} −yi(wxi+b)=∣wxi+b∣>0(4)
我们说过,y的取值时-1和1。所以对于被误分类的点。式子(4)是成立滴。
所以我们的损失函数就出来啦!
L ( w ) = − ∑ j = 1 m y j ( w x j + b ) ∣ ∣ w ∣ ∣ 2 = − 1 ∣ ∣ w ∣ ∣ 2 ∑ j = 1 m y j ( w x j + b ) (5) \begin{equation} \begin{aligned} L(w)&=-\sum\limits_{j=1}^m \frac{y_j(wx_j+b)}{||w||_2} \\&=-\frac{1}{||w||_2}\sum\limits_{j=1}^m y_j(wx_j+b) \end{aligned} \end{equation}\tag{5} L(w)=−j=1∑m∣∣w∣∣2yj(wxj+b)=−∣∣w∣∣21j=1∑myj(wxj+b)(5)
其中,m代表假设有m个被错误分类的点。但是,还没完。在一些文章甚至在李航老师的统计机器学习中直接说此处不考虑 1 ∣ ∣ w ∣ ∣ 2 \frac{1}{||w||_2} ∣∣w∣∣21,直接变成
L ( w ) = − ∑ j = 1 m y j ( w x j + b ) (6) L(w)=-\sum\limits_{j=1}^m y_j(wx_j+b)\tag{6} L(w)=−j=1∑myj(wxj+b)(6)
为什么可以忽略掉 1 ∣ ∣ w ∣ ∣ 2 \frac{1}{||w||_2} ∣∣w∣∣21?实际上,对于式子(5),它被称为几何间隔。而式子(6)则叫函数间隔。将这个去掉,最终很大可能会导致最终的w与不去掉的时候不一样的。但是为什么还是可以去掉呢?事实上,在一个模型中,我们的解总不是唯一的,而是存在很多的解析解,而我们的 1 ∣ ∣ w ∣ ∣ 2 \frac{1}{||w||_2} ∣∣w∣∣21,实际上就是对向量的归一化,我们可以将其视作为该损失函数的一个条件。因此,去掉 1 ∣ ∣ w ∣ ∣ 2 \frac{1}{||w||_2} ∣∣w∣∣21就相当于去掉这个这个条件,当我们正确分类所有点时,因为损失函数的值为0,所以这个 1 ∣ ∣ w ∣ ∣ 2 \frac{1}{||w||_2} ∣∣w∣∣21存在与否,笔者觉得都不重要。
以上只是从当前算法得角度去出发。要是原理的话,笔者在知乎上看到的一个大佬的用例子讲解,我觉得很是生动形象。
有了损失函数直接求梯度然后利用梯度下降就可以求解啦!
在此之前,先对x,y,w做一下定义。
x = ( x 1 T ⋮ x n T ) = ( x 11 ⋯ x 1 p ⋮ ⋱ ⋮ x n 1 ⋯ x n p ) n ∗ p ; y = ( y 1 ⋮ y n ) n ∗ 1 ; w = ( w 1 T ⋮ w p T ) p ∗ 1 (7) x = \begin{pmatrix} {x_{1}}^T \\ \vdots \\ {x_{n}}^T \\ \end{pmatrix} = \begin{pmatrix} x_{11} & \cdots & x_{1p} \\ \vdots & \ddots & \vdots \\ x_{n1} & \cdots & x_{np} \\ \end{pmatrix}_{n*p};y = \begin{pmatrix} y_{1} \\ \vdots \\ y_{n} \\ \end{pmatrix}_{n*1};w = \begin{pmatrix} w_{1}^T \\ \vdots \\ w_{p}^T \\ \end{pmatrix}_{p*1}\tag{7} x= x1T⋮xnT = x11⋮xn1⋯⋱⋯x1p⋮xnp n∗p;y= y1⋮yn n∗1;w= w1T⋮wpT p∗1(7)
其中p代表维度。为了方便理解,我们直接将b写入w之中。
x = ( x 1 T ⋮ x n T ) = ( x 11 ⋯ x 1 p 1 ⋮ ⋱ ⋮ ⋮ x n 1 ⋯ x n p 1 ) n ∗ ( p + 1 ) ; w = ( w 1 T ⋮ w p T b ) ( p + 1 ) ∗ 1 (8) x = \begin{pmatrix} {x_{1}}^T \\ \vdots \\ {x_{n}}^T \\ \end{pmatrix} = \begin{pmatrix} x_{11} & \cdots & x_{1p} & 1\\ \vdots & \ddots & \vdots & \vdots\\ x_{n1} & \cdots & x_{np} & 1\\ \end{pmatrix}_{n*(p+1)};w = \begin{pmatrix} w_{1}^T \\ \vdots \\ w_{p}^T \\ b \end{pmatrix}_{(p+1)*1}\tag{8} x= x1T⋮xnT = x11⋮xn1⋯⋱⋯x1p⋮xnp1⋮1 n∗(p+1);w= w1T⋮wpTb (p+1)∗1(8)
下面对损失函数求导求梯度,设其为GD
G D = ∂ L ( w ) ∂ w = ∂ ∂ w ( − ∑ j = 1 m y j ( w x j ) ) = − ∑ j = 1 m x j y j = − ( x 1 x 2 ⋯ x m ) ( y 1 y 2 ⋮ y m ) = − x T y (9) \begin{equation} \begin{aligned} GD=\frac{\partial L(w)}{\partial w}&= \frac{\partial }{\partial w}\left ( -\sum\limits_{j=1}^m y_j(wx_j) \right )\\ &=-\sum\limits_{j=1}^m x_jy_j\\ &=-\begin{pmatrix} x_1 & x_2 \cdots x_m \end{pmatrix} \begin{pmatrix} y_1 \\ y_2 \\ \vdots \\ y_m \end{pmatrix}\\ &=-x^Ty \end{aligned} \end{equation}\tag{9} GD=∂w∂L(w)=∂w∂(−j=1∑myj(wxj))=−j=1∑mxjyj=−(x1x2⋯xm) y1y2⋮ym =−xTy(9)
然后直接利用梯度下降法就可以啦!(迭代求解,所以需要不断更新w的值,直到模型收敛,不了解梯度下降建议去了解一下)
w t = w t − 1 − λ G D w_t=w_{t-1}-λGD wt=wt−1−λGD
其中GD就是公式(9)求的梯度。 w t − 1 w_{t-1} wt−1是前一时刻的w值,当前的w值即为 w t w_t wt
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
plt.ion()
class Perceptron():
def __init__(self,x,y):
#因为要对将b写入w之中,因此,此处对x增加一个全为1的维度
self.x=np.insert(x,x.shape[1],1,axis=1)
self.y=y
#获取x的维度信息
self.m,self.n=self.x.shape
#初始化w
self.w=np.ones([self.n,1])
def train(self,epoch):
'''
:param epoch: 训练轮次
:return:
'''
for i in range(epoch):
#计算w*x*y
p=(self.x@self.w)*self.y
#获取小于0的那一部分的索引
index=np.argwhere(p<0)[:,0]
#计算小于0的那一部分的损失
loss=-p[p<0].sum()
#绘制实时训练过程
plot_figure(self.x,self.y,self.w)
#计算梯度
GD=-self.x[index,:].T@self.y[index,:]
#更新梯度,其中0.001为lambda步长
self.w-=0.001*GD
print(loss)
def predict(self,x):
'''
:param x: 样本
:return:
'''
#增加一个维度
x = np.insert(x, x.shape[1], 1, axis=1)
#计算结果
result=x@self.w
#令大于0的全部等于1
result[result>=0]=1
#小于0则为-1
result[result<0]=-1
return result
def plot_figure(x,y,w=np.array([])):
'''
功能:绘图
:param x: 样本值
:param y: 标签值
:param w: 权值
:return:
'''
if w.size!=0:
map_color = {-1: "r", 1: "g"}
color = [map_color[i] for i in y.squeeze()]
plt.cla()
x2=-(w[0]*x[:,0]+w[2])/w[1]
plt.scatter(x[:, 0], x[:, 1], c=color)
plt.plot(x[:,0],x2)
plt.pause(0.01)
else:
map_color={-1:"r",1:"g"}
color=[ map_color[i] for i in y.squeeze()]
plt.scatter(x[:,0],x[:,1],c=color)
plt.show()
def main():
#生成x1和对应的分类为-1
data_x1=stats.norm.rvs(0,1,(100,2))
y1=np.ones([data_x1.shape[0],1])*-1
#生成x2和对应的分类为1
data_x2=stats.norm.rvs(6,1,(100,2))
y2=np.ones([data_x2.shape[0],1])
x=np.concatenate([data_x1,data_x2],axis=0)#合并x
y=np.concatenate([y1,y2],axis=0)#合并y
model=Perceptron(x,y)#初始化模型
model.train(100)#训练
plt.close()
plt.ioff()
result=model.predict(x)#预测
plot_figure(x,result)#绘制预测结a果
if __name__ == '__main__':
main()