ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

万能逼近定理:神经网络函数拟合能力的数学基石与工程实践

万能逼近定理:神经网络函数拟合能力的数学基石与工程实践

万能逼近定理这个名字,听起来学术味很重,但它实际上是理解神经网络能力的核心钥匙。一句话概括:一个足够宽的前馈神经网络,在合适的非线性激活函数配合下,可以对定义在有界区域内的任意连续函数做到任意精度的拟合。也就是说,只要网络规模与数据条件允许,理论意义上就没有神经网络“学不出来”的映射关系。在实际项目里做回归、做分类、做人脸识别、做时序预测,本质上都是在让网络去拟合一个从输入到输出的隐函数;无论你用的是前馈神经网络、卷积神经网络、LSTM还是图神经网络,底层逻辑都逃不开这条定理兜底。这篇文章我会从定理本身的含义出发,把“为什么能拟合”背后的数学直觉拆开讲清楚,再结合不同网络架构的实战差异、训练中的常见坑来展开,希望能让刚入门的同学建立起一个坚实的整体认知,也让已经跑过不少模型的人重新理解那些看似理所当然却容易被误读的细节。

1. 先从结论说起:神经网络凭什么敢说“什么函数都能拟合”

1.1 万能逼近定理到底在讲什么

定理的原始表述大致是这样的:考虑一个单隐层的前馈神经网络结构,输入层接收一个向量,隐层有足够多的神经元,输出层做线性组合;只要激活函数选择得当(比如 sigmoid、tanh、ReLU 这类非多项式函数),那么这个网络可以以任意小的误差去逼近任意定义在紧集上的连续函数。

很多人第一次看这个定义会懵,我建议把里面几个关键词拆开。

  • 任意连续函数:函数本身不能有跳跃间断点。现实里的科学实验曲线、金融时序、图像特征映射大多都是连续的,这在工程上已经覆盖了绝大多数场景。
  • 紧集:通俗说,就是一个有界且闭的区间或区域,比如[0,1]区间、一张像素值 0 到 255 的图像域、一个固定范围的价格区间。这意味着定理承诺的范围不是无穷大的坐标轴,而是在给定范围内。超出范围,网络给出的结果属于外推,不受定理保护。
  • 任意小的误差:这个“任意”在数学上很关键。误差可以做到 0.001、0.0001,只要神经元数量够多,精度理论上是没有极限的。
  • 单隐层就够:定理最反直觉的地方就在这里,一个隐含层理论上已经具备万能逼近能力,不需要堆很多层。

所以从理论层面讲,神经网络的拟合上限是非常高的。这个结论是上世纪八十年代末被严格证明的,属于数学上的存在性证明。它告诉你:优化目标存在,方向正确,真正要解决的问题变成了怎么找到那一组合适的权重,而不是怀疑网络结构本身表达不了复杂函数。

1.2 定理成立是有前提条件的

越是听起来绝对正确的结论,越要注意它的前提,定理本身是“带条件”的。

第一,目标函数必须是连续的,且定义在紧集上。如果你要拟合的目标本身存在突变,比如分段阶梯函数,单独一个连续网络拟合起来会有震荡现象,需要相当大的容量去“削尖”转折处。

第二,激活函数必须是非多项式且带有非线性。如果用线性激活函数,无论网络叠多少层,最终都等价于一个线性变换,压根不存在逼近任意连续函数的能力。这就像用直尺永远画不出真正的曲线,只能画出折线。

第三,足够多的神经元数量。理论上需要无限多个才能做到“任意精度”,工程上做不到,所以我们只能用足够多、够用就行。这里的代价是参数量和计算量,需要在精度和资源之间权衡。

第四,逼近是在数学度量意义上的逼近,不是要求网络上每个点的输出都和真实函数完全一样。比如在函数空间里的 L2 范数逼近,意思是整体误差很小,但个别点可能存在明显偏差,甚至会出现“吉布斯式”的抖动。这在实际训练里很常见:整体 MSE 很低,但曲线两端抖得厉害。

1.3 直觉类比:乐高积木和分段折线

我一直觉得理解定理最好的方式是拿乐高积木做类比。单个神经元只负责一个非常简单的行为:拿一条直线或超平面对输入做切割,再用激活函数把输出压到一个非线性区间里。就像一块积木,平凡无奇。但很多块积木以不同角度、不同位置拼在一起,就可以拼出任何你能想象的结构;形状越复杂,需要的积木块越多。

另一个更数学化的直觉是高中就学过的分段线性逼近。随便画一条曲线,你可以用很多条小线段首尾相接去近似它,线段越多、切得越细,逼近效果越好。ReLU 网络本质上干的就是这件事:每增加一个神经元,就往输入空间里多切一刀,切出来的每个小区域上,网络整体是一个线性函数。无数局部线性函数拼在一起,就能在宏观上拟合出非线性的曲面。这个直觉我一直觉得比死记定理更管用,因为用来解释实际训练表现也非常顺手。

2. 拆解定理背后的数学逻辑:单个神经元到复杂映射

2.1 单个神经元到底在做什么

从底层看,一个神经元只做两件事:加权求和,然后经过一个非线性的激活函数。设输入是向量 x,权重是向量 w,偏置是 b,输出就是 a(w·x + b)。这里 w·x 是一个线性投影,几何上它决定了输入空间中一个超平面的方向;偏置 b 决定这个超平面离原点有多远;激活函数则负责在超平面两侧做区分。

拿 sigmoid 举例,w·x + b 的值域是负无穷到正无穷,经由 sigmoid 压到 0 到 1 之间。当输入落在超平面的一侧时,输出趋向 1;另一侧趋向 0;只有边界附近有一个平滑过渡。所以单个 sigmoid 神经元本质上是一个“柔化的阶梯函数”。ReLU 更直白,小于零直接截断,大于零保持线性,等价于一个可以实时调整激活范围的“折线基元”。

理解了这一点,你就会明白为什么神经网络的表达力不在于某个神经元多厉害,而在于大量简单基元组合后的协同效果。每个神经元从不同角度切割输入空间,激活函数提供非线性扭曲,最终映射出复杂的输出曲面。

2.2 多层组合如何拼出任意形状

把多个神经元放在同一层,相当于对输入空间做了多把不同位置的“刀切”。第二层神经元的输入,已经是第一层形成的中间特征,于是第二层的切割会建立在第一层的组合之上。这时候网络不再是对原始输入做简单分块,而是对中间表示继续做抽象。

一个很关键的认识是:对于 ReLU 网络,整个模型其实是一个连续的分段线性函数。每一层每个神经元的开关状态,决定了输入空间被切分为多少个多面体区域。理论上,如果神经元数量足够多,切分的区域数量是指数级增长。每个区域内网络都是一个线性函数,而这么多线性函数拼起来,足以逼近任意光滑的连续曲面。这也是为什么网络越宽、越深,拟合复杂函数的能力越强——因为可切的“区域”更多,细节表达更细腻。

2.3 为什么激活函数必须是“非多项式”

这是特别容易被忽视的一个点。如果你只用线性激活,不管网络叠多少层,做乘法求和的复合变换仍然还是线性函数。L1 层的线性输出再经过 L2 层的线性变换,数学上等价于一个大矩阵乘法,和一层网络没有本质差别,表达能力完全受限。

但如果用多项式激活函数呢?比如 f(x)=x²,多层网络确实有了非线性,表达能力不再退化成线性,但依然有限制:多项式的次数是有限的,复合后依然是多项式,只能逼近一类多项式函数,很难表达高频变化、尖峰之类的形态。而 sigmoid、tanh、ReLU 这类函数本质上包含了无限阶的变化特征,基元足够“灵活”,才能支撑定理要求的任意精度逼近。

实际工程中大家喜欢用 ReLU 而不是 sigmoid,除了梯度消失问题的考量,分段线性的特性也让它做函数逼近时数值更稳定,不会出现大范围饱和导致的梯度熄灭。这些细节配合定理的铺垫来看,就更能理解架构设计的初衷了。

3. 从“理论上能逼近”到“工程上真正拟合”之间有多远

3.1 定理保证存在性,但不保证你能找到参数

这是很多人最大的认知误区。万能逼近定理告诉你,某个足够宽的网络结构一定可以表达目标函数。但它没有告诉你这组权重该怎么求。实际上,寻找那组权重靠的是反向传播和梯度下降,这是一个数值优化问题。

神经网络的损失函数是非凸的,存在大量局部最优点和鞍点。基于梯度的优化算法从一个随机初始化出发,沿着损失下降最快的方向逐步摸索。这个过程受学习率、初始化方式、数据质量影响极大。同一个结构,可能你随机初始化运气好,训出来的模型效果不错;换个种子,损失卡住不降也是常有的事。所以实践者常说要“多跑几个随机种子”,不是玄学,是因为优化过程的随机性确实存在。

另一个重要变量是数据分布。定理说网络能逼近函数,前提是训练数据的采样点足够覆盖目标定义域。你只在函数的一小块区间提供样本,网络就只能在这一块区间上拟合。你拿它去预测没有数据覆盖的区域,等于让一个从未见过真实值的人猜答案,结果自然不可控。实战里很多拟合失败案例,本质不是网络能力不够,而是数据覆盖不足。

3.2 网络规模与优化难度的权衡

既然越宽越能逼近,那是不是网络越大越好?并不是。

从表达上看,一个很宽的单隐层网络确实具备理论逼近能力,但实际训练时优化难度会剧增。神经元一多,参数量暴涨,梯度传播路径复杂化,模型更容易记住训练集中的噪声而不是真实规律。这就是典型的过拟合。

从实践看,深度比宽度在某些情况下更高效。一个深层的网络可以用更少的参数量表达同样复杂的函数,因为每一层都在做特征的逐级抽象。比如第一层识别边缘,第二层识别纹理,第三层识别物体部件,深层的表达能力是有积累效应的。但在数据量有限时,太深的网络又容易出现梯度问题或收敛慢,所以深度不是无限制加深越好。

我自己的经验是:先从一个小规模的网络跑通,观察训练损失能不能降下去,再逐步加宽加深。如果训练损失都下不去,说明模型容量不足;如果训练损失降下去了但验证损失不降反升,说明已经过拟合,要考虑加数据或加正则化。用这样一个“先欠拟合后过拟合”的找分界点思路,比一开始就堆大模型要高效得多。

3.3 数据、归一化与训练的配合细节

拟合一个函数,不只是网络结构和优化算法的事,输入数据的形态对结果影响也非常大。如果特征之间量级差异巨大,比如一个特征在 0 到 1 之间,另一个特征在 1000 到 10000 之间,那么梯度下降会偏向主导量级的特征,训练速度会变得极其缓慢,甚至难以收敛。这就是为什么几乎所有神经网络项目都要做数据归一化或标准化。

归一化还有个容易被忽视的作用:让激活函数工作在合适的区间。比如 tanh 和 sigmoid 在输入绝对值很大时进入饱和区,梯度几乎为零,参数更新停滞。标准化后的数据能让网络初始输出落在一个相对适中的范围内,减轻这种饱和问题。虽然 ReLU 没有饱和上限,但负区间输入会截断梯度,如果大多数样本落在负区间,对应神经元基本学不动,这就是所谓的“神经元死亡”。常见缓解方式是使用 Leaky ReLU、PReLU,或者仔细调学习率。

数据量也是工程中迈不过去的坎。拟合多维函数时,需要的样本数量会随着维数上升而迅速增大。这就是“维数灾难”的现实体现:在二维空间均匀采样 100 个点可能够了,但在二十维空间里同样密度需要天量的点数。万能逼近定理不会帮你解决数据稀疏问题,它能做的最多是从理论上告诉你,数据给够了,结构足够大,是有望向目标函数逼近的。

4. 不同神经网络架构与它们“擅长拟合的函数”

4.1 前馈神经网络(MLP):最通用的函数拟合器

前馈神经网络是万能逼近定理最直接的应用场景。输入一个固定维度的向量,输出一个固定维度的向量,中间经过若干隐层,整体就是一个向量到向量的映射。正因为结构中性,它能拟合的类型非常宽泛:用来做销售预测、建材价格回归、用户行为分类,本质都是在拟合一个人们难以用解析式写出来的函数。

做这类任务时我常提醒自己:MLP 虽然万能,但不会自动选择“重要特征”。所有维度一视同仁地喂进网络,如果输入里包含大量无关特征,网络需要更多容量去忽略它们,训练也会更慢。所以特征工程对 MLP 依然重要。像用 word2vec 产出向量再输入网络这类做法,本质上就是把原始文本先映射成稠密向量,再交给 MLP 去拟合下游分类或回归函数;word2vec 那个环节已经提前完成了一部分“函数构造”。

4.2 卷积神经网络(CNN):给拟合加上先验约束

CNN 在图像领域之所以碾压普通 MLP,不是因为它更“万能”,恰恰是因为它放弃了部分万能性,把“局部相关性”这一图像数据的内在规律编码进结构。卷积操作让网络在拟合函数时默认相邻像素关系紧密,权重共享大幅减少参数量,同时池化层让特征具有平移不变性。这些约束让 CNN 在图像数据上以更小的模型规模达到更好的泛化性能。

人脸识别是一个特别典型的例子。图像输入网络后,第一层卷积提取边缘和颜色块,中间层提取眼睛、鼻子这样的小部件,深层网络把整个面部结构编码成一个高维向量——也就是常说的“特征向量”。这个过程本质上就是网络在拟合一个函数:像素矩阵到身份向量的映射。你可以说 MLP 理论上也能做到,但同样效果下 CNN 的参数量少一个数量级,优化更容易,泛化也更强。

4.3 循环神经网络(RNN/LSTM):拟合时间序列上的映射

RNN 处理的问题不再是单个向量到另一个向量的映射,而是序列到序列的映射。它有一个隐藏状态在时间步之间传递,相当于用一组共享权重反复作用于不同的时间切片,拟合的其实是一个动态函数:给定过去一段输入,预测未来趋势。LSTM 引入门控机制后,还能拟合更长时间跨度的依赖关系。

实际项目里,海洋水文波浪拟合、期货价格波段预测、天气数据回归这类任务都会用到时序模型。这类目标函数常常是时变的,并且受大量外部因素影响,指望网络完全精确拟合是不现实的。工程上更实际的目标是把模型的预测趋势做到尽量贴近真实曲线,同时量化误差的范围。只要损失函数选得恰当,比如分位数损失或平滑 L1 损失,训练时网络会主动避开对异常尖峰过度敏感,输出的拟合曲线整体会更稳。

4.4 图神经网络(GNN):拟合非欧空间的关系函数

GNN 是这几年的热门方向,它处理的输入不再是我们熟悉的网格或向量,而是图结构数据,比如社交网络、分子结构、知识图谱。它做拟合的方式很有意思:通过对每个节点聚合邻居信息并更新表示,网络在拟合一个“节点表达”的函数,这个函数以节点特征和图拓扑为输入,以节点或者图级别的输出为目标。

这个“函数”还有个特殊约束:排列等变性。也就是说,你把节点的顺序打乱,模型的输出不该跟着乱变。图神经网络通过聚合操作的对称性天然保证了这一点,相当于在拟合函数的同时内嵌了图结构的先验。做表情识别时,如果把面部关键点建模成图结构,GNN 能比纯 CNN 更直接地利用关键点之间的连接关系,拟合出从关键点空间坐标到表情类别的映射。这正好体现了“网络结构决定你能高效拟合哪类函数”的理念。

5. 实操中的常见问题与排查实录

5.1 过拟合、欠拟合与“真正”的拟合

很多人说“我训练出来模型拟合得很好”,然后用训练集上的评估指标证明自己。在我看来,这顶多算记住,不叫拟合。真正有意义的是验证集或者测试集上的表现。

欠拟合的特征很典型:训练集和验证集上的误差都迟迟降不下来,损失曲线像一条水平的直线。这时候别怀疑梯度下降,先考虑模型容量问题。我的做法是把网络宽度翻倍或者加深两层,观察训练损失有没有明显下降趋势。如果依然纹丝不动,那再检查输入输出是否有问题、损失函数是否匹配、数据是否归一化。

过拟合的特征大家很熟悉:训练损失一路狂降,验证损失降到某个低点后开始反弹,两条曲线逐渐拉开距离。标准的应对手段是增加训练数据、引入 dropout、加 L2 正则化、做早停。但要注意一点:正则化强度别一上来就拉满。太强的 dropout 会把网络逼成“和稀泥”,拟合精度反而变差。我会先用小权重正则跑通,再逐步加大,找到验证集表现最好的那一个强度档位。

5.2 训练曲线怎么看:损失面板诊断思路

我每次训练都会盯着损失曲线面板看,不是看热闹,而是通过曲线形态快速定位问题。

如果训练损失震荡剧烈、忽高忽低,多半是学习率太大,权重更新步子跨过头了。做法是把学习率降到当前值的十分之一,或者配合学习率衰减策略。如果损失下降得极其缓慢,像爬坡一样,先检查特征归一化是否做好,再确认学习率是不是太小;不要急着无限调大学习率,当学习率超过某个临界点时,损失会瞬间发散到无穷大。这个临界点在不同数据集上不同,所以更推荐用热身策略或现成的学习率搜索工具。

还有一个容易被忽略的细节是 batch size。batch size 太小时梯度噪声大,收敛不稳定;太大时每个 step 更新慢,训练时间拉长,而且模型容易收敛到尖锐的局部极小值,泛化性能可能变差。实际中 32、64、128 是我常用的起点,根据显存和数据量调整。

5.3 环境与代码层面容易踩的坑

很多人刚把网络代码写出来,却卡在连环境都跑不起来。最常见的报错是终端提示“无法将‘pip’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”,这个其实是 PATH 环境变量没有包含 Python 的 Scripts 目录。解决方案很多:最省心的做法是在命令行用“python -m pip install 包名”来替代直接敲 pip,因为这样调用的是当前 Python 解释器自带的 pip 模块,不受 PATH 设置影响。也可以找到 Python 安装目录下的 Scripts 文件夹手动加入 PATH。conda 出现同类报错也同理,优先检查环境变量和当前激活的环境。

还有一类问题是代码层面的 shape 不匹配。网络输入层维度、标签维度、损失函数的计算方式这三者常常对接不上。我排查这类问题会先打印每一步张量的 shape,然后对照网络定义手算一遍从输入到输出的维度变化,快速定位是哪一层多乘少乘了一个维度。这类问题看起来很初级,但在拟合任务里恰恰是最消耗时间的关卡。

6. 一个综合拟合的心得:从定理出发设计实验路线

早期我做拟合实验时,对定理的理解只停留在“神经网络很强”的层面,遇到拟合效果不好就急着改网络结构,一会儿加深一会儿加宽,浪费时间也摸不着方向。后来我养成了一个更系统化的习惯:先判断问题属于哪类范畴。

如果连训练集的损失都降不下去,那大概率是模型容量问题,不用怀疑优化细节,优先扩宽度,也可以用更大的激活函数族。如果在训练集上表现良好、验证集一塌糊涂,那说明模型有足够容量但是泛化能力不足,开始做正则化、做数据增强、找更多数据。如果模型在各组数据上表现都不错,但个别区域的预测值偏差很大,考虑训练数据覆盖是否均匀,或者是否需要分段建模。

这个流程本质上是在利用万能逼近定理做反向推理:网络理论上能拟合你要的那个函数,所以“拟合不好”一定是在容量、优化、数据、正则化其中一个环节出了问题。理清是哪一环,对症下药,比盲目改结构有效得多。

最后分享一个小技巧。当你用神经网络去拟合某个被测物理过程时,如果目标函数本身带有已知的边界条件、对称性或者守恒性质,不妨把这些作为约束加到输入特征或者损失函数里。比如拟合波浪时,把波高和周期的历史统计量作为额外输入,往往比单纯让网络从原始序列里硬学效果更稳。定理保证了网络具备万能表达能力,但人类对问题的理解,仍然能够给网络提供最省力的“解题路径”。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进