感知机:教你用Python一步步实现

感知器

问题描述

感知器是二分类的线性分类模型。输入为分类对象的特殊诊断向量,输出为%5Cpm%201,用于区分分类对象的类型。这有点抽象,所以这里有一个例子。
感知机:教你用Python一步步实现
如上图,

  • 上图对应的例子就是每个点
  • 实例的特征向量指的是这些点的横坐标和纵坐标,我们记为%28x_1%2C%20x_2%29%5ET
  • 我们根据每个点的颜色将点标记为1-1,这就是我们的输出y

使用这些已知坐标的红蓝点,我们需要训练以下模型,
感知机:教你用Python一步步实现
该模型共有3参数%28%5Ctheta_0%2C%20%5Ctheta_1%2C%20%5Ctheta_2%29,使其能够实现以下功能:

  1. y%3D%7B%5Crm%20sign%7D%28%5Ctheta_1%20x_1%20%2B%20%5Ctheta_2%20x_2%20%2B%20%5Ctheta_0%29%3D%2B1时,我们知道该点是红色的。
  2. y%3D%7B%5Crm%20sign%7D%28%5Ctheta_1%20x_1%20%2B%20%5Ctheta_2%20x_2%20%2B%20%5Ctheta_0%29%3D-1时,我们知道该点是蓝色的。


%5Cbegin%7Baligned%7D%20%7B%5Crm%20sign%7D%28x%29%20%3D%20%5Cleft%5C%7B%5Cbegin%7Baligned%7D%20%2B1%2C%26x%20%5Cgeq%200%20%5C%5C%20-1%2C%26x%3C0%20%5Cend%7Baligned%7D%5Cright.%20%5Cend%7Baligned%7D

数据集构建

在开始之前,我们需要自己的整个数据集进行训练。
先导入一些需要的包

import numpy as np
import matplotlib.pyplot as plt
import random
from typing import List, Tuple

然后是时候构建我们​​的数据集了。

# 随机生成一些点,并根据直线将点划分为2个区域
def sample_point(w: float, b: float, num: int) -> Tuple[List[List[float]], List[float]]:
    x, y = [], []
    for _ in range(num):
        p_x1 = np.random.random_sample(1) * 20 - 10
        p_x2 = np.random.random_sample(1) * 20 - 10
        p_y = 1 if w * p_x1 + b - p_x2 > 0 else -1
        x.append([p_x1, p_x2])
        y.append(p_y)

    return x, y

# 先随机生成一条直线
w_ideal = np.random.random_sample(1) * 10 - 5
b_ideal = np.random.random_sample(1) * 10 - 5

x = np.linspace(-10, 10, 1000)
line_ideal = w_ideal * x + b_ideal
# 搭建数据集
sample_x, sample_y = sample_point(w_ideal, b_ideal, 500)

为了更直观,我们可以用matplotlib可视化这些点

# 可视化
plt.xlim(xmax=-10, xmin=10)
plt.ylim(ymax=-10, ymin=10)
plt.plot(x, line_ideal, 'g', linewidth=10)
for i, p_x in enumerate(sample_x):
    if sample_y[i] == 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)
plt.show()

我们会得到下面的图片,
感知机:教你用Python一步步实现
绿线是实际中可以区分红蓝点的直线。

接下来我们要做的就是在代码中假装不知道这一行的参数,即w_idealb_ideal,看看能否从数据集中得到我们估计的参数,即w_estb_ideal
(可能有人要问了,我们上面不是说三个参数%28%5Ctheta_0%2C%20%5Ctheta_1%2C%20%5Ctheta_2%29吗?怎么又变成了估计两个参数?别急,后面会介绍)。

模型训练

模型训练的理论支持

回到我们的问题,如何根据点的横纵坐标对点的颜色进行分类?

为了能够实现这个预测功能,我们知道我们需要训练3参数%28%5Ctheta_0%2C%20%5Ctheta_1%2C%20%5Ctheta_2%29
假设我们现在有这样一组参数%28%7B%5Ctheta%7D_0%27%2C%7B%5Ctheta%7D_1%27%2C%20%7B%5Ctheta%7D_2%27%29,如何衡量这组参数的好坏?如果这组参数不够好,我们该如何优化这些参数呢?

因此,我们需要定义一个损失函数来衡量这个参数的好坏,并利用损失函数的梯度来最小化损失函数。

损失函数的定义

直观地说,一组好的参数不应该错误分类一个点,因此使用错误分类点的数量作为损失函数是一个合理的想法。那么误分类点有哪些特点呢?
y_i%20%5Ccdot%20%28%5Csum_%7Bj%3D1%7D%20%5Ctheta_%7Bj%7D%20x_%7Bij%7D%20%2B%20%5Ctheta_0%29%20%5Cleq%200
对于第i个样本,

  • 当样本点为蓝色时,y_i%3D-1,被误归为红色,即%5Csum_%7Bj%3D1%7D%20%5Ctheta_%7Bj%7D%20x_%7Bij%7D%20%2B%20%5Ctheta_0%20%5Cgeq%200
  • 当样本点为红色时,y_i%3D%2B1被误归为蓝色,即%5Csum_%7Bj%3D1%7D%20%5Ctheta_%7Bj%7D%20x_%7Bij%7D%20%2B%20%5Ctheta_0%20%5Cleq%200

总之,我们的损失函数定义为
%5Cbegin%7Baligned%7D%20%5Cmathcal%7BL%7D%28%5Ctheta%29%20%26%3D%20-%5Csum_%7Bx_i%20%5Cin%20M%7D%20y_i%20%5Ccdot%20%28%5Csum_%7Bj%3D1%7D%20%5Ctheta_%7Bj%7D%20x_%7Bij%7D%20%2B%20%5Ctheta_0%29%20%5C%5C%20%26%3D%20-%5Csum_%7Bx_i%20%5Cin%20M%7D%20y_i%20%5Ccdot%20%28%5Ctheta%20x_i%29%20%5Cend%7Baligned%7D
其中,M是误分类点的集合,%5Ctheta%20x_i%20%3D%20%5Csum_%7Bj%3D0%7D%20%5Ctheta_%7Bj%7D%20x_%7Bij%7D%EF%BC%8C%20x_%7Bi0%7D%3D1

使用损失函数优化参数

感知器学习算法是错误分类驱动的,特别是使用随机梯度下降。我们首先随机选择一组参数%5Ctheta,然后使用梯度下降不断最小化目标函数。

  • 这里的最小化过程不是一次将所有错误分类点的梯度下降,而是一次随机选择一个错误分类点,使其梯度下降
  • %5Cbigtriangledown_%5Ctheta%20%5Cmathcal%7BL%7D%28%5Ctheta%29%20%3D%20-%5Csum_%7Bx_i%5Cin%20M%7D%20y_ix_i
  • 随机选择一个误分类点,利用该点优化参数
  • %5Ctheta%20%5Cleftarrow%20%5Ctheta%20%2B%20%5Ceta%20y_i%20x_i
  • %5Ceta是学习率,取值范围是%5B0%2C1%5D

Python代码的实现

def perceptron(x, y, lr, t) -> Tuple[np.ndarray, List[int]]:
	"""
	x: 点坐标
	y: 理想输出,+1 或 -1
	lr: learning rate, 学习率
	t: 参数优化次数
	返回:训练完的参数,每次优化前误分类点的个数
	"""
	# 初始化参数
    theta = np.zeros((len(x[0])+1, 1))
    error_list = []  # 误分点列表

    # 开始训练
    for _ in range(t):
        error_count = 0
        error_index = []
        for i, x_i in enumerate(x):
            y_i = theta[0] * x_i[0] + theta[1] * x_i[1] + theta[2]
            # 如果该点被分类错误
            if y_i * y[i] <= 0:
                error_index.append(i)
                error_count += 1
            # print(theta)
        error_list.append(error_count)
        # 随机选取一个误分类点进行参数优化
        if error_count > 0:
            i = random.choice(error_index)
            theta[0] += lr * y[i] * x[i][0]
            theta[1] += lr * y[i] * x[i][1]
            theta[2] += lr * y[i] * 1

    return theta, error_list

调用perceptron函数可以完成我们感知器的训练,得到一组合适的参数%5Ctheta,我们可以将其转换成直线参数,转换公式如下:

  • w_%7Best%7D%20%3D%20-%20%5Ctheta_0%20/%20%5Ctheta_1
  • b_%7Best%7D%20%3D%20-%20%5Ctheta_2%20/%20%5Ctheta_1

并与我们的理想线参数进行比较(如果样本点较少,可能与理想线有较大差距。那是因为对于这个样本,区分红蓝点的线不是唯一的)。

然后我们可视化数据,代码如下:

# 根据数据集得到参数
theta, error_list = perceptron(sample_x, sample_y, 0.5, 100)

# 可视化
plt.rcParams['figure.figsize'] = (12.0, 4.0)
plt.subplot(121)
plt.xlim(xmax=-10, xmin=10)
plt.ylim(ymax=-10, ymin=10)
# plt.plot(x, y_ideal)
for i, p_x in enumerate(sample_x):
    if sample_y[i] == 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)
# 将 theta 转换为直线参数,绘制图像
w_est = - theta[0] / theta[1]
b_est = - theta[2] / theta[1]
print("the estimation of parameter are \n", w_est, "\n", b_est)
y_est = w_est * x + b_est
plt.plot(x, y_est, 'g', linewidth=10)

plt.subplot(122)
plt.plot(np.arange(len(error_list)), error_list, 'g+-')
plt.show()

得到下面的图片
感知机:教你用Python一步步实现
可以看出,绿线很好地将红点和蓝点分开。
如果性能不好(意味着最后仍然有大量点被错误分类),可以通过修改学习率和优化次数得到更准确的模型。

完整的实验代码

import numpy as np
import matplotlib.pyplot as plt
import random
from typing import List, Tuple


def sample_point(w: float, b: float, num: int) -> Tuple[List[List[float]], List[float]]:
    x, y = [], []
    for _ in range(num):
        p_x1 = np.random.random_sample(1) * 20 - 10
        p_x2 = np.random.random_sample(1) * 20 - 10
        p_y = 1 if w * p_x1 + b - p_x2 > 0 else -1
        x.append([p_x1, p_x2])
        y.append(p_y)

    return x, y


def perceptron(x, y, lr, t) -> Tuple[np.ndarray, List[int]]:

    theta = np.zeros((len(x[0])+1, 1))
    error_list = []  # 误分点列表

    # 开始训练
    for _ in range(t):
        error_count = 0
        error_index = []
        for i, x_i in enumerate(x):
            y_i = theta[0] * x_i[0] + theta[1] * x_i[1] + theta[2]
            # 如果该点被分类错误
            if y_i * y[i] <= 0:
                error_index.append(i)
                error_count += 1
            # print(theta)
        error_list.append(error_count)
        if error_count > 0:
            i = random.choice(error_index)
            theta[0] += lr * y[i] * x[i][0]
            theta[1] += lr * y[i] * x[i][1]
            theta[2] += lr * y[i]

    return theta, error_list


def all_code():
    # 生成散点图
    w_ideal = np.random.random_sample(1) * 10 - 5
    b_ideal = np.random.random_sample(1) * 10 - 5
    print("the ideal parameter are \n", w_ideal, "\n", b_ideal)

    x = np.linspace(-10, 10, 1000)
    # line_ideal = w_ideal * x + b_ideal
    # 搭建数据集
    sample_x, sample_y = sample_point(w_ideal, b_ideal, 500)

    # 根据数据集得到参数
    theta, error_list = perceptron(sample_x, sample_y, 0.5, 100)

    # 可视化
    plt.rcParams['figure.figsize'] = (12.0, 4.0)
    plt.subplot(121)
    plt.xlim(xmax=-10, xmin=10)
    plt.ylim(ymax=-10, ymin=10)
    # plt.plot(x, y_ideal)
    for i, p_x in enumerate(sample_x):
        if sample_y[i] == 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[0] / theta[1]
    b_est = - theta[2] / theta[1]
    print("the estimation of parameter are \n", w_est, "\n", b_est)
    y_est = w_est * x + b_est
    plt.plot(x, y_est, 'g', linewidth=10)

    plt.subplot(122)
    plt.plot(np.arange(len(error_list)), error_list, 'g+-')
    plt.show()


if __name__ == '__main__':
    all_code()

参考

  • “统计学习方法”
  • Implementing the Perceptron Algorithm in Python

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年3月14日
下一篇 2022年3月14日

相关推荐