🧠 第2章 神经网络基础:神经元与感知机、激活函数、前向与反向传播

从生物神经元到NPU加速——这一章我们聊聊神经网络最底层的砖石。

神经元 感知机 激活函数 前向传播 反向传播 核心知识流:神经元 → 感知机 → 激活函数 → 前向/反向传播 NPU加速本质:将大量神经元计算并行化、流水线化

2.1 从生物神经元到数学神经元

我个人习惯把神经网络比作一个「乐高积木系统」。最基础的积木块,就是神经元。生物神经元有树突、轴突、突触,而数学神经元就三样东西:输入、权重、激活。

说白了,一个神经元就是做了一件事:加权求和 + 非线性映射。公式长这样:

z = w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + b
a = f(z)   // f 就是激活函数

我在项目中遇到过不少新手,一上来就堆层数,结果连单个神经元输出都算不对。嗯,先把这个基础打牢,后面NPU的SIMD流水线才能理解透彻。

2.2 感知机:最简单的神经网络

感知机就是单层神经元,输出只有0或1。你想想看,它本质上是一个线性二分类器。当年Rosenblatt提出感知机时,大家兴奋得不行,后来发现连异或(XOR)都搞不定……

⚡ 感知机决策边界:w·x + b = 0,就是一条直线(或超平面)。

不过别小看它。我在做NPU指令集设计时,感知机就是「向量-矩阵乘-累加」的最小原型。NPU里一个周期能算几百个感知机,这就是加速的起点。

2.3 激活函数:给神经网络注入非线性

没有激活函数,再多层也只是线性变换,那跟单层没区别。激活函数就是「灵魂」。常用的就两个:

激活函数公式特点我常用的场景
Sigmoidσ(z) = 1/(1+e⁻ᶻ)输出(0,1),平滑,但梯度饱和二分类输出层,偶尔用在门控单元
ReLUf(z) = max(0, z)稀疏激活,计算极快,梯度不饱和隐藏层首选,NPU上延迟极低
💡 我个人习惯:隐藏层无脑用ReLU。Sigmoid只在需要概率输出时用。曾经有一次我在NPU上部署Sigmoid,发现查表法比直接计算快3倍,后来就改成了分段线性近似。

为什么会这样?因为NPU擅长线性运算,ReLU就是个比较器,硬件开销极小。而Sigmoid涉及指数运算,在NPU上要么查表,要么用PWL(分段线性)拟合。

2.4 前向传播:数据流过网络

前向传播就是输入数据一层层往前算,直到输出。每一层都是:加权求和 → 激活。代码实现非常直观:

def forward(x, weights, biases):
    z = np.dot(weights, x) + biases
    a = relu(z)   # 或者 sigmoid
    return a

你想想看,这个计算过程在NPU上就是典型的「矩阵乘+向量加+激活」。我设计NPU微架构时,前向传播的流水线是重中之重——数据从DMA搬进SRAM,然后 systolic array 做矩阵乘,激活单元直接跟在后面,一个时钟出结果。

⚠️ 注意:前向传播时,中间层的激活值必须保留!反向传播要用到它们。我见过有人为了省内存把中间结果丢掉,结果反向传播时傻眼了……

2.5 反向传播:神经网络的学习引擎

反向传播就是链式法则的工程实现。从损失函数开始,逐层计算梯度,然后更新权重。公式链长这样:

∂L/∂w = ∂L/∂a · ∂a/∂z · ∂z/∂w

说白了,就是误差从输出层往回传,每一层都算出权重该调整多少。我曾经在调试NPU反向传播算子时,发现梯度爆炸,最后定位到是激活函数导数没做clip。嗯,细节决定成败。

🔁 反向传播四部曲:
1. 计算损失函数梯度
2. 计算激活函数导数
3. 计算权重梯度
4. 更新权重(SGD / Adam 等)

在NPU上,反向传播比前向传播更吃带宽。因为需要同时读取激活值、权重和梯度。我建议做异构计算时,把反向传播的梯度更新放在CPU或者专用梯度累加单元上,别跟前向争带宽。

2.6 从基础到NPU加速的直觉

讲完这些基础,你大概能感觉到:神经网络的计算核心就是矩阵乘法 + 激活函数 + 梯度链式法则。NPU之所以快,就是把这三种操作硬化为专用流水线。

我个人习惯在写NPU驱动时,先把前向和反向的访存模式画出来,再分配DMA通道。这样能避免一半以上的性能坑。

🧪 避坑指南:我曾经在部署一个三层网络时,发现NPU利用率只有30%。后来发现是激活函数用了Sigmoid,导致流水线频繁停顿。换成ReLU后,利用率直接飙到85%。所以,激活函数的选择直接影响硬件效率。