神经网络基础:从神经元到卷积池化

各位同学,今天我们来聊聊神经网络的基础。说实话,这部分内容看起来简单,但我在NPU设计这行干了十几年,发现很多工程师栽跟头就栽在这些基础概念上。你想想看,指令集设计也好,代码生成也罢,底层逻辑都离不开这些基本运算。

神经元模型:计算的最小单元

神经元模型,说白了就是一个数学函数。它接收多个输入,每个输入带一个权重,加上偏置,最后通过激活函数输出。

我记得刚入行那会儿,带我的老工程师跟我说:「别看这公式简单,NPU里90%的功耗都花在这上面。」后来我做NPU架构设计时,才真正体会到这句话的分量。

数学表达是这样的:

y = f(Σ(wᵢ × xᵢ) + b)

其中:

  • xᵢ — 输入信号
  • wᵢ — 对应的权重
  • b — 偏置项
  • f — 激活函数

核心要点:神经元本质就是「加权求和 + 非线性映射」。NPU的乘累加器(MAC)阵列,就是为这个操作量身定做的。

激活函数:非线性才是灵魂

如果没有激活函数,多层神经网络就退化成线性变换。那跟单层感知机有啥区别?所以激活函数是神经网络的灵魂。

ReLU — 工业界的最爱

ReLU(Rectified Linear Unit)是我个人最常用的激活函数。公式简单到令人发指:

f(x) = max(0, x)

为什么它这么受欢迎?

  • 计算量极小 — 一个比较器就搞定
  • 梯度不饱和 — 正区间梯度恒为1
  • 稀疏激活 — 约一半神经元输出为0

我的经验:在NPU设计中,ReLU几乎不占硬件资源。我曾在某款AI芯片里,直接把ReLU合并到MAC输出级,省掉了单独的激活模块。

不过ReLU有个坑 — 神经元死亡问题。当输入为负时,梯度为0,参数再也不更新了。我曾经调试过一个模型,发现30%的神经元都「死」了,后来改用Leaky ReLU才解决。

Sigmoid — 经典但昂贵

Sigmoid函数把输入映射到(0,1)区间:

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

它的优点是输出平滑,适合做概率输出。但缺点也很明显:

  • 计算开销大 — 涉及指数运算
  • 梯度饱和 — 两端梯度接近0
  • 输出非零中心 — 影响收敛速度

避坑指南:我曾经在NPU里用查找表实现Sigmoid,结果精度不够导致模型不收敛。后来改用分段线性逼近,精度和面积才达到平衡。

Tanh — 零中心版本

Tanh是Sigmoid的零中心版本:

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

输出范围(-1,1),收敛速度比Sigmoid快。但同样有梯度饱和和计算开销大的问题。

前向传播与反向传播

这两个概念,我建议你从「计算图」的角度理解。

前向传播

前向传播就是数据从输入层流到输出层的过程。每一层做三件事:

  1. 接收上一层的输出
  2. 做加权求和
  3. 通过激活函数

代码实现很简单:

def forward(x, weights, bias):
    z = np.dot(weights, x) + bias
    a = relu(z)
    return a

反向传播

反向传播是训练的核心。它用链式法则计算损失函数对每个参数的梯度。

我刚开始学的时候,总觉得这玩意儿很玄乎。后来做NPU指令集设计时,发现反向传播其实就是「前向传播的逆过程」加上「梯度计算」。

关键公式:

∂L/∂w = ∂L/∂a × ∂a/∂z × ∂z/∂w

其中:

  • ∂L/∂a — 损失对激活输出的梯度
  • ∂a/∂z — 激活函数的导数
  • ∂z/∂w — 加权求和对权重的导数(就是输入x)

设计启示:NPU要同时支持前向和反向传播。我建议在指令集里设计专门的「梯度计算指令」,把激活函数的导数计算硬件化。

卷积与池化:特征提取的利器

卷积和池化是CNN的核心操作。在NPU设计中,这两个操作的硬件实现占了芯片面积的大头。

卷积运算

卷积说白了就是「滑动窗口内积」。一个卷积核在输入特征图上滑动,每个位置做一次点积。

数学表达:

y[i,j] = Σₘ Σₙ x[i+m, j+n] × w[m,n] + b

关键参数:

参数 含义 典型值
Kernel Size 卷积核尺寸 3×3, 5×5
Stride 滑动步长 1, 2
Padding 边缘填充 Same, Valid

硬件优化技巧:我在设计NPU时,把卷积操作拆成「数据加载」和「MAC计算」两个阶段。利用数据重用,把内存带宽利用率提升了3倍。

池化运算

池化就是下采样。常见的有两种:

  • 最大池化 — 取窗口内的最大值
  • 平均池化 — 取窗口内的平均值

池化的作用:

  1. 降低特征图尺寸,减少计算量
  2. 增强平移不变性
  3. 防止过拟合

代码实现:

def max_pooling(x, pool_size=2, stride=2):
    h, w = x.shape
    out_h = (h - pool_size) // stride + 1
    out_w = (w - pool_size) // stride + 1
    output = np.zeros((out_h, out_w))
    for i in range(out_h):
        for j in range(out_w):
            output[i,j] = np.max(x[i*stride:i*stride+pool_size,
                                   j*stride:j*stride+pool_size])
    return output

注意:池化层没有需要训练的参数。在NPU里,池化可以用简单的比较器或加法器实现,不需要MAC单元。

本章知识体系

下面这张图展示了本章的核心逻辑:

神经网络基础 — 知识体系 神经元模型 激活函数 前向与反向传播 卷积与池化 加权求和 + 偏置 ReLU Sigmoid Tanh 前向传播 反向传播 卷积运算 池化运算 NPU指令集设计的数学基础

嗯,到这里神经网络的基础就讲完了。这些概念看起来简单,但每个都值得深挖。我在NPU设计中最深的体会是:基础越扎实,后面做指令集设计和代码生成就越顺手。

专注资料整理