
LeetCode 与 GPT 系列教程Sigmoid 与 ReLU 激活函数从公式推导到 NumPy 向量化实现【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode激活函数是神经网络的非线性之源没有它无论堆叠多少层线性层网络都只能表达线性关系。本篇文章以仓库中 sigmoid-and-relu.md 文档为主线系统讲解 Sigmoid 与 ReLU 的数学定义、梯度特性、NumPy 向量化实现、常见坑位并结合本仓库 single-neuron.md、backpropagation.md、sentiment-analysis.md、dead-relu-detector.md 等配套文档厘清二者在 GPT 项目与分类模型中的真实分工。读完你将掌握两种激活函数的选型依据、逐元素向量化实现的正确写法以及数值稳定性与死亡神经元等工程陷阱的规避方法。前置知识在动手实现之前建议先熟悉以下三点NumPy 数组运算逐元素运算element-wise与广播broadcasting让我们可以把函数一次性应用到整个数组而无需显式循环。这是本实现np.exp、np.maximum能向量化的前提。导数Derivatives激活函数的导数决定了反向传播时梯度如何流动直接决定训练能否收敛。相关完整推导可参见 backpropagation.md。非线性为什么重要没有激活函数时堆叠线性层会坍缩成单个线性变换网络只能学习线性关系。这一点在 single-neuron.md 中有更直观的推导。核心概念为什么需要激活函数一个线性层计算的是加权和 $z Wx b$。把两个线性层堆叠起来得到的仍然是线性函数——无论网络多深都只能拟合直线超平面。激活函数通过在每一步线性变换后引入一个非线性的弯折打破了这个限制。Sigmoid压缩到 $(0, 1)$ 的置信度仪表$$\sigma(z) \frac{1}{1 e^{-z}}$$Sigmoid 把任意实数压缩到区间 $(0, 1)$绝对值很大的正数 → 趋近于 $1$绝对值很大的负数 → 趋近于 $0$$z 0$ → 恰好等于 $0.5$。因此 Sigmoid 天然适合二分类输出层输出可直接解读为概率接近 1 表示正类。其缺点是梯度消失问题vanishing gradient当 $|z|$ 很大时导数$$\sigma(z) \sigma(z)(1 - \sigma(z))$$趋近于 $0$反向传播中梯度被层层压缩至消失深层网络停止学习。导数 $\hat{y}(1-\hat{y})$ 在 $\hat{y}0.5$ 时峰值也仅有 $0.25$参见 backpropagation.md 中的推导。ReLU简单直接的整流线性单元$$\text{ReLU}(z) \max(0, z)$$正输入原样通过负输入一律归零。ReLU 是隐藏层的默认激活函数原因有三计算快一次比较运算即可没有指数开销正区间不饱和正输入梯度恒为 $1$不随 $z$ 增大而衰减产生稀疏激活负输入被置零神经元输出稀疏利于特征选择。它的弱点是dying ReLU死亡神经元若某个神经元长期只收到负输入其梯度恒为 $0$权重永远无法更新神经元死掉。关于该问题的检测与修复策略dead-relu-detector.md 有完整的诊断方法LeakyReLU 等变体通过给负区间非零梯度规避此问题。解决方案直觉逐元素向量化不写循环两个函数都是对 NumPy 数组做逐元素运算Sigmoid 直接用公式配合np.expReLU 用np.maximum把每个元素与 0 比较。全程无需 for 循环这正是 NumPy 广播能力的体现。实现import numpy as np from numpy.typing import NDArray class Solution: def sigmoid(self, z: NDArray[np.float64]) - NDArray[np.float64]: return np.round(1 / (1 np.exp(-z)), 5) def relu(self, z: NDArray[np.float64]) - NDArray[np.float64]: return np.maximum(0, z)代码要点说明np.exp(-z)对数组整体取指数输出仍是与z同形状的数组np.round(..., 5)将结果保留 5 位小数保证输出稳定、便于测试比对np.maximum(0, z)执行逐元素比较0被广播到与z相同形状输出每个位置取max(元素, 0)两个函数都不含可学习参数不向模型引入任何额外的参数量详见下文关键要点。逐步推演以z [-1.0, 0.0, 1.0, 2.0]为例元素Sigmoid: $\frac{1}{1e^{-z}}$ReLU: $\max(0, z)$$-1.0$$1/(1e^{1}) \approx 0.26894$$0$$0.0$$1/(11) 0.5$$0$$1.0$$1/(1e^{-1}) \approx 0.73106$$1.0$$2.0$$1/(1e^{-2}) \approx 0.88080$$2.0$注意观察Sigmoid 把所有值压缩进 $(0, 1)$而 ReLU 对正值保持恒等identity、对负值置零。放到单神经元前向传播的语境single-neuron.md中理解先算 $z x \cdot w b$再决定用哪个激活函数塑造输出范围——Sigmoid 输出概率用于二分类ReLU 用于隐藏层线性$a z$用于回归。时间复杂度与空间复杂度时间复杂度$O(n)$其中 $n$ 为输入数组的元素个数逐元素运算每个元素处理一次空间复杂度$O(n)$用于存放输出数组。常见陷阱陷阱一把np.max当成np.maximum用np.max返回数组中的单个最大值标量np.maximum做逐元素比较。用错会导致形状不匹配或结果完全错误# 错误返回单个标量且 np.max 不接受两个参数做逐元素比较 result np.max(0, z) # 正确与 0 做逐元素比较 result np.maximum(0, z)补充说明当处理的是标量而非数组时Python 内置max(0.0, z)也能工作single-neuron.md 中正是如此一旦向量化为数组就必须使用np.maximum。陷阱二Sigmoid 的数值溢出当 $z$ 为绝对值很大的负数时$e^{-z}$ 会溢出超出 float64 可表示范围。常见的修复思路是分段条件公式不过对于标准 float64 范围内的输入NumPy 通常能优雅处理。更稳妥的写法是裁剪输入或利用恒等式 $\sigma(-z) 1 - \sigma(z)$# 极端取值下可能不稳定 result 1 / (1 np.exp(-z)) # 更稳健先用 np.clip 裁剪或用恒等式 sigma(-z) 1 - sigma(z) z_clipped np.clip(z, -500, 500) result 1 / (1 np.exp(-z_clipped))在本项目的 GPT 系列中的位置原文档所属的 GPT 教程系列中本实现对应foundations/activations.py这一环节。在该系列的设计里GPT 模型每个 Transformer 块的**前馈网络feed-forward network**中的隐藏层使用 ReLU 家族激活transformer-block.md 中 GPT 实际采用 GELU它通过给负输入非零梯度规避了 ReLU 的死亡神经元问题简单模型Sigmoid 出现在情感分类器与手写数字分类器等较简单的模型中。以 sentiment-analysis.md 为例Embedding → 序列平均 → Linear 投影 →Sigmoid输出概率完成二分类handwritten-digit-classifier.md 同样在输出端使用 Sigmoid。这两类模型的分工恰恰呼应了本文的核心结论输出层要概率 → Sigmoid隐藏层要梯度流通 → ReLU 家族。想进一步理解激活函数选择对训练动态的影响可阅读 training-diagnostics.md 与 weight-initialization.md。关键要点Sigmoid 输出落在 $(0, 1)$适合二分类输出层但梯度消失使其不适合作为深层网络的隐藏层激活。ReLU 是隐藏层默认激活梯度要么是 0 要么是 1正输入区间不会遭遇梯度消失。二者均为无参数函数、逐元素应用不向模型引入任何可学习参数对比nn.Linear的权重与偏置才是参数来源见 basics-of-pytorch.md。工程上注意np.maximum与np.max的区别、Sigmoid 极端输入的数值稳定性以及 ReLU 的死亡神经元问题修复方案见 dead-relu-detector.md。【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考