首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >有没有什么“标准”的方法来计算数值梯度?

有没有什么“标准”的方法来计算数值梯度?
EN

Stack Overflow用户
提问于 2016-08-09 23:05:57
回答 6查看 8.8K关注 0票数 3

我正在尝试在c++中计算光滑函数的数值梯度。参数值可以从0到非常大的数字(可能是1e10到1e20?)

我使用函数f(x,y) = 10*x^3 + y^3作为测试平台,但我发现如果x或y太大,我无法获得正确的梯度。

下面是我计算梯度的代码:

代码语言:javascript
运行
复制
#include <iostream>
#include <cmath>
#include <cassert>
using namespace std;
double f(double x, double y)
{
    // black box expensive function
    return 10 * pow(x, 3) + pow(y, 3);
}
int main()
{
    // double x = -5897182590.8347721;
    // double y = 269857217.0017581;
    double x = 1.13041e+19;
    double y = -5.49756e+14;
    const double epsi = 1e-4;

    double f1 = f(x, y);
    double f2 = f(x, y+epsi);
    double f3 = f(x, y-epsi);
    cout << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;
    cout << f1 - f2 << endl; // 0
    cout << f2 - f3 << endl; // 0
    return 0;
}

如果我使用上面的代码来计算梯度,那么梯度将为零!

测试平台函数,10*x^3 + y^3,只是一个演示,我需要解决的真正问题实际上是一个黑盒函数。

那么,有没有什么“标准”的方法来计算数值梯度呢?

EN

回答 6

Stack Overflow用户

发布于 2016-08-10 04:18:16

首先,你应该使用中心差分格式,它更精确(通过取消泰勒发展的多一项)。

代码语言:javascript
运行
复制
(f(x + h) - f(x - h)) / 2h

而不是

代码语言:javascript
运行
复制
(f(x + h) - f(x)) / h

那么h的选择是至关重要的,使用固定的常量是最糟糕的选择。因为对于较小的xh会过大,使得近似公式不再适用;而对于较大的xh会太小,导致严重的截断误差。

一个更好的选择是采用相对值h = x√ε,其中ε是机器epsilon (1 ulp),这提供了一个很好的折衷。

代码语言:javascript
运行
复制
(f(x(1 + √ε)) - f(x(1 - √ε))) / 2x√ε

请注意,当为x = 0时,相对值不起作用,您需要回退到常量。但是,没有任何东西告诉您应该使用哪一个!

票数 9
EN

Stack Overflow用户

发布于 2016-08-10 00:31:22

您需要考虑所需的精度。

乍一看,由于|y| = 5.49756e14epsi = 1e-4,您至少需要⌈log2(5.49756e14)-log2(1e-4)⌉ = 63位的有效位精度(即用于编码数字的位数,也称为尾数),才能将yy+epsi视为不同。

双精度浮点格式只有53位有效数精度(假设它是8字节)。因此,目前f1f2f3是完全相同的,因为yy+epsiy-epsi是相等的。

现在,让我们考虑一下限制:y = 1e20,以及函数10x^3 + y^3的结果。让我们暂时忽略x,所以让我们以f = y^3为例。现在我们可以计算出f(y)f(y+epsi)需要不同的精度:f(y) = 1e60f(epsi) = 1e-12。这提供了⌈log2(1e60)-log2(1e-12)⌉ = 240位的最小有效位精度。

即使您使用long double类型,假设它是16字节,您的结果也不会有所不同:f1f2f3仍然是相等的,即使yy+epsi不是。

如果我们将x考虑在内,f的最大值将是11e60 (带有x = y = 1e20)。因此精度的上限是⌈log2(11e60)-log2(1e-12)⌉ = 243位,或至少31个字节。

解决问题的一种方法是使用另一种类型,可能是用作定点的bignum。

另一种方法是重新思考你的问题,并以不同的方式处理它。归根结底,您需要的是f1 - f2。您可以尝试分解f(y+epsi)。同样,如果忽略xf(y+epsi) = (y+epsi)^3 = y^3 + 3*y^2*epsi + 3*y*epsi^2 + epsi^3。所以f(y+epsi) - f(y) = 3*y^2*epsi + 3*y*epsi^2 + epsi^3

票数 2
EN

Stack Overflow用户

发布于 2016-08-09 23:14:38

计算梯度的唯一方法是微积分。

渐变是一个向量:

代码语言:javascript
运行
复制
g(x, y) = Df/Dx i + Df/Dy j

其中(i,j)分别是x和y方向上的单位向量。

近似导数的一种方法是一阶差分:

代码语言:javascript
运行
复制
Df/Dx ~ (f(x2, y)-f(x1, y))/(x2-x1)

代码语言:javascript
运行
复制
Df/Dy ~ (f(x, y2)-f(x, y1))/(y2-y1)

这看起来不像你在做的事。

您有一个封闭形式的表达式:

代码语言:javascript
运行
复制
g(x, y) = 30*x^2 i + 3*y^2 j

您可以插入(x,y)的值,并在任何点精确计算梯度。将其与您的差异进行比较,看看您的近似值做得有多好。

如何在数字上实现它是你的责任。(10^19)^3 = 10^57,对吗?

你的机器上的双倍大小是多少?它是64位IEEE双精度浮点数吗?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38854363

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档