本篇文章主要从感知机的公式推导以及代码实现两个方面讲起。
首先我们来介绍下感知机的定义:感知机是二分类的线性分类模型,输入为实例的特征向量,输出为实例的类别(取+1和-1)。感知机对应于输入空间中将实例划分为两类的分离超平面。因此,感知机旨在求出该超平面,为求得超平面我们这里引入了基于误分类的损失函数,利用梯度下降法对损失函数进行最优化。
知道了感知机的基本定义之后,接下来我们需要来了解下感知机模型。
感知机模型可以这样定义:假设输入空间(特征向量)是,输出空间为,输入表示实例的特征向量,对应于输入空间的点,输出表示实例的类别,则由输入空间到输出空间的表达形式为
其中上面该函数称为感知机,其中w,b称为模型的参数,称为权值,b称为偏置,w*x表示为w和x的内积。
并且符号函数为:
这里我网上找了张图来阐述下感知机线性方程(也可以称为超平面)如何将不同的两类区分开来:
在这张图中我们可能不理解为什么w为超平面的法向量(可以参考下面这个证明)
此外,为了能更好的区分正负样本我们需要找到一个超平面去将正负样本分离开来,那如何找到这样的一个超平面呢,这是我们在这里需要思考的。
在机器学习中,我们首先需要去找损失函数,然后通过损失函数转化为最优问题,利用梯度下降方法进行参数的更新,最后获得我们需要的最优参数。
在感知机模型中,我们同样也需要通过寻找损失函数来确定我们这个超平面,如果我们单纯的来通过更新误分类点的总数是无法求得最佳参数(w、b)因为我们的感知机线性方程为:
w*x + b = 0,所以我们需要转换思路,那该怎么弄。我们可以通过误分类点到超平面的距离来寻找最佳参数w、b。
那么点到超平面的距离公式为:
这里我网上找了一位博主写的公式推导(原文链接),顺便我自己也在这里推导下吧(可能有些地方说的不准确请多多体谅)
如图所示,倾斜的直线为超平面S,w为超平面S的法向量,X1为超平面一点,X0为X1在超平面S上的投影,所以我们可以看到w和向量X0X1平行,
所以可得:(模的乘积等于乘积的模)
则:
又因为:
且X0在超平面S上,所以有w*X0 + b = 0
故:
综上所述,可得:(这里b+wx1外面忘了加一个绝对值)
化简下公式可得:
好了,下面我们需要思考的是我们该如何区分误分类点?
书本给了一个公式:
所以一个误分类点到超平面的距离为:
那么所有误分类点到超平面的距离为:
书上说了句:
这句话我也不填了解,知乎上有位兄台是这样说的:
所以我们也可以找到损失函数:
然后我们对w和b求梯度可得:
最后更新参数w和b:
至此,我们也完成了感知机公式推导,下面我们开始编码实现感知机模型吧。(如果觉得我推导的你不是很理解这里有个视频可以参考下:手推感知机)
1.准备数据集(这里我们使用的是鸢尾花数据集)
这里我们可以通过写几句代码查看下鸢尾花数据集里面的数据是啥。
from sklearn.datasets import load_iris
# 加载数据集(这里使用的是鸢尾花数据集)
iris = load_iris()
print(iris)
格式有点乱,总体概括下就是包含特征值以及标签值这两大类,其中特征值主要包含:花萼长度(sepal length)、花萼宽度(sepal width)、花瓣长度(petal length)、花瓣宽度(petal width) 标签值主要为:0、1、2
这样看起来不方便,我们就对数据进行一个小小的处理吧。
2.数据处理
对于上面的数据,我们需要的是特征值以及标签值。所以下面我们通过代码来提取我们需要的数据。
import pandas as pd
from sklearn.datasets import load_iris
# 加载数据集(这里使用的是鸢尾花数据集)
iris = load_iris()
# 将iris数据集中的特征属性提取出来
df = pd.DataFrame(iris.data,columns=iris.feature_names)
print(df)
运行结果如下:
然后我们在提取标签值:
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
# 加载数据集(这里使用的是鸢尾花数据集)
iris = load_iris()
# 将iris数据集中的特征属性提取出来
df = pd.DataFrame(iris.data,columns=iris.feature_names)
# 将iris中的target作为df的一列
df['label'] = iris.target
print(df)
运行结果如下:
我们可以看到上面的运行结果的每一列的列名看起来不是很规范,所以我们自己来修改下:
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
# 加载数据集(这里使用的是鸢尾花数据集)
iris = load_iris()
#print(iris)
# 将iris数据集中的特征属性提取出来
df = pd.DataFrame(iris.data,columns=iris.feature_names)
# 将iris中的target作为df的一列
df['label'] = iris.target
# 给每一列添加备注
# 行列数据标注
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
print(df)
运行结果如下:
很好,达到我们的要求了,接下来我们需要选取sepal length(花萼长度)和sepal width(花萼宽度)以及label(标签值)作为我们训练的数据,这样做的目的就是为了方便实验,我们将speal length作为x,sepal width 作为y对应坐标轴上不就是(x,y)。通过选取的列我们可视化下他们在坐标轴上的分布
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
# 加载数据集(这里使用的是鸢尾花数据集)
iris = load_iris()
#print(iris)
# 将iris数据集中的特征属性提取出来
df = pd.DataFrame(iris.data,columns=iris.feature_names)
# 将iris中的target作为df的一列
df['label'] = iris.target
# 给每一列添加备注
# 行列数据标注
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
# 数据可视化展示
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0') #选取sepal length和sepal width的前50行数据,并将他们归为标签0
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1') # 选取sepal length和sepal width的后50行数据,并将他们归为标签1
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
运行结果:
好了,可以看到他们的分布大致是这样的,接下来我们就需要去寻找一个超平面将他们分散,在找超平面之前我们需要找到最优参数w和b
3.提取数据
我们分别将需要的数据特征值以及标签提取出来,并将标签集中不是1的标签设置为-1
# 提取数据的特征和标签(提取第0、1以及最后一列数据的前100行)
data = np.array(df.iloc[:100, [0, 1, -1]])
# 提取特征值
x = data[:,:-1]
# 提取标签集
y = data[:,-1]
# 将标签集中的0变为-1
y = np.array([1 if i == 1 else -1 for i in y])
4.定义感知机模型
class Model:
# 初始化参数
def __init__(self):
# 初始化权重
self.w = np.ones(len(data[0]) - 1,np.float32) # len(data[0]) = 3
# 初始化偏置
self.b = 0
# 初始化步长
self.lr = 0.01
# 定义符号函数
def sign(self,x,w,b):
y = np.dot(x,w) + b
return y
# 梯度下降
def gradent(self,x,y):
is_wrong = False
while not is_wrong:
wrong_count = 0
for i in range(len(x)):
x_train = x[i]
y_train = y[i]
# 判断是否为误分类点
if y_train * self.sign(x_train,self.w,self.b) <= 0:
# 更新参数
self.w = self.w + self.lr * np.dot(y_train,x_train)
self.b = self.b + self.lr * y_train
# 统计误分类点个数
wrong_count += 1
# 如果没有误分类点,设置为true
if wrong_count == 0 :
is_wrong = True
# 计算准确率
accruRate = 1 - (wrong_count / len(x) )
# 返回更新后的参数
return self.w,self.b,accruRate
上面的代码如果你看了我的公式推导,并且我还有详细的注释,你是能看懂的。
5.测试
# 创建实例类
per = Model()
# 获取更新后的参数w,b
w,b,accruRate= per.gradent(x,y)
print('accuracy rate is:', accruRate)
# 创建超平面
x_points = np.linspace(4, 7, 10) # 将4到7分成10等分,这个主要根据数据集的大小来划分的
y_points = -(w[0] * x_points + b) / w[1] # 主要根据距离公式求得y
# 可视化超平面
plt.plot(x_points,y_points)
# 可视化展示
plt.plot(data[:50, 0], data[:50, 1], 'bo', color='blue', label='0') # 前50行归为0
plt.plot(data[50:100, 0], data[50:100, 1], 'bo', color='orange', label='1') # 后50行归为1
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
运行结果:
我觉得上面你们可能这句代码看不懂:
y_points = -(w[0] * x_points + b) / w[1] # 主要根据距离公式求得y
这个y_points如何来寻找,这里主要是根据公式:wx + b = 0来推导出来 的(这里的w和x都是向量)
对应我们之间的特征值如下:
之前我们不是说了将speal length作为x,sepal width 作为y对应坐标轴上不就是(x,y),所以这里的X1也就是坐标轴上的y
然后公式可以变换下:
将式子化简下,所以可得:
以上就是我个人对感知机算法的一个肤浅的理解,有啥不对的请在评论区交流交流。