☰
感知机入门:从零实现到多层感知机突破异或问题
2026/10/13 13:01:11 网站建设 项目流程

感知机(Perceptron)是神经网络最初的模样,也是很多人入门深度学习时绕不过去的第一个模型。说它门槛低,是因为你只要会加权求和,再写一个 if 判断,就能把一个感知机跑起来;说它后劲大,是因为从感知机到多层感知机,再到反向传播、特征表示、深度网络,这一条线几乎把神经网络的核心逻辑全串了起来。经常有刚入门的同学问我:先学哪个模型好?我的回答一直是,先学感知机,认真跑通一个例子,搞清楚权重到底怎么变的,比追着热门框架看十遍教程都管用。这篇文章就从感知机讲起,包含概念拆解、完整代码实现、它的致命局限(异或问题),以及多层感知机如何突破这堵墙。适合刚入门机器学习、想踏踏实实打好神经网络基础的人,也适合那些学了一阵但老觉得一知半解的同学。

1. 感知机是做什么的:一个最简决策引擎

1.1 先从“出门要不要带伞”说起

很多教材喜欢一上来就丢数学公式,我换个方式,先聊聊生活里的决策。你站在门口,纠结今天要不要带伞。这时你会看两个信息:天空阴沉程度 x1,天气预报降雨概率 x2。如果你更信实时天气,你会给 x1 更高的权重;如果你更信天气预报,你会给 x2 更高的权重。最后,你把两项加权加起来,超过某一个自己的心理阈值,就带伞,否则不带。

感知机做的其实就是这件事:把输入特征乘以对应的权重,求和,再拿这个结果跟一个偏置比较,超过阈值输出1,没超过输出0。整个过程没有任何“智能”可言,它只是把一条线性决策规则给学出来。关键点是,它不需要你手动告诉他“下雨概率高于60%就带伞”,而是给一堆带伞/不带伞的例子,让它自己把权重和偏置找出来。这是它跟普通规则判断最不一样的地方。

1.2 用数学语言把感知机写下来

假设输入是 x = (x1, x2, ..., xn),权重是 w = (w1, w2, ..., wn),偏置是 b,感知机的输出可以写成:

y = 1,如果 w1x1 + w2x2 + ... + wn*xn + b > 0;否则 y = 0。

这里的判断条件可以看作一个符号函数,有的教材写成 sign(w·x - θ),θ 叫阈值,其实和 b 的差别只是符号问题,本质完全一样。在代码里直接维护一个 bias 参数更顺手。

这里有两个词要划重点:一个是“线性”,加权求和是输入的一次函数,所以感知机在几何上等价于用一条直线,或者更高维空间里的一个超平面,把两类样本分开;另一个是“二分类”,感知机的输出只有两个值,0 或 1。理解了这两个特点,后面异或问题为什么搞不定就顺理成章了。

1.3 用 AND 门和 OR 门验证感知机的表达力

在动手训练之前,我们先手动设置一组权重,验证感知机确实能表达逻辑运算。最经典的例子是 AND 门:x1 和 x2 都为 1 时输出 1,其余输出 0。取 w1=1,w2=1,b=-1.5,然后逐个验证:

(0,0):0 + 0 - 1.5 = -1.5,输出 0
(0,1):0 + 1 - 1.5 = -0.5,输出 0
(1,0):1 + 0 - 1.5 = -0.5,输出 0
(1,1):1 + 1 - 1.5 = 0.5,输出 1

结果完全符合真值表。OR 门更简单,取 w1=1,w2=1,b=-0.5:

(0,0):-0.5,输出 0
(0,1):0.5,输出 1
(1,0):0.5,输出 1
(1,1):1.5,输出 1

所以感知机可以轻松表示 AND、OR、NOT 这些基础逻辑规则。也正因为它看起来这么能干,早期研究者的期望一度非常高,觉得很快就能用它模拟更复杂的推理。但期望越高,后面异或问题的打击就越重,这部分我在第 3 节细说。

2. 从零实现感知机:代码与实操细节

2.1 先把感知机的类搭起来

我习惯把预测和更新分开写,这样每一步在做什么都很清楚,也方便调试。我们先用纯 Python 实现,不依赖任何第三方库,几行就能跑起来。

class Perceptron: def __init__(self, num_features, lr=0.1): self.weights = [0.0] * num_features self.bias = 0.0 self.lr = lr def predict(self, x): z = sum(w * xi for w, xi in zip(self.weights, x)) + self.bias return 1 if z > 0 else 0 def train(self, X, y, epochs=50): for epoch in range(epochs): total_error = 0 for x, target in zip(X, y): pred = self.predict(x) error = target - pred if error != 0: total_error += abs(error) for i in range(len(self.weights)): self.weights[i] += self.lr * error * x[i] self.bias += self.lr * error if total_error == 0: print(f"epoch {epoch}: 全部预测正确,收敛") break

我特意把 weights 初始化成全 0,单层感知机这样做没问题;但到了多层网络就不能这么干,第 4 节会详细解释原因。predict 里的 z 是线性加权和,判断条件用了 z > 0,对应的就是前面公式里的符号函数。

2.2 学习规则:错了就纠偏

感知机的训练规则非常朴素:预测正确就什么都不改,预测错了就按误差方向调整权重。误差 error 只有三种可能:0、1、-1。当 error=1,说明该输出 1 却输出 0,属于漏判,这时要把权重往“更激活”的方向拉;当 error=-1,说明该输出 0 却输出 1,属于误报,要把权重往“更抑制”的方向压。统一更新规则就是:

w_i = w_i + lr * error * x_i
b = b + lr * error

你可以把 error 理解成给模型的“纠错信号”:告诉它这次该往右拉还是往左拉。拉多少由学习率 lr 控制。这个机制属于在线学习,每看到一个样本就更新一次,而不是等整个数据集算完再更新。对线性可分的数据,感知机收敛是有理论保证的;但要注意,这个保证建立在“数据本身线性可分”的前提下,这一点非常关键。

2.3 用 AND 门数据完整跑一遍

直接上完整代码,训练数据就是 AND 门的四种输入组合:

X = [[0, 0], [0, 1], [1, 0], [1, 1]] Y_AND = [0, 0, 0, 1] Y_OR = [0, 1, 1, 1] p = Perceptron(num_features=2, lr=0.1) p.train(X, Y_AND, epochs=50) for x in X: print(x, p.predict(x))

跑下来很快就收敛,输出结果会是 0 0 0 1。这个过程的意义在于:你从没告诉它“AND 公式是什么”,它只看了四个例子,就自己找出了合适的权重和偏置。你可以顺手打印训练后的 weights 和 bias,通常会看到一组正权重加上负偏置,等价于画了一条直线,把 (1,1) 这个点和其他三个点分开。还有一个小细节:样本顺序会影响它先学哪条样本,但只要数据线性可分,最后总能收敛,只是收敛所需轮数可能不同。

2.4 学习率为什么不能随便给

学习率 lr 控制每次更新幅度。取大了,比如 1.0,误差为 ±1 时权重直接跳一个很大的步长,很容易一步跨过目标,训练在两种错误状态之间反复横跳;取小了,比如 0.0001,模型收敛慢到让你怀疑人生。我一般从 0.1 起步:发现训练震荡就降到 0.01,发现收敛太慢就提到 0.5。对单层感知机这种极简模型,学习率稍微大一点问题还不算大;但到了多层神经网络,学习率选择就成了头号调参问题。所以最好现在就开始养成习惯:先打印每个 epoch 的总错误数或者损失,观察它是单调下降,还是来回抖动,再决定要不要动学习率。

3. 感知机的天花板:解不开的异或问题

3.1 线性可分:感知机能学到的边界

感知机的加权求和是线性的,这意味着它学出来的决策边界一定是直线,或者高维空间里的超平面。如果两类样本能被一条直线完全分开,就称为线性可分。AND、OR 都属于线性可分:比如 AND 的四个点画在二维平面,一类是 (1,1),另一类是 (0,0)、(0,1)、(1,0),你总能找到一条直线把 (1,1) 单独划出来。只要数据线性可分,感知机就一定能找到那条线,这是它的优点,也是它早期被寄予厚望的原因。但反过来说,一旦数据不是线性可分的,感知机的能力立刻到头。

3.2 异或为什么让感知机束手无策

异或门(XOR)的真值表是:

x1x2y
000
011
101
110

把四个点画到平面上,你会发现一个很尴尬的事实:你需要把左下 (0,0) 和右上 (1,1) 归为一类,再把左上 (0,1) 和右下 (1,0) 归为另一类。这样的点分布是交错式的,所以无论你怎么画直线,都不可能把两类分开。这不是某个权重参数没找到的问题,而是感知机这个模型类型在数学上就不包含可行解。哪怕你把学习率调到极致,跑一万个 epoch,结果还是在震荡。当时的研究者发现这个问题后,神经网络研究一度被泼了冷水,就是这个原因。

3.3 训练不收敛时,先怀疑数据而不是模型

我调试时有个经验:感知机如果训练轮数一直增加,但错误数始终不降,或者权重在来回跳,第一反应不是调学习率,而是把训练数据可视化,看看两类点到底能不能用一条直线分开。如果是异或那种交叉排布,再怎么调参都是白费。把这个问题想明白之后,你自然就会产生一个念头:单层做不到,那就别用单层了,加一层行不行?这就引出了多层感知机。

4. 多层感知机:加层就能破局

4.1 一条直线不够,就用两条、三条

一个很自然的想法是:一条直线分不开,那就用两条直线组合起来。多层感知机(MLP)就是在输入和输出之间加一层或者多层隐藏层。隐藏层里的每个神经元,本质上还是在做“线性加权再激活”的操作,但多个隐藏神经元的输出会被输出层再次组合,相当于先画几条直线,再把它们拼成更复杂的决策区域。拿异或来说,可以这么理解:我们希望模型先学出“有一个输入为真”和“两个输入都为真”两个子规则,输出层再把两者组合成“前者成立,后者不成立”,正好就是异或。训练好的隐藏层不一定会有这么解释得清的语义,但它的确给模型提供了远超单条直线的表达空间。

4.2 没有激活函数的多层是纸老虎

很多人以为“多层感知机”就是把感知机多堆几层,这是一个很大的误区。如果隐藏层只做线性组合,不加任何非线性变换,那多个线性函数的复合依然是线性函数。数学上展开一下:

y = W2 * (W1 * x + b1) + b2 = (W2 * W1) * x + (W2 * b1 + b2)

括号里面还是输入的线性式子。所以无论堆多少层,能表示的决策边界还是直线,跟单层感知机没有本质区别。要让多层真正起作用,必须在每个神经元的线性输出之后再加一次非线性变换,也就是激活函数。早期常用 sigmoid 或者 tanh,现在更常用 ReLU。正是因为激活函数的存在,网络才具备了逼近弯曲分类边界的能力。这也是为什么把它理解成“多层线性分类器叠加”并不准确,真正准确的说法是“线性组合 + 非线性激活”的反复交替。

4.3 反向传播:误差如何一层层分摊

加了隐藏层之后,一个老问题冒出来了:输出层出错时,隐藏层的权重到底该承担多少责任?这个问题叫信用分配问题。反向传播用链式法则解决这件事:从输出层开始,先算出误差对输出层参数的梯度,再往上一层一层传回去,把最终误差拆到每一层参数上。对一个隐藏层网络来说,核心计算可以写成下面这个形式:

# 假设前向传播为: # h = sigmoid(X @ W1 + b1) # out = sigmoid(h @ W2 + b2) # # 反向传播需要计算: # δ_out = (out - y) * out * (1 - out) # δ_h = (δ_out @ W2.T) * h * (1 - h) # # 参数更新: # W2 = W2 - lr * h.T @ δ_out # W1 = W1 - lr * X.T @ δ_h

这里面的 out * (1 - out) 来自 sigmoid 函数的导数。实际项目里,现代深度学习框架已经帮你自动完成了整个求导过程,你不需要自己手算链式法则;但你必须理解误差是怎么“从后往前传”的,否则后面看网络结构、调训练策略时很容易一脸迷茫。

4.4 训练多层感知机的几个关键起点

第一,权重初始化不能全为 0。单层感知机可以全零初始化,多层网络如果全零,隐藏层所有神经元会收到完全相同的梯度,无论怎么更新都保持对称,模型等于退化成一个只有单通道的线性系统。通常的做法是使用小的随机数初始化。第二,学习率不要贪大。误差梯度经过多层相乘之后,一个不小心就可能被放大很多倍,学习率太大会极端震荡甚至发散。第三,输出层的激活函数要跟随任务选择:二分类一般用 sigmoid,隐藏层现在多半用 ReLU 系列。这些规则在早期手写多层感知机时尤为重要,到了自动求导时代,你依然要理解它们,才能在训练曲线出现异常时迅速定位问题。

5. 实战避坑清单:这些坑我都踩过

5.1 感知机不收敛,先看数据是否线性可分

这是最容易踩的坑。表现是:训练很多轮之后,还是有样本预测错,而且错误样本一直在换。这个时候不要盲目调学习率,先画图或者简单分析一下两类样本是不是线性可分。如果数据是异或那种交叉排布,感知机本身就不会收敛,应该直接换多层感知机。很多初学者卡在这里大半天,最后发现不是参数问题,而是模型能力压根不够。

5.2 学习率震荡时怎么办

我的习惯是先把学习率降到 0.01 级别,跑一轮看趋势。如果错误数像过山车一样来回弹,说明步长太大;如果错误数迟迟不下降,说明步长可能太小。在入门阶段,直接打印每个 epoch 的总错误数,对比它是在单调下降还是在原地抖动,比看一堆平滑曲线直观得多。训练曲线会告诉你很多信息,只是很多人习惯跳过这一步。

5.3 特征取值范围差太多,权重更新会“打架”

特征缩放的问题特别隐蔽。如果一个特征范围是 0 到 1,另一个特征范围是 0 到 1000,那加权和很容易被大数值特征主导,权重更新方向也会整体偏向它。这会让模型表现莫名其妙地差,而且不好查。我建议在所有基于距离或线性组合的模型上都做特征归一化,要么把特征缩放到 0 到 1,要么做标准化。数据只有两三个特征时你可能看不出差别,一旦特征变多,这个坑就会非常明显。

5.4 常见概念误区速查

常见说法实际结论
感知机就是神经网络,懂了感知机就等于懂了深度学习感知机只是单个阈值神经元,深度学习的核心在于多层结构、激活函数和自动求导,但感知机确实是很好的起点
多层感知机就是把感知机多堆几层必须配合非线性激活函数,否则堆多少层都等同于一层线性模型
权重初始化为 0 很省事,为什么不能用单层感知机可以,多层网络会对称失效,必须用随机初始化打破对称
感知机训练不收敛就是学习率问题更大概率是数据线性不可分,先检查数据特性

5.5 多层感知机起步时先小后大

我训练多层感知机时,习惯从微型网络开始:先一个隐藏层,每层几个神经元,把数据跑通、看到损失下降之后,再逐步加宽加深。很多人一上来就搭一个很大的网络,结果训练慢、调参难,出了问题也分不清是模型结构的问题还是数据的问题。作为入门者,用 2 个输入、两个隐藏神经元、一个输出这种结构把异或问题跑通,体验一次从“损失降不下来”到“收敛正常”的过程,比套一个大模型跑一遍有价值得多。

我个人的习惯是,把感知机收敛、异或不收敛、两层网络跑通这三件事,都亲手敲一遍代码,盯着权重在训练过程里的变化,比背十遍公式都管用。这篇文章作为入门第一篇,把线性分类、权重更新、激活函数、多层结构、反向传播这些核心概念串了起来,下一篇再往实际应用走的时候,你会发现问题基本上都是这些概念的延伸和工程化。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询