2017-11-19 5 views
2

コールバックを使用せずにコスト関数を再実行することなく、scipy.minimizeを使用して反復ごとにコスト関数にアクセスする方法はありますか?scipy.minimize - コスト関数と反復を得る?

options.dispは、これを行うことを意図しているようですが、オプティマイザが終了メッセージを出力するだけです。

io.StringIOと一緒に使用してデータを収集して解析しても問題ありませんが、各繰り返しでコスト関数に効率的にアクセスする方法が見つかりません。

答えて

1

を印刷し、私はそれがsys.stdoutのの "深い" リダイレクトを使用して、STDLIB機能を使用してハックのようなものを考え出しました。 IPythonが.fileno属性を削除するsys.stdoutを乗っ取っているので、これはjupyterでは機能しないことに注意してください。

この方法でtempfile.SpooledTemporaryFileを使用してJupyterを修正することは、この問題を取り除くことができます。知りません。

これはOSレベルのファイル記述子を使用するため、スレッドセーフではないと考えています。

import os 
import sys 
import tempfile 

class forcefully_redirect_stdout(object): 
    ''' Forces stdout to be redirected, for both python code and C/C++/Fortran 
     or other linked libraries. Useful for scraping values from e.g. the 
     disp option for scipy.optimize.minimize. 
    ''' 
    def __init__(self, to=None): 
     ''' Creates a new forcefully_redirect_stdout context manager. 

     Args: 
      to (`None` or `str`): what to redirect to. If type(to) is None, 
       internally uses a tempfile.SpooledTemporaryFile and returns a UTF-8 
       string containing the captured output. If type(to) is str, opens a 
       file at that path and pipes output into it, erasing prior contents. 

     Returns: 
      `str` if type(to) is None, else returns `None`. 

     ''' 

     # initialize where we will redirect to and a file descriptor for python 
     # stdout -- sys.stdout is used by python, while os.fd(1) is used by 
     # C/C++/Fortran/etc 
     self.to = to 
     self.fd = sys.stdout.fileno() 
     if self.to is None: 
      self.to = tempfile.SpooledTemporaryFile(mode='w+b') 
     else: 
      self.to = open(to, 'w+b') 

     self.old_stdout = os.fdopen(os.dup(self.fd), 'w') 
     self.captured = '' 

    def __enter__(self): 
     self._redirect_stdout(to=self.to) 
     return self 

    def __exit__(self, *args): 
     self._redirect_stdout(to=self.old_stdout) 
     self.to.seek(0) 
     self.captured = self.to.read().decode('utf-8') 
     self.to.close() 

    def _redirect_stdout(self, to): 
     sys.stdout.close() # implicit flush() 
     os.dup2(to.fileno(), self.fd) # fd writes to 'to' file 
     sys.stdout = os.fdopen(self.fd, 'w') # Python writes to fd 

if __name__ == '__main__': 
    import re 
    from scipy.optimize import minimize 

    def foo(x): 
     return 1/(x+0.001)**2 + x 

    with forcefully_redirect_stdout() as txt: 
     result = minimize(foo, [100], method='L-BFGS-B', options={'disp': True}) 

    print('this appears before `disp` output') 
    print('here''s the output from disp:') 
    print(txt.captured) 
    lines_with_cost_function_values = \ 
     re.findall(r'At iterate\s*\d\s*f=\s*-*?\d*.\d*D[+-]\d*', txt.captured) 

    fortran_values = [s.split()[-1] for s in lines_with_cost_function_values] 
    # fortran uses "D" to denote double and "raw" exp notation, 
    # fortran value 3.0000000D+02 is equivalent to 
    # python value 3.0000000E+02 with double precision 
    python_vals = [float(s.replace('D', 'E')) for s in fortran_values] 
    print(python_vals) 
2

least_squaresの方法は、パラメータverbose=2で行います。しかし、それは汎用関数最小化関数ではなく、与えられた関数の二乗和を最小にすることを目的としています。例:

minimizeのような方法の場合、そのようなオプションはありません。コールバックを使用してコスト関数を再評価する代わりに、関数自体にいくつかのログを追加することができます。最小化アルゴリズムは近似ヤコビアンおよび/またはヘッセ行列を計算し、周りに見えるように、各反復ステップ4つの同様の関数値が存在する。この例では

def fun(x): 
    c = x[0]**2 - 2*x[0] + x[1]**4 
    cost_values.append(c) 
    return c 

cost_values = [] 
minimize(fun, [3, 2]) 
print(cost_values) 

:例えば、ここでfunグローバル変数cost_valuesに計算された値を追加します。したがって、print(cost_values[::4])は、ステップごとにコスト関数の1つの値を取得する方法になります。

ただし、必ずしも1ステップあたり4つの値であるとは限りません(ディメンションと使用方法によって異なります)。したがって、コールバック関数を使用して各ステップの後にコストを記録する方が良いです。現在のコストはグローバル変数に格納する必要があるため、再計算する必要はありません。

def fun(x): 
    global current_cost 
    current_cost = x[0]**2 - 2*x[0] + x[1]**4 
    return current_cost 

def log_cost(x): 
    cost_values.append(current_cost) 

cost_values = [] 
minimize(fun, [3, 2], callback=log_cost) 
print(cost_values) 

これは

[3.5058199763814986, -0.2358850818406083, -0.56104822688320077, -0.88774448831043995, -0.96018358963745964, -0.98750765702936738, -0.99588975368993771, -0.99867208501468863, -0.99956795994852465, -0.99985981414137615, -0.99995446605426996, -0.99998521591611178, -0.99999519917089297, -0.99999844105574265, -0.99999949379700426, -0.99999983560485239, -0.99999994662329761, -0.99999998266175671] 
+0

これはスマートです! – MaxU

+0

この回答は非常に賢いです!閉鎖せずにそれを行う方法があり、リストから常にn番目の価値を取ることに対処する必要がありますか?複雑なコスト関数では、いくつかの次元に沿ってjac/hessを再計算する必要がないため、「スキップ」長さ自体が反復の関数となることが想像されます。 –

+0

@ 6'whitemale - 私が投稿した答えを見てください。あなたがIPythonで動作する必要がない限り、解決策があります。 –

関連する問題