私は制御システムの研究を行っており、Matlabが私の主な作業ツールです。 MatLabの可能性の1つは、数値の最適化です。 入力として変数パラメーターのベクトルを取り、最小化された基準の値を返す関数を最適化(最小化)できます。 当然、最適化のプロセスでは、目的関数が何度も呼び出され、その速度が不可欠です。 Matlabには、コードの可読性とメンテナンスの容易さを維持しながら、多くの場合パフォーマンスを大幅に改善できる優れたソフトウェアツールがあります。 タスクの例を挙げて、匿名関数とネストされた関数が何であるかを示し、次にこれら2つのツールを組み合わせてパフォーマンスを大幅に向上させる方法を示します。
問題の記述と最適化
最適化の例について長い間考えていましたが、運動学的に冗長なマニピュレーターの最適な軌道を見つけ、実験データを近似し、パラメーターを特定する、現実的なものを選択しようとしました。 しかし、このすべてがどういうわけか本質から遠ざかったので、私は非実用的ではあるが、実例となる例にこだわることにしました。 したがって、パラメーターaとbが与えられます。 範囲[0; 1]でxを見つけて、基準を最大化する必要があります。
どこで -明示的にxに依存しないが、基準を計算するために必要な関数。 シードをzに設定したときに取得した最初の100万個の擬似乱数の合計のモジュールとします。 基準の値を計算するmatlab関数を作成します。
function J = GetJ(a,b,x) randn('seed',a); phi_a=abs(sum(randn(1,1e6))); randn('seed',b); phi_b=abs(sum(randn(1,1e6))); J=-(phi_a*x^2+phi_b*sqrt(1-x^2)); end
最大値を見つけたいので、最適化は最小値を探しているため、マイナス記号で値を返すことに注意してください。
ここで、 aとbの特定の値に対して最適化するには、匿名関数を作成する必要があります。 これは次のように行われます。
a=121233; b=31235; fhnd_GetJ = @(x) GetJ(a,b,x);
これで、変数
fhnd_GetJ
は、1つのパラメーターを取る匿名関数へのハンドルが含まれ、この匿名関数の作成時に指定されたaおよびbの値に対して
GetJ(a,b, )
を計算できます。 最小化に直接進むことができます。 100万分の1の精度でxの最適値を見つけたいとしましょう:
opt=optimset('TolX',1e-6); optimal_x=fminbnd(fhnd_GetJ,0,1,opt);
関数
fminbnd(fun,x_min,x_max)
は、区間[ x_min ;上のスカラー引数のスカラー関数を最小化します。 x_max ]。 ここで
fun
は、最適化される関数のハンドルです。 最適化を実行すると、
GetJ
関数
GetJ
12回呼び出されます。これは、オプションの
'Display'
パラメーターを
'iter'
設定することで確認できます。すべての反復が表示されます。 私のコンピューターでは、最適化に平均10ミリ秒かかります。
パフォーマンスの向上
これを最適化する方法を見てみましょう。 明らかに、
GetJ
関数を呼び出すたびに、値
phi_a
および
phi_b
を完全に考慮します。これらは、 xの変化に応じて変化せず、与えられたaおよびbの値のみに依存するためです。 ここでどのように保存できますか? 私が遭遇する最も一般的なオプションは、目的関数から予備計算を行うことです。 そのような機能を作る
function J = GetJ2(phi_a,phi_b,x) J=-(phi_a*x^2+phi_b*sqrt(1-x^2)); end
コードは次のとおりです。
randn('seed',a); phi_a=abs(sum(randn(1,1e6))); randn('seed',b); phi_b=abs(sum(randn(1,1e6))); fhnd_GetJ2 = @(x) GetJ2(phi_a,phi_b,x); optimal_x=fminbnd(fhnd_GetJ2,0,1,opt);
もちろん、これははるかに速くカウントします。 最適化は、平均で0.8ミリ秒で行われます。 しかし、この代償はコードの劣化です。 関数の整合性が
phi_a
ています
phi_a
と
phi_b
計算には独立した値がなく、計算とは
phi_a
、 Jは必要ありません。 最適化のためだけでなく、J(a、b、x)を再度考慮する必要がある場合は、
GetJ
呼び出す代わりに、
phi_a
と
phi_b
計算をドラッグするか、最適化のための関数を個別に作成する必要があります。計算用に個別に。 まあ、あまり美しくない。
matlabがこの状況を回避できるのは良いことです。 これを行うには、
GetJ
関数内にネストされた関数を作成しましょう。
function J = GetJ3(a,b,x) randn('seed',a); phi_a=abs(sum(randn(1,1e6))); randn('seed',b); phi_b=abs(sum(randn(1,1e6))); J=nf_GetJ(x); function out_val = nf_GetJ(x) out_val=-(phi_a*x^2+phi_b*sqrt(1-x^2)); end end
ネストされた関数
nf_GetJ
は、親関数のスコープ内のすべての変数を参照し、原則として、それが何をどのように行うかが明確です。 これまでのところ、パフォーマンスの向上はありませんでした-最適化のための10ミリ秒すべて。
そして今、最も興味深いのは、Matlabが匿名の入れ子関数で動作できることです。
GetJ
関数は、特定の値の代わりに、独自のネストされた関数へのハンドルを返すことができます。 そして、このハンドルで関数が呼び出されると、親関数は実行されませんが、計算されたパラメーターを持つスコープは残ります! 私たちは書きます:
function fhnd_J = GetJ4(a,b,x) randn('seed',a); phi_a=abs(sum(randn(1,1e6))); randn('seed',b); phi_b=abs(sum(randn(1,1e6))); fhnd_J=@(x) nf_GetJ(x); function out_val = nf_GetJ(x) out_val=-(phi_a*x^2+phi_b*sqrt(1-x^2)); end end
これで、関数のハンドルが返された変数
fhnd_J
書き込まれます。これにより、変数
phi_a
および
phi_b
を計算せずに、ハンドルの作成時に計算された値を使用して、使用されたパラメーターaおよびbのJの値を計算できます。 さらに、最適化は次のようになります。
fhnd_GetJ4=GetJ4(a,b,x); optimal_x=fminbnd(fhnd_GetJ4,0,1,opt);
そして、そのような最適化は0.8ミリ秒で実行されます。
結果
GetJ2
例で計算を明示的に実行したときと同じ速度になりましたが、関数の整合性とその使用の利便性は保持されました。
将来、最適化と計算の両方にこの関数を使用する場合、オプションのフラグパラメーターを1つ追加できます。 設定されていない場合、関数は値を返します。 指定した場合、ネストされた関数のハンドルが返されます。