【问题标题】:Passing a dictonary to `scipy.optimize.least_squares`将字典传递给`scipy.optimize.least_squares`
【发布时间】:2022-01-11 21:27:23
【问题描述】:

我有几个在外部库中定义的函数。我无法更改这些函数的参数或内容。以下面的函数为例(虽然原来的要复杂得多):

def func1(info: dict) -> float:
    return 1 - (1.5 * info["b"] - info["a"])

def func2(info: dict) -> float:
    return 1 - (np.exp(info["c"]) - info["a"])

我有一个初步的猜测,我正在尝试应用scipy.optimize.least_squares 来找到最佳值以最小化func1func2(不是同时),即目标是这样的

import scipy

def func1(info: dict) -> float:
    return 1 - (1.5 * info["b"] - info["a"])

def func2(info: dict) -> float:
    return 1 - (np.exp(info["c"]) - info["a"])

initial_dict = {"a" : 5, "b" : 7}
result = scipy.optimize.least_squares(func1, initial_dict)
initial_dict["c"] = 3
result2 = scipy.optimize.least_squares(func1, initial_dict)

问题是least_squares 只接受floats,而不接受dicts。我认为可以将字典的值转换为列表并编写一个“包装器”函数将列表转换回字典,即

def func1_wrapped(lst: list[float]) -> float:
    a, b, c = lst
    tmp_dict = {"a" : a, "b": b, "c": c}
    return func1(tmp_dict)

result1 = scipy.optimize.least_squares(func1_wrapped,[5, 7, 3])

这样的事情合理吗?有没有更好、更有效的方法?

【问题讨论】:

  • “这样的事情合理吗?” 是的,你需要这样的包装器。
  • Lmfit 使用类似的方法。你应该看看它

标签: python dictionary scipy least-squares


【解决方案1】:

一种可能的包装方式是

import scipy.optimize
import numpy as np

def dict_least_squares(fn, dict0, *args, **kwargs):
    keys = list(dict0.keys());
    result = scipy.optimize.least_squares(
        lambda x: fn({k:v for k,v in zip(keys, x)}), # wrap the argument in a dict
        [dict0[k] for k in keys], # unwrap the initial dictionary
        *args, # pass position arguments
        **kwargs # pass named arguments
    )
    # wrap the solution in a dictionary
    try:
        result.x = {k:v for k,v in zip(keys, result.x)}
    except:
        pass;
    return result;

这通过转发任意位置参数*args 或命名参数**kwargs 来维护原始最小二乘函数的接口。

使用示例

def func1(info: dict) -> float:
    return 1 - (1.5 * info["b"] - info["a"])
initial_dict = {"a" : 5, "b" : 7}
dict_least_squares(func1, initial_dict)

给予

 active_mask: array([0., 0.])
        cost: 1.2378255801353088e-15
         fun: array([4.97559158e-08])
        grad: array([ 4.97559158e-08, -7.46338734e-08])
         jac: array([[ 1.        , -1.49999999]])
     message: '`xtol` termination condition is satisfied.'
        nfev: 37
        njev: 16
  optimality: 7.463387344664022e-08
      status: 3
     success: True
           x: {'a': 6.384615399632931, 'b': 4.92307689991801}

然后

def func2(info: dict) -> float:
    return 1 - (np.exp(info["c"]) - info["a"])
initial_dict["c"] = 3
dict_least_squares(func2, initial_dict)

给予

 active_mask: array([0., 0., 0.])
        cost: 4.3463374554994224e-17
         fun: array([-9.32345157e-09])
        grad: array([-9.32345157e-09,  0.00000000e+00,  2.12348517e-11])
         jac: array([[ 1.        ,  0.        , -0.00227757]])
     message: '`gtol` termination condition is satisfied.'
        nfev: 41
        njev: 20
  optimality: 9.323451566345398e-09
      status: 1
     success: True
           x: {'a': -0.9977224357504928, 'b': 7.0, 'c': -6.0846446250890684}

【讨论】:

  • +1。非常感谢您的回答。这似乎是一种有趣的方法,但我想知道这是否可以进一步扩展。如果我们假设我要传递给least_squaresdict 有这种形式a = {"a" : [{"aa" : 1}], "b": [{"aa":1}] },可以调整方法吗?还是这太复杂了,需要在某个地方“推出”?
  • 可以,在这种情况下,键必须递归构建,字典也必须递归构建。
猜你喜欢
  • 2012-12-10
  • 1970-01-01
  • 2010-09-29
  • 2019-04-02
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多