使用不同的参数生成python函数

时间:2013-09-11 08:16:08

标签: python function numpy scipy

背景

我有一个函数,它接受一些参数并返回一个我想要最小化的错误度量(使用scipy.optimize.leastsq,但现在不在这一点上。)

作为一个玩具示例,让我们假设我的优化功能采用四个参数a,b,c,d:

def f(a,b,c,d):
    err = a*b - c*d
    return err

然后,优化器需要一个带有签名func(x, *args)的函数,其中x是参数向量。

也就是说,我的功能目前写成:

def f_opt(x, *args):
    a,b,c,d = x
    err = a*b - c*d
    return err

但是,现在我想做一些实验,我在修改一些参数的同时在优化步骤中保留一些参数。

我当然可以这样做:

def f_ad_free(x, b, c):
    a, d = x
    return f(a,b,c,d)

但这很麻烦,因为我有超过10个参数,这意味着不同数量的自由与固定参数的组合可能会非常大。

使用dicts的第一种方法

我的一个解决方案是用关键字args而不是位置args编写我的内部函数f,然后像这样包装解决方案:

def generate(func, all_param, fixed_param):
    param_dict = {k : None for k in all_param}
    free_param = [param for param in all_param if param not in fixed_param]
    def wrapped(x, *args):
        param_dict.update({k : v for k, v in zip(fixed_param, args)})
        param_dict.update({k : v for k, v in zip(free_param, x)})
        return func(**param_dict)
    return wrapped

创建一个修复'b'和'c'的函数然后变成以下内容:

all_params = ['a','b','c']
f_bc_fixed = generate(f_inner, all_params, ['b', 'c'])
a = 1
b = 2
c = 3
d = 4
f_bc_fixed((a,d), b, c)

提问时间!

我的问题是,是否有人能想出更简洁的方法来解决这个问题。由于最终函数将在优化步骤中运行,因此我不能为每个函数调用接受太多开销。 生成优化函数所花费的时间是无关紧要的。

2 个答案:

答案 0 :(得分:1)

您的generate功能与functools.partial基本相同,我将在此处使用。

答案 1 :(得分:1)

我可以想到几种避免使用闭包的方法,但是在做了一些测试后,我不确定其中任何一种都会更快。一种方法可能是跳过包装器并编写一个接受

的函数
  1. 向量
  2. 免费名称列表
  3. 将名称映射到值的字典。
  4. 然后做一些非常像你上面做的事情,但是在函数本身:

    def f(free_vals, free_names, params):
        params.update(zip(free_names, free_vals))
        err = params['a'] * params['b'] - params['c'] * params['d']
        return err
    

    对于多次使用变量名的代码,请将vars置于本地,例如

    a = params['a']
    b = params['b']
    

    等等。这可能看起来很麻烦,但它具有使一切都明确的优点,避免了可能使闭包变慢的命名空间搜索类型。

    然后通过args参数将自由名称列表和固定参数字典传递给optimize.leastsq。 (请注意,params字典是可变的,这意味着理论上可能存在副作用;但在这种情况下它应该无关紧要,因为update只覆盖了自由参数,所以我为了速度,省略了复制步骤。)

    这种方法的主要缺点是它将一些复杂性转移到optimize.leastsq的调用中,这使得代码不再可重用。第二种方法可以避免这些问题,尽管它可能不会那么快:使用可调用的类。

    class OptWrapper(object):
        def __init__(self, func, free_names, **fixed_params):
            self.func = func
            self.free_names = free_names
            self.params = fixed_params
    
        def __call__(self, x, *args):
            self.params.update(zip(self.free_names, x))
            return self.func(**self.params)
    

    您可以看到我简化了__init__的参数结构;固定的参数在此处作为关键字参数传递,用户必须确保free_namesfixed_params没有重叠的名称。我认为简单性值得权衡,但您可以像在包装器代码中那样轻松地强制实现两者之间的分离。

    我最喜欢第二种方法;它具有基于闭包的方法的灵活性,但我发现它更具可读性。所有的名字都在本地命名空间中(或者可以通过它访问),我认为这会加快速度 - 但经过一些测试后我认为有理由认为闭包方法仍然比这更快;访问__call__方法似乎每次调用开销增加约100 ns。如果性能是一个真正的问题,我强烈建议测试。