ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深度学习激活函数可视化:sigmoid与tanh绘图实战指南

深度学习激活函数可视化:sigmoid与tanh绘图实战指南 简介这份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))$最大值在 $x0$ 处为 0.25tanh 的导数是 $1 - \tanh^2(x)$最大值在 $x0$ 处为 1。这两个导数曲线的峰值差异很大放在同一张图里纵轴比例会很难看。分开画就能各自用合适的纵轴范围把细节看清楚。3. 分开画sigmoid 和 tanh 各自的最小绘图脚本3.1 用 matplotlib 画 sigmoid 并标出关键点分开画的第一张图给 sigmoid。除了曲线本身我习惯把 $x0$ 处的点、两条渐近线、以及导数曲线一起放上去。这样一张图就能把函数值、饱和趋势和梯度变化全讲清楚。下面是最小可用脚本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, linewidth2.5, labelsigmoid) # 导数曲线用虚线区分 ax1.plot(x, dsigmoid, color#ff7f0e, linewidth2, linestyle--, labelsigmoid derivative) # 标出 x0 处的关键点 ax1.scatter([0], [0.5], colorred, zorder5) ax1.annotate((0, 0.5), xy(0, 0.5), xytext(2, 0.35), arrowpropsdict(arrowstyle-, colorred), fontsize11) # 渐近线 ax1.axhline(y0, colorgray, linewidth0.8, linestyle:) ax1.axhline(y1, colorgray, linewidth0.8, linestyle:) ax1.set_xlabel(x, fontsize12) ax1.set_ylabel(value, fontsize12) ax1.set_title(sigmoid function and its derivative, fontsize13) ax1.legend(locupper left, fontsize10) ax1.grid(alpha0.3) plt.tight_layout() plt.show()这段代码里ax1.plot画主曲线线宽 2.5 是为了在投影或打印时也看得清。导数曲线用虚线颜色选橙色和主曲线的蓝色形成对比。ax1.scatter标出 $(0, 0.5)$ 这个点因为它是 sigmoid 的对称中心也是导数最大处。ax1.annotate的箭头指向这个点文字放在右上方避免遮挡曲线。两条axhline画出 0 和 1 的渐近线提醒读者 sigmoid 永远取不到这两个值。grid(alpha0.3)让网格线淡一点不抢曲线视觉。参数上figsize(8, 5)是单张图的常用比例宽比高多 3 个单位适合展示横向延伸的 S 曲线。zorder5保证散点画在曲线上面。tight_layout()自动调整边距避免标签被裁掉。如果要在 Jupyter 里显示最后一行plt.show()可以保留如果保存图片换成plt.savefig(sigmoid.png, dpi150)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, linewidth2.5, labeltanh) ax.plot(x, dtanh, color#d62728, linewidth2, linestyle--, labeltanh derivative) ax.scatter([0], [0], colorred, zorder5) ax.annotate((0, 0), xy(0, 0), xytext(2, -0.5), arrowpropsdict(arrowstyle-, colorred), fontsize11) ax.axhline(y-1, colorgray, linewidth0.8, linestyle:) ax.axhline(y1, colorgray, linewidth0.8, linestyle:) ax.set_xlabel(x, fontsize12) ax.set_ylabel(value, fontsize12) ax.set_title(tanh function and its derivative, fontsize13) ax.set_ylim(-1.2, 1.2) # 给渐近线留余量 ax.legend(locupper left, fontsize10) ax.grid(alpha0.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))但要注意两个子图的纵轴范围不同不能共用shareyTrue。并排时标题要写清楚哪个是 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)$但在图上加一条 $y0.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, linewidth2.5, labelsigmoid) ax.plot(x, tanh, color#2ca02c, linewidth2.5, labeltanh) # 参考线sigmoid 的中心和 tanh 的中心 ax.axhline(y0.5, color#1f77b4, linewidth0.8, linestyle:, alpha0.6) ax.axhline(y0, color#2ca02c, linewidth0.8, linestyle:, alpha0.6) # 标出两个中心点 ax.scatter([0, 0], [0.5, 0], color[#1f77b4, #2ca02c], zorder5, s60) ax.annotate(sigmoid center (0, 0.5), xy(0, 0.5), xytext(2.5, 0.7), arrowpropsdict(arrowstyle-, color#1f77b4), fontsize10, color#1f77b4) ax.annotate(tanh center (0, 0), xy(0, 0), xytext(2.5, -0.35), arrowpropsdict(arrowstyle-, color#2ca02c), fontsize10, color#2ca02c) ax.set_xlabel(x, fontsize12) ax.set_ylabel(value, fontsize12) ax.set_title(sigmoid vs tanh: shape and center comparison, fontsize13) ax.set_ylim(-1.2, 1.2) ax.legend(locupper left, fontsize11) ax.grid(alpha0.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 到 1sigmoid 的下半段被裁掉。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), sharexTrue) ax1.plot(x, sigmoid, color#1f77b4, linewidth2.5) ax1.set_ylabel(sigmoid, fontsize12) ax1.set_ylim(-0.1, 1.1) ax1.grid(alpha0.3) ax1.spines[top].set_visible(False) ax1.spines[right].set_visible(False) ax2.plot(x, tanh, color#2ca02c, linewidth2.5) ax2.set_xlabel(x, fontsize12) ax2.set_ylabel(tanh, fontsize12) ax2.set_ylim(-1.1, 1.1) ax2.grid(alpha0.3) ax2.spines[top].set_visible(False) ax2.spines[right].set_visible(False) fig.suptitle(sigmoid and tanh: stacked comparison, fontsize13) plt.tight_layout() plt.show()sharexTrue让两个子图共用横轴缩放或平移时同步。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 曲线在左上和右下都有空间左上角通常不会被曲线穿过。如果曲线在左上角有交叉就把图例移到右下角用loclower 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)溢出为inf1 / 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_ylimmatplotlib 自动根据数据范围取纵轴sigmoid 的 0 到 1 和 tanh 的 -1 到 1 叠加后自动范围可能偏向 sigmoid 的数据分布导致 tanh 的负半轴被压缩或裁切。解决合画时始终写ax.set_ylim(-1.2, 1.2)给两条曲线的极值都留余量。如果纵轴范围还是不对检查是不是在plot之后又调用了其他会改变纵轴的函数比如ax.autoscale()。5.4 现象图例遮挡曲线或关键点原因图例默认位置是bestmatplotlib 会自动找空白处但 S 曲线的空白区在左上和右下如果关键点标在 $(0, 0.5)$ 附近图例可能正好压在上面。解决手动指定locupper left或loclower right然后用bbox_to_anchor微调。如果图例还是挡把图例放到图外用bbox_to_anchor(1.02, 1)配合locupper left再调plt.tight_layout()留出边距。关键点标注的xytext也要避开图例区域通常往右移 2 到 3 个单位。5.5 现象保存的图片模糊或字体变样原因plt.savefig默认 dpi 是 100屏幕上看还行插入文档放大后就模糊。字体方面如果系统没有 matplotlib 默认的 DejaVu Sans中文标签会变成方框。解决保存时写plt.savefig(output.png, dpi300, bbox_inchestight)dpi 300 适合打印bbox_inchestight自动裁掉多余白边。中文标签需要提前设置plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] False否则负号显示异常。如果是在服务器上跑没有中文字体就把标签全改成英文避免方框。6. 进阶技巧把导数曲线和饱和区标注一起画出来分开画和合起来画都跑通之后可以加一层进阶操作在图上标出饱和区。sigmoid 在 $|x| 6$ 之后导数小于 0.0025tanh 在 $|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, colorgray, alpha0.12, labelsaturation zone) ax.axvspan(6, 10, colorgray, alpha0.12) ax.plot(x, sigmoid, color#1f77b4, linewidth2.5, labelsigmoid) ax.plot(x, dsigmoid, color#ff7f0e, linewidth2, linestyle--, labelderivative) # 标出导数峰值 ax.scatter([0], [0.25], colorred, zorder5) ax.annotate(max derivative 0.25, xy(0, 0.25), xytext(2, 0.32), arrowpropsdict(arrowstyle-, colorred), fontsize10) ax.set_xlabel(x, fontsize12) ax.set_ylabel(value, fontsize12) ax.set_title(sigmoid: saturation zones and derivative peak, fontsize13) ax.legend(locupper right, fontsize10) ax.grid(alpha0.3) ax.spines[top].set_visible(False) ax.spines[right].set_visible(False) plt.tight_layout() plt.show()axvspan画垂直背景带alpha0.12让灰色很淡不干扰曲线。两个axvspan分别覆盖 $[-10, -6]$ 和 $[6, 10]$这两个区间是 sigmoid 的饱和区。ax.scatter标出导数峰值 0.25annotate的箭头指向该点。图例放在右上角因为饱和区在两侧右上角相对空。这个技巧同样适用于 tanh只需把饱和区改成 $[-10, -3]$ 和 $[3, 10]$导数峰值改成 1。验证方法很简单把x范围缩到 $[-6, 6]$饱和区背景应该消失曲线两端不再压平。再把x范围拉到 $[-20, 20]$饱和区会变得更长导数曲线在两端几乎贴 0。通过调整范围观察饱和区变化能加深对梯度消失的理解。我自己的习惯是每次改完参数都保存一张图文件名带上范围和采样点比如sigmoid_-10_10_1000.png回头对比时不用重新跑代码。希望帮到你。本文还有配套的精品资源点击获取
返回列表