ARTICLE DETAIL

资讯详情

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

Python 实战:手绘 sigmoid 与 tanh 曲线,搞懂梯度消失

Python 实战:手绘 sigmoid 与 tanh 曲线,搞懂梯度消失 简介这份PDF资料面向深度学习入门者与神经网络初学者聚焦Sigmoid与Tanh两种常见激活函数的可视化实现帮助读者理解其数学表达式、值域特征及在模型中的非线性作用。资源以逐行代码详解的方式演示了分开绘制两条曲线与合并绘制在同一坐标系中的两种方案并借助tanh(x)2σ(2x)-1的数学推导简化绘图逻辑便于直观比较两者的形状差异与梯度分布特点。压缩包内仅含1个PDF文件大小约119KB内容紧凑适合作为手边速查与练习参考。目前已有4111人学习下载说明该主题在入门阶段具有较高关注度。读者可从中获得可直接运行的matplotlib绘图代码、坐标轴与图例的定制技巧以及关于二分类任务选Sigmoid、其他场景考虑Tanh的选型思路为后续理解梯度消失等问题打下基础。1. 从一条曲线看懂 sigmoid 与 tanh为什么值得亲手画一遍很多人第一次接触激活函数是在教程里看到一张现成的图然后记住「sigmoid 输出 0 到 1tanh 输出 -1 到 1」就翻篇了。真到调模型的时候梯度消失、输出不以零为中心、饱和区把梯度压成接近零这些问题全冒出来才发现当初那张图根本没看进去。这篇就干一件事用 Python 把 sigmoid 和 tanh 分开画、合起来画逐行拆代码把两条曲线的形状、饱和区、对称性、导数关系全部落到你能自己跑出来的图上。适合刚入门深度学习、想搞懂激活函数到底长什么样的新手也适合想快速复现对比图的老手。画图不是目的看懂曲线背后的数值行为才是。2. 先把公式和数值边界钉死sigmoid 与 tanh 到底差在哪2.1 两个函数的数学定义与值域sigmoid 的标准形式是 σ(x) 1 / (1 e^(-x))。它的输出永远落在 (0, 1) 开区间内x 趋近正无穷时逼近 1趋近负无穷时逼近 0x0 时正好是 0.5。这个 0.5 很关键它意味着 sigmoid 的输出不以零为中心后面接全连接层时输入全为正数会让权重梯度方向出现锯齿式更新收敛变慢。tanh 的标准形式是 tanh(x) (e^x - e^(-x)) / (e^x e^(-x))。它的输出落在 (-1, 1) 开区间x0 时输出 0关于原点对称。正因为输出以零为中心tanh 在隐藏层的表现通常比 sigmoid 好收敛更快。但两者有个共同的毛病当 |x| 变大时曲线进入饱和区导数趋近于零反向传播时梯度被反复相乘很容易消失。这里有个容易混的点tanh 和 sigmoid 不是两个独立的东西它们之间有恒等关系 tanh(x) 2·σ(2x) - 1。也就是说tanh 本质上是 sigmoid 缩放平移后的版本。理解这层关系你就能明白为什么两者的曲线形状那么像只是 tanh 被「拉宽」并「下移」了。2.2 为什么选这两个函数来画而不是 ReLU 或 GELU现在主流隐藏层激活函数早就换成了 ReLU、GELU、SiLU 这些但 sigmoid 和 tanh 依然是理解激活函数的最佳起点。原因有三第一它们处处可导导数形式简洁适合手推反向传播第二它们的饱和特性是梯度消失问题的经典案例看懂它们再看 ReLU 的「死区」问题会顺很多第三sigmoid 至今仍活跃在二分类输出层和门控机制里tanh 在 RNN 的隐藏状态里也没完全退场。所以画这两条曲线不是为了怀旧是为了建立对「激活函数形状如何影响训练」的直觉。你后面看到 GELU 的平滑曲线、SiLU 的非单调性都能拿这两条曲线做参照。2.3 用 NumPy 生成数据并验证边界值动手之前先把数值算一遍确认公式没记错。下面这段代码生成 -10 到 10 的等间距点分别算 sigmoid 和 tanh并打印几个关键位置的输出。import numpy as np # 生成 -10 到 10 之间 1000 个等间距点 x np.linspace(-10, 10, 1000) # sigmoid 定义 sigmoid 1 / (1 np.exp(-x)) # tanh 定义也可以直接用 np.tanh(x) tanh (np.exp(x) - np.exp(-x)) / (np.exp(x) np.exp(-x)) # 验证关键点 for val in [-10, -1, 0, 1, 10]: idx np.argmin(np.abs(x - val)) print(fx{val:3} sigmoid{sigmoid[idx]:.6f} tanh{tanh[idx]:.6f})运行后你会看到 x0 时 sigmoid 输出 0.5、tanh 输出 0x10 时 sigmoid 已经到 0.999955tanh 到 0.999999x-10 时对称地逼近 0 和 -1。这就是饱和区的直观体现——输入稍微大一点输出几乎不再变化。参数说明np.linspace(-10, 10, 1000)的三个参数分别是起点、终点、点数点数越多曲线越平滑画图用 1000 足够。np.exp对数组做逐元素指数运算注意当 x 很大时np.exp(x)可能溢出所以 tanh 更推荐直接用np.tanh它内部做了数值稳定处理。这也是一个踩坑点自己手写 tanh 公式时x 超过 700 左右np.exp(x)就会返回 inf导致结果是 nan。3. 分开画用 Matplotlib 把 sigmoid 和 tanh 各自画清楚3.1 画 sigmoid 的最小可用代码先单独画 sigmoid把坐标轴、网格、关键点标注都加上这样一张图就能说明问题。import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) sigmoid 1 / (1 np.exp(-x)) plt.figure(figsize(8, 5)) plt.plot(x, sigmoid, color#1f77b4, linewidth2, labelsigmoid(x)) plt.axhline(y0.5, colorgray, linestyle--, linewidth0.8) plt.axvline(x0, colorgray, linestyle--, linewidth0.8) plt.scatter([0], [0.5], colorred, zorder5) plt.annotate((0, 0.5), xy(0, 0.5), xytext(1.5, 0.35), arrowpropsdict(arrowstyle-, colorred)) plt.title(Sigmoid Activation Function) plt.xlabel(x) plt.ylabel(sigmoid(x)) plt.grid(True, alpha0.3) plt.legend() plt.tight_layout() plt.savefig(sigmoid_alone.png, dpi150) plt.show()逻辑说明plt.axhline和plt.axvline画出 y0.5 和 x0 的参考线帮你一眼看出中心点位置。plt.scatter单独标出 (0, 0.5) 这个点plt.annotate加箭头和文字。zorder5保证散点画在曲线上面。dpi150让保存的图足够清晰直接放进文档也不糊。参数说明figsize(8, 5)控制画布宽高比8:5 比较适合展示单条曲线。linewidth2让曲线够粗投影或截图后依然清楚。alpha0.3让网格线变淡不抢曲线视觉。如果你在 Jupyter 里跑plt.show()会直接内嵌显示在脚本里跑savefig会存成文件。3.2 画 tanh 并对比饱和速度tanh 的画法几乎一样但要注意它的值域是 (-1, 1)所以参考线画在 y0。import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) tanh np.tanh(x) plt.figure(figsize(8, 5)) plt.plot(x, tanh, color#d62728, linewidth2, labeltanh(x)) plt.axhline(y0, colorgray, linestyle--, linewidth0.8) plt.axvline(x0, colorgray, linestyle--, linewidth0.8) plt.scatter([0], [0], colorblue, zorder5) plt.annotate((0, 0), xy(0, 0), xytext(1.5, 0.3), arrowpropsdict(arrowstyle-, colorblue)) plt.title(Tanh Activation Function) plt.xlabel(x) plt.ylabel(tanh(x)) plt.grid(True, alpha0.3) plt.legend() plt.tight_layout() plt.savefig(tanh_alone.png, dpi150) plt.show()逻辑说明这里直接用np.tanh(x)而不是手写公式避免溢出问题。参考线画在 y0因为 tanh 关于原点对称。散点标出 (0, 0)这是 tanh 的中心点。参数说明颜色用#d62728和 sigmoid 的#1f77b4区分开后面合起来画时也保持这个配色读者一眼能对上。np.tanh是 NumPy 内置函数内部对数值稳定性做了处理比手写公式安全。画完两张图放一起看你会发现 tanh 在 x±2 附近就已经接近饱和而 sigmoid 要到 x±5 左右才明显压平。这意味着 tanh 的梯度消失来得更早但它的输出范围更大、以零为中心实际训练中往往收敛更快。这个「饱和早但对称」的权衡是选型时要记住的。3.3 把导数曲线也画出来理解梯度消失光看原函数不够导数的形状才决定梯度怎么传。sigmoid 的导数是 σ(x)·(1-σ(x))tanh 的导数是 1-tanh²(x)。把导数画出来饱和区的问题一目了然。import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) sigmoid 1 / (1 np.exp(-x)) tanh np.tanh(x) sigmoid_grad sigmoid * (1 - sigmoid) tanh_grad 1 - tanh ** 2 plt.figure(figsize(8, 5)) plt.plot(x, sigmoid_grad, color#1f77b4, linewidth2, labelsigmoid(x)) plt.plot(x, tanh_grad, color#d62728, linewidth2, labeltanh(x)) plt.axhline(y0, colorgray, linestyle--, linewidth0.8) plt.title(Derivatives of Sigmoid and Tanh) plt.xlabel(x) plt.ylabel(gradient) plt.grid(True, alpha0.3) plt.legend() plt.tight_layout() plt.savefig(gradients.png, dpi150) plt.show()逻辑说明sigmoid 导数最大值在 x0 处为 0.25tanh 导数最大值在 x0 处为 1。这意味着 tanh 在原点附近的梯度比 sigmoid 大 4 倍反向传播时信号更强。但两者在 |x|4 之后都趋近于零这就是梯度消失的根源。参数说明sigmoid * (1 - sigmoid)是逐元素乘法NumPy 会自动广播。tanh ** 2是逐元素平方。画在一起对比时纵轴范围会自动适配不需要手动设 ylim。提示如果你发现导数曲线在两端有轻微震荡那是np.linspace点数不够导致的把 1000 调到 5000 就平滑了。4. 合起来画一张图对比 sigmoid 与 tanh 的形状差异4.1 双曲线叠加与图例配置分开画看细节合起来画看关系。把两条曲线放在同一坐标系里值域差异、对称性、饱和速度的对比会非常直观。import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) sigmoid 1 / (1 np.exp(-x)) tanh np.tanh(x) plt.figure(figsize(9, 6)) plt.plot(x, sigmoid, color#1f77b4, linewidth2.5, labelsigmoid(x)) plt.plot(x, tanh, color#d62728, linewidth2.5, labeltanh(x)) plt.axhline(y0, colorblack, linestyle-, linewidth0.6) plt.axhline(y0.5, color#1f77b4, linestyle:, linewidth0.8, alpha0.6) plt.axhline(y-1, color#d62728, linestyle:, linewidth0.8, alpha0.6) plt.axhline(y1, color#d62728, linestyle:, linewidth0.8, alpha0.6) plt.title(Sigmoid vs Tanh) plt.xlabel(x) plt.ylabel(f(x)) plt.grid(True, alpha0.3) plt.legend(locupper left, fontsize11) plt.tight_layout() plt.savefig(sigmoid_vs_tanh.png, dpi150) plt.show()逻辑说明两条曲线用不同颜色和线宽区分。axhline画出 y0、y0.5、y-1、y1 四条参考线其中 y0.5 是 sigmoid 的中心y-1 和 y1 是 tanh 的渐近线。这样读者能一眼看出 sigmoid 的值域是 (0,1)tanh 是 (-1,1)。参数说明linestyle:是点线用来画渐近线不抢主曲线视觉。alpha0.6让参考线更淡。locupper left把图例放左上角避免遮挡曲线。fontsize11让图例文字在缩放后依然可读。4.2 用子图布局展示原函数与导数一张图看形状另一张图看梯度用 subplot 并排展示信息密度更高。import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) sigmoid 1 / (1 np.exp(-x)) tanh np.tanh(x) sigmoid_grad sigmoid * (1 - sigmoid) tanh_grad 1 - tanh ** 2 fig, axes plt.subplots(1, 2, figsize(14, 5)) axes[0].plot(x, sigmoid, color#1f77b4, linewidth2.5, labelsigmoid) axes[0].plot(x, tanh, color#d62728, linewidth2.5, labeltanh) axes[0].set_title(Activation Functions) axes[0].set_xlabel(x) axes[0].set_ylabel(f(x)) axes[0].grid(True, alpha0.3) axes[0].legend() axes[1].plot(x, sigmoid_grad, color#1f77b4, linewidth2.5, labelsigmoid) axes[1].plot(x, tanh_grad, color#d62728, linewidth2.5, labeltanh) axes[1].set_title(Derivatives) axes[1].set_xlabel(x) axes[1].set_ylabel(f(x)) axes[1].grid(True, alpha0.3) axes[1].legend() plt.tight_layout() plt.savefig(sigmoid_tanh_subplots.png, dpi150) plt.show()逻辑说明plt.subplots(1, 2)创建一行两列的子图axes[0]和axes[1]分别操作左右两张图。左图看原函数形状右图看导数衰减。这样一张图就能同时回答「长什么样」和「梯度怎么变」两个问题。参数说明figsize(14, 5)给两个子图足够的横向空间避免标签重叠。tight_layout()自动调整子图间距防止轴标签被裁掉。如果你要放进论文或报告把dpi调到 300 会更清晰。4.3 保存高分辨率图片与常见显示问题画完图要保存这里有几个实操细节。第一savefig必须在show之前调用否则保存的是空白图。第二如果图片边缘被裁加bbox_inchestight。第三中文标签需要设置字体否则会显示成方块。import matplotlib.pyplot as plt # 设置中文字体Windows 用 SimHeiMac 用 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 保存时用 bbox_inchestight 防止边缘裁切 plt.savefig(output.png, dpi300, bbox_inchestight)逻辑说明plt.rcParams是全局配置设置一次后续所有图都生效。axes.unicode_minus False解决负号显示成方块的问题。bbox_inchestight自动计算内容边界去掉多余白边。参数说明dpi300是印刷级分辨率文件会大一些但足够清晰。如果只是网页展示150 就够。SimHei是 Windows 自带黑体Mac 上换成Arial Unicode MS或PingFang SC。注意在 Jupyter Notebook 里如果先调了plt.show()再调plt.savefig()保存出来是空图。正确顺序永远是先savefig再show。5. 避坑与排查画激活函数曲线时最容易翻车的 5 个地方5.1 现象曲线两端出现 nan 或 inf原因手写 tanh 公式时用了(np.exp(x) - np.exp(-x)) / (np.exp(x) np.exp(-x))当 x 超过 700 左右np.exp(x)溢出成 infinf 减 inf 得到 nan。解决直接用np.tanh(x)NumPy 内部做了数值稳定处理。如果非要手写先对 x 做截断比如x np.clip(x, -500, 500)。5.2 现象sigmoid 曲线在 x0 处不是 0.5原因np.linspace的点数设成了偶数比如 1000 个点中间没有精确的 0最接近的是 -0.01 和 0.01算出来是 0.4975 和 0.5025。解决把点数设成奇数比如 1001这样中间点正好是 0。或者不依赖采样点直接单独算1 / (1 np.exp(-0))验证。5.3 现象保存的图片是空白原因先调了plt.show()Matplotlib 把画布清空了再调plt.savefig()保存的就是空白。解决调整顺序先savefig再show。或者在show之前用plt.gcf()获取当前画布再保存。5.4 现象中文标题显示成方块原因Matplotlib 默认字体不支持中文找不到对应字形就画方块。解决设置plt.rcParams[font.sans-serif] [SimHei]Windows或[Arial Unicode MS]Mac同时设plt.rcParams[axes.unicode_minus] False解决负号问题。5.5 现象两条曲线叠在一起分不清原因颜色太接近或者线宽太细或者没加图例。解决用对比色比如蓝色#1f77b4和红色#d62728。线宽至少 2。必须加label和legend()。如果还是分不清给其中一条加linestyle--虚线。6. 进阶技巧用交互式绘图和导数叠加验证数值行为静态图看形状够了但如果你想验证「tanh 的梯度在原点附近是 sigmoid 的 4 倍」这种数值结论交互式绘图更直接。用 Plotly 可以鼠标悬停看具体数值用 Matplotlib 的ginput也能手动取点。不过最实用的技巧是把原函数和导数画在双 y 轴上一眼看出梯度峰值位置。import numpy as np import matplotlib.pyplot as plt x np.linspace(-6, 6, 1000) sigmoid 1 / (1 np.exp(-x)) sigmoid_grad sigmoid * (1 - sigmoid) fig, ax1 plt.subplots(figsize(9, 5)) ax1.plot(x, sigmoid, color#1f77b4, linewidth2.5, labelsigmoid) ax1.set_xlabel(x) ax1.set_ylabel(sigmoid(x), color#1f77b4) ax1.tick_params(axisy, labelcolor#1f77b4) ax2 ax1.twinx() ax2.plot(x, sigmoid_grad, color#ff7f0e, linewidth2.5, linestyle--, labelsigmoid) ax2.set_ylabel(sigmoid(x), color#ff7f0e) ax2.tick_params(axisy, labelcolor#ff7f0e) plt.title(Sigmoid and Its Derivative (Dual Axis)) fig.tight_layout() plt.savefig(sigmoid_dual_axis.png, dpi150) plt.show()逻辑说明ax1.twinx()创建共享 x 轴的第二个 y 轴左轴画原函数右轴画导数。这样能直观看到原函数在 x0 处斜率最大导数在 x0 处达到峰值 0.25向两侧迅速衰减。如果你把 sigmoid 换成 tanh会看到导数峰值是 1衰减也更慢一些。参数说明tick_params把 y 轴刻度和标签染成和曲线一样的颜色避免左右轴混淆。linestyle--区分导数和原函数。fig.tight_layout()防止右轴标签被裁。我自己的习惯是每次换激活函数先把原函数和导数画在双轴上跑一遍确认梯度峰值位置和饱和区间。这个动作花不了两分钟但能避免后面调参时凭感觉猜。希望帮到你。本文还有配套的精品资源点击获取
返回列表