☰
深度学习激活函数可视化:sigmoid与tanh绘图实战指南
2026/9/30 7:33:32 网站建设 项目流程

简介:这份PDF资料面向深度学习入门者与神经网络初学者,聚焦Sigmoid与Tanh两种常见激活函数的可视化实现,帮助读者理解其数学表达式、值域特征及在模型中的非线性作用。资源以逐行代码详解的方式,演示了分开绘制两条曲线与合并绘制到同一张图两种方案,并借助tanh(x)=2σ(2x)-1的数学推导简化绘图逻辑,便于直观比较两者的形状差异与梯度分布特点。包内共1个PDF文件,约119KB,内容紧凑,涵盖函数定义、坐标轴设置、刻度标签、图例与图像保存等完整绘图环节。目前已有4111人学习,适合希望夯实激活函数基础、掌握matplotlib绘图技巧的读者参考,也可作为二分类与对称性选择场景下的辅助学习材料。

1. 从一条曲线看懂 sigmoid 和 tanh:为什么新手画完就忘不掉

很多人第一次接触激活函数,是在 logistic 回归里看到那个 S 形曲线,然后被一句“把输出压到 0 到 1 之间”带过。可真正动手写代码时才发现,sigmoid 和 tanh 的绘制远不只是调个plt.plot那么简单——坐标范围取多少、采样点密度够不够、两条曲线放一起时纵轴怎么对齐、导数曲线要不要一起画,每一步都会影响你对这两个函数的直觉。这篇笔记就围绕“分开画、合起来画”两条路线,把 sigmoid 和 tanh 的绘制逐行拆开讲清楚。适合刚学深度学习、想亲手把公式变成图像的新手,也适合想回头补一补可视化细节的熟手。读完你能自己写出可复用的绘图脚本,知道每个参数为什么这么设,也能避开采样和布局上的常见翻车点。

2. sigmoid 与 tanh 的数学底子:先搞清楚画的是什么

2.1 两个函数的定义与值域差异

sigmoid 的标准形式是 $\sigma(x) = \frac{1}{1 + e^{-x}}$,输出范围是开区间 $(0, 1)$。tanh 的标准形式是 $\tanh(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}$,输出范围是 $(-1, 1)$。两者都是 S 形曲线,但 tanh 关于原点对称,sigmoid 关于点 $(0, 0.5)$ 对称。这个对称性差异直接决定了它们在实际网络里的表现:tanh 的输出零中心化,梯度更新时不会把下一层神经元全部往同一个方向推,收敛通常比 sigmoid 更稳。绘制时如果不把这一点在图上体现出来,画出来的两条线就只是“两条 S 曲线”,看不出本质区别。

从数值计算角度看,sigmoid 在 $x$ 很大或很小时会饱和,$e^{-x}$ 可能溢出。Python 里用numpy计算时,如果直接写1 / (1 + np.exp(-x)),当x是很大的负数,np.exp(-x)会变成inf,结果虽然不会报错但会损失精度。常见做法是用分段写法或者直接依赖scipy.special.expit,它在内部做了数值稳定处理。tanh 同理,np.tanh本身已经足够稳定,不需要自己用指数公式重写。绘制阶段虽然对精度要求没那么苛刻,但养成用稳定实现的习惯,后面迁移到模型代码里就不会踩坑。

2.2 为什么绘制时采样范围不能随便取

很多人画 sigmoid 时习惯性写x = np.linspace(-5, 5, 100),画出来看着没问题,但一旦把范围拉到[-10, 10],曲线两端几乎贴着 0 和 1,中间变化段被压缩得很窄。这不是函数变了,而是采样范围和显示比例的问题。sigmoid 在 $|x| > 6$ 之后基本饱和,tanh 在 $|x| > 3$ 之后也接近饱和。如果只画[-5, 5],tanh 的两端还没完全压平,看起来会比 sigmoid 更“斜”。所以分开画和合起来画时,采样范围要统一,否则视觉对比会失真。

我一般会取x = np.linspace(-10, 10, 1000),步长足够小,曲线平滑,两端也能看到饱和趋势。1000 个点对现代机器来说毫无压力,没必要为了省几个点把曲线画得棱角分明。如果后面要画导数曲线,采样密度更要够,因为导数在饱和区接近 0,点太少会画出锯齿。下面这段代码就是最基础的采样和计算:

import numpy as np import matplotlib.pyplot as plt # 统一采样范围,保证两条曲线对比时横轴一致 x = np.linspace(-10, 10, 1000) # sigmoid 用 scipy 的 expit 更稳,这里为了不额外依赖用分段写法 sigmoid = np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) # tanh 直接用 numpy 内置,数值稳定 tanh = np.tanh(x)

这段代码里np.where的分段写法是为了避免x为很大负数时np.exp(-x)溢出。虽然绘制时x只到 -10,溢出风险很低,但养成这个习惯没坏处。np.tanh不需要额外处理,直接调用即可。采样点 1000 是经验值,低于 200 时曲线在拐点附近会明显不平滑。

2.3 分开画和合起来画分别适合什么场景

分开画适合第一次学的时候,一张图只放一个函数,纵轴范围可以独立设置,sigmoid 用(0, 1),tanh 用(-1, 1),能清楚看到各自的饱和速度和对称中心。合起来画适合对比,但纵轴必须统一,否则 tanh 会被压扁或者 sigmoid 被拉长。我一般会先分开画两张,确认每个函数的形状和关键点,再用一张合图做对比。合图时如果直接把两条线叠在一起,颜色要区分明显,线宽稍微加粗,图例位置避开曲线密集区。

分开画还有一个好处:可以单独给每个函数加导数曲线。sigmoid 的导数是 $\sigma(x)(1 - \sigma(x))$,最大值在 $x=0$ 处为 0.25;tanh 的导数是 $1 - \tanh^2(x)$,最大值在 $x=0$ 处为 1。这两个导数曲线的峰值差异很大,放在同一张图里纵轴比例会很难看。分开画就能各自用合适的纵轴范围,把细节看清楚。

3. 分开画:sigmoid 和 tanh 各自的最小绘图脚本

3.1 用 matplotlib 画 sigmoid 并标出关键点

分开画的第一张图给 sigmoid。除了曲线本身,我习惯把 $x=0$ 处的点、两条渐近线、以及导数曲线一起放上去。这样一张图就能把函数值、饱和趋势和梯度变化全讲清楚。下面是最小可用脚本:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(-10, 10, 1000) sigmoid = np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) dsigmoid = sigmoid * (1 - sigmoid) # sigmoid 导数 fig, ax1 = plt.subplots(figsize=(8, 5)) # 主曲线 ax1.plot(x, sigmoid, color='#1f77b4', linewidth=2.5, label='sigmoid') # 导数曲线,用虚线区分 ax1.plot(x, dsigmoid, color='#ff7f0e', linewidth=2, linestyle='--', label='sigmoid derivative') # 标出 x=0 处的关键点 ax1.scatter([0], [0.5], color='red', zorder=5) ax1.annotate('(0, 0.5)', xy=(0, 0.5), xytext=(2, 0.35), arrowprops=dict(arrowstyle='->', color='red'), fontsize=11) # 渐近线 ax1.axhline(y=0, color='gray', linewidth=0.8, linestyle=':') ax1.axhline(y=1, color='gray', linewidth=0.8, linestyle=':') ax1.set_xlabel('x', fontsize=12) ax1.set_ylabel('value', fontsize=12) ax1.set_title('sigmoid function and its derivative', fontsize=13) ax1.legend(loc='upper left', fontsize=10) ax1.grid(alpha=0.3) plt.tight_layout() plt.show()

这段代码里ax1.plot画主曲线,线宽 2.5 是为了在投影或打印时也看得清。导数曲线用虚线,颜色选橙色,和主曲线的蓝色形成对比。ax1.scatter标出 $(0, 0.5)$ 这个点,因为它是 sigmoid 的对称中心,也是导数最大处。ax1.annotate的箭头指向这个点,文字放在右上方避免遮挡曲线。两条axhline画出 0 和 1 的渐近线,提醒读者 sigmoid 永远取不到这两个值。grid(alpha=0.3)让网格线淡一点,不抢曲线视觉。

参数上,figsize=(8, 5)是单张图的常用比例,宽比高多 3 个单位,适合展示横向延伸的 S 曲线。zorder=5保证散点画在曲线上面。tight_layout()自动调整边距,避免标签被裁掉。如果要在 Jupyter 里显示,最后一行plt.show()可以保留;如果保存图片,换成plt.savefig('sigmoid.png', dpi=150),dpi 150 足够屏幕查看,打印的话提到 300。

3.2 用同一套模板画 tanh 并对比纵轴范围

tanh 的绘图脚本结构和 sigmoid 几乎一样,但纵轴范围要改成(-1.2, 1.2),给渐近线留一点余量。导数曲线峰值是 1,比 sigmoid 的 0.25 大很多,所以纵轴不能共用。下面是 tanh 的版本:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(-10, 10, 1000) tanh = np.tanh(x) dtanh = 1 - tanh ** 2 # tanh 导数 fig, ax = plt.subplots(figsize=(8, 5)) ax.plot(x, tanh, color='#2ca02c', linewidth=2.5, label='tanh') ax.plot(x, dtanh, color='#d62728', linewidth=2, linestyle='--', label='tanh derivative') ax.scatter([0], [0], color='red', zorder=5) ax.annotate('(0, 0)', xy=(0, 0), xytext=(2, -0.5), arrowprops=dict(arrowstyle='->', color='red'), fontsize=11) ax.axhline(y=-1, color='gray', linewidth=0.8, linestyle=':') ax.axhline(y=1, color='gray', linewidth=0.8, linestyle=':') ax.set_xlabel('x', fontsize=12) ax.set_ylabel('value', fontsize=12) ax.set_title('tanh function and its derivative', fontsize=13) ax.set_ylim(-1.2, 1.2) # 给渐近线留余量 ax.legend(loc='upper left', fontsize=10) ax.grid(alpha=0.3) plt.tight_layout() plt.show()

和 sigmoid 版本的关键差异有三处。第一,ax.set_ylim(-1.2, 1.2)显式设置纵轴范围,因为 tanh 的值域是 $(-1, 1)$,不设的话 matplotlib 会自动收紧到数据范围,渐近线可能贴边。第二,关键点标在 $(0, 0)$,这是 tanh 的对称中心,也是导数峰值处。第三,导数曲线颜色换成红色,和主曲线的绿色区分。dtanh = 1 - tanh ** 2是 tanh 导数的标准形式,计算时直接用已经算好的tanh数组,避免重复计算。

如果两张图要并排展示,可以用plt.subplots(1, 2, figsize=(14, 5)),但要注意两个子图的纵轴范围不同,不能共用sharey=True。并排时标题要写清楚哪个是 sigmoid 哪个是 tanh,图例位置也要错开,避免视觉混淆。

3.3 分开画时容易忽略的坐标轴细节

分开画最大的好处是每个函数可以用自己的纵轴范围,但这也带来一个隐患:两张图放在一起看时,读者会下意识比较曲线斜率,而纵轴比例不同会让比较失效。我的做法是在两张图的标题里都写上值域,比如 “sigmoid (range 0 to 1)” 和 “tanh (range -1 to 1)”,这样即使纵轴刻度不同,读者也知道实际范围。另外,横轴范围必须一致,都用[-10, 10],否则饱和段的视觉长度会不一样。

还有一个细节是ax.spines的处理。默认 matplotlib 会画上边框和右边框,对于 S 曲线来说,右边框容易和渐近线混淆。我一般会把上边框和右边框去掉,只留左边和下边,让图看起来更干净:

ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False)

这两行加在ax.grid之前就行。去掉之后,渐近线axhline的虚线会更突出,不会被边框干扰。如果要在论文或报告里用,还可以把左边和下边的线宽调到 1.2,字体统一用 Times New Roman 或类似衬线体,但这些属于排版细节,新手阶段先把曲线画对更重要。

4. 合起来画:把 sigmoid 和 tanh 放进同一张图

4.1 统一采样与纵轴范围的处理

合起来画的核心矛盾是纵轴范围。sigmoid 在 $(0, 1)$,tanh 在 $(-1, 1)$,如果纵轴取 $(-1.2, 1.2)$,sigmoid 只占上半部分,看起来像被压扁了;如果取 $(-0.2, 1.2)$,tanh 的下半部分又被裁掉。我的处理方式是纵轴取 $(-1.2, 1.2)$,但在图上加一条 $y=0.5$ 的参考线,提醒读者 sigmoid 的中心在这里。这样两条曲线的完整形状都能看到,对比饱和速度也公平。

采样点必须用同一个x数组,不能分别采样再拼,否则横轴刻度会对不齐。下面是对比图的完整脚本:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(-10, 10, 1000) sigmoid = np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) tanh = np.tanh(x) fig, ax = plt.subplots(figsize=(9, 5.5)) ax.plot(x, sigmoid, color='#1f77b4', linewidth=2.5, label='sigmoid') ax.plot(x, tanh, color='#2ca02c', linewidth=2.5, label='tanh') # 参考线:sigmoid 的中心和 tanh 的中心 ax.axhline(y=0.5, color='#1f77b4', linewidth=0.8, linestyle=':', alpha=0.6) ax.axhline(y=0, color='#2ca02c', linewidth=0.8, linestyle=':', alpha=0.6) # 标出两个中心点 ax.scatter([0, 0], [0.5, 0], color=['#1f77b4', '#2ca02c'], zorder=5, s=60) ax.annotate('sigmoid center (0, 0.5)', xy=(0, 0.5), xytext=(2.5, 0.7), arrowprops=dict(arrowstyle='->', color='#1f77b4'), fontsize=10, color='#1f77b4') ax.annotate('tanh center (0, 0)', xy=(0, 0), xytext=(2.5, -0.35), arrowprops=dict(arrowstyle='->', color='#2ca02c'), fontsize=10, color='#2ca02c') ax.set_xlabel('x', fontsize=12) ax.set_ylabel('value', fontsize=12) ax.set_title('sigmoid vs tanh: shape and center comparison', fontsize=13) ax.set_ylim(-1.2, 1.2) ax.legend(loc='upper left', fontsize=11) ax.grid(alpha=0.3) ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) plt.tight_layout() plt.show()

这段代码里axhline画了两条参考线,颜色和对应曲线一致,透明度 0.6 避免抢眼。ax.scatter一次标出两个中心点,颜色分别对应。两个annotate的箭头指向各自中心,文字位置错开,避免重叠。set_ylim(-1.2, 1.2)是合图的关键,不设的话 matplotlib 会自动取数据范围,sigmoid 的 0 到 1 和 tanh 的 -1 到 1 叠加后纵轴会变成 -1 到 1,sigmoid 的下半段被裁掉。

4.2 用子图合画:上下排列保留各自纵轴

如果觉得叠在一起纵轴还是别扭,可以用上下子图的方式合画。上面一张 sigmoid,下面一张 tanh,横轴共享,纵轴各自独立。这样既能在同一张图里对比,又不会牺牲各自的纵轴比例。代码结构如下:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(-10, 10, 1000) sigmoid = np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) tanh = np.tanh(x) fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 7), sharex=True) ax1.plot(x, sigmoid, color='#1f77b4', linewidth=2.5) ax1.set_ylabel('sigmoid', fontsize=12) ax1.set_ylim(-0.1, 1.1) ax1.grid(alpha=0.3) ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) ax2.plot(x, tanh, color='#2ca02c', linewidth=2.5) ax2.set_xlabel('x', fontsize=12) ax2.set_ylabel('tanh', fontsize=12) ax2.set_ylim(-1.1, 1.1) ax2.grid(alpha=0.3) ax2.spines['top'].set_visible(False) ax2.spines['right'].set_visible(False) fig.suptitle('sigmoid and tanh: stacked comparison', fontsize=13) plt.tight_layout() plt.show()

sharex=True让两个子图共用横轴,缩放或平移时同步。ax1.set_ylim(-0.1, 1.1)给 sigmoid 上下各留 0.1 余量,ax2.set_ylim(-1.1, 1.1)给 tanh 留余量。fig.suptitle是整张图的标题,位置在顶部居中。这种布局适合放在报告里,读者从上往下看,先看到 sigmoid 的 0 到 1,再看到 tanh 的 -1 到 1,对比清晰。缺点是占用垂直空间较多,如果页面宽度有限,还是叠在一起更省地方。

4.3 合画时的图例、颜色与线型选择

合画时两条曲线叠在一起,颜色必须区分明显。我一般用蓝色系给 sigmoid,绿色系给 tanh,因为这两个颜色在色盲模式下也能区分。线宽都设 2.5,不要一条粗一条细,否则视觉上会误以为细的那条不重要。图例位置放在左上角,因为 S 曲线在左上和右下都有空间,左上角通常不会被曲线穿过。如果曲线在左上角有交叉,就把图例移到右下角,用loc='lower right'。

线型上,主曲线都用实线,导数曲线才用虚线。合画时如果要把导数也放进去,四条线会非常拥挤,我的建议是合图只画函数本身,导数曲线留在分开画的图里。如果非要合画导数,可以用双纵轴ax.twinx(),但这样图例会变得复杂,新手容易搞混哪条线对应哪个轴。更稳妥的做法是合图只做形状对比,导数分析回到分开画的章节。

颜色代码方面,#1f77b4是 matplotlib 默认蓝色,#2ca02c是默认绿色,这两个色值在大多数屏幕和打印设备上表现稳定。如果要在深色背景上展示,需要换成更亮的色值,比如#4fa3d1和#5ec45e。线宽 2.5 是屏幕展示的常用值,投影时如果觉得细,可以提到 3.0。图例字体 11 比正文稍大,保证在缩小查看时也能看清。

5. 避坑与排查:绘制激活函数时最容易翻车的 5 个点

5.1 现象:曲线两端出现锯齿或台阶

原因:采样点太少,np.linspace的第三个参数设成了 50 或 100,在 $x$ 接近 0 的拐点附近,点与点之间跨度太大,直线连接后看起来像台阶。解决:把采样点提到 1000 以上,或者用np.linspace(-10, 10, 2000)。如果只是屏幕查看,1000 足够;如果要放大局部,2000 更稳。这个坑在画导数曲线时更明显,因为导数在饱和区变化平缓,点少时锯齿会出现在两端而不是中间。

5.2 现象:sigmoid 计算出inf或nan

原因:直接用1 / (1 + np.exp(-x)),当x是很大的负数时,np.exp(-x)溢出为inf,1 / inf得到 0,结果虽然不错但会触发运行时警告。如果x里混有nan,整个数组都会变nan。解决:用分段写法np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))),或者直接调scipy.special.expit。绘制时x范围有限,溢出概率低,但养成稳定写法的习惯,后面写模型代码时不会突然翻车。

5.3 现象:合画时 tanh 的下半部分被裁掉

原因:没有显式设置ax.set_ylim,matplotlib 自动根据数据范围取纵轴,sigmoid 的 0 到 1 和 tanh 的 -1 到 1 叠加后,自动范围可能偏向 sigmoid 的数据分布,导致 tanh 的负半轴被压缩或裁切。解决:合画时始终写ax.set_ylim(-1.2, 1.2),给两条曲线的极值都留余量。如果纵轴范围还是不对,检查是不是在plot之后又调用了其他会改变纵轴的函数,比如ax.autoscale()。

5.4 现象:图例遮挡曲线或关键点

原因:图例默认位置是best,matplotlib 会自动找空白处,但 S 曲线的空白区在左上和右下,如果关键点标在 $(0, 0.5)$ 附近,图例可能正好压在上面。解决:手动指定loc='upper left'或loc='lower right',然后用bbox_to_anchor微调。如果图例还是挡,把图例放到图外,用bbox_to_anchor=(1.02, 1)配合loc='upper left',再调plt.tight_layout()留出边距。关键点标注的xytext也要避开图例区域,通常往右移 2 到 3 个单位。

5.5 现象:保存的图片模糊或字体变样

原因:plt.savefig默认 dpi 是 100,屏幕上看还行,插入文档放大后就模糊。字体方面,如果系统没有 matplotlib 默认的 DejaVu Sans,中文标签会变成方框。解决:保存时写plt.savefig('output.png', dpi=300, bbox_inches='tight'),dpi 300 适合打印,bbox_inches='tight'自动裁掉多余白边。中文标签需要提前设置plt.rcParams['font.sans-serif'] = ['SimHei']和plt.rcParams['axes.unicode_minus'] = False,否则负号显示异常。如果是在服务器上跑,没有中文字体,就把标签全改成英文,避免方框。

6. 进阶技巧:把导数曲线和饱和区标注一起画出来

分开画和合起来画都跑通之后,可以加一层进阶操作:在图上标出饱和区。sigmoid 在 $|x| > 6$ 之后导数小于 0.0025,tanh 在 $|x| > 3$ 之后导数小于 0.01。把这些区域用浅色背景标出来,能直观看到梯度消失的范围。下面这段代码在 sigmoid 图上加了饱和区标注:

import numpy as np import matplotlib.pyplot as plt x = np.linspace(-10, 10, 1000) sigmoid = np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x))) dsigmoid = sigmoid * (1 - sigmoid) fig, ax = plt.subplots(figsize=(9, 5.5)) # 饱和区背景,浅灰色 ax.axvspan(-10, -6, color='gray', alpha=0.12, label='saturation zone') ax.axvspan(6, 10, color='gray', alpha=0.12) ax.plot(x, sigmoid, color='#1f77b4', linewidth=2.5, label='sigmoid') ax.plot(x, dsigmoid, color='#ff7f0e', linewidth=2, linestyle='--', label='derivative') # 标出导数峰值 ax.scatter([0], [0.25], color='red', zorder=5) ax.annotate('max derivative 0.25', xy=(0, 0.25), xytext=(2, 0.32), arrowprops=dict(arrowstyle='->', color='red'), fontsize=10) ax.set_xlabel('x', fontsize=12) ax.set_ylabel('value', fontsize=12) ax.set_title('sigmoid: saturation zones and derivative peak', fontsize=13) ax.legend(loc='upper right', fontsize=10) ax.grid(alpha=0.3) ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) plt.tight_layout() plt.show()

axvspan画垂直背景带,alpha=0.12让灰色很淡,不干扰曲线。两个axvspan分别覆盖 $[-10, -6]$ 和 $[6, 10]$,这两个区间是 sigmoid 的饱和区。ax.scatter标出导数峰值 0.25,annotate的箭头指向该点。图例放在右上角,因为饱和区在两侧,右上角相对空。这个技巧同样适用于 tanh,只需把饱和区改成 $[-10, -3]$ 和 $[3, 10]$,导数峰值改成 1。

验证方法很简单:把x范围缩到 $[-6, 6]$,饱和区背景应该消失,曲线两端不再压平。再把x范围拉到 $[-20, 20]$,饱和区会变得更长,导数曲线在两端几乎贴 0。通过调整范围观察饱和区变化,能加深对梯度消失的理解。我自己的习惯是每次改完参数都保存一张图,文件名带上范围和采样点,比如sigmoid_-10_10_1000.png,回头对比时不用重新跑代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询