如果我们从文档中考虑这个例子:
from scipy.optimize import minimize, rosen, rosen_der
x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
result = minimize(rosen, x0, method='Nelder-Mead', tol=1e-6)
print(result)
我们得到以下结果
final_simplex: (array([[ 1.00000002, 1.00000002, 1.00000007, 1.00000015, 1.00000028],
[ 0.99999999, 0.99999996, 0.99999994, 0.99999986, 0.99999971],
[ 1.00000005, 1.00000007, 1.00000017, 1.00000031, 1.00000063],
[ 1.00000004, 1.00000008, 1.00000013, 1.00000025, 1.00000047],
[ 0.99999999, 0.99999996, 0.99999994, 0.99999984, 0.99999963],
[ 1.00000005, 1.00000004, 1.00000003, 1.00000003, 1.00000004]]), array([ 1.94206402e-13, 2.44964782e-13, 3.10422870e-13,
3.37952410e-13, 5.52173609e-13, 7.16586838e-13]))
fun: 1.9420640199868412e-13
message: 'Optimization terminated successfully.'
nfev: 494
nit: 295
status: 0
success: True
x: array([ 1.00000002, 1.00000002, 1.00000007, 1.00000015, 1.00000028])
如您所见,迭代次数为295.我的问题是如何在每次迭代时获取x的值?
答案 0 :(得分:1)
一般方法是使用自定义的callback。
回调:可调用,可选
在每次迭代后调用,作为回调(xk),其中xk是当前参数向量。
现在使用它就像以下一样简单:
history = []
def callback(x):
fobj = rosen(x)
history.append(fobj)
result = minimize(rosen, x0, method='Nelder-Mead', tol=1e-6, callback=callback)
print(history)
但正如上面评论中这两个链接的第一个评论中所提到的(好的链接!),这种方法正在使用额外的功能评估! (这个来源显然是基于经典的软件开发设计原则问题:使用多少抽象?)
根据您的任务,功能评估可能代价高昂(如果没有,则忽略以下内容)!
在这种情况下,您可以使用memoization来缓存以前计算的值,而不是一直重新计算它们。
现在至少你的优化器'Nelder-Mead'是一个无梯度优化器,你需要缓存多个值才能与无回调解决方案相媲美(因为某种程度上需要较旧的值)。这可能取决于所选择的最小化方法(以及一些内部方法)。
当使用数值微分时,scipy对渐变使用了一些记忆,因为这非常昂贵。但是这段代码只缓存了最后一个值,这对你的情况不起作用(NM不同)。
因此我们可以构建自己的memoization-cache,仅基于函数(没有使用渐变):
import numpy as np
from scipy.optimize import minimize, rosen, rosen_der
""" Memoization of function-values -> don't recompute """
class Memoize(object):
""" Modified from https://github.com/scipy/scipy/blob/c96c5294ca73586cadd6f4c10f26b6be5ed35045/scipy/optimize/optimize.py#L53 """
def __init__(self, fun, n, cache_size=8):
self.n = n
self.c_n = cache_size
self.fun = fun
self.fobj = np.full((self.c_n), np.inf)
self.x = np.full((self.c_n, self.n), np.inf)
self.pos = 0 # circular-like buffer; next to replace = oldest
def __call__(self, x, *args):
# Check cache
cands = np.all(x == self.x, axis=1).nonzero()[0]
if cands.size:
return self.fobj[cands]
else:
fobj = self.fun(x)
self.fobj[self.pos] = fobj
self.x[self.pos] = x
self.pos = (self.pos + 1) % self.c_n
return fobj
""" rosen-wrapper to check function-evaluations """
nfev = 0
def rosen_wrapper(x):
global nfev
nfev += 1
return rosen(x)
x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
mem_rosen = Memoize(rosen_wrapper, len(x0))
""" Callback storing history """
history = []
def callback(x):
fobj = mem_rosen(x)
history.append(fobj)
result = minimize(mem_rosen, x0, method='Nelder-Mead', tol=1e-6, callback=callback)
print(result)
print('iteration fun(x)')
print(history[::50]) # every 50th
print(nfev)
使用回调和缓存大小8调用:
final_simplex: (array([[ 1.00000002, 1.00000002, 1.00000007, 1.00000015, 1.00000028],
[ 0.99999999, 0.99999996, 0.99999994, 0.99999986, 0.99999971],
[ 1.00000005, 1.00000007, 1.00000017, 1.00000031, 1.00000063],
[ 1.00000004, 1.00000008, 1.00000013, 1.00000025, 1.00000047],
[ 0.99999999, 0.99999996, 0.99999994, 0.99999984, 0.99999963],
[ 1.00000005, 1.00000004, 1.00000003, 1.00000003, 1.00000004]]), array([ 1.94206402e-13, 2.44964782e-13, 3.10422870e-13,
3.37952410e-13, 5.52173609e-13, 7.16586838e-13]))
fun: 1.9420640199868412e-13
message: 'Optimization terminated successfully.'
nfev: 494
nit: 295
status: 0
success: True
x: array([ 1.00000002, 1.00000002, 1.00000007, 1.00000015, 1.00000028])
iteration fun(x)
[array([ 516.14978061]), array([ 1.16866125]), array([ 0.00135733]), array([ 6.48182410e-05]), array([ 1.03326372e-06]), array([ 7.12094933e-10])]
504
缓存24(不推荐;仅用于演示目的):
nfev: 494
nit: 295
status: 0
success: True
...
494
现在显然需要权衡,因为我们存储了一个大小的缓存:
C_SIZE * N
#x-vectors C_SIZE * 1
#fun-values 我们在每个调用中计算C_SIZE * N
上的线性运算。
如果这样做有所回报,以及如何选择缓存大小,则取决于您的功能,最小化器以及可能还有您的参数。
请记住,所选择的方法基于这样的想法:基于numpy的计算的线性数量可能比使用基于纯python的 logn (或类似)算法更快!
(没有广泛检查记忆码!)