感知机实现鸢尾花三分类

任务

  • 了解感知机的原理并能够实现
  • 感知机是一个二元分类模型,通过其完成三分类任务
  • 从数据集四个特征中选择两个合适的特征来实现分类,并评估模型的准确性和F1分数
  • 将结果可视化:绘制数据和感知机超平面的散点图

感知机原理

感知机(Perceptron)由两层神经元组成,如图所示,输入层接收外界输入信号后传递给输出层,输出层是M-P神经元,亦称“阈值逻辑单元”(threshold logic unit)。

感知机的学习规则非常简单,对训练样例 ( x , y ) (x,y) (x,y),若当前感知机的输出为 y ^ \hat{y} y^,则感知机权重将这样调整
ω i ← ω i + Δ ω i Δ ω i = η ( y − y ^ ) x i \omega _i\leftarrow \omega _i + \Delta \omega _i\\ \Delta\omega _i=\eta(y-\hat{y})x_i ωiωi+ΔωiΔωi=η(yy^)xi
其中 η ∈ ( 0 , 1 ) \eta\in(0,1) η(0,1)称为学习率(learning rate)。可以看出,若感知机对训练样例 ( x , y ) (x,y) (x,y)预测正确,即 y ^ = y \hat{y}=y y^=y则感知机不发生变化,否则将根据错误的程度进行权重调整。

需要注意的是,感知机只有输出层神经元进行激活函数处理,即只拥有一层功能神经元,其学习能力非常有限。若两类模式是线性可分的,即存在一个线性超平面能够将它们分开,则感知机的学习过程一定会收敛(converge)而求得适当的权向量 ω = ( ω 1 ; ω 2 , ⋯   , ω n + 1 ) \omega=(\omega_1;\omega_2,\cdots,\omega_{n+1}) ω=(ω1;ω2,,ωn+1);否则感知机学习过程将会发生振荡, ω \omega ω难以稳定下来,不能求得合适解。

多分类学习

考虑 N N N个类别 C 1 , C 2 , … , C N C_1,C_2,…,C_N C1,C2,,CN,多分类学习的基本思路是“拆解法”,即将多分类任务拆为若干个二分类任务求解

具体来说,先对问题进行拆分,然后为拆分的每个二分类任务训练一个分类器;在测试时,对这些分类器的预测结果进行集成以获得最终的多分类结果。

最经典的拆分策略有三种:“一对一”(One vs. One,简称OvO)、“一对其余”(One vs. Rest,简称OvR)和“多对多”(Many vs. Many,简称MvM)。

给定数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x m , y m ) } , y i ∈ { C 1 , C 2 , … C N } D=\{(x_1,y_1),(x_2,y_2),…,(x_m,y_m)\},y_i\in\{C_1,C_2,…C_N\} D={(x1,y1),(x2,y2),,(xm,ym)},yi{C1,C2,CN}

  1. OvO将这 N N N个类别两两配对,从而产生 N ( N − 1 ) / 2 N(N-1)/2 N(N1)/2个二分类任务。在测试阶段,新样本将同时提交给所有分类器,于是将得到 N ( N − 1 ) / 2 N(N-1)/2 N(N1)/2个分类结果,最终结果可通过投票产生。
  2. OvR则是每次将一个类的样例作为正例、所有其他类的样例作为反例来训练 N N N个分类器。在测试时若仅有一个分类器预测为正类,则对应的类别标记作为最终分类结果。若有多个分类器预测为正类,则通常考虑各分类器的预测置信度,选择置信度最大的类别标记为分类结果。
  3. MvM是每次将若干个类别作为正类,若干个其他类作为反类。显然OvO和OvR是MvM的特例。MvM的正、反类构造必须有特殊的设计,不能随意选取,最常用的MvM技术:“纠错输出码”(Error
    Correcting Output Codes,简称ECOC)

三分类代码实现

导入模块

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

数据处理

数据归一化(normalization):将数据映射到0~1范围之内。该方法实现对原始数据等比例缩放,通过利用变量取值的最大值和最小值将原始数据转换为介于某一特定范围的数据,从而消除量纲和数量级影响:
X i − X m i n X m a x − X m i n \frac{X_i-X_{min}}{X_{max}-X_{min}} XmaxXminXiXmin

def normalization(data):
    _range = np.max(data) - np.min(data)
    return (data - np.min(data)) / _range

将csv格式文件导入python转换为ndarray格式

def get_data(path_1, path_2):
    df_train = pd.read_csv(path_1)  # 打开csv文件(训练集)
    df_test = pd.read_csv(path_2)  # 打开csv文件(测试集)
    X_train = df_train.iloc[0:90, [2, 3]].values  # 取第三个和第四个特征作为输入
    X_test = df_test.iloc[0:60, [2, 3]].values  # 取第三个和第四个特征作为输入

    X_train = normalization(X_train)  # 训练集归一化
    X_test = normalization(X_test)  # 测试集归一化

    X_train_1 = X_train  # 一号感知机的训练集
    X_train_2 = X_train[30:90, :]  # 二号感知机的训练集

    # 一号感知机的训练集标签
    Y_train_1 = df_train.iloc[0:90, [-1]].values
    Y_train_1[0:30, [0]], Y_train_1[30:90, [0]] = -1, 1

    # 二号感知机的训练集标签
    Y_train_2 = df_train.iloc[30:90, [-1]].values
    Y_train_2[0:30, [0]], Y_train_2[30:60, [0]] = -1, 1

    # 将两个感知机的X,Y数据水平拼接
    train_data_1 = np.hstack((X_train_1, Y_train_1))
    train_data_2 = np.hstack((X_train_2, Y_train_2))

    # 测试集的标签
    Y_test = df_test.iloc[0:60, [-1]].values
    Y_test[0:20, [0]], Y_test[20:40, [0]], Y_test[40:60, [0]] = 0, 1, 2

    # 测试集的X,Y数据水平拼接
    test_data = np.hstack((X_test, Y_test))

    return train_data_1, train_data_2, test_data

自定义感知机算法

class Perceptron():
    """自定义感知机算法"""

    def __init__(self, learning_rate=0.01, num_iter=50, random_state=1):
        self.learning_rate = learning_rate  # 学习率
        self.num_iter = num_iter  # 迭代次数
        self.random_state = random_state  # 随机数种子
        self.error_list = []  # 分类错误个数统计列表
        # 通过标准差为0.01的正态分布初始化权重
        rgen = np.random.RandomState(self.random_state)
        self.theta = rgen.normal(loc=0.0, scale=0.01, size=3)

    def fit(self, data):
        """更新权重"""
        # 循环遍历更新权重
        for _ in range(self.num_iter):
            error_count = 0  # 分类错误统计
            grad_1, grad_2, grad_3 = 0, 0, 0  # 梯度初始化
            for i, d in enumerate(data):
                x_i = d[0:2]  # 数据中的x
                target = d[2]  # 数据中的y
                # 分类正确不更新,分类错误更新权重
                y_i = self.predict(x_i)  # 预测值
                if y_i * target <= 0:
                    # 对所有分类错误点的梯度进行求和
                    grad_1 += data[i, -1] * data[i, 0]
                    grad_2 += data[i, -1] * data[i, 1]
                    grad_3 += data[i, -1]
                    error_count += 1
            # 更新参数
            self.theta[0] += self.learning_rate * grad_1
            self.theta[1] += self.learning_rate * grad_2
            self.theta[2] += self.learning_rate * grad_3
            # 将该迭代回合中的分类错误个数添加进列表
            self.error_list.append(error_count)

        return self

    def predict_input(self, X):
        """计算预测值"""
        return np.dot(X, self.theta[0:2]) + self.theta[-1]

    def predict(self, X):
        """得出sign(预测值)即分类结果"""
        return np.where(self.predict_input(X) >= 0.0, 1, -1)

可视化

def show_result_1(data_1, data_2, theta_1, theta_2 , error_list_1, error_list_2):

    x = np.linspace(-10, 10, 1000)
    plt.rcParams['figure.figsize'] = (12.0, 4.0)  # 设置图形尺寸
    plt.subplot(121)  # 将整个图像窗口分为1行2列,当前为位置为1
    plt.xlim(xmax=1.25, xmin=0)  # 设置x轴的数值显示范围
    plt.ylim(ymax=0.5, ymin=-0.2)  # 设置y轴的数值显示范围

    for i, p_x in enumerate(data_1):  # 遍历数据集
        if p_x[-1] == -1:  # 若标签为1
            plt.scatter(p_x[0], p_x[1], c='r', alpha=0.3)  # 生成红色散点
        # else:
        #     plt.scatter(p_x[0], p_x[1], c='b', alpha=0.3)  # 生成蓝色散点
    w_est = - theta_1[0] / theta_1[1]  # 斜率
    b_est = - theta_1[2] / theta_1[1]  # 截距

    y_est = w_est * x + b_est  # 回归直线
    plt.plot(x, y_est, 'g', linewidth=4)  # 画出直线

    for i, p_x in enumerate(data_2):  # 遍历数据集
        if p_x[-1] == -1:  # 若标签为1
            plt.scatter(p_x[0], p_x[1], c='g', alpha=0.3)  # 生成红色散点
        else:
            plt.scatter(p_x[0], p_x[1], c='b', alpha=0.3)  # 生成蓝色散点
    w_est = - theta_2[0] / theta_2[1]  # 斜率
    b_est = - theta_2[2] / theta_2[1]  # 截距


    y_est = w_est * x + b_est  # 回归直线
    plt.plot(x, y_est, 'g', linewidth=4)  # 画出直线

    plt.subplot(122)  # 将整个图像窗口分为1行2列,当前为位置为2
    plt.plot(np.arange(len(error_list_1)), error_list_1, 'g+-')
    plt.plot(np.arange(len(error_list_2)), error_list_2, 'r+-')
    plt.show()

测试

def test(data, theta_1, theta_2):
    right_count = 0
    error_count = 0
    for i, d in enumerate(data):
        x_i = d[0:2]
        target = d[2]
        y_1_i = theta_1[0] * x_i[0] + theta_1[1] * x_i[1] + theta_1[2]
        if y_1_i < 0:  # 预测为’setosa‘
            if target == 0:
                right_count += 1
            else:
                error_count += 1
        else:  # 进入第二个感知机
            y_2_i = theta_2[0] * x_i[0] + theta_2[1] * x_i[1] + theta_2[2]
            if y_2_i < 0:
                if target == 2:
                    right_count += 1
                else:
                    error_count += 1
            else:
                if target == 1:
                    right_count += 1
                else:
                    error_count += 1

    print("正确率为:", right_count/(right_count + error_count))
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值