ARTICLE DETAIL

资讯详情

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

Python+Matplotlib绘制sigmoid与tanh激活函数曲线:从公式到可视化

Python+Matplotlib绘制sigmoid与tanh激活函数曲线:从公式到可视化 简介面向深度学习初学者的一份PDF文档聚焦Sigmoid与Tanh激活函数的可视化绘制通过逐行代码详解帮助读者快速掌握两种函数的图像特征。文档完整覆盖“分开画”与“合起来画”两种场景不仅给出基于Matplotlib和NumPy的可运行代码还细化了坐标轴置中、刻度调整、边框隐藏等绘图细节并利用Tanh2×Sigmoid(2x)−1的数学关系在同一张图中直观对比两者差异。内容同时涉及Sigmoid函数输出为0~1、Tanh函数输出为−1~1的特性以及它们在神经网络非线性变换和梯度传播中的不同表现便于初学者理解激活函数对模型训练的影响。压缩包内仅有1个PDF文件大小119KB内容紧凑随取随用。该文档已有4111人次学习/下载很适合在神经网络入门阶段配合理论一起阅读。1. 为什么要亲手把sigmoid和tanh画出来训练一个两层全连接网络时遇到loss卡住不动第一反应往往是调学习率、换优化器很少有人会先想如果隐藏层全用了sigmoid、tanh这类激活函数输入稍大就被压到饱和区梯度跟着消失网络自然学不动。这就是只背公式、不亲手绘制的代价。下面要做的就是用Python把这两条曲线一步步画出来先分开画、再合起来画每一行代码都拆开解释。看完能直接照抄也能顺手改成ReLU、Swish等任意激活函数的画法。这份内容对刚入门深度学习又想补Python绘图基础的小白尤其友好也适合需要快速产出配图的一线工程师。2. sigmoid与tanh的数学定义和绘图准备2.1 激活函数在神经网络中承担的职责神经网络的隐藏层如果只有yWxb这种线性运算多层叠加起来仍然是线性变换网络容量跟单层没有本质区别。激活函数的作用就是在每一层输出上引入非线性让网络能逼近任意复杂函数。sigmoid和tanh是深度学习早期最常用的两个激活函数今天它们依然活跃在特定位置sigmoid是二分类问题输出层的标配tanh在循环神经网络、生成模型的隐藏层里仍然常见。现代网络里ReLU、Swish等激活函数因为缓解了梯度消失、计算更简单而成为主流但这不代表sigmoid和tanh不值得看。相反很多框架内置的数值稳定实现、初始化策略都和这两条曲线的形状强相关。把它们的图像、值域、导数性质亲手画一遍比背十遍公式都有用。理解它们才能真正看懂为什么后来会出现ReLU以及为什么tanh在不少场景下比sigmoid收敛更快。2.2 两条曲线的定义、值域与导数sigmoid的定义是σ(x) 1 / (1 e^(-x))值域落在(0, 1)之间。x趋向正无穷时输出趋近1趋向负无穷时趋近0x0时输出0.5。因为e^01所以1/(11)0.5。它的导数有一个非常漂亮的性质σ(x) σ(x) * (1 - σ(x))也就是说知道当前输出值就能直接算出梯度。这个性质让反向传播的实现变得极其简单但也带来问题当σ(x)接近0或1时导数为0梯度消失。图像上就是两条水平渐近线附近的平坦区。tanh的定义是tanh(x) (e^x - e^(-x)) / (e^x e^(-x))值域在(-1, 1)之间x0时输出0输出均值是0而不是0.5。这个「零中心」特性让tanh在隐藏层里通常比sigmoid收敛更快。它的导数是tanh(x) 1 - tanh(x)²对比项sigmoidtanh表达式1 / (1 e^(-x))(e^x - e^(-x)) / (e^x e^(-x))值域(0, 1)(-1, 1)x0处输出0.50导数形式σ(x)(1-σ(x))1 - tanh²(x)输出均值恒正均值0.5零中心均值0两函数的导数形状也值得留意。sigmoid的导数在x0处取最大值0.25向两侧迅速衰减这意味着每经过一层sigmoid梯度至少要乘上0.25多层堆叠后梯度消失几乎是必然的。tanh的导数最大值为1但饱和区同样让梯度趋于零。这两个数字在深层网络参数初始化和归一化讨论里经常被引用。两者还有一个数学关联tanh(x) 2σ(2x) - 1。也就是说tanh可以理解成对sigmoid做一次拉伸和平移的结果。这个关系在合起来画的时候可以直接用代码验证。2.3 用NumPy生成数据用Matplotlib绘图的思路绘图的思路说起来只有三步生成x轴上的密集采样点按公式算出对应的y值再用plot把这些点连成曲线。有人会想到用Qt绘制波形图来做桌面应用但做数据分析和科学绘图NumPy加Matplotlib是目前最顺手的组合。NumPy负责向量化计算一次调用算出上千个点的值Matplotlib负责渲染和排版。安装只需要一条命令pip install numpy matplotlib导入和简单验证import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) print(x[:3], x[-3:])np.linspace(-10, 10, 1000)的意思是从-10到10之间均匀取1000个点返回一个NumPy数组。这里的1000就是采样密度点越密后面画出来的曲线越光滑。如果只给10个点plot会把它们连成一条折线看不出曲线特征。在Jupyter Notebook里画图通常不需要额外设置在命令行脚本里如果plt.show()不弹窗可以在导入pyplot之前指定后端import matplotlib matplotlib.use(TkAgg)注意matplotlib.use()必须在import matplotlib.pyplot之前执行否则不生效。3. 分开绘制sigmoid和tanh的逐行代码详解3.1 单独绘制sigmoid的完整代码与逐行拆解先画出sigmoid。以下代码可以直接保存为plot_sigmoid.py运行import numpy as np import matplotlib.pyplot as plt # 1. 生成x轴采样点从-10到10均匀取1000个点 x np.linspace(-10, 10, 1000) # 2. 计算sigmoidσ(x) 1 / (1 e^(-x)) y 1 / (1 np.exp(-x)) # 3. 创建画布figsize控制宽高单位是英寸 plt.figure(figsize(8, 5)) # 4. 画曲线color指定颜色linewidth指定线宽 plt.plot(x, y, colorblue, linewidth2) # 5. 画水平和垂直参考线让坐标轴居中 plt.axhline(0, colorblack, linewidth0.8) plt.axvline(0, colorblack, linewidth0.8) # 6. 设置标题和坐标轴标签 plt.title(Sigmoid Activation Function) plt.xlabel(x) plt.ylabel(sigmoid(x)) # 7. 网格线用虚线半透明y轴范围留一点边距 plt.grid(True, linestyle--, alpha0.6) plt.ylim(-0.1, 1.1) plt.show()这段代码的核心参数需要拆开理解。np.linspace(-10, 10, 1000)的三个参数分别是起点、终点、个数相邻两点间隔约0.02肉眼看起来就是一条连续曲线。y 1 / (1 np.exp(-x))这一行写成np.exp(-x)而不是np.exp(x)是一个值得养成的好习惯当x取较大正值时e^x会快速增大写成负指数可以让分母始终大于1结果自然落在(0, 1)区间也避免了一些场景下的浮点溢出风险。plt.figure(figsize(8, 5))是创建一块宽8英寸、高5英寸的画布。如果不在代码开头调用figureplot会自动创建一块默认尺寸的画布但默认尺寸在写博客配图时往往偏小。plt.plot(x, y, colorblue, linewidth2)把两个等长数组作为横纵坐标matplotlib会把相邻点用线段连起来。color参数可以直接写颜色名也可以写十六进制色值比如#1f77b4。plt.axhline和plt.axvline分别画一条贯穿整个坐标系的水平线和垂直线颜色用细黑线这样曲线在x0和y0的位置有明确参照。如果不加这两行一张只有蓝色曲线的图会显得飘。plt.grid(True, linestyle--, alpha0.6)开启网格并用虚线、半透明显示避免网格线抢走曲线的视觉重点。最后的plt.ylim(-0.1, 1.1)把y轴范围设为比值域略宽曲线不会贴住图的上边缘和下边缘。运行这段代码会看到一条从接近0平滑上升到接近1的S形曲线在x0处穿过0.5。这个形状就是sigmoid在教科书里的标准图。3.2 单独绘制tanh的代码与关键差异tanh的绘制流程几乎一样只是函数换成NumPy内置的np.tanhimport numpy as np import matplotlib.pyplot as plt # 生成x轴采样点 x np.linspace(-10, 10, 1000) # 直接用numpy内置的tanh避免手写指数公式溢出 y np.tanh(x) plt.figure(figsize(8, 5)) plt.plot(x, y, colorred, linewidth2) plt.axhline(0, colorblack, linewidth0.8) plt.axvline(0, colorblack, linewidth0.8) plt.title(Tanh Activation Function) plt.xlabel(x) plt.ylabel(tanh(x)) plt.grid(True, linestyle--, alpha0.6) plt.ylim(-1.1, 1.1) plt.show()这里有一个小知识点为什么用np.tanh而不手写(e^x - e^(-x)) / (e^x e^(-x))numpy内置的双曲函数在底层做了数值保护输入特别大或特别小的时候不会出现明显的溢出问题而且计算性能比自己用exp拼更好。如果你要验证两者的结果一致可以用np.allclose对比这一点在第5章展开。运行后会看到一条经过原点、两端分别趋近1和-1的曲线红色曲线穿过(0, 0)点后先陡后平整体比sigmoid更早进入饱和。这也是tanh在神经网络隐藏层里经常替代sigmoid的直观原因它把输出范围扩大了一倍而且均值是0。3.3 绘图参数的含义与常见调整两个代码块里出现的参数可以按用途分成几组。下表把它们列清楚之后不管画什么激活函数都不用反复查文档参数示例作用figsize(8, 5)画布宽高单位英寸比例影响曲线视觉胖瘦colorblue / #1f77b4曲线颜色颜色名或十六进制色值linewidth2线宽数值越大越粗配图和PPT一般用1.5到2.5linestyle--线型实线-、虚线--、点划线-.alpha0.6透明度0到1越小越淡ylim(-0.1, 1.1)y轴显示范围避免曲线贴边或被裁切平时画图还会碰到几个容易踩的小坑。第一一段脚本里连续调用多次plt.plot而不清理画布图形会在同一个坐标系里不断叠加可以用plt.clf()清空当前画布。第二plt.show()在某些后端里会阻塞脚本运行等窗口关闭后才会继续执行所以保存图片的代码要放在show之前。第三x轴采样范围选[-6, 6]更常见还是[-10, 10]更常见取决于你想重点看什么sigmoid和tanh在离原点超过4左右就已经明显饱和只看曲线走势用[-6, 6]就够想完整展示两条渐近线用[-10, 10]更舒展。4. 合起来画同轴叠加、子图对比与参数取舍单独画完两张图接下来自然要问这两个函数放在一起看能看出什么合起来画有两种常见做法按使用场景选一种就行。4.1 方案一两条曲线画在同一个坐标系同坐标系叠加是最直白的对比方式把两条曲线画在一起用不同颜色和标签区分import numpy as np import matplotlib.pyplot as plt x np.linspace(-10, 10, 1000) # 分别计算两个函数值 y_sigmoid 1 / (1 np.exp(-x)) y_tanh np.tanh(x) plt.figure(figsize(10, 6)) # 两条曲线都画在同一个坐标系里用label区分 plt.plot(x, y_sigmoid, labelsigmoid, colorblue, linewidth2) plt.plot(x, y_tanh, labeltanh, colorred, linewidth2) plt.axhline(0, colorblack, linewidth0.8) plt.axvline(0, colorblack, linewidth0.8) # 图例放在左上角两条线在左侧距离近用图例最容易辨认 plt.legend(locupper left, fontsize12) plt.title(sigmoid vs tanh) plt.xlabel(x) plt.ylabel(y) plt.grid(True, linestyle--, alpha0.6) plt.ylim(-1.2, 1.2) plt.show()这段代码跟单张图相比多了两个关键点。label参数给曲线起名字plt.legend(locupper left, fontsize12)把图例渲染在坐标系左上角。因为sigmoid和tanh在x为负的时候一个趋近0、一个趋近-1图例放左上角正好落在空白区域不会遮挡曲线主体。plt.ylim(-1.2, 1.2)也要特别注意。如果省略这行matplotlib会自动把y轴范围适配到两条曲线的数据范围也就是从-1到1左右虽然不至于离谱但往往会把sigmoid下边缘贴住坐标轴底部、tanh上边缘贴住顶部视觉上不美观手动预留0.2的余量更舒服。更隐蔽的问题是不设ylim时sigmoid那条线由于只覆盖0到1区间在自动缩放后会被纵向拉伸变陡跟单独画时差别明显容易让人误判曲线形状。4.2 方案二用subplots分成上下两个子图如果你希望每条曲线都有完整的纵向空间可以用子图。上面画sigmoid、下面画tanh互不干扰import numpy as np import matplotlib.pyplot as plt # 创建2行1列的子图布局figsize高度调大一点 fig, axes plt.subplots(2, 1, figsize(8, 8)) x np.linspace(-10, 10, 1000) # 上方子图画sigmoid axes[0].plot(x, 1 / (1 np.exp(-x)), colorblue, linewidth2) axes[0].set_title(sigmoid) axes[0].set_xlabel(x) axes[0].set_ylabel(sigmoid(x)) axes[0].axhline(0, colorblack, linewidth0.8) axes[0].axvline(0, colorblack, linewidth0.8) axes[0].grid(True, linestyle--, alpha0.6) # 下方子图画tanh axes[1].plot(x, np.tanh(x), colorred, linewidth2) axes[1].set_title(tanh) axes[1].set_xlabel(x) axes[1].set_ylabel(tanh(x)) axes[1].axhline(0, colorblack, linewidth0.8) axes[1].axvline(0, colorblack, linewidth0.8) axes[1].grid(True, linestyle--, alpha0.6) # 自动调整子图间距避免标题和图像重叠 plt.tight_layout() plt.show()这段代码要理解的核心是plt.subplots(2, 1, figsize(8, 8))的返回值。它返回两个东西fig是整个图对象axes是长度为2的数组分别对应上方和下方的子图坐标系。axes[0]是第一个子图axes[1]是第二个子图。之后的每个设置比如plot、set_title、axhline、grid都作用在具体的子图对象上而不是全局的plt。这里有一个容易忽略的细节在子图模式下axhline和axvline也必须分别对axes[0]和axes[1]调用。如果习惯性地写成plt.axhline(0)有的Matplotlib版本会把这根线画在最后操作的那个子图上结果上下两张图只有一张有参考线。统一用axes[i]的方式操作就不会出错。plt.tight_layout()的作用是自动调整子图之间的间距和边距防止上面的标题压到下面的图。这一步在单图模式下不必须但子图模式下建议每次都加。不加的话上面子图的标题经常会跟下面子图的坐标轴挤在一起。4.3 两种方案怎么选叠加同轴和上下子图没有绝对优劣只看你要服务的目标对比项同轴叠加上下子图视觉重点数值范围差异、零中心性每条曲线的饱和区域和对称性信息密度一张图塞完节省版面两张图纵向排列单图更清晰代码量少适合快速出图多几行结构更规整典型场景面试图、博客对比图论文配图、课程PPT如果觉得上下排列占地方也可以把subplots(2, 1)改成subplots(1, 2)左右排列代码逻辑完全相同。左右排列时两张图宽度被压缩x轴范围显示到[-10, 10]会让曲线横向拉伸观感上不如上下排列自然一般建议配合figsize(12, 5)使用。4.4 用绘图验证 tanh(x) 2σ(2x) - 1两条曲线合起来画之后顺便可以做一件很有用的事验证数学公式。把tanh的另一种写法2σ(2x) - 1算出来跟np.tanh画出来的线做数值对比# 用sigmoid的变换形式表示tanh x np.linspace(-6, 6, 1000) y_transformed 2 / (1 np.exp(-2 * x)) - 1 print(np.allclose(y_transformed, np.tanh(x))) # Truenp.allclose比较两个数组是否在误差范围内相等返回True说明这两种写法在数值上完全一致。这说明tanh确实等价于先把sigmoid的输入压缩一半、再拉伸纵轴、再向下平移1个单位。理解了这层变换关系再看框架源码里某些tanh实现就不会觉得奇怪了。实际应用中也可以把这段代码拿去画三条线sigmoid、tanh、以及2σ(2x)-1后两条线会完全重叠。5. 让图更专业的关键参数与数值自检5.1 用annotate标注关键点用savefig导出高清图配PPT或写教程时光有曲线不够通常还要在关键位置加一个标注。sigmoid在x0处的值是0.5这个点最适合做标注import numpy as np import matplotlib.pyplot as plt x np.linspace(-6, 6, 1000) y 1 / (1 np.exp(-x)) plt.figure(figsize(8, 5)) plt.plot(x, y, linewidth2) # xy是箭头指向的点xytext是文字位置arrowprops控制箭头样式 plt.annotate((0, 0.5), xy(0, 0.5), xytext(0.8, 0.75), arrowpropsdict(arrowstyle-, colorgray)) plt.grid(True, linestyle--, alpha0.6) plt.show()annotate里最常用的是三个参数xy填要标记的坐标xytext填文字放在哪里arrowprops用字典指定箭头样式。arrowstyle-表示普通单箭头colorgray让箭头低调一些。如果文字距离曲线太近觉得乱把xytext的坐标往右上角多挪一点就行比如改成(1.2, 0.8)。图片确认无误后把plt.show()换成plt.savefig(sigmoid.png, dpi300, bbox_inchestight)就能导出高清图。dpi300是印刷级清晰度写博客用150就够bbox_inchestight会自动裁掉图周围的空白边省去手动调整边距的麻烦。5.2 处理中文乱码和负号显示把标题改成中文运行后很可能显示成一堆方块这是因为Matplotlib默认字体没有中文字形。通常用两行配置解决plt.rcParams[font.sans-serif] [WenQuanYi Micro Hei, SimHei] plt.rcParams[axes.unicode_minus] False第一行指定无衬线字体列表Matplotlib按顺序查找系统里存在的字体。Linux常见WenQuanYi Micro HeiWindows常见SimHei。第二行必须加如果不设置坐标轴上的负号会被渲染成方块。如果不想依赖系统字体最简单的方案是图里的文字一律用英文。5.3 用allclose验证手写公式与内置函数一致性换一个激活函数时最容易出错的地方是公式写错但图形看起来又差不多。可以用关键点做数值自检# 抽几个特殊位置对比手写公式和内置函数 x_check np.array([-2.0, 0.0, 2.0]) manual 2 / (1 np.exp(-2 * x_check)) - 1 print(manual) print(np.tanh(x_check)) print(np.allclose(manual, np.tanh(x_check)))这段代码先在x-2、0、2三个位置用手写公式算出tanh的变换式结果再用numpy的tanh算一遍最后用allclose对比。三行输出应该分别是[-0.96402758, 0., 0.96402758]、同样的数组、以及True。日常画图验证到这个程度就够了关键点的数值不差曲线形状就不会差。后面想画ReLU、Swish、GELU把函数体替换掉再用同样的思路做一次关键点自检就能放心把图放进自己的博客和报告里。本文还有配套的精品资源点击获取
返回列表