1、代码
1、算法描述
本博客使用感知器算法来实现鸢尾花数据的分类。因为感知器映射结果为1和-1,为了让感知器能够正常运行,我们舍去了映射类型为0的鸢尾花数据,保留了映射类型为1和-1的鸢尾花数据。
2、测试数据csv
数据格式如下:
sepallength sepalwidth petallength petalwidth class
5.1 3.5 1.4 0.2 Iris-setosa
4.9 3 1.4 0.2 Iris-setosa
4.7 3.2 1.3 0.2 Iris-setosa
4.6 3.1 1.5 0.2 Iris-setosa
5 3.6 1.4 0.2 Iris-setosa
5.4 3.9 1.7 0.4 Iris-setosa
4.6 3.4 1.4 0.3 Iris-setosa
5 3.4 1.5 0.2 Iris-setosa
4.4 2.9 1.4 0.2 Iris-setosa
为了大家能够更好的学习KNN算法,我将这个csv文件放到我博客的资源中,通过下面的链接即可下载,注意在将下面的文件放到代码中运行时,注意修改代码中的文件路径。
https://download.csdn.net/download/weixin_43334389/13130463
3、代码
import numpy as np
import pandas as pd
# 感知器算法
data = pd.read_csv(r"dataset/iris.arff.csv", header=0)
if data.duplicated().any(): # 重复值
data.drop_duplicates(inplace=True) # 删除重复值
print(data["class"].value_counts()) # 计算每个类别的数量
# 因为感知器映射结果为1和-1,这样映射为了和感知器预测的结果相符
data["class"] = data["class"].map({"Iris-versicolor": 0, "Iris-setosa": -1, "Iris-virginica": 1}) # 类别名称映射为数字
# 将class列中不等于0的数据给筛选出来
data = data[data["class"] != 0]
class Perception:
"""
@desc: 感知器算法实现。二分类
"""
def __init__(self, learning_rate, times):
"""
@desc:初始化
@param learning_rate:学习率
@param times:迭代次数
"""
self.learning_rate = learning_rate
self.times = times
def step(self, z):
"""
@desc: 阶跃函数
@param z:数组类型(或者是标量) 阶跃函数参数。将z映射为1或者-1
@return:int z>0返回1.z<0返回1
"""
# 通用方法:对数值或者数组的计算返回
return np.where(z > 0, 1, -1)
def fit(self, X, y):
"""
@desc:根据提供的数据,对模型进行训练
@param X:特征矩阵,可以是List也可以是Ndarray,形状为: [样本数量,特征数量]
@param y:标签数组
@return:
"""
X = np.asarray(X)
y = np.asarray(y)
# 创建权重向量。初始值为0。长度比特征多1.多出的是截距
# X.shape (80, 4)
# X.shape(1) 4
print("X.shape", X.shape)
print("X.shape(1)", X.shape[1])
# 1+4 列数据
self.w_ = np.zeros(1 + X.shape[1])
# 创建损失列表,用来保存每次迭代后的损失值
self.loss_ = []
# 循环指定的次数
for i in range(self.times):
# 感知器与逻辑回归的区别:逻辑回归中。使用所有样本计算梯度来更新权重。
# 而感知器是使用单个样本,依次计算梯度更新权重
# 记录每个样本产生的损失值,最后将每个样本的损失值进行一个累加,然后将其加到损失列表当中
loss = 0
# x表示矩阵的一行数据,一个样本一个样本进行计算
# 每一次循环80次
for x, target in zip(X, y):
# 计算预测值,此时np.dot为计算内积
y_hat = self.step(np.dot(x, self.w_[1:]) + self.w_[0])
# 如果预测值不等于目标值,返回1,loss+1,否 则loss不增加
loss += (y_hat != target)
# 更新权重
# w(j) = w(j) + 学习率 * 0.5*(真实值-预测值)*x(j)
self.w_[0] += self.learning_rate * 0.5 * (target - y_hat)
# [6.3 3.3 6. 2.5]
self.w_[1:] += self.learning_rate * 0.5 * (target - y_hat) * x
# 将循环累计误差值增加到误差列表中,用于后面的误差函数的绘制
self.loss_.append(loss)
def predit(self, X):
"""
@desc:预测函数
@param X:特征矩阵,可以是List也可以是Ndarray,形状为: [样本数量,特征数量]
@return:数组类型, 分类值[1或-1]
"""
# 使用矩阵进行运算
return self.step(np.dot(X, self.w_[1:]) + self.w_[0])
t1 = data[data["class"] == 1]
t2 = data[data["class"] == -1]
t1.sample(len(t1), random_state=0)
t2.sample(len(t2), random_state=0)
train_X = pd.concat([t1.iloc[:40, :-1], t2.iloc[:40, :-1]], axis=0)
train_y = pd.concat([t1.iloc[:40, -1], t2.iloc[:40, -1]], axis=0)
test_X = pd.concat([t1.iloc[40:, :-1], t2.iloc[40:, :-1]], axis=0)
test_y = pd.concat([t1.iloc[40:, -1], t2.iloc[40:, -1]], axis=0)
p = Perception(0.1, 10)
p.fit(train_X, train_y)
result = p.predit(test_X)
# 输出预测结果
# [ 1 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 -1]一共有17个数据
print("result", result)
print("test_y.values", test_y.values)
# 模型训练的权重
# [-0.1 -0.25 -0.34 0.78 0.44]
print("p.w_", p.w_)
# 每次迭代的损失值,第三次之后就预测正确了。
# [3, 2, 0, 0, 0, 0, 0, 0, 0, 0]
print("p.loss_", p.loss_) # 可以看出每次迭代后损失值就下降了
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False # 显示负号
# 绘制真实值
# 绿色圆圈表示真实值
plt.plot(test_y.values, "go", ms=15, label="真实值")
# 红色❌表示预测值
plt.plot(result, "rx", ms=15, label="预测值")
plt.title("感知器二分类")
plt.xlabel("样本序号")
plt.ylabel("类别")
plt.legend()
plt.show()
# 绘制目标函数损失值
plt.plot(range(1, p.times + 1), p.loss_, "o-")
plt.show()
4、结果展示
(1)感知器二分类图
可以看到17个测试数据都进行了正确的分类。
(2)目标函数损失值函数
&emsp可以看到,在训练模型的过程中,在第三次迭代后错误率为0,表明已经训练好了感知器模型,这也刚好印证了感知器二分类图中测试数据集能够正确分类的原因。
2、分析
1、编写sign(x)函数
np.where(condition,x,y) :当where内有三个参数时,第一个参数表示条件,当条件成立时where方法返回x,当条件不成立时where返回y。
return np.where(z > 0, 1, -1)
# 举例
z = np.array([-1, 4, 6])
#使用下面的list类型的,会报错: TypeError: '>' not supported between instances of 'list' and 'int'
# z = [-1, 4, 6]
#输出 [-1 1 1]
print(np.where(z > 0, 1, -1))
2、添加感知器模型参数权值向量w和偏置b
# 其中w_[0]表示偏置b
self.w_ = np.zeros(1 + X.shape[1])
# 产生的数据类型如下,一行n列
# [0. 0.]
print(np.zeros(2))
3、处理一行数据
# x表示矩阵的一行数据,一个样本一个样本进行计算
# 因为X和y都有80行,因此,下面的循环为80次
for x, target in zip(X, y):
# zip函数用于传入多个参数,举例如下:
name = ['jack', 'beginman', 'sony']
age = [2001, 2003, 2005]
# 循环三次,输出如下:
# jack 2001
# beginman 2003
# sony 2005
for n, a in zip(name, age):
print(n, a)
4、使用np.dot()函数计算内积
# 计算预测值,此时np.dot为计算内积
y_hat = self.step(np.dot(x, self.w_[1:]) + self.w_[0])
# 举例如下:
x1 = [1, 2, 3]
x2 = [4, 5, 6]
# 32
print(np.dot(x1, x2))
# 当传进来的数据是一个矩阵是,按照矩阵乘法正常计算
test4 = np.array([[1, 1],
[0, 1]])
test5 = np.array([[0, 1],
[2, 3]])
# [[2 4]
# [2 3]]
test_doc = np.dot(test4, test5)
5、更新权重因子和偏置
# w(j) = w(j) + 学习率 * 0.5*(真实值-预测值)*x(j)
# 当真实值和预测值相等时,权重和偏置不更新;反之,以步长为1进行更新。
self.w_[0] += self.learning_rate * 0.5 * (target - y_hat)
self.w_[1:] += self.learning_rate * 0.5 * (target - y_hat) * x