动态定义函数

8 投票
5 回答
2098 浏览
提问于 2025-04-17 06:39

我正在尝试写一个曲线拟合的函数,这个函数可以返回最佳的参数 a、b 和 c。下面是一个简化的例子:

import numpy
import scipy
from scipy.optimize import curve_fit

def f(x, a, b, c):
    return x * 2*a + 4*b - 5*c

xdata = numpy.array([1,3,6,8,10])
ydata = numpy.array([  0.91589774,   4.91589774,  10.91589774,  14.91589774,  18.91589774])
popt, pcov = scipy.optimize.curve_fit(f, xdata, ydata)

这个方法运行得很好,但我想给用户一个机会,让他们可以提供一些(或者不提供)参数 a、b 或 c。如果用户提供了这些参数,它们就应该被当作常量,而不是去估算。那么我该如何编写 f,使它只拟合用户没有提供的参数呢?

简单来说,我需要动态定义 f,让它接受正确的参数。例如,如果用户已经知道了 a,那么 f 就变成:

def f(x, b, c):
    a = global_version_of_a
    return x * 2*a + 4*b - 5*c

5 个回答

0

如果你想要一个简单的解决方案,可以使用 curve_fit,我建议你把你的函数放在一个类里面。下面是一个最简单的例子:

import numpy
from scipy.optimize import curve_fit

class FitModel(object):
    def f(self, x, a, b, c):
        return x * 2*a + 4*b - 5*c

    def f_a(self, x, b, c):
        return self.f(x, self.a, b, c)

# user supplies a = 1.0
fitModel = FitModel()
fitModel.a = 1.0

xdata = numpy.array([1,3,6,8,10])
ydata = numpy.array([  0.91589774,   4.91589774,  10.91589774,  14.91589774,  18.91589774])
initial = (1.0,2.0)
popt, pconv = curve_fit(fitModel.f_a, xdata, ydata, initial)
1

我通常会用一个lambda表达式来实现这个目的。

user_b, user_c = get_user_vals()
opt_fun = lambda x, a: f(x, a, user_b, user_c)
popt, pcov = scipy.optimize.curve_fit(opt_fun, xdata, ydata)
8

借鉴collections.namedtuple的思路,你可以使用exec来“动态”定义func

import numpy as np
import scipy.optimize as optimize
import textwrap

funcstr=textwrap.dedent('''\
def func(x, {p}):
    return x * 2*a + 4*b - 5*c
''')
def make_model(**kwargs):
    params=set(('a','b','c')).difference(kwargs.keys())
    exec funcstr.format(p=','.join(params)) in kwargs
    return kwargs['func']

func=make_model(a=3, b=1)

xdata = np.array([1,3,6,8,10])
ydata = np.array([  0.91589774,   4.91589774,  10.91589774,  14.91589774,  18.91589774])
popt, pcov = optimize.curve_fit(func, xdata, ydata)
print(popt)
# [ 5.49682045]

注意这一行

func=make_model(a=3, b=1)

你可以随意传递参数给make_model。你传给make_model的参数会变成func中的固定常量。而剩下的参数则是自由参数,optimize.curve_fit会尝试去调整这些参数。

举个例子,上面提到的a=3和b=1就变成了func中的固定常量。实际上,exec语句把它们放在了func的全局命名空间里。因此,func被定义为一个关于x和单个参数c的函数。注意,popt的返回值是一个长度为1的数组,对应于剩下的自由参数c


关于textwrap.dedent:在上面的例子中,调用textwrap.dedent其实是多余的。但在“真实”的脚本中,如果funcstr是在一个函数内部定义的,或者缩进层级更深,使用textwrap.dedent可以让你写出

def foo():
    funcstr=textwrap.dedent('''\
        def func(x, {p}):
            return x * 2*a + 4*b - 5*c
        ''')

而不是看起来不太美观的

def foo():
    funcstr='''\
def func(x, {p}):
    return x * 2*a + 4*b - 5*c
'''

有些人更喜欢

def foo():
    funcstr=(
        'def func(x, {p}):\n'
        '    return x * 2*a + 4*b - 5*c'
        )

但我觉得逐行引用并添加明确的换行符有点麻烦。不过,这样确实可以省去一次函数调用。

撰写回答