scipy.minimize——获取成本函数与迭代?

问题描述 投票:0回答:2

是否有任何方法可以使用

scipy.minimize
在每次迭代的基础上访问成本函数,而不使用回调并重新执行成本函数?

options.disp
似乎是为了这样做,但只会导致优化器打印终止消息。

将其打印到标准输出并使用

contextlib.redirect_stdout
io.StringIO
来收集它并在之后解析数据就可以了,但我找不到一种方法来有效地访问每次迭代的成本函数。

python optimization scipy
2个回答
7
投票

方法

least_squares
使用参数
verbose=2
来实现这一点。然而,它不是通用的最小化器,其目的是最小化给定函数的平方和。示例:

least_squares(lambda x: [x[0]*x[1]-6, x[0]+x[1]-5], [0, 0], verbose=2)

对于其他方法,如

minimize
,没有这样的选项。您可能希望向函数本身添加一些日志记录,而不是使用回调并重新评估成本函数。例如,这里
fun
将计算值附加到全局变量
cost_values
:

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)

在此示例中,每个迭代步骤有 4 个相似的函数值,因为最小化算法环顾四周,计算近似的雅可比矩阵和/或海森矩阵。因此,

print(cost_values[::4])
将是获得每一步成本函数一个值的方法。

但并不总是每步 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]

2
投票

我想出了一种使用 stdlib 功能的 hack,它使用 sys.stdout 的“深度”重定向。 请注意,这不适用于 jupyter,因为 IPython 劫持了 sys.stdout,这会删除 .fileno 属性。

可以通过这种方式使用 tempfile.SpooledTemporaryFile 修补 Jupyter,从而消除此问题。 我不知道。

我相信因为这使用操作系统级文件描述符,所以它也不是线程安全的。

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)
© www.soinside.com 2019 - 2024. All rights reserved.