CppCon 2015 学习:Algorithmic Differentiation C++ & Extremum Estimation

Algorithmic Differentiation (AD) 与极值估计

1. Algorithmic Differentiation (AD)

算法微分 (AD)是一种通过计算程序的具体实现来自动计算函数的导数的方法。与符号微分(Symbolic Differentiation)不同,AD 直接操作程序代码并通过算法获得导数,而非通过解析表达式进行微分。它通过两种基本模式:正向模式(Forward Mode)和反向模式(Reverse Mode)来实现。
AD的基本原理

  • 正向模式(Forward Mode)
    • 通过跟踪每个变量对最终输出的贡献,直接计算导数。
    • 适合计算少数输出(例如标量函数的多变量导数)。
  • 反向模式(Reverse Mode)
    • 在正向计算过程中记录每个变量的中间结果,反向计算梯度。
    • 适合计算多输出(例如一个标量损失函数对大量输入变量的梯度,常见于机器学习中的反向传播)。
      AD的优势
  • 准确性:由于是通过程序的实际计算过程来获得导数,AD比数值微分(例如有限差分法)精确。
  • 自动化:可以在不需要手动推导的情况下,自动计算任何函数的导数,特别是在复杂的数值计算中非常有用。
  • 高效性 :AD利用已有的计算图来避免重复计算,相比手动计算导数更加高效。
    应用场景
  • 机器学习中的梯度计算(如反向传播)
  • 科学计算中的最优化问题(例如极值估计)
  • 动态系统的建模与仿真
2. 极值估计(Extremum Estimation)

极值估计是指通过数值方法(如优化算法)寻找一个函数的局部或全局极值点(最大值或最小值)。在许多实际应用中,我们需要找到一个目标函数的最优解,通常通过以下方法:

  • 梯度下降法(Gradient Descent):常用的极值估计算法,通过沿着梯度的反方向调整变量值,直到找到函数的最小值。
  • 牛顿法(Newton's Method):通过利用函数的一阶和二阶导数来寻找极值点,收敛速度通常较快,但需要计算海森矩阵。
  • 随机搜索算法(如模拟退火) :对于没有明确梯度信息的复杂函数,使用随机搜索来找到最优解。
    与AD的关系
  • 在极值估计中,计算函数的导数是非常关键的一步,尤其是在需要梯度信息的优化算法中。通过使用算法微分,我们可以自动高效地计算梯度,极大地简化极值估计的实现。
  • 例如,在深度学习中,使用反向传播算法来计算梯度,并且利用梯度下降法来优化损失函数,自动调整参数以达到最小化损失。
结合AD与极值估计

在极值估计中,尤其是优化问题中,算法微分(AD)可以显著提高效率和准确性,特别是在求解大规模问题时。AD能够提供精确的梯度信息,而这些梯度信息是大多数优化算法(如梯度下降法、牛顿法等)的核心。
实际应用例子

  • 机器学习与深度学习:训练神经网络时,AD(反向传播)是最常用的计算梯度的方式。
  • 最优化问题:在最优化问题中,特别是在复杂模型下,AD可以帮助快速计算目标函数的梯度和雅可比矩阵,从而加速最优化算法(如最速下降、拟牛顿法等)。
  • 工程与科学计算:在进行参数估计、数据拟合时,AD可以用来自动计算模型的导数,从而提高估计的准确性和效率。
在C++中的实现

在 C++ 中实现算法微分,通常依赖于以下方法:

  • 手动实现正向/反向模式:编写自己的计算图来计算导数,手动管理计算和存储中间结果。
  • 使用库
    • ADOL-C:一个广泛使用的自动微分库,支持正向模式和反向模式的自动微分。
    • CppAD :C++ 库,支持高效的算法微分实现,适用于需要导数计算的优化问题。
      示例(C++ 使用CppAD)
cpp 复制代码
#include <cppad/cppad.hpp>
void compute() {
    // Define independent variables (t)
    CppAD::vector<CppAD::AD<double>> x(1);
    x[0] = 2.0;  // Initialize t
    // Start recording operations for AD
    CppAD::Independent(x);
    // Define the function f(t) = t^2
    CppAD::AD<double> y = x[0] * x[0];
    // Stop recording
    CppAD::Function<CppAD::vector<CppAD::AD<double>>, CppAD::vector<CppAD::AD<double>>> f;
    f.Dependent(x);
    // Evaluate the function and its derivative
    CppAD::vector<CppAD::AD<double>> result = f.Forward(0, x);
    double value = result[0];  // Function value at t=2
    double derivative = f.Forward(1, x)[0];  // Derivative value at t=2
    std::cout << "Function value: " << value << std::endl;
    std::cout << "Derivative value: " << derivative << std::endl;
}

在上面的代码中,我们定义了一个函数 f ( t ) = t 2 f(t) = t^2 f(t)=t2,并使用 CppAD 库来自动计算其在 t = 2 t = 2 t=2 处的值和导数。

总结
  • 算法微分(AD) 提供了计算导数的自动化工具,在许多工程和科学计算中不可或缺,尤其是在进行最优化、梯度计算、机器学习等问题时。
  • 极值估计 与算法微分密切相关,自动计算梯度可以极大地提高优化算法的效率和准确性。
  • 在 C++ 中,像 CppAD 这样的库提供了强大的支持,能够在复杂计算中自动处理导数,简化了优化过程的实现。

Motivation: Data & Models

在现代数据科学和统计学中,我们面临的典型问题是如何通过已有的数据(通常是数值型的 y y y)去理解、解释或预测未知的现象。为了做到这一点,我们通常会构建一个 模型 来拟合数据,通过这个模型来提取规律或者进行预测。

问题描述:
  • 已有的 :数据 y y y(即数值型数据)
  • 需要的:理解(解释数据背后的规律),解释(数据产生的原因或机制),或者预测(基于历史数据预测未来的结果)
工具:模型

我们使用 模型 来连接 数据需求 。一个常见的模型形式为:
f ( y ; θ ) f(y; \theta) f(y;θ)

其中 f f f 是一个函数,描述了数据 y y y 和模型参数 θ \theta θ 之间的关系。模型的目的是通过合适的 θ \theta θ 来解释或预测数据。

如何拟合模型

为了让模型尽可能地描述数据,我们通常会 拟合模型 。拟合的意思就是找到使模型尽可能精确地与数据匹配的参数 θ \theta θ。
拟合方法 :通过 成本函数(Cost Function)来衡量模型与实际数据之间的差距,然后通过最小化或最大化这个成本函数来找到最优的模型参数。具体来说:

  • 最小化:当目标是减少误差时,例如最小化均方误差(MSE)来找到最优的回归模型。
  • 最大化:有时我们需要最大化一个指标,例如最大化似然函数来估计概率模型的参数。
应用领域

这一方法现在被广泛应用于各个领域,包括:

  • 经济计量学和统计学(Estimation):例如,使用最小二乘法拟合线性回归模型。
  • 金融学(Calibration):例如,使用数据拟合金融模型来估计市场风险或定价。
  • 机器学习(Training):特别是监督学习中,我们通过数据训练模型,如回归分析、分类模型等。
现实中的问题

在实际应用中,我们通常会发现:

  • 很少有"封闭形式"(closed-form)解:即很少有直接可以通过公式计算得到的参数估计方法。
  • 因此,估计 问题往往依赖于数值优化技术。数值优化方法(如梯度下降法、牛顿法等)能够通过迭代的方式在不需要封闭形式解的情况下找到模型参数的最优值。
总结
  • 数据 ( y y y)和模型 ( f ( y ; θ ) f(y; \theta) f(y;θ))是我们要理解、解释或预测的工具和目标。
  • 拟合模型 的过程是通过最小化或最大化某个成本函数 来找到最优的参数 θ \theta θ。
  • 数值优化在没有封闭形式解的情况下起到了至关重要的作用,它使得估计、校准和训练变得可行且高效。
实际应用

例如,在机器学习中,通过数据训练一个模型,使用梯度下降法最小化损失函数(如均方误差或交叉熵),并通过这种方式获得最优的模型参数。这些过程都依赖于数值优化技术,尤其是在面对复杂的高维数据时。

参数模型和极值估计量

在统计学和计量经济学中,当我们处理模型时,有不同类型的模型,基于模型的假设和结构。最常见的方法之一是 参数模型,在这个模型中,数据和参数之间的关系通过一组参数来定义。

1. 参数模型:

一个参数模型 是由一个参数向量 θ \theta θ 定义的, θ \theta θ 属于一个参数空间 Θ \Theta Θ。空间 Θ \Theta Θ 是 R p \mathbb{R}^p Rp 的一个子集,其中 p p p 是模型中参数的数量。

  • θ ∈ Θ ⊂ R p \theta \in \Theta \subset \mathbb{R}^p θ∈Θ⊂Rp : 参数向量 θ \theta θ 是参数空间 Θ \Theta Θ 的一个元素, Θ \Theta Θ 是 R p \mathbb{R}^p Rp 中的一个子集。
  • Θ \Theta Θ : 参数空间 是所有可能的参数向量 θ \theta θ 的集合。这个空间定义了对参数的约束或边界。
2. 极值估计量:

一个极值估计量 是通过对标量目标函数 Q ( θ ) Q(\theta) Q(θ) 进行最大化最小化 来获得参数 θ \theta θ 的估计值。

  • 估计问题 :我们的目标是估计参数向量 θ \theta θ,使得某些标准(通常基于数据和模型)得到优化。
  • 数学定义 :如果存在一个函数 Q : Θ → R Q : \Theta \to \mathbb{R} Q:Θ→R,使得:
    θ ^ = arg ⁡ max ⁡ θ ∈ Θ Q ( θ ) \hat{\theta} = \arg\max_{\theta \in \Theta} Q(\theta) θ^=argθ∈ΘmaxQ(θ)
    那么估计量 θ ^ \hat{\theta} θ^ 被称为极值估计量 。这意味着估计量 θ ^ \hat{\theta} θ^ 是参数空间 Θ \Theta Θ 中能最大化(或最小化)目标函数 Q ( θ ) Q(\theta) Q(θ) 的那个 θ \theta θ。
  • 极值可以是最大化最小化,具体取决于问题的性质。
3. 标量目标函数 Q ( θ ) Q(\theta) Q(θ):

目标函数 Q ( θ ) Q(\theta) Q(θ) 是一个标量目标函数 ,用于衡量带有参数 θ \theta θ 的模型与数据的拟合程度或满足某些标准的情况。目标是通过找到能够给出最佳(极值)值的参数 θ \theta θ 来优化这个函数。

  • 最大化或最小化 :极值估计量涉及找到能够最大化最小化 目标函数的 θ \theta θ。例如:
    • 最大化:在最大似然估计(MLE)中,我们最大化似然函数。
    • 最小化:在普通最小二乘法(OLS)中,我们最小化残差的平方和。
4. 极值估计量的示例:

极值估计量框架包括几个广泛使用的方法,这些方法在统计学和计量经济学中应用广泛。这些方法旨在优化不同类型的目标函数:

  • OLS(普通最小二乘法)
    • 目标:最小化观察数据与通过线性模型进行预测的残差平方和。
    • 极值:最小化目标函数给出最佳线性拟合。
  • NLS(非线性最小二乘法)
    • 目标:最小化非线性模型中数据与预测值之间的残差平方和。
    • 极值:这是 OLS 的非线性版本,但目标函数的形式是一样的 --- 最小化残差。
  • GMM(广义矩估计法)
    • 目标:目标是将样本矩(例如,样本均值、方差)与理论矩(例如,从模型中得出的矩)进行匹配。
    • 极值 :GMM 通过最小化一个衡量样本矩与理论矩之间差异的标准函数来估计 θ \theta θ。
  • QMLE(拟最大似然估计法)
    • 目标:最大似然估计的推广,通常用于当似然函数难以指定时。
    • 极值:与 MLE 相似,QMLE 旨在最大化似然函数,但它允许做一些近似。
5. 极值估计量的总结:
  • 参数模型 由一个参数向量 θ \theta θ 定义, θ \theta θ 属于参数空间 Θ \Theta Θ。
  • 极值估计量 是通过对标量目标函数 Q ( θ ) Q(\theta) Q(θ) 进行最大化或最小化来获得的。
  • 极值估计量的类包括常见的方法,如:
    • OLS(普通最小二乘法)
    • NLS(非线性最小二乘法)
    • GMM(广义矩估计法)
    • QMLE(拟最大似然估计法)
      这些估计量依赖于数值优化技术来找到最佳拟合数据的 θ \theta θ。

数值优化:算法

数值优化是用于解决优化问题的强大工具,目标是找到使目标函数最小化或最大化的参数值。优化算法可以根据目标函数的使用方式分为两类:无导数算法基于梯度的算法

1. 无导数算法(Derivative-Free Algorithms)
  • 定义:无导数优化算法不需要目标函数的梯度(或导数)。它们完全依赖于函数值来引导优化过程。
  • 适用场景:无导数算法在目标函数的梯度难以计算或计算成本很高时非常有用。常用于复杂的模型,梯度信息难以获得(例如噪声函数、黑箱优化问题)。
  • 例子
    • Nelder-Mead法(单纯形法)
    • Powell方法
    • 遗传算法
    • 粒子群优化(PSO)
2. 基于梯度的算法(Gradient-Based Algorithms)
  • 定义:基于梯度的算法依赖目标函数的梯度(即一阶导数)来指导如何更新参数向量,以最小化或最大化目标函数。
  • 迭代形式
    在基于梯度的算法中,优化过程是迭代进行的,从初始猜测值 θ 0 \theta_0 θ0 开始。每次迭代 k k k 中,新的参数向量 θ k \theta_k θk 通过用步长 α k \alpha_k αk 和方向 p k p_k pk 更新前一个迭代的参数值 θ k − 1 \theta_{k-1} θk−1 来获得:
    θ k = θ k − 1 + s k \theta_k = \theta_{k-1} + s_k θk=θk−1+sk
    其中:
    • 步长 : s k = α k p k s_k = \alpha_k p_k sk=αkpk,是方向向量 p k p_k pk 的标量倍数。
    • 步长大小 : α k \alpha_k αk 是一个标量,通常大于0。
    • 方向 :方向向量 p k p_k pk 通常根据当前点 θ k \theta_k θk 处的梯度来确定:
      p k = − H k ∇ Q ( θ k ) p_k = -H_k \nabla Q(\theta_k) pk=−Hk∇Q(θk)
      其中 ∇ Q ( θ k ) \nabla Q(\theta_k) ∇Q(θk) 是目标函数 Q ( θ ) Q(\theta) Q(θ) 在当前点 θ k \theta_k θk 处的梯度。
3. 不同方向矩阵 H k H_k Hk 的类型

方向向量 p k p_k pk 是由矩阵 H k H_k Hk 来修改的,不同的优化算法使用不同形式的 H k H_k Hk,这最终决定了更新规则。

  1. 最速上升/下降(Steepest Ascent/Descent) (梯度下降):
    • H k = I H_k = I Hk=I(单位矩阵)
    • 含义 :在这种情况下, p k p_k pk 与负梯度成正比。这是最简单的优化形式(即梯度下降)。
    • 算法:梯度下降(GD)
    • 适用场景:当目标函数平滑,且可以得到梯度时,通常采用此方法。
  2. 牛顿法(Newton's Method)
    • H k = ∇ 2 Q ( θ k ) H_k = \nabla^2 Q(\theta_k) Hk=∇2Q(θk)(Hessian矩阵)
    • 含义 :方向 p k p_k pk 由目标函数的Hessian矩阵(即目标函数的二阶导数)来修正。这个方法考虑了目标函数的梯度和曲率(二阶导数)。
    • 适用场景:牛顿法在目标函数良好时收敛速度非常快,并且收敛是二次的,但它需要计算或近似Hessian矩阵,这在大规模问题中可能计算成本高。
  3. 拟牛顿法(Quasi-Newton Methods)
    • H k H_k Hk 是Hessian矩阵的近似
    • 割线方程 :拟牛顿方法使用割线方程来近似Hessian:
      H k + 1 s k = ∇ Q ( θ k + 1 ) − ∇ Q ( θ k ) H_{k+1} s_k = \nabla Q(\theta_{k+1}) - \nabla Q(\theta_k) Hk+1sk=∇Q(θk+1)−∇Q(θk)
      其中 s k = θ k + 1 − θ k s_k = \theta_{k+1} - \theta_k sk=θk+1−θk。
    • 例子:**BFGS(Broyden--Fletcher--Goldfarb--Shanno)**算法是一个著名的拟牛顿方法。
    • 适用场景:这些方法因不需要完全计算Hessian矩阵且能提供比最速下降更快的收敛速度而广泛使用。它们对于高维问题更具可扩展性。

总结

  1. 无导数方法
    • 不依赖梯度。
    • 当梯度难以计算或不可用时很有用。
    • 例子:Nelder-Mead法、遗传算法。
  2. 基于梯度的方法
    • 依赖目标函数的梯度(即一阶导数)。
    • 每次迭代时通过更新步长 α k \alpha_k αk 和方向 p k p_k pk 来调整参数。
    • 方向矩阵 H k H_k Hk 的类型:
      • 最速下降 (单位矩阵 H k = I H_k = I Hk=I):简单而广泛使用,但可能较慢。
      • 牛顿法 (Hessian矩阵 H k = ∇ 2 Q ( θ k ) H_k = \nabla^2 Q(\theta_k) Hk=∇2Q(θk)):收敛快,但计算成本高。
      • 拟牛顿法(Hessian的近似):在计算成本和收敛速度之间提供平衡。

数值优化:梯度

梯度基优化算法通常表现出优越的收敛速度 ,例如超线性收敛 甚至二次收敛,这使得它们在许多应用中非常有效。然而,要实现这种高效的收敛性能,前提是必须具备以下条件:

1. 梯度的精确性

为了确保良好的收敛性,梯度信息通常需要是精确的 。如果梯度是近似的,它的准确度必须足够高,特别是在渐近 的意义下。换句话说,梯度的计算需要尽可能接近实际值,否则优化过程可能会面临收敛问题。
引用 :Nocedal 和 Wright(2006年,第3章)强调,梯度的准确性对于算法的收敛性至关重要。

2. 梯度公式的难以获得

对于许多现代的优化模型,目标函数的解析梯度公式 可能不可用,或者推导起来非常复杂。这意味着直接利用闭式的数学公式来计算梯度往往是不现实的。尤其在某些高度复杂或非线性的优化问题中,解析梯度可能无法明确给出,或者推导过程会变得过于繁琐。

3. 数值近似:有限差分法(FDM)

在无法直接获得解析梯度的情况下,通常会使用数值方法来近似梯度。最常见的数值近似方法是有限差分法(Finite Difference Methods, FDM)。这种方法通过对目标函数在不同点的值进行差分来估算梯度。常见的有限差分方法包括:

  • 前向差分法 :通过 f ′ ( x ) ≈ f ( x + h ) − f ( x ) h f'(x) \approx \frac{f(x+h) - f(x)}{h} f′(x)≈hf(x+h)−f(x) 来近似梯度。
  • 中心差分法 :通过 f ′ ( x ) ≈ f ( x + h ) − f ( x − h ) 2 h f'(x) \approx \frac{f(x+h) - f(x-h)}{2h} f′(x)≈2hf(x+h)−f(x−h) 来获得更高精度的梯度估计。
    尽管有限差分法不需要明确的梯度公式,但它的计算成本相对较高,因为它需要计算多个目标函数的值。
    引用:Nocedal 和 Wright(2006年,第8章)详细介绍了有限差分法的使用和局限性。
4. 推理:协方差矩阵估计

协方差矩阵估计是许多统计和计量经济学模型中非常重要的一部分。为了估计协方差矩阵,通常需要用到梯度信息,因为梯度在许多情况下与模型参数的估计误差和变化密切相关。

总结
  • 优越的收敛性:基于梯度的算法具有超线性或二次收敛的优势,但前提是需要准确的梯度信息。
  • 无法获得解析梯度:许多现代模型无法给出闭式的解析梯度,导致需要使用数值近似方法(如有限差分法)来计算梯度。
  • 有限差分法(FDM):这种方法通过对目标函数的差分来估算梯度,适用于梯度难以解析推导的情况,但计算开销较大。
  • 协方差矩阵估计:梯度信息在许多统计估计过程中起着重要作用,特别是在推理和误差估计方面。

数值优化:算法微分(Algorithmic Differentiation, AD)

在数值优化中,计算梯度是优化过程中至关重要的一步。为了获得准确且高效的梯度,**算法微分(AD)**是一种常用的技术,其主要特点如下:

1. 目标:快速且准确的梯度计算

优化算法的核心问题之一是如何计算目标函数相对于参数的梯度。理想的情况是,我们能够在优化过程中快速准确地计算梯度,而这正是**算法微分(AD)**的优势所在。

2. 什么是算法微分(AD)?

算法微分是自动化的计算微分方法 ,其基本思想是通过链式法则来系统地求解梯度。其工作原理如下:

  • 自动化:AD 是自动进行微分计算的,不需要手动推导每个公式。
  • 基于链式法则 :AD 的核心思想是通过递归地应用链式法则,将复杂的函数分解成一系列简单的操作,然后对每个操作分别求导。
  • 精度 :AD 在机器精度的范围内,可以精确计算梯度。与数值差分方法不同,AD 可以精确计算梯度,而不是近似计算。
3. AD 的优势:
  • 快速:与数值差分法相比,AD 通常具有更高的效率。因为它不需要多次评估目标函数,而是通过精确的计算过程来获得梯度。
  • 准确 :AD 是精确的,只受限于机器精度。它不像数值差分法那样存在误差累积的问题,因此计算出来的梯度在大多数情况下是非常精确的。
  • 简单:AD 的实现相对简单,不需要复杂的数学推导或公式转换。它可以自动生成梯度,使得编程和实现变得更加直接和高效。
4. 与其他微分方法比较:
  • 与有限差分法(FDM)相比:有限差分法需要多次评估目标函数,来估计梯度,因此在计算时开销较大,而且可能受到数值误差的影响(例如,由于舍入误差,有限差分法会引入偏差)。而 AD 则通过精确的链式法则计算梯度,不需要重复计算目标函数,且误差较小。
  • 与符号微分(Symbolic Differentiation)相比:符号微分是通过解析求解表达式来获取梯度,通常需要大量的符号操作,而且对于非常复杂的函数表达式,符号微分可能会变得非常复杂。而 AD 通过递归应用链式法则计算梯度,不需要复杂的符号推导,适应性更强。
  • 与自动差分(Automatic Differentiation)相比:AD 是自动微分的一种形式,它能够在不需要用户干预的情况下,自动计算目标函数的梯度。它比数值方法更精确,且比符号方法更高效,通常是数值优化中常用的工具。
5. 总结:
  • 算法微分(AD) 是一个高效、准确的计算梯度的方法,基于链式法则,并且能够在机器精度内精确计算梯度。
  • 比有限差分法更高效,因为避免了目标函数多次评估,而且能减少数值误差。
  • 比符号微分更灵活和高效,因为它不需要复杂的符号推导,且能够处理更复杂的函数。
  • 在现代优化问题中,AD 被广泛应用于需要计算梯度的领域,如深度学习、数值优化和机器学习。

计算导数的主要方法

在数值优化、机器学习和科学计算等领域,计算函数的导数(梯度)是至关重要的一步。常见的导数计算方法主要有三种:有限差分法 (Finite Differencing)、算法微分(自动微分) (Algorithmic Differentiation, AD)和符号微分 (Symbolic Differentiation)。

以下是这三种方法的简要介绍和比较:

1. 有限差分法(Finite Differencing)

原理:有限差分法通过对函数值的微小变化进行近似,计算函数的导数。简单来说,它是通过数值方式计算函数在某点的导数。

  • 计算公式
    • 一阶导数:
      f ( x + h ) − f ( x ) h \frac{f(x+h) - f(x)}{h} hf(x+h)−f(x)
      其中 h h h 是一个小的增量。通过此公式,有限差分法可以估算函数在某一点的导数。
    • 高阶导数和更复杂的差分公式也可以推导出来,例如中心差分法:
      f ( x + h ) − f ( x − h ) 2 h \frac{f(x+h) - f(x-h)}{2h} 2hf(x+h)−f(x−h)
  • 优点
    • 简单,易于实现。
    • 不需要知道目标函数的具体形式,只需要能够计算函数值。
  • 缺点
    • 精度有限,特别是当 h h h 很小的时候,可能会引入舍入误差。
    • 计算量大:需要多次评估目标函数,特别是当需要计算多维梯度时,计算开销非常高。
    • 对于高维函数,计算效率较低,因为每个维度都需要计算一次导数。

2. 算法微分(Algorithmic Differentiation, AD)

原理:算法微分是一种基于链式法则的自动计算导数的方法。通过将目标函数分解为简单的基本操作,并在每个操作上应用链式法则,AD 能够自动、精确地计算目标函数的导数。

  • 特点
    • 自动化:不需要用户手动计算导数。
    • 精确性:AD 能够在机器精度内精确计算导数。
    • 高效性:相比有限差分法,AD 不需要多次评估目标函数,因此计算效率更高。
  • 优点
    • 高效:对于复杂的函数,AD 可以高效计算梯度,避免了冗余的函数评估。
    • 精确:AD 可以精确地计算梯度,不会受到有限差分法中近似误差的影响。
    • 可扩展性:在处理高维问题时,AD 的效率仍然较高。
  • 缺点
    • 需要实现 AD 的框架,或者使用第三方工具库来处理。
    • 对于复杂的程序和模型,可能需要一定的开发和调试工作。

3. 符号微分(Symbolic Differentiation)

原理:符号微分通过解析求导,直接给出目标函数的导数表达式。它通常使用计算机代数系统(CAS)来进行符号运算,从而得到准确的导数公式。

  • 特点
    • 精确:符号微分给出的导数是一个完整的公式,没有近似误差。
    • 不依赖数值近似:与有限差分法不同,符号微分不依赖于近似计算。
  • 优点
    • 精确性:计算出的导数是精确的,不会受到数值误差的影响。
    • 对于简单或中等复杂度的函数,可以直接得到导数表达式。
  • 缺点
    • 计算开销大:对于非常复杂的函数,符号微分可能变得非常复杂,并且计算开销大。
    • 不可扩展性:对于大规模、复杂的模型,符号微分难以处理。
    • 处理某些类型的表达式(如条件语句或循环)可能变得困难。

总结与比较:

方法 优点 缺点 适用场景
有限差分法 实现简单,适用于任意函数 计算量大,精度较低,容易受数值误差影响 简单、低维度的函数,或当无法获取函数解析表达式时
算法微分(AD) 高效、精确,适用于复杂函数 需要专门的工具和实现 需要高效和精确计算梯度,尤其是高维优化问题
符号微分 精确计算导数,不受数值误差影响 对复杂函数不适用,计算开销大,无法处理高维模型 简单函数或符号表达式可用的情况

总结:

  • 有限差分法适用于不需要推导函数表达式的情况,但计算效率较低且精度有限。
  • **算法微分(AD)**提供了一个自动化且精确的计算梯度的方法,特别适合于高维优化问题。
  • 符号微分适用于较为简单的数学函数,但对于复杂的模型,效率较低且难以处理高维度问题。

有限差分法(Finite Differencing)

有限差分法是数值计算中常用的一种方法,用于近似计算函数的导数。其基本思想是通过函数值的变化来近似地估计导数。在数值优化中,有限差分法被用来计算梯度或导数,特别是当解析表达式不可得时。

有限差分法的两种常见近似:

1. 前向差分法(Forward Difference)

前向差分法的公式如下:
f ′ ( x ) ≈ f ( x + h ) − f ( x ) h f'(x) \approx \frac{f(x + h) - f(x)}{h} f′(x)≈hf(x+h)−f(x)

其中, h h h 是一个非常小的步长,表示对 x x x 进行微小的扰动。

  • 优点:实现简单,只需要当前点和扰动后的点进行计算。
  • 计算成本:每次计算需要进行一次函数评估。
  • 适用场景:适用于计算单个方向上的导数。
2. 中心差分法(Central Difference)

中心差分法的公式如下:
f ′ ( x ) ≈ f ( x + h ) − f ( x − h ) 2 h f'(x) \approx \frac{f(x + h) - f(x - h)}{2h} f′(x)≈2hf(x+h)−f(x−h)

这里, 2 h 2h 2h 是步长,分别在 x + h x+h x+h 和 x − h x-h x−h 处计算函数值,从而计算其导数。

  • 优点:相比前向差分,中心差分通常能提供更高的精度(更小的误差)。
  • 计算成本 :需要两次函数评估,分别计算 f ( x + h ) f(x + h) f(x+h) 和 f ( x − h ) f(x - h) f(x−h)。

计算成本的比较:

  • 前向差分法 :每次计算需要 1 次函数评估
  • 中心差分法 :每次计算需要 2 次函数评估 (分别计算 f ( x + h ) f(x+h) f(x+h) 和 f ( x − h ) f(x-h) f(x−h))。

误差分析:

  • 前向差分法的误差 :误差是阶数为 O ( h ) O(h) O(h) 的。
    • 即,当步长 h h h 变小的时候,误差的减少速度是线性的。
  • 中心差分法的误差 :误差是阶数为 O ( h 2 ) O(h^2) O(h2) 的。
    • 因此,当步长 h h h 变小的时候,中心差分法的误差减少速度是平方的,通常精度比前向差分法更高。

适用场景:

  • 前向差分法
    • 简单的数值实现。
    • 计算单方向上的导数时很方便,尤其是梯度计算中的一维情形。
    • 对于复杂函数或高维问题,可能计算代价较高。
  • 中心差分法
    • 精度较高,适合于需要较高准确度的计算。
    • 需要更多的函数评估,因此计算成本比前向差分法稍高。
    • 适用于当对精度要求较高时(尤其是对于多维函数的导数计算)。

总结:

  • 前向差分法:计算效率较高,但误差相对较大,适用于对精度要求不高的情况。
  • 中心差分法:精度更高,但计算成本较高,适用于对精度要求较高的场景。

1. 前向差分公式(Forward Difference Formula)

前向差分法用于估算函数的导数,公式如下:
f ′ ( x ) ≈ f ( x + h ) − f ( x ) h f'(x) \approx \frac{f(x + h) - f(x)}{h} f′(x)≈hf(x+h)−f(x)

这里 h h h 是步长,用来表示 f ( x + h ) f(x + h) f(x+h) 和 f ( x ) f(x) f(x) 之间的差距。

2. 泰勒展开(Taylor Expansion)

根据泰勒展开公式,对于单变量函数 f f f 和点 x x x,我们有:
f ( x + h ) = f ( x ) + h f ′ ( x ) + h 2 2 f ′ ′ ( x ) + h 3 6 f ′ ′ ′ ( x ) + O ( h 4 ) f(x + h) = f(x) + h f'(x) + \frac{h^2}{2} f''(x) + \frac{h^3}{6} f'''(x) + O(h^4) f(x+h)=f(x)+hf′(x)+2h2f′′(x)+6h3f′′′(x)+O(h4)

3. 代入前向差分公式

将上述泰勒展开式代入前向差分公式,得到:
f ′ ( x ) = f ( x + h ) − f ( x ) h f'(x) = \frac{f(x + h) - f(x)}{h} f′(x)=hf(x+h)−f(x)

代入泰勒展开式:
f ′ ( x ) = f ( x ) + h f ′ ( x ) + h 2 2 f ′ ′ ( x ) + h 3 6 f ′ ′ ′ ( x ) + O ( h 4 ) − f ( x ) h f'(x) = \frac{f(x) + h f'(x) + \frac{h^2}{2} f''(x) + \frac{h^3}{6} f'''(x) + O(h^4) - f(x)}{h} f′(x)=hf(x)+hf′(x)+2h2f′′(x)+6h3f′′′(x)+O(h4)−f(x)

整理后得到:
f ′ ( x ) = f ′ ( x ) + h 2 f ′ ′ ( x ) + h 2 6 f ′ ′ ′ ( x ) + O ( h 3 ) f'(x) = f'(x) + \frac{h}{2} f''(x) + \frac{h^2}{6} f'''(x) + O(h^3) f′(x)=f′(x)+2hf′′(x)+6h2f′′′(x)+O(h3)

4. 截断误差(Truncation Error)

截断误差是由泰勒展开剩余项产生的,它衡量了我们使用前向差分公式与真实导数 f ′ ( x ) f'(x) f′(x) 之间的差异。根据上面的推导,我们得到误差项:
f ′ ( x ) = f ( x + h ) − f ( x ) h − h 2 f ′ ′ ( x ) − h 2 6 f ′ ′ ′ ( x ) − O ( h 3 ) f'(x) = \frac{f(x + h) - f(x)}{h} - \frac{h}{2} f''(x) - \frac{h^2}{6} f'''(x) - O(h^3) f′(x)=hf(x+h)−f(x)−2hf′′(x)−6h2f′′′(x)−O(h3)

因此,截断误差 是:
截断误差 = − h 2 f ′ ′ ( x ) − h 2 6 f ′ ′ ′ ( x ) − O ( h 3 ) \text{截断误差} = - \frac{h}{2} f''(x) - \frac{h^2}{6} f'''(x) - O(h^3) 截断误差=−2hf′′(x)−6h2f′′′(x)−O(h3)

5. 误差分析

  • 截断误差的主要项是 − h 2 f ′ ′ ( x ) - \frac{h}{2} f''(x) −2hf′′(x),表示误差与步长 h h h 成线性关系。
  • 随着 h h h 的减小,误差会变得更小。也就是说,减小步长 h h h 可以减小误差。
  • 其他项(如 h 2 6 f ′ ′ ′ ( x ) \frac{h^2}{6} f'''(x) 6h2f′′′(x) 和高阶项 O ( h 3 ) O(h^3) O(h3))对误差的影响逐渐减小,但它们存在于更高阶的项中。

总结:

截断误差 是由泰勒级数展开式中的高阶项引起的,前向差分法的误差 可以表示为:
截断误差 = − h 2 f ′ ′ ( x ) − h 2 6 f ′ ′ ′ ( x ) − O ( h 3 ) \text{截断误差} = - \frac{h}{2} f''(x) - \frac{h^2}{6} f'''(x) - O(h^3) 截断误差=−2hf′′(x)−6h2f′′′(x)−O(h3)

浮点数运算中的问题 ,尤其是加法和乘法的不结合性(non-associativity)和不分配性(non-distributivity),这是由于计算机使用有限精度来表示实数。

1. 浮点数运算的基本问题

浮点数运算是有限精度的,即我们只能在一定范围内精确表示数字。超出这个精度范围的数字会被截断或四舍五入 ,这会导致运算结果出现误差。由于浮点数表示的局限性,加法和乘法的结合性不成立 ,即:
( a + b ) + c ≠ a + ( b + c ) (a + b) + c \neq a + (b + c) (a+b)+c=a+(b+c)

这意味着浮点数加法和乘法的顺序会影响结果

2. 示例分析

你给的例子非常直观:

  • 输入
    a = 1234.567 , b = 45.678 , c = 0.0004 a = 1234.567, \quad b = 45.678, \quad c = 0.0004 a=1234.567,b=45.678,c=0.0004
    计算:
    a + b + c 和 c + b + a a + b + c \quad \text{和} \quad c + b + a a+b+c和c+b+a
  • 结果
    a + b + c = 1280.2454 a + b + c = 1280.2454 a+b+c=1280.2454
    c + b + a = 1280.2454 c + b + a = 1280.2454 c+b+a=1280.2454
    虽然两个表达式看起来是相同的,最终结果也是相同的,但如果你计算这两者的差异:
    ( a + b + c ) − ( c + b + a ) = − 2.273737 × 10 − 13 (a + b + c) - (c + b + a) = -2.273737 \times 10^{-13} (a+b+c)−(c+b+a)=−2.273737×10−13
    你会发现结果是一个非常小的数,接近于零,但并不是零。
    这个误差 是由于浮点数运算中的精度丧失舍入误差造成的。尽管数值非常接近,但计算顺序会引入微小的误差。

3. 结合性和分配性丧失的原因

浮点数加法和乘法的结合性和分配性丧失的原因在于有限精度表示

  • 舍入误差:计算机在存储浮点数时,不能完全准确地存储所有小数位,特别是对于较大的或较小的数字。每次运算都会产生舍入误差,导致最终结果偏离正确值。
  • 数值范围:当进行加法和乘法运算时,某些数字可能太大而无法被精确表示,这会导致溢出或精度丧失。例如,在加法中,如果数值差异太大,小的数字可能会被"忽略",从而丧失精度。

4. Knuth的观点

正如Donald E. Knuth在《计算机程序设计的艺术(TAOCP2)》中所提到的,浮点数运算的严格分析非常困难 。很多情况下,数学家不得不放弃严格的分析,而依赖于合理性推断。也就是说,由于浮点数的运算涉及许多不精确的部分(如舍入和溢出),严格的数学证明几乎是不可能的。

5. 总结

  • 浮点数运算的不可结合性和不可分配性是计算机中常见的问题。
  • 舍入误差是导致这一问题的根本原因,即使加法和乘法看似满足结合律,微小的误差仍然会积累。
  • Knuth的观点 表明,浮点运算分析非常复杂,很多时候我们只能用合理的推测和近似来理解浮点数运算中的误差。

关于浮点数表示法IEEE 754标准的基本介绍。让我详细解释一下这些概念。

1. 浮点数表示法的灵感:科学记数法

浮点数的表示方法受到科学记数法 的启发。在科学记数法中,数值可以表示为:
x = significant digits × base exponent x = \text{significant digits} \times \text{base}^{\text{exponent}} x=significant digits×baseexponent

例如: 6.022 × 10 23 6.022 \times 10^{23} 6.022×1023

这种表示法使得我们能够处理非常大的数(如 10 23 10^{23} 1023)以及非常小的数(如 10 − 23 10^{-23} 10−23),并且有效地表示各种数值。

2. IEEE 754 标准

IEEE 754 是浮点数表示的标准,它定义了如何在计算机中存储和操作浮点数。浮点数的表示有两种主要格式:

  • 单精度浮点数(binary32)
  • 双精度浮点数(binary64)

3. 浮点数的标准形式

浮点数的标准形式可以写为:
fl ( x ) = x ( 1 + ϵ ) \text{fl}(x) = x(1 + \epsilon) fl(x)=x(1+ϵ)

其中, ϵ \epsilon ϵ 是浮点数的误差因子,它表示由于计算机精度的限制,浮点数表示的数字和真实数值之间的误差。它通常非常小,但是会影响到最终的结果。

4. 机器精度 ϵ M \epsilon_M ϵM

机器精度 ( ϵ M \epsilon_M ϵM)是指可以表示的最小误差,即浮点数表示系统能够区分的最小数值差异。例如,在浮点数表示中:
ϵ M = inf ϵ > 0 : 10 + ϵ = 10 \epsilon_M = \text{inf} \epsilon > 0 : 10 + \epsilon = 10 ϵM=infϵ>0:10+ϵ=10

换句话说,机器精度是能够表示的最小有效增量 。例如,当我们在浮点数系统中计算 10 + ϵ 10 + \epsilon 10+ϵ 时, ϵ \epsilon ϵ 是使得 10 + ϵ 10 + \epsilon 10+ϵ 和 10 10 10 之间产生区分的最小数值。

5. IEEE 754 单精度(binary32)和双精度(binary64)

  • IEEE 754 单精度(binary32) :它使用 32位 来表示浮点数,其中:
    • 1个符号位
    • 8个指数位
    • 23个尾数位(即有效数字部分)
      其机器精度大约为 1.19209 × 10 − 7 1.19209 \times 10^{-7} 1.19209×10−7,表示能够区分的最小增量。
  • IEEE 754 双精度(binary64) :它使用 64位 来表示浮点数,其中:
    • 1个符号位
    • 11个指数位
    • 52个尾数位
      其机器精度大约为 2.22045 × 10 − 16 2.22045 \times 10^{-16} 2.22045×10−16,相对于单精度,它的精度更高,可以表示更小的数字差异。

6. 浮点数的表示形式

  • fl(x) = x(1 + \epsilon) :这表示浮点数表示的数值 x x x 与真实数值之间的误差 ϵ \epsilon ϵ。计算机无法完美地表示一个无限精度的小数,因此浮点数表示会有小的误差,这个误差是计算机存储和操作浮点数时不可避免的。

7. 总结

  • 浮点数表示法:模拟科学记数法来处理大数和小数,便于计算机表示和计算。
  • IEEE 754 标准:定义了浮点数的存储格式,确保浮点运算的一致性。
  • 机器精度:浮点数能表示的最小误差,影响计算的精确度。
  • 单精度和双精度:根据不同的存储位数,提供不同的精度和表示范围,双精度提供更高的精度。

浮点数表示及其相关概念。

浮点数表示:

  • fl(x) = x(1 + δ) ,其中 |δ| < ε_M
    • 这是浮点数的表示方法:
      • x 是原始的实数(真实值)。
      • fl(x)x 的浮点近似值。
      • δ 是浮点表示中引入的一个小误差(即偏差)。它通常非常小,且 |δ| 的大小由机器精度 ε_M(机器的舍入误差)约束。
      • ε_M单位舍入误差 ,通常用 u 表示,它是浮点计算中可能出现的最小相对误差。

表示(舍入)误差:

  • 舍入误差为:x - fl(x)
    • 这是浮点数表示中的误差,也就是真实数值与其浮点近似值之间的差异。

绝对舍入误差:

  • |x - fl(x)| < ε_M |x|
    • 这表示绝对误差(即 xfl(x) 之间的差值的绝对值)是由 ε_M 与真实数 x 的绝对值的乘积所界定的。
    • 换句话说,绝对舍入误差相对于真实数 x 的大小是受限的。

相对舍入误差:

  • |x - fl(x)| / |x| < ε_M
    • 这表示相对误差,是绝对误差与 x 的绝对值之比。
    • 该表达式告诉我们,误差相对于数字本身的大小是如何变化的,并且误差的大小与机器精度 ε_M 成正比。

ε_M(单位舍入误差):

  • ε_M (也叫 u )是浮点数表示中允许的最小相对误差。它表示机器的精度限制。
    • 在二进制浮点数系统中,ε_M 是可以表示的最后一个二进制位所代表的最小值。
    • 比如,在 64 位双精度系统中,ε_M 大约是 2.22 × 10 − 16 2.22 \times 10^{-16} 2.22×10−16,这意味着数字的表示误差最大可以达到这个级别。

ULP(单位最后一位):

  • ULP(单位最后一位) 是衡量浮点数精度的方式。
    • 它指的是两个连续可表示的浮点数之间的距离。如果有两个浮点数 ab ,并且 a ≤ x ≤ ba ≠ b ,那么它们之间的距离就是 ULP(x)
    • 对于一个数字 x ,可以表示为:
      • ULP(x) = ε_M · b^E ,其中:
        • b 是数字系统的基数(例如二进制中是 2)。
        • E 是数字 x 在浮点表示中的指数。
        • ε_M 是机器精度(最小相对误差)。
          换句话说,ULP(x) 就是浮点系统中表示两个连续浮点数之间的精度。

总结:

  • fl(x) = x(1 + δ) 表示浮点数是近似值,δ 是由机器精度 ε_M 限制的小误差。
  • 舍入误差 是真实值 x 与其浮点表示 fl(x) 之间的差异。
  • 绝对舍入误差ε_M |x| 所限制,相对舍入误差ε_M 所限制。
  • ε_M 是机器的单位舍入误差或最小相对误差,ULP 用来表示浮点数表示精度的单位。

后面看不懂

相关推荐
橙子1991101644 分钟前
Kotlin 中的 Object
android·开发语言·kotlin
callJJ1 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
java·开发语言·spring boot·后端·spring·restful·ioc di
Python开发吖2 小时前
【已解决】python的kafka-python包连接kafka报认证失败
开发语言·python·kafka
@老蝴5 小时前
C语言 — 通讯录模拟实现
c语言·开发语言·算法
ThreeYear_s7 小时前
基于FPGA的PID算法学习———实现P比例控制算法
学习·fpga开发
♚卜卦7 小时前
面向对象 设计模式简述(1.创建型模式)
开发语言·设计模式
安全系统学习7 小时前
网络安全之RCE简单分析
开发语言·python·算法·安全·web安全
恰薯条的屑海鸥7 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)
学习·安全·web安全·渗透测试·网络安全学习
Swift社区8 小时前
Swift 解法详解:如何在二叉树中寻找最长连续序列
开发语言·ios·swift
yutian06068 小时前
C# 支持 ToolTip 功能的控件,鼠标悬停弹提示框
开发语言·microsoft·c#