数学の計算に使用したいx^2+y^2
などの式があります。私がやりたいことの1つは、式の偏導関数を取ることです。
したがって、f(x,y) = x^2 + y^2
の場合、f
に関するx
の部分は2x
になり、y
に関する部分は2y
になります。
有限差分法を使用してちっぽけな関数を作成しましたが、浮動小数点の精度に関して多くの問題が発生しています。たとえば、1.99234
ではなく2
になってしまいます。シンボリック微分をサポートするライブラリはありますか?他に何か提案はありますか?
私はそのようなライブラリをいくつかの異なる言語で実装しましたが、残念ながらCではありません。多項式(合計、積、変数、定数、および累乗)のみを扱っている場合、それは非常に簡単です。トリガー機能もそれほど悪くはありません。もっと複雑なことなら、時間をかけて他の人のライブラリをマスターしたほうがいいでしょう。
あなたがあなた自身を転がすことを決定した場合、私はあなたの人生を簡素化するいくつかの提案があります:
式を表すには、不変のデータ構造(純粋に関数型のデータ構造)を使用します。
Hans Boehmのガベージコレクター を使用して、メモリを管理します。
線形和を表すには、有限マップ(たとえば、二分探索木)を使用して、各変数をその係数にマップします。
Lua をCコードに埋め込んで計算を行う場合は、Luaコードを http://www.cs.tufts.edu/~nr)に配置しました。/drop/lua 。より優れた機能の1つは、シンボリック式を取り、それを区別し、結果をLuaにコンパイルできることです。もちろん、ドキュメントは何も見つかりません:-(
(エラーを最小限に抑えるという意味で)数値微分を「正しく」取得するのは非常に難しい場合があります。開始するには、 数値微分 に関する数値レシピのセクションを確認することをお勧めします。
無料の記号数学パッケージについては、 GiNaC を参照してください。 SymPy 、自己完結型の純粋なPython記号数学パッケージもチェックしてください。 SymPyは、Pythonコマンドラインからインタラクティブに使用できるため、探索がはるかに簡単であることがわかります。
商用の面では、MathematicaとMapleの両方にCAPIがあります。ライブラリを使用するには、インストール/ライセンスされたバージョンのプログラムが必要ですが、どちらも、必要なタイプの記号の区別を簡単に行うことができます。
2つの簡単な方法で数値微分の精度を向上させることができます
小さいデルタを使用します。 _1e-2
_前後の値を使用したようです。 _1e-8
_から始めて、小さな傷や助けが得られるかどうかをテストします。明らかに、マシンの精度に近づきすぎることはできません-doubleの場合は約_1e-16
_です。
前方(または後方)の違いではなく、中央の違いを使用します。つまり、df_dx =(f(x+delta) - f(x-delta)) / (2.0*delta)
より高い切り捨て項のキャンセルに関係する理由により、中央の差の推定値の誤差は、前方の差のデルタではなく、_delta^2
_のオーダーになります。 http://en.wikipedia.org/wiki/Finite_difference を参照してください
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
==========================
_
独自のパッケージを作成するよりも既存のパッケージを活用する方が確かに簡単ですが、独自のパッケージを作成することに決めており、C++のいくつかの暗い部分について学ぶ準備ができている場合は、 Boost.Proto from Boost を使用して、独自のライブラリを設計します。
基本的に、Boost.Protoを使用すると、_x * x + y * y
_などの有効なC++式を 式テンプレート に変換できます。基本的には、を使用してその式の解析ツリーを表現します。ネストされたstruct
s-その後、proto::eval()
を呼び出すことにより、後でその解析ツリーに対して任意の計算を実行します。デフォルトでは、proto::eval()
は、ツリーが直接実行されたかのように評価するために使用されますが、代わりにシンボリック導関数を取得するように各関数または演算子の動作を変更できない理由はありません。
これは問題に対する非常に複雑な解決策ですが、それでも、C++テンプレートメタプログラミング手法を使用して独自の式テンプレートをロールするよりもはるかに簡単です。
私はC++でそのようなライブラリを作成しました、ここでサンプルを見ることができます: https://github.com/MartinKosicky/auto_diff/blob/master/sample/main.cpp
使い方はとても簡単で、行列もサポートしています。リカレントニューラルネットワークを作成するためにそれを使用しました... GPU行列の乗算を追加する必要がありますが
これが本当に使用したい種類の関数である場合は、クラスライブラリを作成するのは簡単です。係数と指数を持つ単一の項から始めます。用語のコレクションで構成される多項式があります。
関心のある数学的方法のインターフェースを定義する場合(たとえば、add/sub/mul/div/differentiate/integrate)、GoFコンポジットパターンを見ていることになります。 TermとPolynomialの両方がそのインターフェースを実装します。多項式は、コレクション内の各項を単純に反復します。
これはC/C++ではなくLISPに適用されるため、ちょっと脇に置いてありますが、他の人が同様のタスクを探すのに役立つか、C/C++で同様の何かを自分で実装するためのアイデアを得ることができます。 SICPには、LISPの主題に関するいくつかの講義があります。
LISPでは、それは非常に簡単です(そして、強力なパターンマッチングとポリモーフィックタイプを備えた他の関数型言語では)。 Cでは、同じパワーを得るには、列挙型と構造体を多用する必要があります(割り当て/割り当て解除は言うまでもありません)。 1時間以内にocamlで必要なものを確実にコーディングすることができます-タイピング速度が制限要因だと思います。 Cが必要な場合は、実際にCからocamlを呼び出すことができます(またはその逆)。
Theanoを見てください、それは(ニューラルネットの文脈で)象徴的な差別化をサポートしています。プロジェクトはオープンソースですが、彼らがどのようにそれを行っているかを見ることができるはずです。
導関数の1次のみを計算することは、実装するのが非常に簡単です。しかし、それを速くすることは芸術です。を含むクラスが必要です
次に、加算や減算などの演算子と、この操作の基本的でよく知られたルールを実装するsin()のような関数を記述します。
高階導関数を計算するには、切り捨てられたテイラー級数を使用して実行する必要があります。上記のクラスをそれ自体に適用することもできます。値と微分値の型はテンプレート引数である必要があります。しかし、これはデリバティブの計算と保存を複数回行うことを意味します。
切り捨てられたテイラー級数-これに使用できるライブラリは2つあります。