首页 > 技术文章 > 单层感知器

kimj 2021-11-06 06:00 原文

单层感知器

生物神经元

神经细胞结构大致可分为:树突、突触、细胞体及轴突。单个神经细胞可被视为一种只有两种状态的机器——激动时为‘是’,而未激动时为‘否’。神经细胞的状态取决于从其它的神经细胞收到的输入信号量,及突触的强度(抑制或加强)。当信号量总和超过了某个阈值时,细胞体就会激动,产生电脉冲。电脉冲沿着轴突并通过突触传递到其它神经元。为了模拟神经细胞行为,与之对应的感知机基础概念被提出,如权量(突触)、偏置(阈值)及激活函数(细胞体)。

神经元图片生物_万图壁纸网

感知器

受到生物神经网络的启发,计算机学家Frank Rosenblatt在20世纪60年代提出了一种模拟生物神经网络的的人工神经网络结构,称为感知器(Perceptron)。 由上述的神经元细胞一样,感知器也有两种状态,其本质是一个二分类的线性分类模型,输入为实例的特征向量,输出为实例的类别,取+1和-1二值。
在这里插入图片描述

x 1 … x n x_1\dots x_n x1xn位n维输入向量的各个分量。

w 1 … w n w_1\dots w_n w1wn为各个输入分量连接到感知器的权值(权重),其可以调整输入向量值的大小让输入信号变大(w>0),不变(w=0)或者减小(w<0)。可以理解为生物神经网络中的信号作用,信号经过树突传递到细胞核的过程中信号会发生变化。

w 0 w_0 w0为偏置

第一步

这里z称为净输入(net input),它的值等于一个样本的每个维度值x与维度对应的权重值w相乘后的和。
z = w 0 x 0 + w 1 x 1 + w 2 x 2 + ⋯ + w n x n = ∑ i = 0 n w i x i = W T X z=w_0x_0+w_1x_1+w_2x_2+\dots+w_nx_n=\sum_{i=0}^nw_ix_i=W^TX z=w0x0+w1x1+w2x2++wnxn=i=0nwixi=WTX

第二步

其中,z称为净输入(net input)。然而,这样的计算结果是一个连续的值,而感知器的输出为+1和-1二值,因此,我们需要将结果转换为离散的分类值,这里我们使用一个转换函数,该函数称为激励函数(激活函数)。
s i g n ( z ) = { + 1 , z ≥ 0 − 1 , z < 0 sign(z)=\begin{cases} +1, &z\ge0\\ -1, &z\lt0\\ \end{cases} sign(z)={+1,1,z0z<0

y = s i g n ( ∑ i = 0 n w i x i ) y=sign(\sum_{i=0}^nw_ix_i) y=sign(i=0nwixi)

x i x_i xi表示第i个样本的第j个特征

第三步

感知器是一个自学习算法,即可以根据输入的数据,不断地调整权重的更新,最终完成分类。权重的更新公式如下:
w i = w i + Δ w i Δ w i = η ( t − y ) x i \begin{aligned} &w_i=w_i+Δw_i \\ &Δw_i=η(t-y)x_i \end{aligned} wi=wi+ΔwiΔwi=η(ty)xi
η为学习率,t为真实值, y y y为预测值

注:感知器的权重更新依据是:如果预测准确,则权重不进行更新,否则,增加权重,使其更趋向于正确的类别。

代码实现

简单实现

import numpy as np
import matplotlib.pyplot as plt

#输入数据
X = np.array([[1, 3, 3],
              [1, 4, 3],
              [1, 1, 1],
              [1, 0, 2]])

#标签
Y = np.array([[1],
             [1],
             [-1],
             [-1]])

#权值初始化,3行1列,取值范围-1到1
W = (np.random.random([3,1])-0.5)*2
print(W)

#学习率设置
lr = 0.11
#神经网络输出
O = 0

def update():
    global X,Y,W,lr
    O = np.sign(np.dot(X,W))
    #因为此处用的是矩阵,故X.T*(Y-O)的计算机结果是行乘列的和,故需要除以数据个数(也可不除)
    W_C = lr*(X.T.dot(Y-O))/X.shape[0]
    W = W + W_C
    
for i in range(100):
    update()#更新权值
    print(W)#打印当前权值
    print(i)#打印迭代次数
    O = np.sign(np.dot(X,W))#计算当前输出
    if(O == Y).all():#预测值全部等于真实值
        print('Finished')
        print('epoch',i)
        break

鸢尾花的分类

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 返回一个元组,进行元组拆包。
X, y = load_iris(return_X_y=True)
# 合并X与y。注意,在合并的时候,需要将y转换为二维数组。
data = pd.DataFrame(np.concatenate((X, y.reshape(len(y), -1)), axis=1))
# 鸢尾花数据集存在重复的记录,删除。
data.drop_duplicates(inplace=True)
len(data)
# 之所以映射为1与-1,是为了与感知器预测的值相符。
data[4] = data[4].map({0:1, 1:-1, 2:2})
# 将类别为2的鸢尾花数据过滤。
data = data[data[4] != 2]
# data.shape

class Perceptron:
    """使用Python语言实现感知器。进行二分类。"""
    
    def __init__(self, alpha, times):
        """初始化方法。
        
        Parameters
        -----
        alpha : float
            学习率。
        times : int
            最大迭代次数。
        """
        self.alpha = alpha
        self.times = times
        
    def step(self, z):
        """阶跃函数。
        
        Parameters
        -----
        z : 数组类型(或标量类型)。
            阶跃函数的参数。即感知器的净输入。
            
        Returns
        -----
        value : int
            如果z >= 0,返回1,否则返回-1。
        """
        return np.where(z >= 0, 1, -1)
    
    def fit(self, X, y):
        """根据提供的训练数据,对模型进行训练。
        
        Parameters
        -----
        X : 类数组类型。形状为[样本数量, 特征数量]
            待训练的样本特征属性。
        y : 类数组类型,形状为[样本数量]
            每个样本的目标值(标签)。
        """
        X = np.asarray(X)
        y = np.asarray(y)
        # 创建权重的向量,初始值为0,长度比特征数多1。(多出的一个值是截距)
        self.w_ = np.zeros(1 + X.shape[1])
        # 创建损失列表,用来保存每次迭代后的损失值。
        self.loss_ = []
        # 循环指定的次数。
        for i in range(self.times):
            # 定义每次循环的损失值,即预测错误的数量。
            loss = 0
            # 依次获取训练集中的每个样本(特征x与目标标签y)。
            for x, target in zip(X, y):
                # 计算预测值。
                y_hat = self.step(np.dot(x, self.w_[1:]) + self.w_[0])
                # 如果预测值与真实值不符,则增加误差。
                loss += y_hat != target
                # 计算更新的梯度值。计算方式为:δw(j) = 学习率 * (真实目标值 - 预测值) * x(j)
                # w += δw
                # 对权重进行更新。
                self.w_[0] += self.alpha * (target - y_hat)
                self.w_[1:] += self.alpha * (target - y_hat) * x
            # 将循环中累计的误差增加到误差列表中。
            self.loss_.append(loss)
            
    def predict(self, X):
        """根据参数传递的样本,对样本数据进行预测。
        
        Parameters
        -----
        X : 类数组类型,形状为[样本数量, 特征数量]
            待测试的样本特征(属性)
        
        Returns
        -----
        result : 数组类型。
            预测的结果(分类值)。
        """
        return self.step(np.dot(X, self.w_[1:]) + self.w_[0])
        
X, y = data.iloc[:, 0:4], data[4]
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.2, random_state=0)
p = Perceptron(0.1, 10)
p.fit(train_X, train_y)
result = p.predict(test_X)
display(result)
display(test_y.values)
display(p.w_)
display(p.loss_)
display(np.sum(result == test_y) / len(result))


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-")
.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-")

推荐阅读