单层感知器
生物神经元
神经细胞结构大致可分为:树突、突触、细胞体及轴突。单个神经细胞可被视为一种只有两种状态的机器——激动时为‘是’,而未激动时为‘否’。神经细胞的状态取决于从其它的神经细胞收到的输入信号量,及突触的强度(抑制或加强)。当信号量总和超过了某个阈值时,细胞体就会激动,产生电脉冲。电脉冲沿着轴突并通过突触传递到其它神经元。为了模拟神经细胞行为,与之对应的感知机基础概念被提出,如权量(突触)、偏置(阈值)及激活函数(细胞体)。
感知器
受到生物神经网络的启发,计算机学家Frank Rosenblatt在20世纪60年代提出了一种模拟生物神经网络的的人工神经网络结构,称为感知器(Perceptron)。 由上述的神经元细胞一样,感知器也有两种状态,其本质是一个二分类的线性分类模型,输入为实例的特征向量,输出为实例的类别,取+1和-1二值。
x 1 … x n x_1\dots x_n x1…xn位n维输入向量的各个分量。
w 1 … w n w_1\dots w_n w1…wn为各个输入分量连接到感知器的权值(权重),其可以调整输入向量值的大小让输入信号变大(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=0∑nwixi=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,z≥0z<0
y = s i g n ( ∑ i = 0 n w i x i ) y=sign(\sum_{i=0}^nw_ix_i) y=sign(i=0∑nwixi)
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=η(t−y)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-")