第二章:神经网络核心原理——感知机、多层感知机与激活函数

各位同学,咱们今天聊聊神经网络最底层的那些事儿。说实话,我刚入行那会儿,觉得神经网络就是个黑盒子,往里灌数据,往外出结果。后来踩了不少坑才明白——理解底层原理,比调参重要十倍。

2.1 感知机:一切从这里开始

感知机(Perceptron)是1957年Frank Rosenblatt提出的。说白了,它就是模仿单个神经元的数学模型。

一个感知机接收多个输入,每个输入有个权重,然后求和,再经过一个阶跃函数,输出0或1。数学表达很简单:

y = f(w₁x₁ + w₂x₂ + ... + wₙxₙ + b)

其中f是阶跃函数:

f(z) = 1  if z > 0
f(z) = 0  if z ≤ 0

我在项目中遇到过用感知机做简单的故障检测。比如监测发动机的油温和转速,判断是否过热。效果嘛...对付线性可分的问题还行,稍微复杂点就歇菜了。

核心局限:感知机只能解决线性可分问题。经典的XOR(异或)问题,单层感知机永远搞不定。这就是为什么后来有了多层感知机。

2.2 多层感知机:打破线性天花板

既然一层不够,那就堆多层呗。多层感知机(MLP)就是在输入和输出之间,加了若干隐藏层。

你想想看,每一层都在做特征变换。第一层可能学到边缘,第二层学到形状,第三层学到物体。在汽车电子里,这个思路特别有用。

我记得做ADAS(高级驾驶辅助系统)的目标检测时,早期用单层网络,识别行人总是漏检。换成三层MLP后,准确率直接提升了15%。为什么?因为多层结构能捕捉更复杂的特征关系。

MLP的结构可以这样理解:

输入层 → 隐藏层1 → 隐藏层2 → ... → 输出层
每个箭头都代表:加权求和 + 激活函数

我的经验:隐藏层不是越多越好。在汽车嵌入式平台上,每多一层,推理延迟就增加几毫秒。我一般从2-3层开始试,够用就行。

2.3 激活函数:让网络学会非线性

如果没有激活函数,多层网络就等价于单层——全是线性变换,堆再多层也没用。激活函数就是给网络注入「非线性」的灵魂。

咱们重点讲三个:Sigmoid、Tanh、ReLU。这三个我在实际项目中都用过,各有各的脾气。

2.3.1 Sigmoid:经典但过时

Sigmoid把输入压缩到(0,1)之间,公式是:

σ(x) = 1 / (1 + e⁻ˣ)

优点:输出可以解释为概率,平滑可导。
缺点:梯度饱和问题严重。当|x|很大时,梯度几乎为0,网络学不动了。

我曾经在早期的车载语音识别模型里用Sigmoid,训练到后面loss死活降不下去。查了两天,发现是梯度消失了。换成ReLU,问题立马解决。

避坑指南:千万别在深层网络的隐藏层用Sigmoid。输出层做二分类可以用,但隐藏层最好别碰。这是我用一次流片失败换来的教训。

2.3.2 Tanh:比Sigmoid好一点

Tanh把输入压缩到(-1,1),公式是:

tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ)

它解决了Sigmoid输出不是零均值的问题。零均值意味着下一层输入不会全部为正或全部为负,训练更稳定。

但梯度饱和问题依然存在。在汽车电子里,我偶尔在循环神经网络(RNN)中用Tanh,效果还行。前馈网络嘛,还是ReLU更香。

2.3.3 ReLU:工业界的最爱

ReLU(Rectified Linear Unit)简单粗暴:

ReLU(x) = max(0, x)

正区间梯度恒为1,负区间输出0。这带来了几个好处:

  • 计算极快,没有指数运算
  • 缓解梯度消失
  • 产生稀疏激活,提升效率

我现在做的大部分汽车视觉模型,隐藏层清一色ReLU。但要注意一个问题——神经元死亡。如果某个神经元一直输出负值,它的梯度永远是0,就再也活不过来了。

我的建议:学习率别设太大,否则容易导致大量神经元死亡。我一般从0.001开始调,配合Batch Normalization使用,效果很稳。

2.4 激活函数怎么选?一张表说清楚

激活函数 输出范围 适用场景 我的推荐指数
Sigmoid (0, 1) 二分类输出层 ★★☆☆☆
Tanh (-1, 1) RNN、某些生成模型 ★★★☆☆
ReLU [0, +∞) CNN、MLP隐藏层 ★★★★★

说白了,现在做深度学习,隐藏层首选ReLU及其变体(Leaky ReLU、PReLU等)。输出层根据任务选:回归用线性,二分类用Sigmoid,多分类用Softmax。

2.5 一个完整的MLP示例

咱们用PyTorch写个简单的MLP,用于汽车传感器数据的分类:

import torch
import torch.nn as nn

class SimpleMLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(SimpleMLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, hidden_size)
        self.fc3 = nn.Linear(hidden_size, num_classes)
        # 输出层用Softmax做多分类
        self.softmax = nn.Softmax(dim=1)

    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        out = self.relu(out)
        out = self.fc3(out)
        out = self.softmax(out)
        return out

# 假设输入是8维传感器数据,隐藏层128维,输出3类故障
model = SimpleMLP(input_size=8, hidden_size=128, num_classes=3)
print(model)

这个网络我实际用在发动机故障诊断上。输入是转速、温度、压力等8个传感器值,输出是正常、轻微故障、严重故障三类。训练了500个epoch,准确率92%。

关键点总结:

  • 感知机是基础,但只能解决线性问题
  • 多层感知机通过堆叠隐藏层,实现非线性拟合
  • 激活函数是引入非线性的关键
  • ReLU是当前工业界的主流选择
  • 选激活函数要考虑任务类型和网络深度

嗯,这一章就到这儿。下一章咱们聊聊反向传播和梯度下降——说白了,就是网络到底怎么「学习」的。到时候我会分享一个我在实车测试中遇到的梯度爆炸案例,保证让你印象深刻。