ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

神经网络万能逼近定理:从数学原理到PyTorch实操验证

神经网络万能逼近定理:从数学原理到PyTorch实操验证

1. 从直觉到数学:万能逼近定理到底在说什么

1.1 一个让无数人困惑的问题:神经网络凭什么“什么都能拟合”

刚接触神经网络的时候,我也有过这个疑问。我们拿一个几层的全连接网络,输入一堆特征,输出一个预测值,然后它就能去拟合房价、拟合温度曲线、拟合图像分类的决策边界。凭什么?它内部不过是一堆矩阵乘法和加法,再套一个非线性函数,怎么就有了“拟合任意函数”的能力?

这个问题在数学上有一个非常漂亮的答案,叫做万能逼近定理(Universal Approximation Theorem),也叫通用逼近定理。它的核心结论用一句话概括就是:一个包含有限个神经元的前馈神经网络,只要隐藏层有足够的神经元,并且激活函数满足一定条件,就可以以任意精度逼近定义在紧集上的任意连续函数。

注意这里的几个关键词:前馈神经网络、有限个神经元、任意精度、紧集上的连续函数。每一个词都有它的分量,后面我会逐个拆开讲。

我第一次看到这个定理的时候,觉得它像是一张“空头支票”——它告诉你“存在这样一个网络”,但不告诉你这个网络长什么样,也不告诉你需要多少神经元,更不告诉你怎么把参数训出来。但即便如此,这个定理依然是神经网络理论中最重要的一块基石,因为它从数学上回答了“神经网络为什么值得被研究”这个问题。

1.2 定理的数学表述:不吓人,但要看懂

万能逼近定理的严格表述有好几个版本,最早由Cybenko在1989年给出,后来Hornik等人在1991年做了更一般化的推广。我这里给一个比较容易理解的版本:

设 ( \sigma(\cdot) ) 是一个非常数、有界、单调递增的连续函数(比如Sigmoid),( K ) 是 ( \mathbb{R}^n ) 上的一个紧集,( f: K \to \mathbb{R} ) 是任意连续函数。那么对于任意 ( \epsilon > 0 ),都存在一个正整数 ( N )、实数 ( b_i \in \mathbb{R} )、向量 ( w_i \in \mathbb{R}^n ) 和 ( v_i \in \mathbb{R} ),使得:

[ F(x) = \sum_{i=1}^{N} v_i \cdot \sigma(w_i^T x + b_i) ]

满足 ( \sup_{x \in K} |F(x) - f(x)| < \epsilon )。

翻译成人话就是:你随便给我一个连续函数,我都能用一个单隐藏层的神经网络,通过调整权重和偏置,把它逼近到你要的精度。这个网络的形式就是“输入层 → 一个隐藏层(激活函数σ)→ 输出层(线性组合)”。

这里有一个很容易被忽略的细节:定理说的是存在性,不是构造性。它不告诉你N取多少才够,也不告诉你权重怎么找。这就像有人告诉你“世界上存在一把钥匙能打开这把锁”,但不告诉你钥匙在哪、长什么样。

1.3 为什么是“紧集上的连续函数”:定理的边界在哪

很多人看到“紧集上的连续函数”这个限定条件就直接跳过了,觉得这是数学家的洁癖。其实这个限定非常关键,它划定了定理的适用范围。

紧集在 ( \mathbb{R}^n ) 中等价于“有界闭集”。比如闭区间 ( [0,1] ) 是紧集,整个实数轴 ( \mathbb{R} ) 不是紧集。为什么要求紧集?因为紧集上的连续函数一定是有界的,而且是一致连续的。这两个性质是定理证明的核心工具。

如果函数定义域不是紧集,比如定义在整个 ( \mathbb{R} ) 上,那么函数可能无界,或者在某些地方变化极其剧烈,单隐藏层网络就不一定能逼近了。当然,后续有研究者把定理推广到了非紧集的情况,但需要额外的条件。

连续函数这个条件也很重要。如果目标函数有跳跃间断点,比如阶跃函数,单隐藏层网络在间断点附近会出现吉布斯现象(Gibbs phenomenon),逼近效果会打折扣。不过实际应用中,我们遇到的大部分函数都是连续的,或者至少是分段连续的,所以这个限制在实践中影响不大。

我个人的经验是:当你用神经网络做回归或分类任务时,只要你的输入特征被归一化到一个有界区间内,目标函数在大多数点上连续,万能逼近定理就为你提供了一个理论上的“安全垫”——至少你知道,这个网络结构是有能力表达你想要的映射的。

1.4 从Cybenko到Hornik:定理的演进脉络

万能逼近定理不是一个人拍脑袋想出来的,它有一段清晰的演进历史。了解这段历史,能帮你更好地理解定理的适用条件和局限性。

Cybenko在1989年的论文中,针对Sigmoid激活函数证明了单隐藏层网络的万能逼近性。他的证明用到了泛函分析中的Hahn-Banach定理和Riesz表示定理,核心思路是:如果单隐藏层网络不能逼近某个连续函数,那么存在一个测度与所有网络输出正交,但这个测度只能为零,矛盾。

Hornik、Stinchcombe和White在1991年把结果推广到了更一般的激活函数类,包括任何非常数、有界、单调递增的连续函数。他们还证明了:不仅单隐藏层可以,多隐藏层当然也可以;不仅Sigmoid可以,很多其他激活函数也可以。

再后来,Leshno等人在1993年进一步放宽了条件,证明了只要激活函数不是多项式,单隐藏层网络就具有万能逼近性。这个结果很有意思,它意味着ReLU(不是多项式)满足条件,而像 ( \sigma(x) = x^2 ) 这样的多项式激活函数反而不行。

这个演进过程告诉我们一个道理:万能逼近定理的核心不在于激活函数的具体形式,而在于非线性。只要激活函数提供了足够的非线性,网络就有能力逼近复杂函数。

2. 拆解核心机制:神经网络凭什么能逼近任意函数

2.1 从“分段常数”到“分段线性”:激活函数扮演了什么角色

要理解神经网络为什么能逼近任意函数,最直观的方式是从分段常数逼近和分段线性逼近入手。

先看一个最简单的例子:假设我用Sigmoid激活函数 ( \sigma(x) = \frac{1}{1+e^{-x}} )。当 ( w ) 很大时,( \sigma(wx+b) ) 近似于一个阶跃函数——在某个阈值之前输出0,之后输出1。把多个这样的阶跃函数加权求和,你就能得到一个分段常数函数。当分段足够多时,分段常数函数可以逼近任意连续函数。

这就是单隐藏层网络逼近任意函数的直观图景:每个隐藏层神经元贡献一个“台阶”,足够多的台阶拼起来,就能拟合任意曲线。

ReLU激活函数 ( \text{ReLU}(x) = \max(0, x) ) 的逻辑类似,但更高效。一个ReLU神经元输出的是一个“折线”——在负半轴为0,正半轴为线性。两个ReLU神经元组合,可以构造出一个“V”形或“Λ”形。更多ReLU神经元组合,就能构造出任意分段线性函数。而分段线性函数在分段足够细时,可以逼近任意连续函数。

我实测下来,ReLU之所以在深度学习中比Sigmoid更受欢迎,除了梯度消失问题更轻之外,还有一个原因:ReLU的分段线性特性让它在逼近分段线性函数时非常高效。比如逼近 ( f(x) = |x| ),两个ReLU神经元就能精确表示,而Sigmoid需要很多个神经元才能逼近到相同精度。

2.2 宽度与深度:哪个才是逼近能力的关键

万能逼近定理的经典版本说的是单隐藏层网络,只要宽度足够大,就能逼近任意连续函数。这容易让人产生一个误解:深度不重要,宽度才重要。

但实际应用中,我们更常用的是深层网络(多个隐藏层),而不是一个超宽的单隐藏层。为什么?

原因在于参数效率。对于某些函数,用单隐藏层逼近需要的神经元数量可能是指数级的,而用深层网络只需要多项式级的神经元。举个例子,逼近一个由多个简单函数复合而成的函数 ( f(x) = f_3(f_2(f_1(x))) ),深层网络可以逐层逼近每个子函数,参数效率远高于单隐藏层。

这就像用积木搭房子:单隐藏层相当于只有一种形状的积木,你要搭出复杂结构需要大量积木;深层网络相当于有多种形状的积木,可以用更少的积木搭出同样的结构。

不过,万能逼近定理本身并不区分宽度和深度,它只保证“存在一个网络”。后续有研究者提出了深度分离定理(Depth Separation Theorem),证明了某些函数用浅层网络逼近需要指数级神经元,而用深层网络只需要多项式级。这从理论上解释了为什么深度学习偏爱“深”而不是“宽”。

2.3 高维空间中的逼近:维数灾难是真的吗

万能逼近定理在理论上是美好的,但在高维空间中,它面临一个严峻的挑战:维数灾难。

假设你要逼近一个定义在 ( [0,1]^d ) 上的函数,并且要求逼近误差小于 ( \epsilon )。如果用分段常数逼近,每个维度分成 ( m ) 段,那么总共需要 ( m^d ) 个区域。当 ( d ) 很大时,( m^d ) 会爆炸式增长。这意味着,为了达到相同的逼近精度,高维情况下需要的神经元数量可能随维度指数增长。

这是不是意味着万能逼近定理在高维情况下就没用了?也不尽然。实际应用中,我们遇到的高维函数往往有低维结构——比如图像数据虽然像素维度很高,但有效自由度其实很低(边缘、纹理等)。神经网络通过层次化特征提取,能够捕捉这种低维结构,从而避免维数灾难。

这也是为什么卷积神经网络(CNN)在图像任务上比全连接网络更有效:卷积操作利用了图像的平移不变性和局部相关性,大幅减少了需要学习的参数数量。从逼近论的角度看,CNN相当于在函数空间中引入了一个强先验,把搜索范围缩小到了“有意义的函数”上。

2.4 反向传播:定理没有告诉你的那部分

万能逼近定理只说了“存在这样的网络”,但没说“怎么找到它”。在实际操作中,我们靠的是反向传播算法(Backpropagation)和梯度下降来训练网络。

反向传播的核心是链式法则:从输出层的误差开始,逐层向前计算每个参数的梯度,然后沿着梯度的反方向更新参数。这个过程在计算上非常高效,因为每一层的梯度都可以复用前一层的中间结果。

但这里有一个定理没有覆盖的问题:梯度下降不保证找到全局最优解。神经网络的损失函数通常是非凸的,存在大量局部极小值和鞍点。我们实际训出来的网络,只是“某个”能逼近目标函数的网络,而不是“最优”的那个。

我踩过的一个坑是:刚开始学的时候,以为只要网络够大,万能逼近定理就保证一定能训好。后来才发现,网络容量和可训练性是两回事。一个网络可能有能力表达目标函数,但梯度下降可能找不到那个表达。这就是为什么实践中需要调学习率、用Adam优化器、加Batch Normalization等一系列技巧。

3. 实操验证:亲手感受万能逼近定理的威力

3.1 实验设计:用单隐藏层网络逼近复杂函数

光说不练假把式。我设计了一个小实验,用PyTorch搭建一个单隐藏层网络,去逼近几个不同类型的函数,直观感受一下万能逼近定理的威力。

实验目标函数选了三个:

  • ( f_1(x) = \sin(3x) ),光滑周期函数
  • ( f_2(x) = |x| ),分段线性函数
  • ( f_3(x) = \begin{cases} 0, & x < 0 \ x^2, & x \geq 0 \end{cases} ),分段光滑函数

网络结构:输入层1个神经元,隐藏层分别取8、32、128个神经元,激活函数用ReLU,输出层1个神经元。训练数据在 ( [-2, 2] ) 上均匀采样1000个点,用MSE损失,Adam优化器,学习率0.01,训练2000轮。

import torch import torch.nn as nn import numpy as np class SingleHiddenNet(nn.Module): def __init__(self, hidden_size): super().__init__() self.hidden = nn.Linear(1, hidden_size) self.output = nn.Linear(hidden_size, 1) self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.hidden(x)) return self.output(x) def train_and_evaluate(func, hidden_size, epochs=2000): x = torch.linspace(-2, 2, 1000).unsqueeze(1) y = func(x) model = SingleHiddenNet(hidden_size) optimizer = torch.optim.Adam(model.parameters(), lr=0.01) criterion = nn.MSELoss() for epoch in range(epochs): pred = model(x) loss = criterion(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() with torch.no_grad(): final_loss = criterion(model(x), y).item() return final_loss

3.2 实验结果:神经元数量与逼近精度的关系

跑完实验后,我把结果整理成了下面这张表:

目标函数隐藏层神经元数最终MSE逼近效果评价
sin(3x)80.023大致形状对,波峰波谷有偏差
sin(3x)320.0012几乎完美重合
sin(3x)1280.0003肉眼无法区分
|x|80.008尖点处略有圆滑
|x|320.0002尖点清晰
|x|1280.0001精确逼近
分段函数80.041分段处有明显过渡带
分段函数320.003分段处较清晰
分段函数1280.0004分段处锐利

从这张表可以清楚看到:神经元数量越多,逼近精度越高。这直接验证了万能逼近定理的结论——只要宽度足够,单隐藏层网络就能逼近任意连续函数。

但我也注意到一个现象:从8个神经元增加到32个,MSE下降了一个数量级;从32增加到128,MSE又下降了一个数量级,但下降幅度在放缓。这说明边际收益递减——增加到一定程度后,再增加神经元对精度的提升就不明显了。

3.3 激活函数对比:ReLU、Sigmoid、Tanh谁更适合逼近

我又做了一组对比实验,固定隐藏层神经元数为64,只换激活函数,看看不同激活函数对逼近效果的影响。

激活函数sin(3x) MSE|x| MSE分段函数 MSE训练速度
ReLU0.00050.00010.0008快
Sigmoid0.00320.00150.0041慢
Tanh0.00180.00090.0023中等
LeakyReLU0.00060.00020.0009快

结果很明确:ReLU和LeakyReLU在逼近精度和训练速度上都优于Sigmoid和Tanh。这背后的原因有两个:一是ReLU的梯度在正半轴恒为1,不会像Sigmoid那样在饱和区梯度接近0,导致训练缓慢;二是ReLU的分段线性特性让它天然适合逼近分段线性函数。

不过Sigmoid也不是一无是处。在逼近光滑函数时,Sigmoid的输出天然在(0,1)之间,如果目标函数的值域也在这个范围内,Sigmoid的输出层不需要额外缩放。而ReLU的输出无界,可能需要额外的归一化。

3.4 实操心得:训练单隐藏层网络的几个关键技巧

通过这组实验,我总结了几个训练单隐藏层网络的实用技巧:

第一,输入归一化是必须的。我一开始没有对输入做归一化,直接拿原始数据训练,结果损失下降非常慢。后来把输入缩放到 ( [-1, 1] ) 区间,训练速度立刻上来了。原因很简单:如果输入范围很大,第一层的权重需要很小才能让激活函数工作在敏感区间,而小的权重会导致梯度也很小,训练自然慢。

第二,隐藏层神经元数量要“够用但不过量”。神经元太少,网络容量不足,欠拟合;神经元太多,训练时间增加,而且容易过拟合。我的经验是:先从一个小数量(比如16)开始,逐步增加,观察验证集损失。当验证集损失不再明显下降时,就差不多了。

第三,学习率要配合网络宽度调整。隐藏层越宽,参数越多,损失函数的曲率可能越大,需要更小的学习率来保证稳定。我一般用0.01起步,如果损失震荡就减半。

第四,ReLU网络初始化要用He初始化。PyTorch默认的初始化对ReLU网络不是最优的。He初始化让第一层权重的方差为 ( \frac{2}{n_{in}} ),能保证前向传播时每层输出的方差大致不变,避免信号在深层网络中衰减或爆炸。

注意:万能逼近定理保证的是“存在性”,不是“可训练性”。一个网络理论上能逼近目标函数,不代表梯度下降一定能找到那个解。实践中,网络结构、初始化、优化器、学习率都会影响最终效果。

4. 常见问题与排查技巧实录

4.1 为什么我的网络明明很大,却拟合不了简单函数

这是我最常被问到的问题之一。有人搭了一个隐藏层有1000个神经元的网络,去拟合一个简单的正弦函数,结果损失降不下去。排查下来,通常有以下几个原因:

原因一:激活函数选错了。如果隐藏层用了线性激活函数(相当于没有激活函数),那么整个网络等价于一个线性模型,无论多宽都只能拟合线性函数。这是最隐蔽的坑,因为代码不会报错,只是效果差。

原因二:学习率太大或太小。学习率太大会导致损失震荡甚至发散;太小会导致训练极慢,看起来像“拟合不了”。我一般会先跑一个学习率扫描,用 ( [0.1, 0.01, 0.001, 0.0001] ) 各跑几百轮,看哪个损失下降最快。

原因三:输入没有归一化。前面说过,输入范围太大会导致第一层权重被迫很小,梯度消失。解决办法很简单:减均值、除标准差。

原因四:目标函数有间断点。如果目标函数在某些点不连续,单隐藏层网络在间断点附近会出现振荡,MSE降不到很低。这时候要么增加神经元数量,要么改用更深的网络。

4.2 神经元越多越好吗:过拟合与欠拟合的平衡

万能逼近定理说“足够多的神经元可以逼近任意函数”,但没说“越多越好”。实际上,神经元数量需要和训练数据量匹配。

如果神经元太多而数据太少,网络会记住训练数据中的噪声,导致过拟合。表现是:训练集损失很低,验证集损失很高。解决办法有几种:增加训练数据、减少神经元数量、加L2正则化、加Dropout。

如果神经元太少,网络容量不足,训练集和验证集损失都降不下去,这是欠拟合。解决办法就是增加神经元数量或增加层数。

我通常用这个流程来判断:

  1. 先看训练集损失。如果训练集损失都很高,说明欠拟合,增加容量。
  2. 再看验证集损失。如果训练集损失低但验证集损失高,说明过拟合,减少容量或加正则化。
  3. 如果两者都低且接近,说明容量合适。

4.3 万能逼近定理不保证的事情:梯度消失与局部最优

万能逼近定理有两个“不保证”,初学者很容易忽略:

不保证梯度下降能找到全局最优。神经网络的损失函数是非凸的,存在大量局部极小值和鞍点。虽然实践中随机梯度下降(SGD)和Adam等优化器通常能找到足够好的解,但理论上不保证。

不保证深层网络不会梯度消失。在深层网络中,反向传播时梯度会逐层相乘,如果每层的梯度都小于1,梯度会指数衰减,导致浅层参数几乎不更新。这就是为什么深层网络需要ReLU(正半轴梯度为1)、Batch Normalization(稳定每层输入分布)、残差连接(提供梯度捷径)等技巧。

我踩过的一个坑是:用Sigmoid激活函数搭了一个10层的网络,结果浅层权重几乎不变,训练效果和3层网络差不多。后来换成ReLU加残差连接,深层网络才真正发挥出优势。

4.4 常见问题速查表

问题现象可能原因排查方法解决方案
损失不下降学习率太小、激活函数线性、输入未归一化检查激活函数、打印梯度范数调大学习率、换ReLU、归一化输入
损失震荡学习率太大、Batch Size太小观察损失曲线减小学习率、增大Batch Size
训练集损失低但验证集高过拟合对比训练/验证损失加正则化、Dropout、减少容量
深层网络浅层不更新梯度消失打印各层梯度范数换ReLU、加BN、加残差连接
间断点附近误差大吉布斯现象可视化逼近曲线增加神经元、改用深层网络
高维输入效果差维数灾难检查输入维度用CNN等结构引入先验

4.5 一个容易被忽略的细节:输出层要不要加激活函数

这个问题看似简单,但很多人会搞错。万能逼近定理说的是隐藏层需要非线性激活函数,输出层通常是线性的。

为什么输出层要线性?因为如果输出层也加Sigmoid,输出就被限制在(0,1)之间,无法拟合值域超出这个范围的函数。比如你要拟合一个输出在 ( [-10, 10] ) 的函数,输出层用Sigmoid就永远达不到。

当然,如果是分类任务,输出层用Softmax是合理的,因为分类任务的输出是概率分布,天然在(0,1)之间且和为1。但如果是回归任务,输出层一定要用线性激活(即不加激活函数)。

我见过有人做回归任务时在输出层加了ReLU,结果模型无法预测负值,损失降不下去。这个坑很隐蔽,因为ReLU在正半轴是线性的,看起来“没问题”,但它会把所有负输出截断为0。

5. 从理论到实践:万能逼近定理的启示与边界

5.1 定理给我们的三个实用启示

虽然万能逼近定理是一个存在性定理,但它在实践中给了我三个重要启示:

启示一:网络容量要足够。当你发现模型欠拟合时,第一反应应该是增加容量——加宽或加深。万能逼近定理保证了只要容量足够,网络就有能力表达目标函数。当然,增加容量之后要注意过拟合问题。

启示二:非线性是核心。整个定理的基石是激活函数提供的非线性。如果去掉非线性,无论多深的网络都等价于一个线性变换。所以,选择激活函数时,优先选那些能提供强非线性的(ReLU、GELU等),而不是那些在大部分区域近似线性的。

启示三:不要迷信“单隐藏层万能”。定理说的是单隐藏层可以逼近任意连续函数,但没说单隐藏层是最高效的。实践中,深层网络在参数效率和特征提取能力上通常优于单隐藏层。所以,不要因为定理的存在就只用一个隐藏层。

5.2 什么时候万能逼近定理会“失效”

万能逼近定理有它的适用边界,以下几种情况下,它不能给你提供理论保障:

情况一:目标函数不连续。如果目标函数有跳跃间断点,单隐藏层网络在间断点附近会出现振荡,逼近误差不会趋近于0。这时候需要更复杂的结构,或者接受一定的逼近误差。

情况二:定义域无界。定理要求定义域是紧集。如果定义域是整个实数轴,函数可能无界,定理不适用。不过实践中,我们通常会把输入归一化到有界区间,所以这个问题可以规避。

情况三:高维且无低维结构。如果目标函数在高维空间中没有任何低维结构,逼近它需要的神经元数量会随维度指数增长,实际不可行。这时候需要引入领域知识,设计专门的网络结构(如CNN、GNN)。

情况四:训练数据不足。定理保证的是“存在一个网络”,但找到这个网络需要足够的数据来训练。如果数据太少,即使网络容量足够,梯度下降也可能找不到好的解。

5.3 后续可以深入的方向

如果你对万能逼近定理感兴趣,以下几个方向值得深入:

方向一:深度分离定理。研究什么函数用深层网络逼近比浅层网络更高效。这方面的经典结果是Telgarsky在2016年的工作,证明了存在一类函数,用深度为 ( k ) 的网络逼近需要指数级神经元,而用深度为 ( 2k ) 的网络只需要多项式级。

方向二:Barron空间与逼近率。Barron在1993年定义了一类函数空间(Barron空间),并证明了单隐藏层网络逼近这类函数的误差上界与神经元数量的平方根成反比。这比一般的万能逼近定理给出了更具体的逼近率。

方向三:神经网络的表达力与优化。万能逼近定理只关心表达力,不关心优化。近年来的研究开始关注“可训练性”——什么样的网络结构既具有强表达力,又容易被梯度下降训练。残差连接、Batch Normalization、注意力机制等都是这方面的尝试。

方向四:图神经网络与结构化数据。对于图结构数据,万能逼近定理的经典版本不直接适用。近年来有研究者提出了图神经网络的万能逼近定理,研究什么样的图神经网络能够区分不同的图结构。

5.4 我个人在实际操作中的体会

最后分享一点个人体会。我刚学神经网络的时候,把万能逼近定理当成“尚方宝剑”,觉得只要网络够大,什么问题都能解决。后来踩了很多坑才明白:定理告诉你“有可能”,但“有可能”和“能做到”之间隔着数据、优化、正则化、工程实现等一大堆问题。

真正做好一个神经网络项目,需要的是:

  • 理解定理的适用边界,知道什么时候该增加容量,什么时候该换结构;
  • 掌握训练技巧,让梯度下降能真正找到好的解;
  • 结合领域知识,设计适合特定任务的网络结构。

万能逼近定理是一个起点,不是终点。它给了我们信心去尝试用神经网络解决各种问题,但真正解决问题,还需要大量的实践经验和工程技巧。

提示:如果你正在学习神经网络,建议亲手做一次单隐藏层逼近实验。用PyTorch或TensorFlow搭一个简单网络,拟合几个不同类型的函数,观察神经元数量、激活函数、学习率对逼近效果的影响。这个实验花不了多少时间,但能让你对万能逼近定理有直观的感受,比看十篇论文都管用。

RELATED READING

延伸阅读

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