资讯动态

用Python+Keras绘制激活函数图像:从Sigmoid到Swish的可视化实战

发布时间:2026/10/15 22:19:43 来源:尧图企业网站定制
开头想先聊一个很实际的感受很多朋友学 Keras 时第一个接触的“非线性”就是激活函数书里写“Sigmoid 将输入压缩到 0 到 1 之间”你一看公式好像懂了但等到真正搭网络、看 loss 曲线、分析某一层输出分布时才发现自己对激活函数的“性格”根本不熟。后来我养成了一个习惯每个新项目开始前先把要用的激活函数用 Python 加 Matplotlib 画出来放在手边当参照。这比背公式有效得多。这篇内容就围绕“用 Python Keras 画出各种激活函数对应图像”这件事把常见函数的原理、图像特征、绘制代码和踩坑记录一次讲清楚。适合刚上手深度学习的新手也适合正在调参、想快速对比函数差异的工程师。1. 为什么要给激活函数画图从“纸上公式”到“直观手感”1.1 激活函数决定神经网络的“表达性格”神经网络本质上是在做一系列线性变换加非线性映射。如果没有激活函数多层网络无论叠多少层最终都等价于一个线性变换那“深度”就没有意义了。激活函数就是给网络注入非线性能力的关键角色。但不同激活函数的“非线性方式”差别巨大。Sigmoid 是光滑的 S 形曲线Tanh 是零中心的 S 形ReLU 则是一根折线。它们对梯度传播、神经元死亡、输出分布的影响完全不同。光看公式很难建立起直观判断比如 ReLU 在负数区域输出恒为 0这个“硬截断”到底会造成什么效果你只有把图像画出来再叠加它的导数曲线才能一下子理解为什么深层网络里 ReLU 会导致部分神经元“坏死”。我个人的经验是画图不只是为了好看而是为了建立“函数行为”与“网络训练现象”之间的映射。比如训练时发现某层输出大量接近 0你回头看一眼激活函数图像就能猜到是 ReLU 的稀疏性导致的如果发现梯度爆炸看看 Sigmoid 两端的饱和区也就明白问题出在哪了。1.2 图像可视化带给我们的三个关键信息第一看输出范围。Sigmoid 输出范围是 0 到 1Tanh 是 -1 到 1ReLU 是 0 到正无穷。输出范围直接影响下一层输入的分布比如 BN 层之前用 Tanh 和用 ReLU统计特性完全不同。第二看梯度行为。激活函数图像的“陡峭程度”就是导数的绝对值。函数在某个区间太平坦意味着梯度趋近于 0参数更新会非常慢这就是“饱和”问题。图像能直观显示哪些区域是危险区。第三看非线性强度。ReLU 在正区间是线性的但它整体又是非线性的这种“分段线性”特性让优化更容易。而 Sigmoid 处处非线性但容易饱和。通过图像对比你能理解为什么不同任务要选不同的函数。所以与其死记硬背“ReLU 比 Sigmoid 好”不如自己动手画一次图。画完你自然会发现Sigmoid 在 0 附近还有一点梯度但到 ±3 之后就几乎是平的ReLU 在正区间梯度恒为 1负区间恒为 0这是一种非常“极端”的性格。2. 环境准备与绘图工具选型2.1 最小依赖清单与安装绘制激活函数图像最核心的依赖是 NumPy 和 Matplotlib。Keras 本身也提供了激活函数接口但绘图时我们通常直接用 NumPy 构造输入数据再调用 Keras 的激活函数计算输出或者干脆用 NumPy 自己实现函数表达式。推荐环境版本Python 3.8 及以上TensorFlow 2.x内置 KerasNumPy 1.21 以上Matplotlib 3.5 以上安装命令很简单pip install tensorflow numpy matplotlib如果只想画图不想装完整的 TensorFlow也可以只装 Keras 相关的轻量包但实际项目中反正都要用 Keras直接装完整版最省事。我习惯用虚拟环境隔离项目依赖避免不同版本之间互相干扰。这里多说一句TensorFlow 2.x 的 Keras 接口和早期 1.x 有一些差异但激活函数部分基本没变大家放心用。2.2 为什么用 NumPy 手动计算而不是只用 Keras 内置调用Keras 里每个激活函数都有对应实现比如tf.keras.activations.sigmoid(x)。我们当然可以直接调用它来生成 y 值但有一个问题如果你只想看函数的数学形态不涉及 Tensor 的梯度计算直接用 NumPy 写公式其实更轻快而且能在任何环境下运行不依赖深度学习框架。比如 Sigmoid 的公式是1 / (1 np.exp(-x))一行代码就搞定。用 Keras 调用也完全可以但初学者容易混淆“激活函数作为网络层的一部分”和“激活函数作为一个纯数学函数”这两种用法。绘图是为了理解数学形态我建议先用 NumPy 理解公式再用 Keras 验证接口调用这样两头都清楚。实际上在后面的代码示例里我会把两种方式都展示一遍。先用 Keras 的get方法拿到激活函数对象然后对 NumPy 数组调用它注意这种方式需要先将数组转换为 Tensor 或者直接用 Keras 内置函数处理具体细节下面会讲。2.3 一套可复用的绘图模板我把自己常用的绘图模板整理成了一个函数输入激活函数名和区间范围就能输出一张带网格、带零轴参考线的图像。模板的关键点有三个用np.linspace生成密集的 x 点一般取 1000 个点以上曲线才够平滑。用plt.axhline(0)和plt.axvline(0)画出坐标轴参考线方便观察函数在零点附近的行为。用plt.grid(True)开启网格网格线建议设置半透明否则会干扰曲线阅读。模板代码如下import numpy as np import matplotlib.pyplot as plt def plot_activation(func, x_range(-6, 6), titleActivation Function): x np.linspace(x_range[0], x_range[1], 2000) y func(x) plt.figure(figsize(8, 5)) plt.plot(x, y, linewidth2.5, labeltitle) plt.axhline(0, colorgray, linestyle--, linewidth1) plt.axvline(0, colorgray, linestyle--, linewidth1) plt.grid(True, alpha0.3) plt.legend() plt.title(title) plt.xlabel(x) plt.ylabel(f(x)) plt.tight_layout() plt.show()这套模板基本可以覆盖所有激活函数。遇到需要画导数曲线的情况再用np.gradient或手动求导公式补一条曲线就行。3. Keras 常用激活函数逐个拆解与图像解读3.1 Sigmoid平滑饱和的“压扁器”Sigmoid 的公式是f(x) 1 / (1 exp(-x))它的图像是一条单调递增的 S 形曲线输出范围是 (0, 1)。在 x 接近 0 时曲线斜率最大大约为 0.25x 越大或越小曲线越平坦梯度越接近 0。画图时你会看到当 x 超过 4 或者小于 -4 时曲线几乎完全贴住上下边界。这就是“饱和区”。饱和区的梯度非常小如果网络某一层的输入长期落在这些区间反向传播时梯度会逐层衰减导致深层网络训练缓慢这就是 Sigmoid 在深层网络中不再受欢迎的主要原因。另外注意 Sigmoid 的输出不是零中心的它总是正的。这会导致上一层输出的分布均值整体偏移后续层的梯度更新可能出现“Z 字形”震荡。虽然单看图像觉得挺平滑实际用起来并没有那么友好。在 Keras 中可以用tf.keras.activations.sigmoid(x)直接调用或者作为层参数传入Dense(64, activationsigmoid)。但我个人建议只在二分类输出层或需要输出概率的场景使用隐藏层尽量避开它。3.2 Tanh零中心的改进版Tanh 的公式是f(x) (exp(x) - exp(-x)) / (exp(x) exp(-x))它的图像和 Sigmoid 很像也是 S 形但输出范围是 (-1, 1)而且关于原点中心对称。这个“零中心”特性让 Tanh 在隐藏层中的表现通常优于 Sigmoid因为输出均值更接近 0有利于下一层权重的梯度更新。图像上最直观的区别是Tanh 在 x0 附近的斜率比 Sigmoid 更陡达到 1而 Sigmoid 只有 0.25。这意味着同样在 0 附近Tanh 的梯度信息更强。但 Tanh 的饱和问题依然存在。当 x 的绝对值大于 3 时曲线同样趋于平坦梯度接近 0。所以 Tanh 适合浅层网络或 RNN 的门控结构直接堆很多层 Tanh 照样会遇到梯度消失。在 Keras 里调用方式同样是两种Dense(64, activationtanh)或tf.keras.activations.tanh(x)。如果你需要把输出限制在 -1 到 1 之间Tanh 是一个比 Sigmoid 更好的选择。3.3 ReLU稀疏激活的“整流器”ReLU 的公式极其简单f(x) max(0, x)它的图像是一条在 x0 处转折的折线x 小于 0 时输出恒为 0x 大于 0 时输出等于 x是一条 45 度直线。这个函数看起来“太简单”却是现代深度学习默认配置。为什么因为它在正区间的梯度恒为 1不会饱和能有效缓解梯度消失。同时它把负数全部置为 0产生稀疏激活让网络更高效。但 ReLU 有一个致命问题观察图像左半边就能发现所有负数输入都被“杀掉”了。如果某个神经元的输入长期为负它的输出永远是 0梯度也永远是 0这个神经元就“死”了而且不会复活。这就是所谓的“Dead ReLU Problem”。学习率设置过大时这种情况尤其常见。画 ReLU 图像时建议顺便把梯度曲线画出来你会看到梯度在 x0 处从 0 直接跳到 1这是不连续点。虽然数学上不可导但在工程实现里通常把 x0 处的导数定义为 0 或 1不影响实际使用。Keras 中 ReLU 是默认常用的激活函数Dense(64, activationrelu)。从图像上理解为什么它在 CNN 和全连接层中这么流行比单纯记住结论要深刻得多。3.4 Leaky ReLU / PReLU / ELUReLU的变体家族ReLU 的负区间直接把输出置零太“暴力”了。于是出现了各种改进版核心思路就是让负数区域也能有一点点输出避免神经元彻底死亡。Leaky ReLU 在负区间引入一个很小的斜率比如 0.01f(x) x (x 0) f(x) 0.01 * x (x 0)图像上负数区间变成一条非常平缓的向下直线不再是水平线。这样负数也能携带很小的梯度神经元不容易死。PReLU 是 Leaky ReLU 的“学习版”它把负区间的斜率 alpha 也当作可训练参数网络自己学出来。图像形状和 Leaky ReLU 一样但斜率会随着训练变化。ELU 则更平滑公式是f(x) x (x 0) f(x) alpha * (exp(x) - 1) (x 0)ELU 的负区间不是直线而是一条逼近 -alpha 的平滑曲线在 x0 处连续且可导。它在负区间的输出均值更接近 0能进一步缓解偏移问题但计算量比 ReLU 稍大。画这些变体时最好把 ReLU 当作基准同时画出才能直观看出差异。它们的共同点是正区间都保持线性所以正区间的梯度传播能力与 ReLU 一致。Keras 中调用方式LeakyReLU(alpha0.01)需要作为一个层来使用不能直接写在activation参数里比如model.add(Dense(64)) model.add(LeakyReLU(alpha0.01))PReLU 和 ELU 也有对应的层接口用起来都很方便。3.5 Softmax概率分布的“竞争机制”Softmax 和前面所有激活函数有本质区别。它是针对一个向量进行运算输出每个元素的概率所有输出加起来等于 1。公式如下f(x_i) exp(x_i) / sum(exp(x_j)) for all j图像没法简单地用“一个输入一个输出”的二维曲线表示因为它的输入输出都是高维向量。通常我们在多分类任务的输出层使用它将网络输出的 logits 转换成概率分布。如果一定要可视化 Softmax可以做两类图一类是固定其他元素不变改变某一个元素看它的概率变化另一类是画出二维输入空间上的概率等高线。大多数人会用第一种方式也就是保持一个向量里其余元素为 0只让某个位置变化看对应输出概率如何从接近 0 增加到接近 1。从图像里能看到一个关键特征指数运算放大了输入之间的差异。如果 logits 是 [2, 1, 0]经过 Softmax 后概率大约为 [0.67, 0.24, 0.09]。它让最大值更突出但不会像 Hardmax 那样把其他项直接置为 0保留了一定的梯度信息。在 Keras 中Dense(num_classes, activationsoftmax)是分类模型的标准结尾层。单独画 Softmax 曲线时不要和 ReLU 放在同一套对比图里因为它本质是向量函数。3.6 Softplus、Swish / SiLU、GELU新锐激活函数Softplus 可以看作是 ReLU 的平滑版本公式是f(x) log(1 exp(x))图像上它整体和 ReLU 很像但负数区域不是 0而是一个缓慢衰减到 0 的正值正区间渐渐逼近一条斜率为 1 的直线。好处是光滑可导坏处是计算量稍大而且负区间的输出不是严格 0稀疏性不如 ReLU。Swish 是近年来非常流行的函数公式是f(x) x * sigmoid(x)它的图像很有趣在负数区域会先下降再回升有一个“V 形”凹槽全局最小值大约在 x-1.28 附近。这个非单调特性让它在一些深层次模型上效果不错。SiLU 其实就是 Swish 当 beta1 时的别名Keras 里可以直接用activationswish。GELU 的公式是f(x) 0.5 * x * (1 erf(x / sqrt(2)))其中 erf 是高斯误差函数。图像上 GELU 与 Swish 非常接近都是负数区域先微微下降再回升。它在 Transformer 类模型中非常常见。这些新型激活函数的图像差异很细微但不要小看这些细微之处。非单调的凹槽能提供一定的负向梯度让激活值有机会“自我修正”。画出图像对比时要重点观察负区间的形状而不是正区间因为正区间基本都是线性的。4. 实操用 Keras Matplotlib 绘制激活函数图像全流程4.1 绘制单个激活函数图像的完整代码下面给出一段可以直接运行的代码同时使用 Keras 激活函数接口和 NumPy 生成输入数据。import numpy as np import matplotlib.pyplot as plt import tensorflow as tf x np.linspace(-6, 6, 2000).astype(np.float32) x_tensor tf.convert_to_tensor(x) # 通过 Keras 激活函数模块计算输出 y_sigmoid tf.keras.activations.sigmoid(x_tensor).numpy() y_tanh tf.keras.activations.tanh(x_tensor).numpy() y_relu tf.keras.activations.relu(x_tensor).numpy() y_softplus tf.keras.activations.softplus(x_tensor).numpy() y_swish tf.keras.activations.swish(x_tensor).numpy() plt.figure(figsize(12, 8)) plt.subplot(2, 3, 1) plt.plot(x, y_sigmoid) plt.title(Sigmoid) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth1) plt.subplot(2, 3, 2) plt.plot(x, y_tanh) plt.title(Tanh) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth1) plt.subplot(2, 3, 3) plt.plot(x, y_relu) plt.title(ReLU) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth1) plt.subplot(2, 3, 4) plt.plot(x, y_softplus) plt.title(Softplus) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth1) plt.subplot(2, 3, 5) plt.plot(x, y_swish) plt.title(Swish) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth1) plt.tight_layout() plt.show()这段代码里有两个值得注意的转换点第一np.linspace生成的是 NumPy 数组需要tf.convert_to_tensor转成 TensorKeras 激活函数才会正常处理第二计算结果需要用.numpy()转回 NumPy 数组Matplotlib 才能绘制。这种“来回转换”看起来麻烦但能让你同时熟悉 Tensor 和 NumPy 两种数据结构。如果你不想接触 Tensor也可以用 NumPy 直接实现函数公式效果完全一样。两种方式我都测试过数值精度差异可以忽略。4.2 多子图对比的封装函数实际项目里我经常需要把六七个激活函数放在同一个大图里对比。手动写subplot太繁琐我封装了一个函数输入函数列表自动生成网格子图。def plot_activations(functions, x_range(-6, 6), cols3): x np.linspace(x_range[0], x_range[1], 2000) rows (len(functions) cols - 1) // cols plt.figure(figsize(5 * cols, 4 * rows)) for idx, (name, func) in enumerate(functions.items(), 1): plt.subplot(rows, cols, idx) y func(x) plt.plot(x, y, linewidth2) plt.title(name) plt.grid(True, alpha0.3) plt.axhline(0, colorgray, linewidth0.8, linestyle--) plt.axvline(0, colorgray, linewidth0.8, linestyle--) plt.tight_layout() plt.show()调用时传入一个字典键是函数名值是接收 NumPy 数组并返回 NumPy 数组的函数。比如funcs { Sigmoid: lambda x: 1 / (1 np.exp(-x)), Tanh: lambda x: np.tanh(x), ReLU: lambda x: np.maximum(0, x), LeakyReLU: lambda x: np.where(x 0, x, 0.01 * x), ELU: lambda x: np.where(x 0, x, 1.0 * (np.exp(x) - 1)), Swish: lambda x: x / (1 np.exp(-x)), } plot_activations(funcs)这种封装的好处是以后想加新函数只要往字典里加一行 lambda 表达式就行不需要改动绘图逻辑。对比多个阶段实验时这个函数帮我省了大量重复代码。4.3 输出图像保存与高清配置画图只是第一步很多场景下需要把图像保存下来放进文档或实验报告。保存图像时建议用dpi参数控制清晰度同时开启bbox_inchestight裁掉空白边。plt.savefig(activation_functions.png, dpi300, bbox_inchestight)关于配色和样式我个人建议使用简洁的白色背景、深色曲线尽量不用花哨的渐变。激活函数图像本身以信息展示为主不是艺术创作可读性优先。如果要在论文或博客里使用推荐设置成矢量图格式plt.savefig(activation_functions.pdf, formatpdf, dpi300, bbox_inchestight)PDF 格式在文档中放大不会失真投稿和打印都方便。如果只是屏幕观看PNG 加 300 dpi 完全够用。还有一个小技巧如果同一张图里要画多条曲线做对比用不同颜色同时加label并在plt.legend()里显示图例标注清楚每条曲线的含义。5. 常见问题与排查技巧5.1 图像中出现 NaN 或锯齿怎么办画激活函数图像时最常遇到的问题就是 NaN。尤其是 Softplus 和 GELU 这类包含指数运算的函数当 x 非常小比如 -100时exp(x)接近 0 没问题但log(1 exp(x))在 x 极大时会溢出变成 inf再参与运算就可能出现 NaN。解决办法有两种一是把 x 范围控制在合理区间比如 -10 到 10避免极端值二是使用数值稳定的公式。比如 Softplus 在 x 很大时可以近似为x可以分段计算def softplus_stable(x): return np.where(x 20, x, np.log1p(np.exp(x)))用np.log1p代替np.log(1 exp(x))能提高小值区域的精度。另外一个常见问题是 matplotlib 绘图时数据点不够多导致 ReLU 在转折处看起来是斜线而不是直角。解决方法是增加np.linspace的点数或者把转折点单独插入数组中。5.2 为什么Sigmoid图像“看起来太平”如果你把 Sigmoid 曲线的坐标范围放得很大比如 x 从 -100 到 100那么中间 0 附近的变化就会被压缩成一条几乎垂直的线两侧变成水平线看起来整个图像就像一个非常窄的过渡带。这不是函数画错了而是坐标系选择的问题。解决办法是优先使用 -6 到 6 的区间这是观察激活函数的标准范围。少数函数比如 GELU 和 Swish 会用到 -8 到 8因为它们的凹槽位置在 -1.3 附近但左右各有延伸。如果始终觉得太平可以检查是不是 x 的步长太大导致曲线拐点没被采到。另外Sigmoid 图像“平”还有一层含义是梯度接近 0这是真实现象。如果你的网络初始化不当输入到激活函数前的值落在 ±5 之外那梯度的确会非常小。这时不是图像的问题而是网络设计的问题。5.3 Keras中自定义激活函数如何参与绘图有时候我们需要画自己定义的激活函数比如带参数的 Leaky ReLU或者某种实验性函数。Keras 允许自定义激活函数只需要定义一个接收 Tensor 并返回 Tensor 的函数即可。import tensorflow as tf def my_activation(x): return tf.where(x 0, x, 0.05 * x)绘图时可以直接对这个函数传入 NumPy 数组吗不行因为tf.where需要 Tensor 输入。简单做法是在自定义函数内部用tf.convert_to_tensor转一下或者在外面先转成 Tensor 再调用x_tensor tf.convert_to_tensor(x, dtypetf.float32) y my_activation(x_tensor).numpy()如果你想完全避开 Tensor也可以写纯 NumPy 版本的自定义函数原理一样。但既然项目用 Keras我还是建议保留 Tensor 版本这样自定义函数也能直接用在网络构建中。5.4 不同版本Keras的API差异Keras 经历过独立库到 TensorFlow 内置的变化。TensorFlow 2.x 中推荐使用tf.keras.activations早期 Keras 的keras.activations在独立版中也能用但如果你装的是较新的 TensorFlow继续用keras开头可能会遇到兼容性警告。我这里整理了一个小表方便大家对照激活函数Keras 层参数写法独立调用写法Sigmoidactivationsigmoidtf.keras.activations.sigmoid(x)Tanhactivationtanhtf.keras.activations.tanh(x)ReLUactivationrelutf.keras.activations.relu(x)LeakyReLULeakyReLU(alpha0.01)作为层tf.keras.layers.LeakyReLUSoftmaxactivationsoftmaxtf.keras.activations.softmax(x)Swish/SiLUactivationswishtf.keras.activations.swish(x)ELUactivationelutf.keras.activations.elu(x)如果你的代码报错比如module tensorflow has no attribute keras说明你的 TensorFlow 版本太老或安装不完整建议升级到 2.x 最新稳定版。还有一种情况是混用了keras和tf.keras这会导致模型权重不通用最好统一使用tf.keras。6. 一点实操心得画激活函数图像这件事看起来很简单但它是理解深度学习模型行为最廉价的手段。我在实际项目中每次设计新网络都会把候选激活函数的图像和导数图像放在一起比较。尤其是遇到训练不收敛或者 loss 波动异常时回到激活函数图像上检查输入范围是否落在饱和区往往比盲目调学习率更有效。最后分享一个小技巧不要只画函数本身把它的导数也画在同一个坐标系里。你可以直接用 NumPy 的数值差分计算导数或者手动写出导函数公式。观察函数值与导数的叠加关系你会更清楚“饱和”“梯度消失”“神经元死亡”这些词到底在说什么。每个函数都有自己的脾气摸透了用起来才能得心应手。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑