
神经网络“简单性”最近越来越像一门可以量化的硬科学。过去我们聊一个模型复不复杂先看参数量再看 FLOPs最多加上参数量剪枝但在函数空间里参数量大的网络完全可能学出一个非常平滑、非常“简单”的函数而参数量小的网络也可能因为初始化或数据异常而变得极端崎岖。静态指标很难回答一个很实际的问题这个网络学到的函数到底需要多高阶的多项式才能表达ICML 2026 方向里有一个很有意思的工作标题缩写叫 ED核心是用多项式表示来量化并优化神经网络的简单性。这个思路把神经网络从“层和参数”的视角拉回到“函数表达式”的视角先把你训练好的网络看成一个多项式函数再问它跨越了多少阶、系数衰减得有多快最后把“有效度数”作为一个可优化的指标。这个角度对理解过拟合、欠拟合、泛化差距以及模型剪枝都有帮助。本文不依赖论文原文细节而是从技术原理和工程实现两个层面把这个方向拆开讲清楚ED 到底在度量什么、多项式表示为什么适合做这件事、用 PyTorch 怎么快速算出网络的“有效度数”、以及如何把“简单性”真正放进训练目标里。读完你至少能跑通一套可复用的复杂度分析脚本也能理解为什么说“低 ED”不等于“低精度”更不等于“模型无用”。1. 这篇文章真正要解决的问题1.1 参数量不是函数复杂度神经网络模型的复杂度目前最常用的代理指标是参数量。模型参数量多往往意味着更强的表达能力和更高的显存占用但这并不代表最终学到的函数一定复杂。一个典型场景是你用一个宽度为 512 的 MLP 去拟合一个很平滑的 1D 函数训练时加了强权重衰减结果学出来的函数几乎和二次函数曲线一样光滑。此时从参数量看模型是复杂的但从函数空间看它的多项式展开阶数非常低ED 很小行为很“简单”。反过来一个宽度只有 16 的 MLP如果在没有正则的情况下训练过久完全可能学到高频抖动严重的函数。这种情况下模型参数量小但它的高阶梯级、噪声拟合现象非常严重。参数量和函数简单性之间并没有严格的对应关系。所以如果我们要改进训练、控制过拟合、做模型可视化需要的是一个能够反映“函数本身复杂程度”的指标。多项式表示恰好提供了一种非常自然的方式先把函数展开成幂级数再观察高阶项还有多少能量。1.2 为什么用多项式表示泰勒展开是刻画光滑函数局部行为最经典的工具。任何一个足够光滑的函数在某一点附近都可以表示成常数项、一次项、二次项…… 对神经网络来说只要激活函数不是 ReLU而是 tanh、sigmoid、gelu 这类光滑函数网络的整体函数就是一个光滑函数理论上可以做任意阶展开。多项式表示最直观的地方在于一个再复杂的网络一旦写到多项式形式那它“几阶”就变得一目了然。如果一个函数在目标区间内只需要三阶多项式就能达到 99% 的拟合能量那么你很难说它本质上是个复杂函数。如果同样的拟合需要十五阶以上说明它包含大量高频细节对输入扰动会非常敏感。这也引出了 ED 的核心定义思路在网络的泰勒展开系数中计算一个“有效度数”也就是保留指定能量比例所需的最小阶数。有效度数越低代表网络当前学到的函数越简单。1.3 简单性为什么值得优化优化简单性并不是为了让模型变蠢。传统正则化是用权重衰减、Dropout、数据增强来间接限制函数复杂度而 ED 把目标从“参数变小”换成了“函数多项式阶数变小”。低阶多项式函数在外推、噪声鲁棒性和对抗稳定性上通常有更好的表现。你拟合一个带有少量噪声的平滑函数时真正需要的可能是三阶或四阶的信息而十五阶以上的项里往往装的是噪声。把 ED 作为可优化的正则项加入训练模型会更倾向于用尽可能低的阶数去完成任务而不是靠高阶项硬磨训练集。这在大模型时代同样有意义。大模型参数量越来越多但参数量大不等于学到的函数就复杂对模型做函数级分析反而能帮助我们在不牺牲能力的前提下把不必要的复杂度压缩掉。2. 多项式表示与神经网络核心概念2.1 从网络前向计算到泰勒展开一个常见的 MLP 可以写成h1 W1 * x b1 a1 activation(h1) h2 W2 * a1 b2 a2 activation(h2) y W3 * a2 b3这里是线性变换和激活函数的复合。如果激活函数是 tanh那 h1 经过 tanh 后得到的 a1 本身就是一个可以用泰勒展开表示的函数。继续复合多层之后整体函数仍然是一个光滑函数理论上可以进行泰勒展开。之所以要考虑多项式表示是因为线性层只会做加法和乘法并不会改变函数的“多项式阶数”真正提升函数复杂度的是激活函数的非线性。以 tanh 为例它在零点附近的泰勒展开是tanh(z) z - z^3/3 2*z^5/15 - ...所以激活函数天然会把低阶输入推向高阶。网络越深、每一层非线性越强最终函数的高阶成分就越容易出现。2.2 有效度数 ED 的直觉ED 这个名字在论文里可能对应不同的完整拼写但从标题的定位来看它要回答的是一个很直接的问题这个网络学到的函数最少需要多少阶多项式来描述下面我们用一种可实现的示例定义来理解它。假设给定输入点 x0计算网络输出 y(x) 在 x0 处的泰勒系数y(x) c0 c1*(x - x0) c2*(x - x0)^2 ... cN*(x - x0)^N其中第 k 阶系数为ck y^(k)(x0) / k!然后计算各阶系数的平方和把它们看成“能量分布”。选择一个阈值例如 0.95表示要保留 95% 的能量所需的最小阶数这个阶数就是我们说的有效度数。如果系数从低阶到高阶衰减得很快有效度数就小函数就是简单的如果高阶系数仍然很大有效度数就大函数就是复杂的。2.3 局部展开与全局复杂度要特别注意泰勒展开只是局部工具。同一个函数在不同位置展开得到的有效度数可能不同。比如 ReLU 网络在绝大多数位置的一阶导数都是常数二阶及以上的泰勒系数为 0局部 ED 很低。但 ReLU 网络真实函数是一个分段函数转折点非常多本质上并不“光滑”。因此使用多项式表示量化神经网络复杂度时采样点选择和采样范围就非常关键。单点 ED 容易误导工程上更稳妥的方法是在输入空间中有代表性地采样多个点计算每个点的 ED然后取平均或分布统计。这样能更好地反映网络函数的全局复杂程度。3. ED量化神经网络简单性的核心思路3.1 泰勒系数能量分布我们可以把网络输出看成一个无穷级数y(x) Σ ck * (x - x0)^k但计算机只能算有限阶所以实际取前 N 阶。这里 N 是一个超参数一般根据任务选择比如 6 到 10 阶。假设已经得到系数列表[c0, c1, c2, c3, c4, c5, c6]每一阶的“能量”可以用系数平方表示。一个简单函数系数平方随阶数增加快速下降一个复杂函数高阶系数可能反而出现抬升。采用阈值法计算 ED 时先计算总能量total_energy c0^2 c1^2 ... c6^2然后从第 0 阶开始累加能量当累积能量超过阈值乘以总能量时选择当前阶数作为 ED。这个 ED 越小说明低阶项已经能解释绝大部分函数行为。3.2 为什么不是直接用 L2 权重范数很多人会问简单性不是已经有 L2 权重范数吗为什么要引入 EDL2 权重范数约束的是“参数的大小”但它只对参数空间中靠近原点的模型有效。一个网络参数很小函数仍可能因为权重矩阵之间的相互作用而出现高频振荡。ED 约束的是“函数的实际导数结构”它和输入空间的几何直接相关。区别可以这样理解指标衡量对象主要风险参数数量模型存储和计算规模与函数真实复杂度脱钩L2 范数参数离零点的距离参数小不代表函数简单梯度范数局部敏感程度只反映一阶信息ED高阶多项式成分的能量需要计算高阶导数成本高ED 的价值在于它把“简单性”从参数空间翻译到了函数空间让复杂度变成一个更贴近模型行为的信号。3.3 从量化到优化量化出 ED 之后自然的下一步是优化它。理想情况下训练损失可以写成loss task_loss λ * ED(x_sample)但由于真实 ED 涉及阈值比较和不可导的阶数选择直接把 ED 塞进损失并反向传播并不优雅。工程上通常用两类替代方案一类是代理正则例如对固定采样点的高阶泰勒系数做 L2 惩罚鼓励所有高阶系数趋向于 0另一类是用一阶梯度的平方作为平滑惩罚。后者不算真正的 ED 优化但实现简单、训练稳定可以作为粗粒度版本。从论文标题看ED 方法应该给出了一个更优雅的量化与优化框架。我们这里实现的是一套便于理解的工程近似。4. 环境准备与演示项目设计4.1 运行环境本文代码基于 PyTorch 实现适合在本地或 Colab 中运行。建议环境如下Python 3.9 以上PyTorch 2.xNumPyMatplotlib用于可视化安装命令pip install torch numpy matplotlib这里的版本并不需要特别精确只要 PyTorch 支持torch.autograd.grad并且create_graphTrue即可。4.2 演示项目目标我们构造一个 1D 函数拟合任务y sin(3*x) 0.3*x^3输入 x 范围从 -2 到 2。这个函数既有平滑的三次项也有较高频的三角函数成分适合用来观察网络的简单性变化。然后设计两个实验训练一个 tanh 激活的 MLP在训练过程中定期计算一个固定采样点的泰勒系数和 ED观察网络从随机初始化到拟合完成时ED 如何变化。在损失中加入一阶导数平方作为简单性代理正则观察 ED 是否下降并和普通训练做对比。这样设计的好处是不依赖任何外部数据集单文件即可复现整个分析流程。4.3 为什么选择 tanh 激活示例中不选 ReLU是因为 ReLU 局部二阶导数几乎处处为 0泰勒展开会直接得到 ED1失去了“观察高阶系数”的意义。tanh 激活函数的泰勒展开包含完整的奇偶次项网络复合之后会产生丰富的高阶成分更适合展示多项式表示的作用。如果你以后想分析真的 ReLU 网络需要把“局部泰勒展开”替换成“分段多项式表示”那是另一个更复杂的话题。5. 核心代码用多项式表示观察网络复杂度5.1 计算泰勒系数下面这个函数接收一个模型和采样点通过torch.autograd.grad循环计算高阶导数并返回各阶泰勒系数。# 文件ed_utils.py import math import torch import torch.nn as nn def taylor_coeffs(model, x0, order6): 计算 model 在 x0 处的 0 到 order 阶泰勒系数。 参数: model: 输出为 1 维的 PyTorch 网络 x0: shape (1, 1) 的输入张量 order: 展开的最高阶数 返回: coeffs: 长度为 order 1 的 float 列表 model.eval() x x0.clone().requires_grad_(True) y model(x) # 第 0 阶系数就是函数值 coeffs [y.detach().item()] # grad_outputs 保持为 ones用于计算一阶导数 grad_output torch.ones_like(y) for k in range(1, order 1): g torch.autograd.grad( outputsy, inputsx, grad_outputsgrad_output, create_graphTrue, retain_graphTrue, )[0] # 泰勒展开的系数需要除以 k! coeffs.append(g.detach().item() / math.factorial(k)) # 下一轮需要继续对当前导数求导 y g return coeffs这里的关键点是create_graphTrue。它会让高级导数本身也保留计算图从而支持我们后续做更深入的可微分析。retain_graphTrue保证了循环内多次调用autograd.grad时计算图不会被提前释放。实际运行时如果输入范围很大高阶导数可能非常大建议对输入先做标准化或者把order限制在 8 以内。5.2 根据泰勒系数计算 ED拿到系数后用能量阈值法计算有效度数。# 文件ed_utils.py继续 def effective_degree(coeffs, energy_threshold0.95): 计算有效度数 ED。 将各阶系数的平方视为能量返回累积能量达到阈值所需的最小阶数。 powers [c * c for c in coeffs] total_energy sum(powers) if total_energy 1e-12: return 0 cumulative 0.0 for degree, energy in enumerate(powers): cumulative energy if cumulative energy_threshold * total_energy: return degree return len(coeffs) - 1这里有一个容易误解的点ED 不一定是阶数越高越好也不是越低越好。它描述的是当前函数在以 x0 为中心时多项式能量主要集中在哪些阶数。用一个很低的 ED 去拟合一个本身需要高频细节的任务自然会导致欠拟合但如果任务本身是平滑的低 ED 通常对应更好的泛化潜力。5.3 训练脚本普通训练 ED 定期监控下面脚本训练一个 tanh MLP并每 250 步计算一次采样点上的 ED。# 文件train_ed_demo.py import torch import torch.nn as nn from ed_utils import taylor_coeffs, effective_degree torch.manual_seed(0) # 生成 1D 平滑数据 x torch.linspace(-2, 2, 200).reshape(-1, 1) y torch.sin(3 * x) 0.3 * x ** 3 model nn.Sequential( nn.Linear(1, 32), nn.Tanh(), nn.Linear(32, 32), nn.Tanh(), nn.Linear(32, 1), ) optimizer torch.optim.Adam(model.parameters(), lr0.01) mse nn.MSELoss() # 固定采样点观察 ED 变化 x_sample torch.tensor([[0.5]]) for step in range(2000): model.train() optimizer.zero_grad() y_pred model(x) loss mse(y_pred, y) # 给损失加一个简单性代理正则采样点一阶梯度的平方 xg x_sample.clone().requires_grad_(True) yg model(xg) (dx,) torch.autograd.grad( yg, xg, grad_outputstorch.ones_like(yg), create_graphTrue ) smooth_loss 0.05 * (dx ** 2).mean() loss loss smooth_loss loss.backward() optimizer.step() if step % 250 0: coeffs taylor_coeffs(model, x_sample, order6) ed effective_degree(coeffs, energy_threshold0.95) print(fstep{step:04d} loss{loss.item():.6f} ed{ed}) print(训练完成)这一步本质上不是把 ED 直接作为损失而是用一个代理正则“诱导”网络变得简单。从多项式角度看压低一阶导数的局部大小会同时让 Taylor 展开各项系数趋向更平滑因此 ED 会随着训练而下降。如果你只想观察普通训练下 ED 的变化把smooth_loss那一行去掉即可。对比两种训练方式的 ED就能看到正则化是如何改变函数复杂度的。5.4 可视化 ED 随训练的变化为了更直观可以在训练过程中把 ED 记录到列表最后画图。# 文件plot_ed.py import matplotlib.pyplot as plt history [] for step in range(2000): # 训练代码同上省略 if step % 250 0: coeffs taylor_coeffs(model, x_sample, order6) ed effective_degree(coeffs, energy_threshold0.95) history.append(ed) plt.plot(range(0, 2000, 250), history, markero) plt.xlabel(step) plt.ylabel(ED) plt.title(Effective Degree During Training) plt.grid(True) plt.savefig(ed_curve.png) plt.show()这段代码只是为了说明思路。真正的论文工作中ED 的统计方式会更严谨比如多采样点平均、跨区间统计而不是只看单点。6. 运行结果与验证方法6.1 预期输出运行上面的train_ed_demo.py你会看到类似下面的输出step0000 loss1.213456 ed5 step0250 loss0.230212 ed4 step0500 loss0.052341 ed3 step0750 loss0.021554 ed2 step1000 loss0.015432 ed2 step1250 loss0.012276 ed2 step1500 loss0.011340 ed2 step1750 loss0.010134 ed2 step2000 loss0.009832 ed1请注意这些数字只是示意不同 PyTorch 版本、不同随机种子下会不一样。你更应该关注的是趋势训练初期函数比较崎岖ED 较高随着训练进行简单性正则开始发挥作用高阶系数能量占比下降ED 逐渐变小。如果去掉正则你会发现 ED 有可能并不会持续下降甚至在拟合后期因为过拟合而重新升高。这正是“优化简单性”能带来泛化优势的一个直观实验证据。6.2 如何判断实验成功判断这个实验是否跑通可以从三个维度看训练 loss 在下降说明模型确实在拟合目标函数。采样点上的泰勒系数能够稳定计算没有出现 NaN。加上正则后ED 相比普通训练更低或者至少不会持续上升。如果训练 loss 一直不下降优先检查学习率是否过大、数据范围是否过大。如果高阶导数出现 NaN则优先考虑缩小输入范围或降低展开阶数。6.3 更严谨的验证思路单点 ED 只能说明局部复杂度。工程上更严谨的做法是将输入范围划分成若干个小区间每个区间内取代表性采样点分别计算 ED再统计均值、方差或百分位数。比如在 [-2, 2] 区间内均匀取 20 个点sample_points torch.linspace(-2, 2, 20).reshape(-1, 1) ed_list [] for xp in sample_points: coeffs taylor_coeffs(model, xp, order6) ed_list.append(effective_degree(coeffs, energy_threshold0.95)) mean_ed sum(ed_list) / len(ed_list) max_ed max(ed_list)这样可以避免模型在一个点附近很平滑却在另一个点附近剧烈振荡的情况被漏掉。7. 常见问题与排查思路问题现象可能原因排查方式解决方案高阶导数出现 NaN输入取值范围过大导数数值爆炸打印各阶导数检查输入统计量对输入做标准化或降低展开阶数ED 一直是 0模型输出接近常数或采样点梯度非常小检查当前输入输出值换一个更有区分度的采样点检查网络偏置ED 一直等于 1激活函数使用 ReLU二阶以上系数为 0打印泰勒系数确认激活函数改用 tanh、gelu 等光滑激活函数训练 loss 不下降学习率过大或正则项过强先关闭正则项跑一次降低学习率调整正则系数内存占用过高create_graphTrue保存了高阶计算图观察显存变化减小 batch size降低 order关闭历史图缓存加了正则后模型欠拟合简单性约束太强模型表达能力不足对比不同 λ 的 ED 和 loss减小正则系数或增大模型宽度这里要提醒的是高阶导数的计算在数值上并不稳定这是多项式方法在实际工程中绕不开的坎。论文中的方法如果要用到大规模模型上必然需要某种近似计算或高效实现这也是后续值得继续关注的方向。8. 最佳实践与工程建议8.1 把 ED 当监控指标而不是唯一目标ED 是一个很有解释力的指标但它不应该替代验证集准确率。实际使用中最好是把它作为训练监控指标和 loss、验证集误差放在一起看。如果发现验证集误差开始上升但训练集误差还在下降同时 ED 也在快速上升说明模型正在把更多高阶多项式能量用于拟合训练噪声。这时即使精度暂时没有变差也是一个值得警惕的信号。8.2 采样与展开参数要统一计算 ED 时展开中心、展开阶数、能量阈值三个参数必须固定。否则不同实验之间的 ED 没有可比性。建议在项目文件头部集中定义ED_ORDER 6 ED_THRESHOLD 0.95 ED_SAMPLE_POINTS torch.linspace(-2, 2, 20).reshape(-1, 1)这样复现实验时不会因为某次改了采样点而得出偏差结论。8.3 从代理正则到全局 ED如果你要把“简单性优化”应用到实际问题可以先从一阶梯度的平方正则开始因为它实现简单、训练稳定。等确认整体流程可用之后再尝试高阶泰勒系数的 L2 惩罚或者直接对多采样点 ED 做可微近似。要特别小心高阶导数的梯度信号可能非常大导致训练发散。设置正则系数时先给一个很小的值比如 0.001观察梯度范数的变化再逐步提高。8.4 大模型场景下的适用性有人可能觉得“泰勒展开算高阶导数”只适合小网络大模型根本走不通。这种担忧合理但 ED 的思想并不局限于逐点泰勒展开。大模型的参数量虽然巨大但它每一层输出的函数空间依然可以用低维有效子空间来刻画。更实际的做法是在某个子模块的输出上采样计算该子模块的多项式有效度数。这相当于把函数复杂度分析从“全局网络”下沉到“局部组件”从而避开整网高阶导数的计算瓶颈。9. 总结与后续学习方向ED 这篇工作的核心价值是把“简单性”从一个模糊概念变成了一个可以用多项式展开来量化的问题。它的表达很直接网络学到的函数需要多少阶多项式才能有效表示。低 ED 意味着函数光滑、高频成分少高 ED 意味着函数崎岖、容易对输入扰动敏感。本文给出的 PyTorch 实现虽然只是示例却覆盖了整套分析链路泰勒系数计算、ED 求解、训练中监控、简单性正则化。对于想理解模型复杂度的读者这是个值得收藏的起步模板。如果你对方向感兴趣下一步可以沿着这几条线继续深入阅读多项式神经网络、KAN、傅里叶特征相关论文理解“低阶函数表示”在模型设计中的作用。尝试将 ED 用于模型剪枝、知识蒸馏用函数复杂度替代参数量作为剪枝依据。把 ED 分析扩展到 CNN 或 Transformer 的某个中间层观察不同层在不同训练阶段的有效度数变化。另外提醒一句如果是为了复现论文效果一定要回到 ICML 2026 论文原文查看 ED 的精确定义、实验设置和代码实现。本文中的 ED 计算公式是一个便于理解的示例版本论文里的定义可能更严格也可能使用了更高效的近似算法。技术学习最怕只看概念不动手建议你打开编辑器先跑通上面的train_ed_demo.py再去修改采样点和正则项你很快就能感受到多项式表示在神经网络分析中的特殊优势。