我想使用scipy.optimize.minimize
在优化例程中执行一些测试,特别是在多次测试中绘制每次迭代的收敛性(或相当客观的函数)图。
假设我有以下线性约束二次优化问题:
最小化:x_i Q_ij x_j + a | x_i |
符合:sum(x_i)= 1
我可以将其编码为:
def _fun(x, Q, a):
c = np.einsum('i,ij,j->', x, Q, x)
p = np.sum(a * np.abs(x))
return c + p
def _constr(x):
return np.sum(x) - 1
我将在scipy
中以如下方式实现优化:
x_0 = # some initial vector
x_soln = scipy.optimise.minimize(_fun, x_0, args=(Q, a), method='SLSQP',
constraints={'type': 'eq', 'fun': _constr})
我看到有一个callback
自变量,但每次迭代仅接受参数值的一个自变量。在更深奥的情况下,我可能需要将其他参数提供给回调函数时,该如何利用呢?
答案 0 :(得分:1)
我解决此问题的方法是使用每次从回调函数中引用的通用回调缓存对象。假设您要进行20次测试,并在同一图表中的每次迭代后绘制目标函数。您将需要一个外部循环来运行20个测试,但我们稍后会创建。
首先让我们创建一个类,该类将为我们存储所有迭代目标函数值,以及一些额外的细节:
class OpObj(object):
def __init__(self, Q, a):
self.Q, self.a = Q, a
rv = np.random.rand()
self.x_0 = np.array([rv, (1-rv)/2, (1-rv)/2])
self.f = np.full(shape=(500,), fill_value=np.NaN)
self.count = 0
def _fun(self, x):
return _fun(x, self.Q, self.a)
还可以添加一个回调函数来处理该类obj。不必担心它现在有一个以上的论点,因为我们稍后将解决此问题。只要确保第一个参数是解决方案变量即可。
def cb(xk, obj=None):
obj.f[obj.count] = obj._fun(xk)
obj.count += 1
所有这些操作就是使用对象的函数和值更新自身,并计算每次迭代的次数。每次迭代后都会调用此函数。
将所有这些放在一起,我们需要做的还有两件事:1)一些matplotlibing来进行绘制,并将回调固定为只有一个参数。我们可以使用装饰器来做到这一点,这正是functools部分所做的。它返回的函数的参数要少于原始函数。因此,最终代码如下所示:
import matplotlib.pyplot as plt
import scipy.optimize as op
import numpy as np
from functools import partial
Q = np.array([[1.0, 0.75, 0.45], [0.75, 1.0, 0.60], [0.45, 0.60, 1.0]])
a = 1.0
def _fun(x, Q, a):
c = np.einsum('i,ij,j->', x, Q, x)
p = np.sum(a * np.abs(x))
return c + p
def _constr(x):
return np.sum(x) - 1
class OpObj(object):
def __init__(self, Q, a):
self.Q, self.a = Q, a
rv = np.random.rand()
self.x_0 = np.array([rv, (1-rv)/2, (1-rv)/2])
self.f = np.full(shape=(500,), fill_value=np.NaN)
self.count = 0
def _fun(self, x):
return _fun(x, self.Q, self.a)
def cb(xk, obj=None):
obj.f[obj.count] = obj._fun(xk)
obj.count += 1
fig, ax = plt.subplots(1,1)
x = np.linspace(1,500,500)
for test in range(20):
op_obj = OpObj(Q, a)
x_soln = op.minimize(_fun, op_obj.x_0, args=(Q, a), method='SLSQP',
constraints={'type': 'eq', 'fun': _constr},
callback=partial(cb, obj=op_obj))
ax.plot(x, op_obj.f)
ax.set_ylim((1.71,1.76))
plt.show()