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 前向传播:数据是怎么流过去的?

前向传播,说白了就是“输入数据从输入层,一层一层往前算,直到输出层”。

过程很简单:

  1. 输入层接收原始数据(比如一张28x28的图片,展平成784个像素值)。
  2. 每个神经元计算加权和:z = w*x + b
  3. 通过激活函数:a = f(z)
  4. 把 a 传给下一层,重复步骤2和3。
  5. 直到输出层,得到最终的预测结果。

举个例子,一个简单的两层网络(输入层→隐藏层→输出层):

# 伪代码示意
# 输入 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)或交叉熵损失。

反向传播要干的事就是:计算每个参数(权重和偏置)对损失函数的贡献度(梯度),然后沿着梯度的反方向更新参数,让损失变小。

这个过程基于微积分中的链式法则。你想想看,损失函数是输出层的函数,输出层又是隐藏层的函数,隐藏层又是输入层的函数。这一层套一层的复合函数,求导就得用链式法则一层层往回传。

具体步骤:

  1. 计算输出层梯度:损失对输出层激活值的偏导。
  2. 误差往回传:利用链式法则,把输出层的梯度传到隐藏层。
  3. 计算参数梯度:根据传回来的梯度,计算每个权重和偏置的梯度。
  4. 更新参数:w = w - learning_rate * gradient

我的经验:刚开始学反向传播时,别急着看复杂的数学推导。你先用手算一个最简单的网络(比如1个输入、1个隐藏神经元、1个输出),把前向和反向的每一步数值都算一遍。我当年就是这么干的,算完一遍之后,什么链式法则、梯度消失,全都通了。

这里有个关键点:学习率。学习率太大,参数更新步子迈得太大,容易在最优解附近震荡甚至发散;学习率太小,收敛太慢,训练时间长得让人崩溃。我建议在嵌入式平台上,一开始可以设0.01,然后根据损失曲线的下降情况动态调整。

3.5 小结与避坑指南

这一章的内容,是后续所有章节的基石。如果你现在觉得有点晕,没关系,先记住以下几点:

  • 感知机是基础单元,多层堆叠才能解决非线性问题。
  • 激活函数引入非线性,ReLU 是隐藏层的首选,但要注意神经元死亡。
  • 前向传播是“算”,反向传播是“学”。
  • 反向传播的核心是链式法则,梯度是参数更新的方向。

我曾经踩过的坑:有一次在STM32F4上部署一个模型,为了省内存,我把所有激活函数都换成了Sigmoid。结果推理速度慢得离谱,而且精度还不如一个简单的线性回归。后来才发现,Sigmoid的指数运算在MCU上太费时间了,而且梯度消失导致模型根本学不到东西。从那以后,我只要在嵌入式平台上做AI,隐藏层一律用ReLU或其变种,输出层根据任务选Sigmoid或Softmax。

下一章,咱们就要开始动手了。我会带你在STM32CubeAI里搭建第一个真正的神经网络模型。准备好了吗?