一.前言
神经网络算法属于机器学习领域中比较热门的内容,而谈到神经网络算法的历史,就不得不提到M-P神经元和基于其构建的感知机模型,虽然该算法有巨大的不足,但是对于我们深入理解复杂的神经网络也是很有帮助的,为此本文聚焦于感知机模型的解读及其实现,话不多说,请看下文。
二.算法详解
2.1 神经元
在正式介绍感知机前,需要先了解神经元的概念。我们知道,生物神经网络是由一个个神经元构成的,每个神经元都具有多个树突和一个轴突,其中树突用来接受信息,轴突可以通过尾端的轴突末梢(即突触)跟其他神经元的树突连接进行信号的传递。
神经元之间的信息传递属于化学物质传递。当神经元兴奋时,其会向与其相连的神经元发送化学物质,从而改变这些神经元内的电位,当其电位超过一定的阈值后,神经元便会被激活,即兴奋起来,然后向其它神经元发送化学物质。
注:图1来源于神经元的维基百科。
1943年,McCulloch和Pitts参考生物的神经元结构,提出了M-P神经元模型。图2中展示的便是M-P神经元,从图中便可以看出,该模型可以接收其他神经元输入的信号,这些信号通过带权重的连接进传递,神经元接受到的总输入信号会与神经元的阈值进行比较,然后通过激活函数处理以产生神经元的输出。
注:图2来源于周志华《机器学习》的图5-1。
对于M-P神经元,其使用的激活函数为阶跃函数,其将输入映射为输出值0(神经元抑制)或1(神经元兴奋),其数学定义为:
sgn
(
x
)
=
{
1
,
x
≥
0
0
,
x
<
0
(1)
\text{sgn}(x) = \begin{cases} 1,x \geq 0 \\ 0, x < 0 \end{cases} \tag{1}
sgn(x)={1,x≥00,x<0(1)
阶跃函数的数学性质不太好,例如不连续,因此经常使用Sigmoid来进行替代。
2.2 感知机
感知机(Perceptron)模型包含两层神经元,即输入层和输出层(示例参见图3),其中输入层接受外界信号后传递给输出层,输出层为M-P神经元。
感知机模型的数学形式为:
y
=
f
(
∑
i
w
i
x
i
−
θ
)
(2)
y = f(\sum_{i}w_ix_i - \theta) \tag{2}
y=f(i∑wixi−θ)(2)
其中
f
f
f表示阶跃函数,
x
i
x_i
xi和
w
i
w_i
wi分别表示第
i
i
i个输入及其对应的输入权重,
θ
\theta
θ表示阈值。感知机模型中的权重参数
w
i
w_i
wi和阈值参数
θ
\theta
θ都可以通过学习得到,其中阈值
θ
\theta
θ可以看作一个固定输入为
−
1.0
-1.0
−1.0的哑结点所对应的连接权重。
感知机的学习规则为:对训练样例
(
x
,
y
)
(\bold{x},y)
(x,y),若当前感知机的输出为
y
^
\hat{y}
y^,则感知机将这样调整权重:
w
i
←
w
i
+
Δ
w
i
Δ
w
i
=
η
(
y
−
y
^
)
x
i
(3)
w_i \leftarrow w_i + \varDelta w_i \\ \varDelta w_i = \eta (y - \hat{y})x_i \tag{3}
wi←wi+ΔwiΔwi=η(y−y^)xi(3)
其中
η
∈
(
0
,
1
)
\eta \in (0,1)
η∈(0,1)被称之为学习率,从上述公式可知,当模型的预测正确时,
Δ
w
i
=
0
\varDelta w_i=0
Δwi=0,此时感知机将不发生变化,否则将根据错误的程度进行权重调整。
感知机只能处理线性可分的问题,这是其致命的缺陷,当年也正是关于这个缺陷的证明,使得深度学习进入第一个”寒冬“。
三.具体实现
3.1 数据集构造
为了可视化方便,本算法拟构造二维数据集,数据集中的样本分为两类,一类的标签为 1 1 1,另一类的标签为 0 0 0,下面是构造数据集的源码:
def generateDataset(num_samples, loc = 4, scale = 2, num_features = 2):
"""
生成二分类数据集
num_samples: 样本数
num_features: 样本的特征数
"""
size = num_samples // 2
# 生成样本
x1 = np.random.normal(loc, scale, (size, num_features))
x2 = np.random.normal(-loc, scale, (num_samples - size, num_features))
x = np.vstack((x1, x2))
# 生成标签
y = np.zeros(num_samples)
y[:size] = 1
# 打乱数据集
indices = np.arange(num_samples)
np.random.shuffle(indices)
x = x[indices]
y = y[indices]
return x,y
构造好数据集后,还对数据集进行了打乱操作。
3.2 模型实现、训练及结果
根据第二节中的描述,我们很容易就可以实现一个感知机模型:
class Perceptron():
def __init__(self,in_feats) -> None:
# + 1 means \theta
self.w = np.random.randn(1, in_feats + 1) * 0.01
def sgn(self,x):
"""
阶跃函数
"""
return int(x >= 0)
def forward(self,x):
"""
x: 样本 (num_features, 1)
"""
y_hat = np.dot(self.w,x)
return self.sgn(y_hat)
def update(self,y_hat,y,lr,x):
"""
权重调整
y_hat: 预测值
y: 真实标签
lr: 学习率
x: 样本 (num_features, 1)
"""
self.w += lr * (y - y_hat) * x.T
在实现的感知机模型中需要传入样本的特征数,在实验过程中,先调用数据集构造函数先生成一个包含1000个样本的二维点集,某次实验中生成的数据集可视化如下图4所示。
然后需要创建Perceptron
类的实例并进行模型的训练,实现的完整源码如下所示:
from data import generateDataset
import matplotlib.pyplot as plt
from model import Perceptron
import numpy as np
def train(model,x,y,lr,accuracy=None):
flag = True
epoch = 0
while flag:
epoch += 1
count = 0
for i in range(x.shape[0]):
y_hat = model.forward(x[i, :].reshape(-1, 1))
if y[i] != y_hat:
model.update(y_hat, y[i], lr, x[i, :].reshape(-1, 1))
count += 1
acc = 1 - count / x.shape[0]
print("Epoch {}: Accuracy: {:.4f}".format(epoch, acc))
# 完全分类正确或分类的准确率达到设定的标准
if not count or ( accuracy and acc >= accuracy):
flag = False
if __name__ == "__main__":
loc = 5
scale = 2
num_samples = 1000
num_features = 2
lr = 0.01
x,y = generateDataset(num_samples, loc, scale, num_features)
# 生成数据的可视化
plt.figure()
plt.scatter(x[:,0],x[:,1],c=y)
plt.savefig("org_dataset.png")
plt.show()
# 添加阈值对应的固定输入列
neg_ones = -np.ones((num_samples, 1))
x = np.hstack((x,neg_ones))
# 初始化模型
model = Perceptron(in_feats=num_features)
# 训练
train(model, x, y, lr)
w1,w2,theta = model.w.flatten()
x1 = np.linspace(-10, 10, 1000)
y1 = (-w1 * x1 + theta) / w2
plt.figure()
plt.scatter(x[:,0],x[:,1],c=y)
plt.plot(x1,y1,c='r')
plt.savefig("outcome.png")
plt.show()
训练后的结果展示如下:
结论:可见,经过训练感知机模型能够成功学得一个将线性可分的数据集划分的超平面。但在实际中,完全线性可分是很难做到的,因此可以在训练过程中设置一个结果指标,当训练的结果达到该指标即可,而不必过于苛求完美(源码也由对应的实现)。
四.结语
参考文献:《机器学习》——周志华
以上便是本文的全部内容,要是觉得不错的话,可以点个赞或关注一下博主,后续还会持续带来各种干货,当然要是有问题的话也请批评指正!!!