Ceres の紹介と事例 (8) デリバティブについて (解析的デリバティブ)

次の曲線 ( Rat43 )のフィッティング問題を考えてみましょう
: y = b 1 ( 1 + eb 2 − b 3 x ) 1 / b 4 y = \frac{b_1}{(1+e^{b_2-b_3x})^ { 1/b_4}}y=( 1+eb2b3×1/ b4b1
つまり、あるデータ{ xi , yi } , ∀ i = 1 , . . . , n \{x_i, y_i\},\ \forall i=1,... ,n が与えられたとします。{ ×私はy私は}  ∀i _=1 ... n 、データb_1、b_2、b_3、b_4 に最もよく適合するパラメーター b 1b1b2b3b4

私たちが直面している問題は、b 1 、 b 2 、 b 3 、 b 4 b_1、 b_2、 b_3、 b_4 を解くことです。b1b2b3b4 使下列表达式的取值最小:
E ( b 1 , b 2 , b 3 , b 4 ) = ∑ i f 2 ( b 1 , b 2 , b 3 , b 4 ; x i , y i ) = ∑ i ( b 1 ( 1 + e b 2 − b 3 x i ) 1 / b 4 − y i ) 2 \begin{split} E(b_1, b_2, b_3, b_4) &= \sum_i f^2(b_1, b_2, b_3, b_4 ; x_i, y_i)\\ &= \sum_i \left(\frac{b_1}{(1+e^{b_2-b_3x_i})^{1/b_4}} - y_i\right)^2\\ \end{split} E ( b1b2b3b4)=f2 (b1b2b3b4;バツ私はy私は)=(( 1+eb2b3バツ私は)1/ b4b1y私は)2

最良の適合の概念は、適合の品質を測定するための目的関数の選択に依存し、結果として、観測値を生成した根底にあるノイズの多いプロセスに依存します。ノイズがガウスの場合、差の二乗和を最小限に抑えるのが正しい方法です。この場合、パラメータの最適値は最尤推定値です。

Ceres ソルバーを使用してこの問題を解決するには、x と y が与えられた場合に b1、b2、b3、b4 に関する残差 f とその導関数を計算する CostFunction を定義する必要があります。高度な数学における微積分の知識によれば、f の一連の導関数を計算できます。
D 1 f ( b 1 , b 2 , b 3 , b 4 ; x , y ) = 1 ( 1 + eb 2 − b 3 x ) 1 / b 4 D 2 f ( b 1 , b 2 , b 3 , b 4 ; x , y ) = − b 1 eb 2 − b 3 xb 4 ( 1 + eb 2 − b 3 x ) 1 / b 4 + 1 D 3 f ( b 1 , b 2 , b 3 , b 4 ; x , y ) = b 1 xeb 2 − b 3 xb 4 ( 1 + eb 2 − b 3 x ) 1 / b 4 + 1 D 4 f ( b 1 , b 2 , b 3 , b 4 ; x , y ) = b 1 log ⁡ ( 1 + eb 2 − b 3 x ) b 4 2 ( 1 + eb 2 − b 3 x ) 1 / b 4 \begin{split} D_1 f(b_1, b_2, b_3, b_4; x,y ) &= \frac{1}{(1+e^{b_2-b_3x})^{1/b_4}}\\ D_2 f(b_1, b_2, b_3, b_4; x,y) &= \frac{- b_1e^{b_2-b_3x}}{b_4(1+e^{b_2-b_3x})^{1/b_4 + 1}} \\ D_3 f(b_1, b_2, b_3, b_4; x,y) &= \ frac{b_1xe^{b_2-b_3x}}{b_4(1+e^{b_2-b_3x})^{1/b_4 + 1}} \\ D_4 f(b_1, b_2, b_3, b_4; x,y) & = \frac{b_1 \log\left(1+e^{b_2-b_3x}\right) }{b_4^2(1+e^{b_2-b_3x})^{1/b_4}} \end{split}D1f ( b1b2b3b4;× y )D2f ( b1b2b3b4;× y )D3f ( b1b2b3b4;× y )D4f ( b1b2b3b4;× y )=( 1+eb2b3×1/ b41=b4( 1+eb2b3×1/ b4+1 _b1eb2b3×=b4( 1+eb2b3×1/ b4+1 _b1× eb2b3×=b42( 1+eb2b3×1/ b4b1ログ_( 1+eb2b3×

これらの手動で計算された導関数から、CostFunction を実装できるようになりました。

class Rat43Analytic : public SizedCostFunction<1,4> {
    
    
   public:
     Rat43Analytic(const double x, const double y) : x_(x), y_(y) {
    
    }
     virtual ~Rat43Analytic() {
    
    }
     virtual bool Evaluate(double const* const* parameters,
                           double* residuals,
                           double** jacobians) const {
    
    
       const double b1 = parameters[0][0];
       const double b2 = parameters[0][1];
       const double b3 = parameters[0][2];
       const double b4 = parameters[0][3];

       residuals[0] = b1 *  pow(1 + exp(b2 -  b3 * x_), -1.0 / b4) - y_;

       if (!jacobians) return true;
       double* jacobian = jacobians[0];
       if (!jacobian) return true;

       jacobian[0] = pow(1 + exp(b2 - b3 * x_), -1.0 / b4);
       jacobian[1] = -b1 * exp(b2 - b3 * x_) *
                     pow(1 + exp(b2 - b3 * x_), -1.0 / b4 - 1) / b4;
       jacobian[2] = x_ * b1 * exp(b2 - b3 * x_) *
                     pow(1 + exp(b2 - b3 * x_), -1.0 / b4 - 1) / b4;
       jacobian[3] = b1 * log(1 + exp(b2 - b3 * x_)) *
                     pow(1 + exp(b2 - b3 * x_), -1.0 / b4) / (b4 * b4);
       return true;
     }

    private:
     const double x_;
     const double y_;
 };

これは、読みにくく、冗長性が多い、退屈なコードです。したがって、実際には、効率を向上させるためにいくつかの部分式をキャッシュします。その結果、次のようになります。

class Rat43AnalyticOptimized : public SizedCostFunction<1,4> {
    
    
   public:
     Rat43AnalyticOptimized(const double x, const double y) : x_(x), y_(y) {
    
    }
     virtual ~Rat43AnalyticOptimized() {
    
    }
     virtual bool Evaluate(double const* const* parameters,
                           double* residuals,
                           double** jacobians) const {
    
    
       const double b1 = parameters[0][0];
       const double b2 = parameters[0][1];
       const double b3 = parameters[0][2];
       const double b4 = parameters[0][3];

       const double t1 = exp(b2 -  b3 * x_);
       const double t2 = 1 + t1;
       const double t3 = pow(t2, -1.0 / b4);
       residuals[0] = b1 * t3 - y_;

       if (!jacobians) return true;
       double* jacobian = jacobians[0];
       if (!jacobian) return true;

       const double t4 = pow(t2, -1.0 / b4 - 1);
       jacobian[0] = t3;
       jacobian[1] = -b1 * t1 * t4 / b4;
       jacobian[2] = -x_ * jacobian[1];
       jacobian[3] = b1 * log(t2) * t3 / (b4 * b4);
       return true;
     }

   private:
     const double x_;
     const double y_;
 };

これら 2 つの実装のパフォーマンスはどのように異なりますか?

コスト関数

時間 (ns)

Rat43分析

255

Rat43Analytic最適化済み

92

Rat43AnalyticOptimizedの2.8 倍高速ですRat43Analyticこの実行時間の違いは珍しいことではありません。分析的に計算された導関数から最高のパフォーマンスを得るには、多くの場合、共通の部分式を考慮してコードを最適化する必要があります。

分析的導関数はどのような場合に使用する必要がありますか?

  • 1. 式が単純である (例: ほとんどが線形)

  • 2. Maple、Mathematica、symy などのコンピューター代数システムを使用して、目的関数を記号的に微分し、それらを計算するための C++ を生成できます。

  • 3. 式には、自動微分よりも優れたパフォーマンスを達成できる代数構造がいくつかあります。
    そうは言っても、逆数の計算以外で最大のパフォーマンスを得るのはかなりの労力がかかります。この道を進む前に、ヤコビアンの計算コストが全体の解法時間の一部であると見積もることが役立ちます。ライブ アムダールの法則は次のとおりであることを思い出してください。あなたの友達。

  • 4. 導関数を計算する他の方法はありません。たとえば、多項式の根の導関数を計算したい場合:
    a 3 ( x , y ) z 3 + a 2 ( x , y ) z 2 + a 1 ( x , y ) z + a 0 ( x , y ) = 0 a_3(x,y)z^3 + a_2(x,y)z^2 + a_1(x,y)z + a_0(x,y) = 0ある3( x ,y ) z3+ある2( x ,y ) z2+ある1( x ,y ) z+ある0( x ,y )=0

    x、y については、逆関数定理を使用する必要があります。

  • 5. あなたは連鎖の法則が好きで、代数計算を手作業で行います。

おすすめ

転載: blog.csdn.net/wanggao_1990/article/details/129712740