我有一些表达式,比如x^2+y^2
,我想用它们进行一些数学计算。其中一件事是要对这些表达式进行偏导数。
例如,如果 f(x,y) = x^2 + y^2
,那么对于 x
的偏导数将会是 2x
,对于 y
的偏导数则是 2y
。
我使用了一个有限差分法编写的小型函数,但是遇到了很多浮点精度问题。例如,我得到的结果是1.99234
而不是2
。是否有任何支持符号微分的库?还有其他建议吗?
我有一些表达式,比如x^2+y^2
,我想用它们进行一些数学计算。其中一件事是要对这些表达式进行偏导数。
例如,如果 f(x,y) = x^2 + y^2
,那么对于 x
的偏导数将会是 2x
,对于 y
的偏导数则是 2y
。
我使用了一个有限差分法编写的小型函数,但是遇到了很多浮点精度问题。例如,我得到的结果是1.99234
而不是2
。是否有任何支持符号微分的库?还有其他建议吗?
我已经在几种不同的语言中实现了这样的库,但遗憾的是没有C。如果你只处理多项式(求和、乘积、变量、常数和指数),那么它就很容易。三角函数也不算太难。任何更复杂的东西,你可能最好花时间去掌握别人的库。
如果您决定自己开发,我有一些建议,可以简化您的生活:
使用不可变数据结构(纯函数式数据结构)表示表达式。
使用Hans Boehm 的垃圾收集器来管理内存。
为了表示线性求和,使用有限映射(例如二叉搜索树)将每个变量映射到其系数。
如果您愿意将Lua嵌入到C代码中,并在那里进行计算,我已经将我的Lua代码放在http://www.cs.tufts.edu/~nr/drop/lua上。其中一个较好的功能是它可以接受符号表达式,对其进行微分,并将结果编译成Lua。当然,你会发现完全没有文档 :-(
使用更小的delta。您似乎使用了约为1e-2
的值。从1e-8
开始,测试是否使得更小的值有益或有害。显然,您不能太接近机器精度——双精度约为1e-16
。
使用中心差分而不是前向(或后向)差分。
即df_dx=(f(x+delta)-f(x-delta))/(2.0*delta)
由于抵消更高截断项的原因,中心差分估计的误差是前向差分的delta
阶。请参见http://en.wikipedia.org/wiki/Finite_difference。
如果这确实是您想要使用的函数类型,编写一个类库将非常容易。从单个项开始,包括系数和指数。创建一个多项式,它由一组项组成。
如果您为感兴趣的数学方法定义一个接口(例如,add/sub/mul/div/differentiate/integrate),那么您正在考虑GoF组合模式。Term和Polynomial都将实现该接口。Polynomial将简单地迭代其集合中的每个Term。
利用已有的软件包要比编写自己的软件包更容易,但是如果你决心要编写自己的软件包,并且准备花一些时间学习 C++ 的一些黑暗角落,那么你可以使用Boost中的Boost.Proto来设计你自己的库。
基本上,Boost.Proto 允许你将任何有效的C++表达式(如x * x + y * y
)转换为表达式模板——使用嵌套的结构体表示该表达式的语法树,然后通过在稍后调用 proto::eval()
对该解析树执行任意计算。默认情况下,proto::eval()
用于按照直接运行的方式评估该树,但没有理由不能修改每个函数或运算符的行为,以取代符号导数。
虽然这可能是一个极其复杂的解决方案,但它仍然比尝试使用C ++模板元编程技术来滚动自己的表达式模板要容易得多。
很抱歉在6年后提出这个问题。然而,我正在寻找一个适合我的项目的库,我看到@eduffy建议使用FADBAD++。我已经阅读了文档并回来回答你的问题。我觉得我的答案会有所帮助,因此,以下代码适用于你的情况。
#include <iostream>
#include "fadiff.h"
using namespace fadbad;
F<double> func(const F<double>& x, const F<double>& y)
{
return x*x + y*y;
}
int main()
{
F<double> x,y,f; // Declare variables x,y,f
x=1; // Initialize variable x
x.diff(0,2); // Differentiate with respect to x (index 0 of 2)
y=1; // Initialize variable y
y.diff(1,2); // Differentiate with respect to y (index 1 of 2)
f=func(x,y); // Evaluate function and derivatives
double fval=f.x(); // Value of function
double dfdx=f.d(0); // Value of df/dx (index 0 of 2)
double dfdy=f.d(1); // Value of df/dy (index 1 of 2)
std::cout << " f(x,y) = " << fval << std::endl;
std::cout << "df/dx(x,y) = " << dfdx << std::endl;
std::cout << "df/dy(x,y) = " << dfdy << std::endl;
return 0;
}
f(x,y) = 2
df/dx(x,y) = 2
df/dy(x,y) = 2
sin()
的一阶导数感兴趣。从解析上来看,它是 cos
。这很好,因为我们需要比较给定函数的真实导数和其数值近似值以计算真实误差。#include <iostream>
#include "fadiff.h"
using namespace fadbad;
F<double> func(const F<double>& x)
{
return sin(x);
}
int main()
{
F<double> f,x;
double dfdx;
x = 0.0;
x.diff(0,1);
f = func(x);
dfdx=f.d(0);
for (int i(0); i < 8; ++i ){
std::cout << " x: " << x.val() << "\n"
<< " f(x): " << f.x() << "\n"
<< " fadDfdx: " << dfdx << "\n"
<< "trueDfdx: " << cos(x.val()) << std::endl;
std::cout << "==========================" << std::endl;
x += 0.1;
f = func(x);
dfdx=f.d(0);
}
return 0;
}
x: 0
f(x): 0
fadDfdx: 1
trueDfdx: 1
==========================
x: 0.1
f(x): 0.0998334
fadDfdx: 0.995004
trueDfdx: 0.995004
==========================
x: 0.2
f(x): 0.198669
fadDfdx: 0.980067
trueDfdx: 0.980067
==========================
x: 0.3
f(x): 0.29552
fadDfdx: 0.955336
trueDfdx: 0.955336
==========================
x: 0.4
f(x): 0.389418
fadDfdx: 0.921061
trueDfdx: 0.921061
==========================
x: 0.5
f(x): 0.479426
fadDfdx: 0.877583
trueDfdx: 0.877583
==========================
x: 0.6
f(x): 0.564642
fadDfdx: 0.825336
trueDfdx: 0.825336
==========================
x: 0.7
f(x): 0.644218
fadDfdx: 0.764842
trueDfdx: 0.764842
==========================
看看Theano,它支持符号微分(在神经网络的上下文中)。该项目是开源的,因此您应该能够看到他们如何实现它。
计算一阶导数非常容易实现。 但是让它快速运行则需要一定技巧。 您需要一个包含以下内容的类:
然后,您可以编写加法和减法等操作符以及像sin()这样实现此操作的基本和众所周知规则的函数。
计算更高阶导数应该使用截断的泰勒级数。 您还可以将上述类应用于自身--值和导数值的类型应该是模板参数。 但这意味着需要计算和存储多次导数。
截断的泰勒级数--有两个库可用于此: