3、神经网络基础回顾:感知机、激活函数(ReLU/Sigmoid/Tanh)、前向传播与反向传播概念
各位同学,咱们今天聊点基础但极其重要的东西。说实话,很多做嵌入式AI的朋友,一上来就急着调库、跑模型,结果模型量化后精度掉得厉害,或者推理速度上不去,根本原因就是基础没打牢。我个人习惯是,不管项目多急,先把这几块骨头啃下来。
这一章,咱们就掰开揉碎了讲讲:感知机到底是个啥?激活函数为什么非用不可?前向传播和反向传播又是怎么一回事?嗯,别急,一个一个来。
3.1 感知机:神经网络的最小单元
感知机,说白了就是模仿人脑神经元的一个简化模型。你想想看,人脑里一个神经元,接收多个树突传来的信号,经过细胞体处理,然后通过轴突输出一个信号。感知机干的也是这个活。
它的数学表达式很简单:
y = f(w1*x1 + w2*x2 + ... + wn*xn + b)
其中:
- x1, x2, ..., xn 是输入信号
- w1, w2, ..., wn 是权重,代表每个输入的重要性
- b 是偏置,可以理解为神经元的“阈值”
- f 是激活函数,决定是否“激活”输出
核心要点:感知机只能解决线性可分问题。比如用一条直线就能把两类数据分开,那感知机就能搞定。但像异或(XOR)这种非线性问题,单个感知机就无能为力了。我在项目中遇到过有人想用单层感知机做手势识别,结果怎么调参数都分不开,这就是典型的“工具选错了”。
那怎么办?把多个感知机堆叠起来,就成了多层感知机(MLP),也就是我们常说的全连接神经网络。层数一多,非线性表达能力就上来了。
3.2 激活函数:为什么不能没有它?
如果没有激活函数,你想想看,每一层都是线性变换,那不管堆多少层,本质上还是一个线性模型。说白了,就是“叠罗汉叠得再高,也还是一个人”。
激活函数的作用,就是引入非线性。让神经网络有能力去拟合那些弯弯曲曲的复杂函数。下面我重点讲三个最常用的:
3.2.1 Sigmoid 函数
公式:σ(x) = 1 / (1 + e^(-x))
输出范围在 (0, 1) 之间。它最大的特点是“平滑”,而且可以把任意实数映射到0到1之间,很适合做二分类的输出层。
但是,它有两个致命缺点:
- 梯度消失:当 x 很大或很小时,梯度几乎为0。反向传播时,梯度一乘再乘,传到前面几层就没了。我记得有一次训练一个5层的网络,用Sigmoid做隐藏层,结果前面几层的权重几乎纹丝不动,训练了三天精度还是原地踏步。
- 输出不是零中心:输出恒为正,会导致下一层的输入全正,影响收敛速度。
3.2.2 Tanh 函数
公式:tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
输出范围在 (-1, 1) 之间。它解决了Sigmoid“输出不是零中心”的问题。所以,在隐藏层中,Tanh 通常比 Sigmoid 表现更好。
但梯度消失的问题依然存在。当 x 的绝对值很大时,梯度还是会趋近于0。
3.2.3 ReLU 函数
公式:ReLU(x) = max(0, x)
这玩意儿,现在几乎是默认的隐藏层激活函数了。为什么?因为它简单、粗暴、有效。
- 计算极快:就是一次比较操作,没有指数运算。在嵌入式平台上,这个优势太明显了。
- 缓解梯度消失:当 x > 0 时,梯度恒为1,不会衰减。
- 稀疏性:一部分神经元输出为0,相当于网络变得稀疏,有助于防止过拟合。
注意:ReLU 也有个问题叫“神经元死亡”。如果某个神经元在训练过程中,权重更新后对所有样本的输入都变成负数,那它的输出就永远是0,梯度也是0,这个神经元就“死”了,再也无法恢复。我曾经在STM32上部署一个模型时,就遇到了这个问题,后来改用Leaky ReLU才解决。
下面这个表格,方便大家对比记忆:
| 函数 | 输出范围 | 优点 | 缺点 | 常用场景 |
|---|---|---|---|---|
| Sigmoid | (0, 1) | 平滑,适合概率输出 | 梯度消失,非零中心 | 二分类输出层 |
| Tanh | (-1, 1) | 零中心,比Sigmoid好 | 仍有梯度消失 | 隐藏层(早期) |
| ReLU | [0, +∞) | 计算快,缓解梯度消失 | 神经元死亡 | 隐藏层(默认) |
3.3 前向传播:数据是怎么流过去的?
前向传播,说白了就是“输入数据从输入层,一层一层往前算,直到输出层”。
过程很简单:
- 输入层接收原始数据(比如一张28x28的图片,展平成784个像素值)。
- 每个神经元计算加权和:
z = w*x + b - 通过激活函数:
a = f(z) - 把 a 传给下一层,重复步骤2和3。
- 直到输出层,得到最终的预测结果。
举个例子,一个简单的两层网络(输入层→隐藏层→输出层):
# 伪代码示意
# 输入 x (假设是2维)
x = [0.5, -0.2]
# 隐藏层权重和偏置
W1 = [[0.1, 0.3], [-0.2, 0.4]]
b1 = [0.1, -0.1]
# 隐藏层计算
z1 = W1 * x + b1 # 得到 [0.5*0.1 + (-0.2)*0.3 + 0.1, 0.5*(-0.2) + (-0.2)*0.4 + (-0.1)]
a1 = ReLU(z1)
# 输出层权重和偏置
W2 = [[0.5, -0.3]]
b2 = [0.2]
# 输出层计算
z2 = W2 * a1 + b2
y_pred = Sigmoid(z2) # 假设是二分类
嗯,就是这么简单。前向传播就是一次“计算接力赛”。
3.4 反向传播:模型是怎么学会的?
前向传播算出了预测值,但预测值和真实值之间肯定有差距。这个差距,我们用损失函数来衡量,比如均方误差(MSE)或交叉熵损失。
反向传播要干的事就是:计算每个参数(权重和偏置)对损失函数的贡献度(梯度),然后沿着梯度的反方向更新参数,让损失变小。
这个过程基于微积分中的链式法则。你想想看,损失函数是输出层的函数,输出层又是隐藏层的函数,隐藏层又是输入层的函数。这一层套一层的复合函数,求导就得用链式法则一层层往回传。
具体步骤:
- 计算输出层梯度:损失对输出层激活值的偏导。
- 误差往回传:利用链式法则,把输出层的梯度传到隐藏层。
- 计算参数梯度:根据传回来的梯度,计算每个权重和偏置的梯度。
- 更新参数:
w = w - learning_rate * gradient
我的经验:刚开始学反向传播时,别急着看复杂的数学推导。你先用手算一个最简单的网络(比如1个输入、1个隐藏神经元、1个输出),把前向和反向的每一步数值都算一遍。我当年就是这么干的,算完一遍之后,什么链式法则、梯度消失,全都通了。
这里有个关键点:学习率。学习率太大,参数更新步子迈得太大,容易在最优解附近震荡甚至发散;学习率太小,收敛太慢,训练时间长得让人崩溃。我建议在嵌入式平台上,一开始可以设0.01,然后根据损失曲线的下降情况动态调整。
3.5 小结与避坑指南
这一章的内容,是后续所有章节的基石。如果你现在觉得有点晕,没关系,先记住以下几点:
- 感知机是基础单元,多层堆叠才能解决非线性问题。
- 激活函数引入非线性,ReLU 是隐藏层的首选,但要注意神经元死亡。
- 前向传播是“算”,反向传播是“学”。
- 反向传播的核心是链式法则,梯度是参数更新的方向。
我曾经踩过的坑:有一次在STM32F4上部署一个模型,为了省内存,我把所有激活函数都换成了Sigmoid。结果推理速度慢得离谱,而且精度还不如一个简单的线性回归。后来才发现,Sigmoid的指数运算在MCU上太费时间了,而且梯度消失导致模型根本学不到东西。从那以后,我只要在嵌入式平台上做AI,隐藏层一律用ReLU或其变种,输出层根据任务选Sigmoid或Softmax。
下一章,咱们就要开始动手了。我会带你在STM32CubeAI里搭建第一个真正的神经网络模型。准备好了吗?