感知器算法实现鸢尾花数据的分类

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值