同じ関数における目的と非線形制約
この例では、ソルバーベースのアプローチを使用して、目的関数と制約の両方に対して値を計算する場合に関数を 2 回呼び出すことを回避する方法を説明します。問題ベースのアプローチを使用して関数を 2 回呼び出すことを回避する方法については、共通の関数を持つ目的関数と制約の逐次評価または並列評価、問題ベースを参照してください。
このような関数は一般にシミュレーションで使用されます。fmincon
などのソルバーでは、目的関数と非線形制約関数が別々に評価されます。両方の結果に対して同じ計算を使用する場合は、この評価は無駄なものです。
時間を無駄にしないためには、時間のかかる計算の値を保持することによって、必要な場合にのみ入れ子関数を使用して目的関数と制約関数を評価するようにします。このアプローチでは、グローバル変数を使用しなくても、中間結果を保持して目的関数と制約関数間で共有できます。
メモ
ga
(Global Optimization Toolbox) が非線形制約関数を呼び出す方法のため、この例の手法では通常、目的関数または制約関数の呼び出し回数が少なくなりません。
手順 1: 目的と制約を計算する関数を記述します。
たとえば、関数 computeall
は計算量が多く (時間がかかり)、目的関数と非線形制約関数の両方によって呼び出されるとします。また、fmincon
をオプティマイザーとして使用するとします。
Rosenbrock 関数 f1
の一部を計算し、原点を中心とする半径 1 の円板内に解を維持する非線形制約 c1
を含める関数を記述します。Rosenbrock 関数は次のようになります。
これは、(1,1) で一意的な最小値 0 をもちます。詳細については、[最適化] ライブ エディター タスクまたはソルバーを使用した制約付き非線形問題を参照してください。
この例には非線形等式制約が含まれていないため、ceq1 = []
となります。時間のかかる計算をシミュレーションするためにステートメント pause(1)
を追加します。
function [f1,c1,ceq1] = computeall(x) ceq1 = []; c1 = x(1)^2 + x(2)^2 - 1; f1 = 100*(x(2) - x(1)^2)^2 + (1-x(1))^2; pause(1) % Simulate expensive computation end
MATLAB® パス上に computeall.m
をファイルとして保存します。
手順 2: 最新の値を保持する入れ子関数に関数を組み込みます。
次のような目的関数について考えます。
y = 100(x2 – x12)2 + (1 – x1)2
+ 20*(x3 – x42)2 + 5*(1 – x4)2.
computeall
は目的関数の最初の部分を返します。次のようにして、入れ子関数に computeall
の呼び出しを組み込みます。
function [x,f,eflag,outpt] = runobjconstr(x0,opts) if nargin == 1 % No options supplied opts = []; end xLast = []; % Last place computeall was called myf = []; % Use for objective at xLast myc = []; % Use for nonlinear inequality constraint myceq = []; % Use for nonlinear equality constraint fun = @objfun; % The objective function, nested below cfun = @constr; % The constraint function, nested below % Call fmincon [x,f,eflag,outpt] = fmincon(fun,x0,[],[],[],[],[],[],cfun,opts); function y = objfun(x) if ~isequal(x,xLast) % Check if computation is necessary [myf,myc,myceq] = computeall(x); xLast = x; end % Now compute objective function y = myf + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2; end function [c,ceq] = constr(x) if ~isequal(x,xLast) % Check if computation is necessary [myf,myc,myceq] = computeall(x); xLast = x; end % Now compute constraint function c = myc; % In this case, the computation is trivial ceq = myceq; end end
MATLAB パス上にこの入れ子関数を runobjconstr.m
という名前のファイルとして保存します。
手順 3: 入れ子関数を使用した場合の実行時間を決定します。
tic
と toc
で呼び出しの時間を測定しながら、関数を実行します。
opts = optimoptions(@fmincon,'Algorithm','interior-point','Display','off'); x0 = [-1,1,1,2]; tic [x,fval,exitflag,output] = runobjconstr(x0,opts); toc
Elapsed time is 259.364090 seconds.
手順 4: 入れ子関数を使用しない場合の実行時間を決定します。
入れ子関数を使用する場合と使用しない場合で、ソルバーの実行時間を比較します。入れ子関数なしの実行では、次のように myrosen2.m
を目的関数ファイルとして保存し、constr.m
を制約として保存します。
function y = myrosen2(x) f1 = computeall(x); % Get first part of objective y = f1 + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2; end function [c,ceq] = constr(x) [~,c,ceq] = computeall(x); end
tic
と toc
で呼び出しの時間を測定しながら、fmincon
を実行します。
tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
[],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 518.364770 seconds.
目的と制約を別々に評価しなければならないため、ソルバーの実行時間はこの場合 2 倍になります。
手順 5: 並列計算による計算時間の短縮
Parallel Computing Toolbox™ のライセンスをお持ちの場合は、UseParallel
オプションを true
に設定すると、計算時間をさらに短縮できます。
parpool
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 6). ans = ProcessPool with properties: Connected: true NumWorkers: 6 Cluster: local AttachedFiles: {} AutoAddClientPath: true IdleTimeout: 30 minutes (30 minutes remaining) SpmdEnabled: true
opts = optimoptions(opts,'UseParallel',true);
tic
[x,fval,exitflag,output] = runobjconstr(x0,opts);
toc
Elapsed time is 121.151203 seconds.
この場合は、並列計算を有効にすると、入れ子関数を使用した逐次的実行に比べて、計算時間が半分になります。
入れ子関数を使用する場合と使用しない場合で、並列計算の実行時間を比較します。
tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
[],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 235.914597 seconds.
この例では、入れ子関数を使用しないで並列計算を行った場合と、入れ子関数を使用して並列計算を行わなかった場合の実行時間はほぼ同じになります。入れ子関数と並列計算の両方を使用すると、いずれか一方を使用した場合と比べて実行時間は半分になります。