ARTICLE DETAIL

资讯详情

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

泰勒展开:从小学近似到程序员数值计算必备思维

泰勒展开:从小学近似到程序员数值计算必备思维 各位读者朋友大家好。今天这篇想聊一个看似“高深”但其实贯穿了整个数学与工程体系的话题——泰勒展开。之所以起这个标题是因为你仔细琢磨后会发现泰勒展开背后的核心直觉并不是大学高等数学课上那堆复杂的公式而是“用简单的东西去近似复杂的东西”。这种想法小学二年级就已经在用了。举个例子小学生算圆的周长时最直观的方法是把圆看成一个非常接近圆的正多边形用多边形边长去近似圆周长再比如估算一个数的平方根先找一个接近它的完全平方数再通过加减来逼近真实值。这些本质上都是“近似”的思想而泰勒展开就是把这种近似思想进行系统化、公式化、可控制误差的升级。这篇文章我会尽可能用程序员友好的方式把泰勒展开从头拆一遍先说明它到底在做什么为什么说它本质上并不难然后给出核心公式、常用展开式、手算示例再用 Python 亲手算一遍看看展开式逼近真实函数的效果最后聊聊它在数值计算、机器学习、游戏开发、最优化算法里出现的场景。文中会给出可以直接运行的代码和清晰的结果说明希望能让基础薄弱的人看懂概念也能让有开发经验的读者快速上手。1. 泰勒展开到底在做什么为什么说它“小学二年级”就会了1.1 一个最简单的近似场景先想象这样一个问题你要计算 ( \sqrt{4.1} ) 的值。如果按普通人的直觉你会想4.1 离 4 很近( \sqrt{4}2 )那 4.1 的平方根大概是 2.025。这个估算过程其实就暗含了泰勒展开的雏形用一个已知点附近的信息推断未知点的近似值。数学家泰勒Brook Taylor在 1715 年前后提出了一件事如果函数 $f(x)$ 在某个点 $x_0$ 附近足够光滑那么可以用一个多项式去逼近它。这个多项式的每一项都是根据函数在 $x_0$ 点处的导数构造出来的。关键就在这里小学二年级时你虽然没有背过泰勒公式但已经做过无数次“近似估计”。泰勒展开不是一种全新的思维方式而是把“近似估计”变成了一个可以精确控制误差的数学工具。1.2 从“切线近似”到“多项式近似”在高中阶段我们学过函数在某一点的切线是它附近最好的“直线近似”。比如函数 $y e^x$在 $x0$ 处的切线是 $y 1 x$。当 $x 0.1$ 时$e^{0.1} \approx 1 0.1 1.1$。实际计算一下$e^{0.1} \approx 1.10517$误差大约是 0.005已经比较接近了。但如果你想更精确怎么办答案是用“曲线”去近似而不是用“直线”。泰勒展开做的事情就是把“直线近似”推广成“二次曲线近似”“三次曲线近似”甚至更高次的多项式近似。零阶展开用常数近似$f(x) \approx f(x_0)$一阶展开用直线近似$f(x) \approx f(x_0) f(x_0)(x - x_0)$二阶展开用抛物线近似$f(x) \approx f(x_0) f(x_0)(x - x_0) \frac{f(x_0)}{2!}(x - x_0)^2$阶数越高局部逼近的效果通常越好。这个逻辑非常自然你完全不需要把它想成一个从天而降的公式。1.3 为什么程序员需要理解泰勒展开很多开发者觉得泰勒展开是纯数学和编程没有关系。但事实是计算机里大量基础函数库的实现就依赖泰勒展开以及相关的级数展开。例如C 语言标准库中的sin、cos、exp、log在部分实现中会使用多项式逼近GPU 上的 shader 中很多超越函数为了性能会使用近似多项式机器学习里的梯度下降、牛顿法底层都有泰勒展开的身影游戏物理引擎中小角度近似会把 $\sin\theta \approx \theta$这也是一阶泰勒展开。可以说泰勒展开是连接“数学模型”和“计算机数值计算”的一座重要桥梁。理解了它你能看懂很多算法为什么会那样设计也更容易定位一些数值异常问题。2. 泰勒展开的核心公式与概念拆解2.1 泰勒展开公式长什么样设函数 $f(x)$ 在 $x_0$ 的某个邻域内具有 $n1$ 阶导数那么对于该邻域内的任一点 $x$可以写成[ f(x) f(x_0) f(x_0)(x - x_0) \frac{f(x_0)}{2!}(x - x_0)^2 \cdots \frac{f^{(n)}(x_0)}{n!}(x - x_0)^n R_n(x) ]其中 $R_n(x)$ 是余项表示“用 n 次多项式近似后剩下的误差”。如果使用等号实际上应该写成[ f(x) P_n(x) R_n(x) ]其中 $P_n(x)$ 就是泰勒多项式。通常在工程近似中我们会忽略 $R_n(x)$但心里必须清楚忽略余项是有代价的。2.2 每一项为什么是这样很多初学者被这个公式劝退是因为不理解为什么会有 $n!$阶乘出现。可以这样理解假设我们想用一个 $n$ 次多项式来拟合 $f(x)$[ P_n(x) a_0 a_1(x - x_0) a_2(x - x_0)^2 \cdots a_n(x - x_0)^n ]我们希望这个多项式在 $x_0$ 点处的各阶导数和原函数完全一致[ P_n(x_0) f(x_0) ] [ P_n(x_0) f(x_0) ] [ P_n(x_0) f(x_0) ]对 $P_n(x)$ 求导可以发现第 $k$ 项 $a_k(x - x_0)^k$ 求 $k$ 阶导后是[ a_k \cdot k! ]为了让它等于 $f^{(k)}(x_0)$自然有[ a_k \frac{f^{(k)}(x_0)}{k!} ]所以阶乘不是人为设定的而是求导过程中自然产生的。这个解释如果你能想明白泰勒公式就不再是一堆死记硬背。2.3 麦克劳林展开当 $x_0 0$ 时泰勒展开的简化版本叫麦克劳林展开[ f(x) f(0) f(0)x \frac{f(0)}{2!}x^2 \cdots \frac{f^{(n)}(0)}{n!}x^n R_n(x) ]在计算机计算中麦克劳林展开非常常用因为很多函数在 0 点处的导数非常好求。2.4 余项与误差控制余项 $R_n(x)$ 有多种形式最常用的是拉格朗日余项[ R_n(x) \frac{f^{(n1)}(\xi)}{(n1)!}(x - x_0)^{n1} ]其中 $\xi$ 是 $x_0$ 和 $x$ 之间的某个数。拉格朗日余项的价值在于它能帮助我们估算误差的上界虽然在实际工程中很难精确求出 $\xi$但我们可以根据导数的取值范围估算误差范围。比如在金融工程、物理模拟中如果需要在某个精度范围内做近似通常就会先用拉格朗日余项判断需要展开到多少阶然后才去写代码。这一点很多开发者容易忽略导致近似误差失控。3. 几个重要的展开式与手算示例3.1 必须背下来的四个展开式在工程和算法面试中下面四个展开式出现频率极高指数函数[ e^x 1 x \frac{x^2}{2!} \frac{x^3}{3!} \cdots \sum_{n0}^{\infty} \frac{x^n}{n!} ]正弦函数[ \sin x x - \frac{x^3}{3!} \frac{x^5}{5!} - \frac{x^7}{7!} \cdots ]余弦函数[ \cos x 1 - \frac{x^2}{2!} \frac{x^4}{4!} - \frac{x^6}{6!} \cdots ]对数函数[ \ln(1x) x - \frac{x^2}{2} \frac{x^3}{3} - \frac{x^4}{4} \cdots \quad (-1 x \leq 1) ]注意 $\ln(1x)$ 的收敛区间是 $(-1, 1]$如果 $x$ 的绝对值接近或超过 1直接展开会发散工程中通常需要先做变量变换。3.2 手算示例用二阶泰勒展开估算 $\sqrt{4.1}$回到开头的例子。设 $f(x) \sqrt{x}$取 $x_0 4$。一阶导数 [ f(x) \frac{1}{2\sqrt{x}} ] 在 $x_04$ 处 [ f(4) \frac{1}{4} ]二阶导数 [ f(x) -\frac{1}{4x^{3/2}} ] 在 $x_04$ 处 [ f(4) -\frac{1}{32} ]于是二阶泰勒展开是 [ \sqrt{x} \approx \sqrt{4} \frac{1}{4}(x - 4) - \frac{1}{64}(x - 4)^2 ]代入 $x 4.1$ [ \sqrt{4.1} \approx 2 \frac{1}{4} \times 0.1 - \frac{1}{64} \times 0.01 ] [ 2 0.025 - 0.00015625 2.02484375 ]计算器给出的真实值是 $2.024845673\cdots$误差不到小数点后第五位。这说明二阶展开已经能提供非常好的近似。3.3 手算示例用泰勒展开计算 $e^{0.1}$对于 $f(x) e^x$ 在 $x_0 0$ 处展开[ e^{0.1} \approx 1 0.1 \frac{0.01}{2} \frac{0.001}{6} ] [ 1 0.1 0.005 0.0001667 1.1051667 ]实际值 [ e^{0.1} 1.105170918\cdots ]取到三次项时误差已经小于 $5 \times 10^{-6}$。这个手算过程展示了泰勒展开在“没有计算器”时的强大之处也是很多数值算法的基础思路。4. 用 Python 亲手验证泰勒展开这一节我们进入实操。我会用 Python 分别演示用sympy符号计算库直接求泰勒展开式用matplotlib画图比较原始函数与不同阶数展开式的逼近效果手动实现一个三角函数的近似函数模拟库函数的底层思路。4.1 环境准备本文示例使用以下环境Python 3.8示例基于 3.10 验证通过sympy 1.11matplotlib 3.6numpy 1.23如果你的环境中没有安装这些库可以用 pip 安装pip install sympy matplotlib numpy版本不需要和上面完全一致只要不低于要求即可。下文重点演示的是代码逻辑和泰勒展开的收敛规律版本差异不影响运行效果。4.2 使用 sympy 求泰勒展开式先来看符号计算的写法。# 文件路径taylor_sympy.py import sympy as sp x sp.symbols(x) # 对 e^x 在 x0 处展开到 5 阶 expr1 sp.exp(x) taylor1 sp.series(expr1, x, 0, 6) # 第4个参数 6 表示展开到 x^5 print(e^x 在 x0 处展开到 x^5) print(taylor1) print() # 对 sin(x) 在 x0 处展开到 7 阶 expr2 sp.sin(x) taylor2 sp.series(expr2, x, 0, 8) print(sin(x) 在 x0 处展开到 x^7) print(taylor2) print() # 对 ln(1x) 在 x0 处展开到 5 阶 expr3 sp.log(1 x) taylor3 sp.series(expr3, x, 0, 6) print(ln(1x) 在 x0 处展开到 x^5) print(taylor3)运行这段代码你会得到类似下面的输出e^x 在 x0 处展开到 x^5 1 x x**2/2 x**3/6 x**4/24 x**5/120 O(x**6) sin(x) 在 x0 处展开到 x^7 x - x**3/6 x**5/120 - x**7/5040 O(x**8) ln(1x) 在 x0 处展开到 x^5 x - x**2/2 x**3/3 - x**4/4 x**5/5 O(x**6)其中O(x**6)表示余项是 $x^6$ 级别的小量通常称为皮亚诺余项的写法。如果你想去掉余项只看多项式部分可以使用sp.removeO()taylor1_poly sp.series(expr1, x, 0, 6).removeO() print(taylor1_poly)输出x**5/120 x**4/24 x**3/6 x**2/2 x 1在工程计算里这种符号展开可以用来推导近似公式例如在推导小角度单摆运动方程时直接对 $\sin\theta$ 做一阶展开就能得到线性化的微分方程。4.3 用 numpy 和 matplotlib 观察收敛效果接下来我们写一段绘图脚本对比 $e^x$ 在 $x0$ 附近的真实曲线以及用不同阶泰勒多项式逼近的效果。# 文件路径taylor_plot.py import numpy as np import matplotlib.pyplot as plt def taylor_exp(x, n): 计算 e^x 在 x0 处的 n 阶泰勒展开。 n0 表示只取常数项n1 表示取到 x 项以此类推。 result 0.0 for k in range(n 1): result x**k / np.math.factorial(k) if hasattr(np, math) else x**k / np.math.factorial(k) return result这段代码里对阶乘的处理不够优雅实际上numpy并没有math子模块。更稳妥的写法是用 Python 标准库math.factorial# 文件路径taylor_plot.py import numpy as np import matplotlib.pyplot as plt import math def taylor_exp(x, n): 计算 e^x 在 x0 处的 n 阶泰勒展开。 n0 表示只取常数项n1 表示取到 x 项以此类推。 result 0.0 for k in range(n 1): result x**k / math.factorial(k) return result x np.linspace(-3, 3, 400) y_true np.exp(x) plt.figure(figsize(10, 6)) plt.plot(x, y_true, k-, linewidth2, labeltrue e^x) for n in [1, 2, 3, 5]: y_approx [taylor_exp(xi, n) for xi in x] plt.plot(x, y_approx, --, linewidth1.5, labelfTaylor n{n}) plt.ylim(-2, 8) plt.axhline(0, colorgray, linewidth0.5) plt.axvline(0, colorgray, linewidth0.5) plt.title(Comparison of e^x and Taylor Expansions) plt.xlabel(x) plt.ylabel(y) plt.legend() plt.grid(True, linestyle:, alpha0.6) plt.show()运行这段代码你可以看到在 $x0$ 附近所有展开式都贴合得很好展开阶数越高贴合区间越宽当 $x$ 偏离 0 较远时低阶展开会迅速发散。这说明泰勒展开本质上是一种“局部近似”离展开中心越远误差越大。工程上如果要在大范围内使用近似公式通常需要分区间展开而不是只取一个点作为展开中心。4.4 手动实现一个正弦近似函数计算机里的sin函数底层实现虽然不完全是套用泰勒公式但多项式逼近思想是一致的。下面我们手动实现一个基于泰勒展开的近似正弦函数精度足够满足演示需求。# 文件路径my_sin.py import math def my_sin(x, terms10): 使用泰勒展开计算 sin(x)x 为弧度。 terms 表示参与计算的项数默认取 10 项。 result 0.0 for k in range(terms): coefficient (-1) ** k exponent 2 * k 1 result coefficient * (x ** exponent) / math.factorial(exponent) return result # 测试 test_values [0.0, 0.1, 0.5, 1.0, 1.5] for v in test_values: approx my_sin(v, terms10) real math.sin(v) print(fx{v:5.2f} my_sin{approx:.12f} math.sin{real:.12f} 误差{abs(approx - real):.12e})预期输出类似x 0.00 my_sin0.000000000000 math.sin0.000000000000 误差0.000000000000e00 x 0.10 my_sin0.099833416647 math.sin0.099833416647 误差0.000000000000e00 x 0.50 my_sin0.479425538604 math.sin0.479425538604 误差0.000000000000e00 x 1.00 my_sin0.841470984808 math.sin0.841470984808 误差0.000000000000e00 x 1.50 my_sin0.997494986604 math.sin0.997494986604 误差1.110223024625e-16从结果看10 项展开在 $[-1.5, 1.5]$ 范围内的误差已经接近浮点数精度极限。但如果把 $x$ 扩大到 10直接展开可能误差很大。这就是为什么实际库函数会先通过“参数约减”把角度变换到 $[-\pi/2, \pi/2]$ 附近再使用多项式逼近。你可以把terms改小比如 2 或 3观察近似效果的变化这样可以直观理解“阶数越高误差越小”这个结论。5. 泰勒展开在工程中的典型应用与思考5.1 数值计算库中的多项式逼近在计算机出现早期计算 $\sin x$、$e^x$ 这类函数时硬件没有专用指令软件层面只能依赖级数展开。现代 libm 虽然使用了更加复杂的 minimax 多项式但其思想仍然是“用一个多项式在某个区间内尽量逼近目标函数”。这一点对性能敏感的 C/C 程序有直接启发如果你的程序需要高频调用sin、exp、log但精度要求不高可以考虑提前用泰勒展开或查表法实现自己的近似版本以换取更高的吞吐量。不过要小心这属于典型的“用精度换性能”的取舍必须经过性能测试和误差测试不能一拍脑袋就替换掉标准库函数。5.2 机器学习与最优化从梯度到二阶近似机器学习中模型训练的核心是参数优化。以梯度下降为例[ \theta_{t1} \theta_t - \eta \nabla f(\theta_t) ]这个公式实际上可以理解为对损失函数 $f(\theta)$ 在 $\theta_t$ 处做一阶泰勒展开然后沿负梯度方向迈出一步。如果保留二阶项就会得到牛顿法[ \theta_{t1} \theta_t - \eta [H f(\theta_t)]^{-1} \nabla f(\theta_t) ]其中 $H f$ 是 Hessian 矩阵。二阶方法收敛更快但计算 Hessian 的开销太大所以实际中有大量近似技巧例如 BFGS、L-BFGS都在用低秩或历史梯度信息近似二阶信息。如果你在阅读优化算法论文时觉得“梯度下降”和“牛顿法”的来龙去脉不清晰可以从泰勒展开的角度重新整理一遍会发现这些方法本质上都是“用局部多项式近似损失函数然后去求近似函数的最小值”。5.3 小角度近似与物理引擎在游戏开发和机器人控制中经常遇到如下的简化[ \sin\theta \approx \theta, \quad \cos\theta \approx 1 - \frac{\theta^2}{2} ]这就是麦克劳林展开的低阶形式。当 $\theta$ 很小时这种近似能极大简化计算尤其在实时物理模拟中每一帧都要处理大量刚体运动。只要角度处于较小范围近似的误差肉眼不可见。但这里有一个工程陷阱如果物体旋转速度很快或者经过极端状态$\theta$ 不再是小量继续使用小角度近似会出现明显的“飘移”或能量不守恒现象。此时就应该换用更高阶展开或者干脆使用严格的三角函数计算。5.4 金融衍生品定价中的解析近似金融工程中很多期权定价模型没有解析解。一个典型的做法是在某个基准参数附近做泰勒展开得到近似解析表达式从而避免耗时的蒙特卡洛模拟。例如当波动率微小变动时期权价格的变化可以用 Delta、Gamma 这些希腊字母来描述[ P(\sigma \Delta\sigma) \approx P(\sigma) \frac{\partial P}{\partial \sigma} \Delta\sigma \frac{1}{2}\frac{\partial^2 P}{\partial \sigma^2}(\Delta\sigma)^2 ]这其实就是泰勒展开在金融风险度量中的直接应用。理解这一点对做量化风控、衍生品定价的开发者会很有帮助。6. 常见误区与 FAQ6.1 泰勒展开和泰勒级数是一回事吗严格来说“泰勒展开”是指把函数在某点附近表示成一个有限项多项式加余项而“泰勒级数”是指当项数趋于无穷时的级数形式。工程中经常不严格区分但心里要清楚有限项是近似无限项才可能严格相等。6.2 所有函数都能做泰勒展开吗不是。函数必须在展开点处有足够阶的导数。如果函数在某点不连续、不可导或者函数虽然有任意阶导数但泰勒级数收敛不到自身就不能直接展开。最经典的例子是[ f(x) \begin{cases} e^{-1/x^2}, x \neq 0 \ 0, x 0 \end{cases} ]这个函数在 $x0$ 处的所有导数都是 0所以它在 0 点的泰勒级数恒为 0但它本身并不是 0 函数。这就是“无穷阶可导”不等于“能展开成泰勒级数并收敛到原函数”的典型反例。工程中大多数常见函数不存在这个问题但理解这一边界能避免在一些特殊模型上踩坑。6.3 展开阶数越高越准吗在展开点附近通常是的。但如果距离展开点过远高阶项可能因为 $x^k$ 的快速增长而导致多项式数值爆炸这时增加阶数反而会让误差变大。例如 $e^x$ 在 $x10$ 处取 10 阶展开和取 3 阶展开比10 阶误差可能反而更大。实际工程中要根据 $x$ 的范围选择合适的阶数或者干脆换一个展开中心。6.4 余项在工程中怎么处理工程中不会每次都用拉格朗日余项去精确计算误差边界。更好的做法是先用粗略的理论分析确定大致的展开阶数再用数值实验检查最大误差最后根据误差阈值决定是否提高阶数。我把这种情况整理成一个简单的排查思路表格问题现象常见原因解决思路近似结果偏差大展开中心离目标点太远更换展开中心或缩小 $x-x_0$增加阶数后误差反而增大高阶项数值溢出或区间过大控制使用区间或做参数约减在边界处出现剧烈振荡级数收敛半径太小换用其他逼近方法如切比雪夫多项式函数不可导或特殊点附近失真展开点不满足可导条件检查函数定义域重新选择展开中心近似公式在极端参数下失效没有考虑参数变化范围和误差控制加入误差测试和参数边界检查7. 工程实践中的建议与学习路线7.1 把泰勒展开当成“分析工具”而非“计算负担”很多初学者把泰勒展开理解成“考试时用来求极限的公式”这太窄了。在工程中泰勒展开是一种分析工具用于判断一个系统在某个工作点附近的线性稳定性简化复杂的非线性模型推导算法迭代公式估计某些计算方法的误差量级。所以学习时不要只背公式可以试着用泰勒展开去推导一遍梯度下降、牛顿法、小角度近似这些工程中常见的结论理解会比死记硬背牢固得多。7.2 代码中避免直接滥用泰勒展开虽然泰勒展开思想很好但在实际代码中我并不建议你轻易手写一堆x**n / factorial(n)去替代标准库函数。原因包括标准库已经经过充分优化和测试手写展开式容易忽略浮点误差累积方案的收敛区间需要严格验证代码维护成本高。如果你的确需要在特定场景下使用泰勒展开比如在 GPU shader 中做低成本近似建议遵循以下步骤明确输入参数的数值范围选择适当的展开中心和阶数编写误差测试用例与高精度标准结果对比在真实负载下做性能基准测试把近似的适用范围和精度指标写进注释。7.3 学习路线从直观理解到深入细节如果你想继续深入可以按下面的顺序学习先理解“用多项式逼近函数”的几何直觉亲手推导 $e^x$、$\sin x$、$\cos x$、$\ln(1x)$ 的展开式用 Python 画图直观感受不同阶数的逼近效果学习拉格朗日余项、皮亚诺余项的联系与区别学习“收敛半径”的概念理解为什么 $\ln(1x)$ 只能在 $(-1,1]$ 内展开进一步了解傅里叶级数那是用“三角函数”逼近函数的另一个分支了解 minimax 逼近、切比雪夫逼近等现代数值方法。这里最值得推荐的练习是自己写一个my_exp函数然后用它实现随机数的指数分布采样和numpy.random.exponential的结果对比误差。这个过程会逼你真正理解展开项数、数值范围、误差控制之间的关系比单纯刷题有效得多。写这篇长文的契机是我最近在一个实时渲染项目里想用低阶多项式代替高开销的次方计算。最初直接套了一阶展开结果边缘光斑失真严重后来才意识到必须在合适的展开中心、合适的阶数和误差验证之间做权衡。泰勒展开这套数学工具本身并不神秘真正需要修炼的是知道什么时候该用它什么时候不该用以及用了之后如何验证它没有在边界情况下悄悄背叛你。如果你能亲手跑一遍上面几段代码再去观察一下标准库函数在不同区间下的误差变化对“近似”这件事的理解应该会上一个台阶。
返回列表