
感知机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。取 w11w21b-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 门更简单取 w11w21b-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, lr0.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, epochs50): 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(fepoch {epoch}: 全部预测正确收敛) break我特意把 weights 初始化成全 0单层感知机这样做没问题但到了多层网络就不能这么干第 4 节会详细解释原因。predict 里的 z 是线性加权和判断条件用了 z 0对应的就是前面公式里的符号函数。2.2 学习规则错了就纠偏感知机的训练规则非常朴素预测正确就什么都不改预测错了就按误差方向调整权重。误差 error 只有三种可能0、1、-1。当 error1说明该输出 1 却输出 0属于漏判这时要把权重往“更激活”的方向拉当 error-1说明该输出 0 却输出 1属于误报要把权重往“更抑制”的方向压。统一更新规则就是w_i w_i lr * error * x_ib 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_features2, lr0.1) p.train(X, Y_AND, epochs50) 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的真值表是x1x2y000011101110把四个点画到平面上你会发现一个很尴尬的事实你需要把左下 (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 个输入、两个隐藏神经元、一个输出这种结构把异或问题跑通体验一次从“损失降不下来”到“收敛正常”的过程比套一个大模型跑一遍有价值得多。我个人的习惯是把感知机收敛、异或不收敛、两层网络跑通这三件事都亲手敲一遍代码盯着权重在训练过程里的变化比背十遍公式都管用。这篇文章作为入门第一篇把线性分类、权重更新、激活函数、多层结构、反向传播这些核心概念串了起来下一篇再往实际应用走的时候你会发现问题基本上都是这些概念的延伸和工程化。