我在用scipy.optimize.minimize(method='COBYLA')
运行一个约束优化。
为了评估成本函数,我需要运行一个相对昂贵的模拟来计算输入变量的数据集,成本函数是一个(计算成本低)属性。然而,我的两个约束也依赖于昂贵的数据。到目前为止,我发现约束优化的唯一方法是使每个约束函数重新计算成本函数已经计算的相同数据集(简化准代码):
def costfun(x):
data = expensive_fun(x)
return(cheap_fun1(data))
def constr1(x):
data = expensive_fun(x)
return(cheap_fun2(data))
def constr2(x):
data = expensive_fun(x)
return(cheap_fun3(data))
constraints = [{'type':'ineq', 'fun':constr1},
{'type':'ineq', 'fun':constr2}]
# initial guess
x0 = np.ones((6,))
opt_result = minimize(costfun, x0, method='COBYLA',
constraints=constraints)
这显然效率不高,因为对于每个x
,expensive_fun(x)
被调用三次。
我可以稍微修改一下,加入一个通用的“evaluate some cost”函数来运行代价高昂的计算,然后对给定的任何标准进行求值,但是,尽管这样可以避免我多次编写“代价高昂”的代码,但它仍然会在优化器的每次迭代中运行三次:
# universal cost function evaluator
def criterion_from_x(x, cfun):
data = expensive_fun(x)
return(cfun(data))
def costfun(data):
return(cheap_fun1(data))
def constr1(data):
return(cheap_fun2(data))
def constr2(data):
return(cheap_fun3(data))
constraints = [{'type':'ineq', 'fun':criterion_from_x, 'args':(constr1,)},
{'type':'ineq', 'fun':criterion_from_x, 'args':(constr2,)}
# initial guess
x0 = np.ones((6,))
opt_result = minimize(criterion_from_x, x0, method='COBYLA',
args=(costfun,), constraints=constraints)
我还没有找到任何方法来设置在每次迭代时使用x
生成data
,然后将data
传递给目标函数和约束函数。
这样的东西存在吗?我注意到callback
的参数,但是那是一个在每一步之后被调用的函数,我需要一些预处理器,在每一步之前被调用,其结果然后可用于成本函数和约束评估。也许有一种方法以某种方式偷偷地?我希望避免编写自己的优化器。
一种更传统的解决方法是评估成本函数中的约束条件(成本函数中包含所需的所有数据),将违反约束条件的惩罚添加到主成本函数中,并在没有显式约束条件的情况下运行优化器,但我以前尝试过这种方法,发现在违反约束条件的情况下,主成本函数可能会变得有些混乱。因此优化器可能会卡在某个违反约束的地方而无法再次找到。
另一种方法是在成本函数中生成某种全局变量,然后编写约束求值来使用该全局变量,但如果涉及到多线程/-处理,或者我为全局变量选择的名称与代码中其他地方使用的名称冲突,这可能会非常危险:“”“定义成本资金(x):全局数据data =昂贵的函数(x)return(cheap_fun1(data))
定义构造1(x):全局数据返回(cheap_fun2(数据))
定义构造2(x):全局数据返回(cheap_fun3(数据))“”“
我知道有些人使用文件I/O的情况下,成本函数涉及运行一个大型模拟,产生一堆输出文件。之后,约束函数可以访问这些文件-但我的问题不是那么大。
我目前使用的是Python v3.9和scipy 1.9.1。
2条答案
按热度按时间42fyovps1#
你可以用与scipy的MemoizeJac相同的方式编写一个装饰器类,它缓存每次调用代价高昂的函数时的返回值:
接下来,昂贵函数在每次迭代中只计算一次,然后,在将所有约束写入一个约束函数后,可以像这样使用它:
fwzugrvs2#
当Joni在写他们的答案时,我发现了另一个,无可否认,这个答案更古怪。我更喜欢他们的答案,但为了完整起见,我也想把这个贴出来。
它来源于https://mdobook.github.io/的材料和杨百翰大学流动实验室的随附视频教程,特别是this video:
诀窍是使用非局部变量来缓存开销函数的最后一次求值:
......然后一切都可以按照问题中的原始代码进行。
我更喜欢Joni基于类的版本的原因:
nonlocal
更清晰这两种方法都允许使用“廉价”约束,即不需要计算昂贵的函数,只需将它们作为单独的函数提供即可。但不确定这是否会对计算时间有很大帮助。我认为这将取决于优化器使用的算法。