Dynamically choose argument for which to minimize a function in python using scipy.optimize

蓝咒 提交于 2021-01-27 23:41:42

问题


I have a function which takes a list of variables as an argument and I would like to minimize this function using scipy.optimize.minimize.
The problem is that it is decided on runtime for which variable in the argument list the minimization should be done. All other variables will get a fixed value.

Let's make an example to clarify:

a = 1
c = 1.1
d = -1.2

def func( b ):
    return function_to_minimize( array=[a,b,c,d] )

sol = scipy.optimize.minimize( func, [b0], args=(a,c,d) )

This works, however, it could be that b, c and d are known and I want to optimize a to find the minimum solution.

To make it even more complicated, the length of the list is not known either. That means there could be a variabel e, f, g, ... and so on.

The actual notation is as follows. The element which is None is the one which should be optimized for.

array = [1, 1.1, None, -0.5, 4]

def func(arr):
    return function_to_minimize(arr)

startvalue = 1.0
sol = scipy.optimize.minimize( func, [startvalue], args='Array without None' )

Is there a way to tell scipy.optimize.minimize for which element to optimize for? Is there perhaps a smart lambda trick which I could do?

I would really appreciate your help!


回答1:


As you know, the function to be minimized changes depending on what parameters are given. So we need to write some code which defines the function dynamically. One way to do this is to define a template string, do some string formatting to modify the template based on which parameters are given, and then use exec to define the function. There is some precedence for this -- the standard library uses this technique to define namedtuples.

So, for example, if the expression we wish to minimize is

4*(b-a)**2 + 5*(c-d)**2

then you could use

import textwrap
import scipy.optimize as optimize

def make_model(*fixed):
    template = textwrap.dedent("""
        def func(variable, {fixed}):
            {variable} = variable
            return 4*(b-a)**2 + 5*(c-d)**2
        """)
    variable = set(('a', 'b', 'c', 'd')).difference(fixed)
    ns = dict()
    funcstr = template.format(variable=', '.join(variable), fixed=', '.join(fixed))
    print(funcstr)  # comment out if you don't want to see the function
    exec funcstr in ns
    return ns['func']

def solve(initial_guess, **givens):
    fixed = tuple(givens.keys())
    vals = tuple(givens.values())
    sol = optimize.minimize(make_model(*fixed), initial_guess, args=vals)
    return sol

print(solve(initial_guess=1, a=1, c=1.1, d=-1.2))

which yields

def func(variable, a, c, d):
    b = variable
    return 4*(b-a)**2 + 5*(c-d)**2

   status: 0
  success: True
     njev: 1
     nfev: 3
 hess_inv: array([[1]])
      fun: array([ 26.45])
        x: array([ 1.])
  message: 'Optimization terminated successfully.'
      jac: array([ 0.])
      nit: 0

print(solve(initial_guess=(1, 1), a=1, c=1.1))

yields

def func(variable, a, c):
    b, d = variable
    return 4*(b-a)**2 + 5*(c-d)**2

   status: 0
  success: True
     njev: 3
     nfev: 12
 hess_inv: array([[1, 0],
       [0, 1]])
      fun: 2.4611848645596973e-16
        x: array([ 0.99999999,  1.1       ])
  message: 'Optimization terminated successfully.'
      jac: array([  1.19209279e-08,   2.88966118e-08])
      nit: 1



回答2:


I just wanted to provide the adaptation of unutbu's answer regarding an unknown number of variables using a list arr as single input, where the parameter to fit is set to None.

The function to minimize fm is a dummy function, simply trying to minimize the standard deviation of the array using numpy.std.

It looks a bit clunky and not very pythonesque, but it works.

import textwrap
import scipy.optimize as optimize


def make_model(n,*fixed):
    template = textwrap.dedent("""
        import numpy as np
        def fm(arr):
            return np.std(arr)
        def func(variable, {fixed}):
            {variable} = variable
            return fm(["""+",".join(["a"+str(i) for i in range(n)])+"""])
        """)
    settuple = tuple(['a'+str(i) for i in range(n)])
    variable = set(settuple).difference(fixed)
    ns = dict()
    funcstr = template.format(variable=', '.join(variable), fixed=', '.join(fixed))
    print(funcstr)  # comment out if you don't want to see the function
    exec funcstr in ns
    return ns['func']


def solve(initial_guess, n, **givens):
    fixed = tuple(givens.keys())
    vals = tuple(givens.values())
    sol = optimize.minimize(make_model(n,*fixed), initial_guess, args=vals)
    return sol


arr = [1, 1.1, None, -0.5, 4, 3]

s = ""
for i,a in enumerate(arr):
    if a is not None:
        s+=", a"+str(i)+"="+str(a)

print "print(solve(initial_guess=1, n="+str(len(arr))+s+"))"  # comment out if you don't want to see the function       
exec "print(solve(initial_guess=1, n="+str(len(arr))+s+"))"


来源:https://stackoverflow.com/questions/31951903/dynamically-choose-argument-for-which-to-minimize-a-function-in-python-using-sci

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!