Как найти экстремумы функции python

How do I find the maximum of a function in Python? I could try to hack together a derivative function and find the zero of that, but is there a method in numpy (or other library) that can do it for me?

asked Apr 13, 2012 at 19:13

Nick T's user avatar

2

You can use scipy.optimize.fmin on the negative of your function.

def f(x): return -2 * x**2 + 4 * x
max_x = scipy.optimize.fmin(lambda x: -f(x), 0)
# array([ 1.])

Nick T's user avatar

Nick T

25.5k11 gold badges80 silver badges121 bronze badges

answered Apr 13, 2012 at 19:17

ely's user avatar

elyely

74k34 gold badges146 silver badges226 bronze badges

5

If your function is solvable analytically try SymPy. I’ll use EMS’s example above.

In [1]: from sympy import *
In [2]: x = Symbol('x', real=True)

In [3]: f = -2 * x**2 + 4*x

In [4]: fprime = f.diff(x)
In [5]: fprime
Out[5]: -4*x + 4

In [6]: solve(fprime, x) # solve fprime = 0 with respect to x
Out[6]: [1]

Of course, you’ll still need to check that 1 is a maximizer and not a minimizer of f

In [7]: f.diff(x).diff(x) < 0
Out[7]: True

answered Apr 19, 2012 at 13:54

MRocklin's user avatar

MRocklinMRocklin

55.1k21 gold badges155 silver badges233 bronze badges

I think scipy.optimize.minimize_scalar and scipy.optimize.minimize are the preferred ways now, that give you access to the range of techniques, e.g.

solution = scipy.optimize.minimize_scalar(lambda x: -f(x), bounds=[0,1], method='bounded')

for a single variable function that must lie between 0 and 1.

Boris Zagoruiko's user avatar

answered Dec 3, 2014 at 9:41

phasor's user avatar

You could try SymPy. SymPy might be able to provide you with the derivative symbolically, find its zeros, and so on.

answered Apr 13, 2012 at 20:50

zarthur's user avatar

zarthurzarthur

5215 silver badges10 bronze badges

Maximum of a function with parameters.

import scipy.optimize as opt

def get_function_max(f, *args):
    """
    >>> round(get_function_max(lambda x, *a: 3.0-2.0*(x**2)), 2)
    3.0

    >>> round(get_function_max(lambda x, *a: 3.0-2.0*(x**2)-2.0*x), 2)
    3.5

    >>> round(get_function_max(lambda x, *a: a[0]-a[1]*(x**2)-a[1]*x, 3.0, 2.0), 2)
    3.5
    """
    def func(x, *arg):
        return -f(x, *arg)
    return f(opt.fmin(func, 0, args=args, disp=False)[0], *args)

answered Dec 5, 2014 at 16:16

rusnasonov's user avatar

rusnasonovrusnasonov

7522 gold badges12 silver badges23 bronze badges

Время на прочтение
5 мин

Количество просмотров 56K

Метод Нелдера — Мида — метод оптимизации (поиска минимума) функции от нескольких переменных. Простой и в тоже время эффективный метод, позволяющий оптимизировать функции без использования градиентов. Метод надежен и, как правило, показывает хорошие результаты, хотя и отсутствует теория сходимости. Может использоваться в функции optimize из модуля scipy.optimize популярной библиотеки для языка python, которая используется для математических расчетов.

Алгоритм заключается в формировании симплекса (simplex) и последующего его деформирования в направлении минимума, посредством трех операций:

1) Отражение (reflection);
2) Растяжение (expansion);
3) Сжатие (contract);

Симплекс представляет из себя геометрическую фигуру, являющуюся n — мерным обобщением треугольника. Для одномерного пространства — это отрезок, для двумерного — треугольник. Таким образом n — мерный симплекс имеет n + 1 вершину.

Алгоритм

1) Пусть

$f(x, y)$ функция, которую необходимо оптимизировать. На первом шаге выбираем три случайные точки (об этом чуть позже) и формируем симплекс (треугольник). Вычисляем значение функции в каждой точке:

$f(V_1)$,

$f(V_2)$,

$f(V_3)$.

Сортируем точки по значениям функции

$f(x, y)$ в этих точках, таким образом получаем двойное неравенство:

$f(V_2) leq f(V_1) leq f(V_3).$

Мы ищем минимум функции, а следовательно, на данном шаге лучшей будет та точка, в которой значение функции минимально. Для удобства переобозначим точки следующим образом:

b =

$V_2$, g =

$V_1$, w =

$V_3$, где best, good, worst — соответственно.

2) На следующем шаге находим середину отрезка, точками которого являются g и b. Т.к. координаты середины отрезка равны полусумме координат его концов, получаем:

$mid = left ( frac {x_1 + x_2} 2; frac {y_1 + y_2} 2 right)$

В более общем виде можно записать так:

$mid = frac 1 nsum_{i=1}^n x_i$

3) Применяем операцию отражения:
Находим точку

$x_r$, следующим образом:

$x_r = mid + α(mid - w)$

Т.е. фактически отражаем точку w относительно mid. В качестве коэффициента берут как правило 1. Проверяем нашу точку: если

$f(x_r) < f(g)$, то это хорошая точка. А теперь попробуем расстояние увеличить в 2 раза, вдруг нам повезет и мы найдем точку еще лучше.

4) Применяем операцию растяжения:
Находим точку

$x_e$ следующим образом:

$x_e = mid + γ(x_r - mid)$

В качестве γ принимаем γ = 2, т.е. расстояние увеличиваем в 2 раза.

Проверяем точку

$x_e$:

Если

$f(x_e) < f(b)$, то нам повезло и мы нашли точку лучше, чем есть на данный момент, если бы этого не произошло, мы бы остановились на точке

$x_r$.

Далее заменяем точку w на

$x_e$, в итоге получаем:

5) Если же нам совсем не повезло и мы не нашли хороших точек, пробуем операцию сжатия.
Как следует из названия операции мы будем уменьшать наш отрезок и искать хорошие точки внутри треугольника.

Пробуем найти хорошую точку

$x_c$:

$x_c = mid + β(w - mid)$

Коэффициент β принимаем равным 0.5, т.е. точка

$x_c$ на середине отрезка wmid.

Существует еще одна операция — shrink (сокращение). В данном случае, мы переопределяем весь симплекс. Оставляем только «лучшую» точку, остальные определяем следующим образом:

$x_j = b + δ(x_j - b)$

Коэффициент δ берут равным 0.5.

По существу передвигаем точки по направлению к текущей «лучшей» точке. Преобразование выглядит следующим образом:

Необходимо отметить, что данная операция дорого обходится, поскольку необходимо заменять точки в симплексе. К счастью было установлено, при проведении большого количества экспериментов, что shrink — трансформация редко случается на практике.

Алгоритм заканчивается, когда:

1) Было выполнено необходимое количество итераций.
2) Площадь симплекса достигла определенной величины.
3) Текущее лучшее решение достигло необходимой точности.

Как и в большинстве эвристических методов, не существует идеального способа выбора инициализирующих точек. Как уже было сказано, можно брать случайные точки, находящиеся недалеко друг от друга для формирования симплекса; но есть решение и получше, которое используется в реализации алгоритма в MATHLAB:

Выбор первой точки

$V_1$ поручаем пользователю, если он имеет некоторое представление о возможном хорошем решении, в противном случае выбирается случайным образом. Остальные точки выбираются исходя из

$V_1$, на небольшом расстоянии вдоль направления каждого измерения:

$V_{i + 1} = V_i + h(V_1, i)*U_i$

где

$U_i$ — единичный вектор.

$h(V_1, i)$ определяется таким образом:

$h(V_1, i)$ = 0.05, если коэффициент при

$U_i$ в определении

$V_1$ не нулевой.

$h(V_1, i)$ = 0.00025, если коэффициент при

$U_i$ в определении нулевой.

Пример:

Найти экстремум следующей функции:

$f(x, y) = x^2 + xy + y^2 - 6x - 9y$

В качестве начальных возьмем точки:

$V_1(0, 0), V_2(1, 0), V_3(0, 1)$

Вычислим значение функции в каждой точке:

$f(V_1) = f(0, 0) = 0$

$f(V_2) = f(1, 0) = -5$

$f(V_3) = f(0, 1) = -8$

Переобозначим точки следующим образом:

$b = V_3(0, 1), g = V_2(1, 0), w = V_1(0, 0)$

Находим середину отрезка bg:

$mid = frac{b+g}2 = left(frac 1 2; frac 1 2 right)$

Находим точку

$x_r$ (операция отражения):

$x_r = mid + α(mid - w),$

если α=1, тогда:

$x_r = 2*mid - w = 2 left(frac 1 2; frac 1 2 right) - left(0, 0 right) = (1, 1)$

Проверяем точку

$x_r$:

$f(x_r) = -12$, т.к.

$f(x_r) < f(b)$ пробуем увеличить отрезок (операция растяжения).

$x_e = mid + γ(x_r - mid), $

если γ = 2, тогда:

$x_e = 2x_r - mid$

$x_e = 2(1, 1) - left(frac 1 2, frac 1 2 right) = (1.5, 1.5)$

Проверяем значение функции в точке

$x_e$:

$f(x_e) = f(1.5, 1.5) = -15.75$

Оказалось, что точка

$x_e $ «лучше» точки b. Следовательно мы получаем новые вершины:

$V_1(1.5, 1.5), V_2(1, 0), V_3(0, 1)$

И алгоритм начинается сначала.

Таблица значений для 10 итераций:

Best Good Worst
$f(0, 1) = -8$ $f(1.0, 0) = -5$ $f(0, 0) = 0$
$f(1.5, 1.5) = -15.75$ $f(0, 1) = -8$ $f(1.0, 0) = -5$
$f(0.25, 3.75) = -20.187$ $f(1.5, 1.5) = -15.75$ $f(0, 1) = -8$
$f(0.25, 3.75) = -20.187$ $f(1.75, 4.25) = -20.1875$ $f(1.5, 1.5) = -15.75$
$f(1.125, 3.375) = -20.671$ $f(1.75, 4.25) = -20.1875$ $f(0.25, 3.75) = -20.1875$
$f(1.140, 3.796) = -20.9638$ $f(1.125, 3.375) = -20.6718$ $f(1.75, 4.25) = -20.1875$
$f(1.140, 3.796) = -20.9638$ $f(1.287, 3.751) = -20.8668$ $f(1.125, 3.375) = -20.6718$
$f(1.140, 3.796) = -20.9638$ $f(1.236, 3.874) = -20.9521$ $f(1.287, 3.751) = -20.8668$
$f(0.990, 4.002) = -20.9951$ $f(1.140, 3.796) = -20.9638$ $f(1.2365, 3.874) = -20.9520$
$f(0.990, 4.002) = -20.9951$ $f(0.895, 3.925) = -20.9855$ $f(1.140, 3.796) = -20.9638$

Аналитически находим экстремум функции, он достигается в точке

$f(1, 4) = -21$.
После 10 итераций мы получаем достаточно точное приближение:

$f(0.990, 4.002) = -20.999916$

Еще о методе:

Алгоритм Нелдера — Мида в основном используется для выбора параметра в машинном обучении. В сущности, симплекс-метод используется для оптимизации параметров модели. Это связано с тем, что данный метод оптимизирует целевую функцию довольно быстро и эффективно (особенно там, где не используется shrink — модификация).

С другой стороны, в силу отсутствия теории сходимости, на практике метод может приводить к неверному ответу даже на гладких (непрерывно дифференцируемых) функциях. Также возможна ситуация, когда рабочий симплекс находится далеко от оптимальной точки, а алгоритм производит большое число итераций, при этом мало изменяя значения функции. Эвристический метод решения этой проблемы заключается в запуске алгоритма несколько раз и ограничении числа итераций.

Реализация на языке программирования python:

Создаем вспомогательный класс Vector и перегружаем операторы для возможности производить с векторами базовые операции. Я намерено не использовал вспомогательные библиотеки для реализации алгоритма, т.к. в таком случае зачастую снижается восприятие.

#!/usr/bin/python
# -*- coding: utf-8 -*-
class Vector(object):
    def __init__(self, x, y):
        """ Create a vector, example: v = Vector(1,2) """
        self.x = x
        self.y = y

    def __repr__(self):
        return "({0}, {1})".format(self.x, self.y)

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def __sub__(self, other):
        x = self.x - other.x
        y = self.y - other.y
        return Vector(x, y)

    def __rmul__(self, other):
        x = self.x * other
        y = self.y * other
        return Vector(x, y)

    def __truediv__(self, other):
        x = self.x / other
        y = self.y / other
        return Vector(x, y)

    def c(self):
        return (self.x, self.y)
        
# objective function
def f(point):
    x, y = point
    return x**2 + x*y + y**2 - 6*x - 9*y

def nelder_mead(alpha=1, beta=0.5, gamma=2, maxiter=10):
    
    # initialization
    v1 = Vector(0, 0)
    v2 = Vector(1.0, 0)
    v3 = Vector(0, 1)

    for i in range(maxiter):
        adict = {v1:f(v1.c()), v2:f(v2.c()), v3:f(v3.c())}
        points = sorted(adict.items(), key=lambda x: x[1])
        
        b = points[0][0]
        g = points[1][0]
        w = points[2][0]
        
        
        mid = (g + b)/2

        # reflection
        xr = mid + alpha * (mid - w)
        if f(xr.c()) < f(g.c()):
            w = xr
        else:
            if f(xr.c()) < f(w.c()):
                w = xr
            c = (w + mid)/2
            if f(c.c()) < f(w.c()):
                w = c
        if f(xr.c()) < f(b.c()):

            # expansion
            xe = mid + gamma * (xr - mid)
            if f(xe.c()) < f(xr.c()):
                w = xe
            else:
                w = xr
        if f(xr.c()) > f(g.c()):
            
            # contraction
            xc = mid + beta * (w - mid)
            if f(xc.c()) < f(w.c()):
                w = xc

        # update points
        v1 = w
        v2 = g
        v3 = b
    return b

print("Result of Nelder-Mead algorithm: ")
xk = nelder_mead()
print("Best poits is: %s"%(xk))

Спасибо за чтение статьи. Надеюсь она была Вам полезна и Вы узнали много нового.
С вами был FUNNYDMAN. Удачной оптимизации!)

День 5: Алгоритмы оптимизации. Знакомимся с SciPy

SciPy – еще одна популярная математическая библиотека Python, реализующая более высокоуровневые математические функции, например из линейной алгебры или теории вероятностей. SciPy использует NumPy (вообще сложно представить, кто не использует NumPy).

Чтобы понять возможности SciPy, нужно знать базовые (а иногда и не совсем базовые) математические понятия. Чтобы посмотреть хоть что-то рассмотрим простейшее понятие – минимум функции.

Минимум функции

Например задача нахождения точки экстремума функции – это задача из области оптимизации. Рассмотрим, что такое экстремум функции на примере функции подсчета погрешности. Функции подсчета погрешности в простейшем случае измеряют отклонение предсказанного результата от проверочных эталонных значений. Чем меньше значение погрешности возвращает проверочная функция, тем точнее результат предсказания. Т.е. мы заинтересованы в нахождении таких параметров для функции, чтобы функция нахождения погрешности возвращала минимальную погрешность. Конкретные значения таких параметров называются точкой минимума. Аналогичные значения параметров функции, при которых она возвращает максимально возможный результат (в данном случае – наибольшую погрешность) называются точкой максимума. Точки минимума и максимума – это точки экстремума функции. Так как нахождение точки экстремума – процесс итеративный, т.е. в простейшем случае пробуем вызвать функцию с произвольными параметрами – смотрим на результат. Далее пробуем увеличить какой-то параметр – смотрим уменьшился результат функции или увеличился.

И так делаем до тех пор, пока не упрёмся в точку глобального минимума, либо не получим достаточно малую погрешность, с который могли бы жить.
И так делаем до тех пор, пока не упрёмся в точку глобального минимума, либо не получим достаточно малую погрешность, с который могли бы жить.

Таким образом, двигаясь на пути к точки минимума, мы уменьшаем значение функции погрешности, то есть оптимизируем нашу функцию.

Почему точка? Это вопрос геометрического смысла функции. Любая чистая функция – это описанный закон зависимости между входными параметрами и выходным значением функции. Любую математическую зависимость можно визуализировать, например откладывая входной параметр по оси X, а результат по оси Y. Если функция непрерывная, то мы получим график зависимости и если выбрать правильный отрезок X для визуализации, то можно увидеть минимальное значение функции Y – точку экстремума.

Нахождение минимума функции с помощью SciPy

Для начала убедимся, что SciPy пакеты у нас есть в нашем Python окружении.
Для начала убедимся, что SciPy пакеты у нас есть в нашем Python окружении.

Функционал SciPy разбит на подпакеты по областям. В данном случае нас будет интересовать sub package optimze. Импортируем только его используя следующий Python синтаксис:

from <package> import <module or sub-package>
from <package> import <module or sub-package>

Ну а теперь зададим произвольную функцию и найдем ее точку минимума:

Функция должна принимать параметры в виде массива, а не отдельных аргументов
Функция должна принимать параметры в виде массива, а не отдельных аргументов

Проверить результат можно по-разному, например, визуализировав функцию на небольшом отрезке:

Вот она наша точка при x = 1.00000001 и y = -5.0
Вот она наша точка при x = 1.00000001 и y = -5.0

The bounds argument goes [(lower1,upper1),(lower2,upper2)], not [(lower1,lower2),(upper1,upper2)]. If you look at your result (max_x) you will see “ERROR: NO FEASIBLE SOLUTION”, which I am guessing is because your bounds specify an empty set.

Here is a correct way to call the function. I assume the square root is just an example. I used -x**2 instead.

import scipy.optimize as opt
import scipy
from numpy import *
def f(x):
    print x
    return -x**(2)

max_x = opt.fmin_l_bfgs_b(lambda x: -f(x), 1.0, bounds=[(-9,9)],approx_grad=True)

Because you are not specifying a gradient function, you need to set approx_grad=True. The 1.0 is my initial guess for the maximum (although it is obviously zero for this example). I added a print statement so I can see each time the function is called, but that’s normally not necessary. For more details on different ways to call fmin_l_bfgs_b, see here.

The above code results in:

[ 1.]
[ 1.]
[ 1.00000001]
[-0.99999999]
[-0.99999999]
[-0.99999998]
[ 0.001]
[ 0.001]
[ 0.00100001]
[ -5.01108742e-09]
[ -5.01108742e-09]
[  4.98891258e-09]

And max_x looks like this:

(array([ -5.01108742e-09]),
 array([  2.51109971e-17]),
 {'funcalls': 4,
  'grad': array([ -2.21748344e-11]),
  'task': 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL',
  'warnflag': 0})

Уведомления

  • Начало
  • » Центр помощи
  • » экстремум функции

#1 Окт. 17, 2021 14:43:34

экстремум функции

Добрый день.
Задался целью изучить Python.
Поставил перед собой задачу – портировать расчет, который используем в работе, из маткада в python.
И вроде все шло удачно, но столкнулся с таким моментом, который не получается реализовать, а именно найти экстремум функции Y(Х) в заданных интервалах X, с заданием начального приближения переменной Х. Вот аналогичный пример в маткаде ссылка на пример – интересующий фрагмент прикрепил картинкой.

Все, что смог накопать, это использование библиотеки sympy, а также использование функции maximum и minimum из sympy.calculus.util:
x = symbols(“x”)
f = -1*(x ** 7) + 5 * (x **3) – 3 * x
interv = Interval(-2.0, 0.0)
res_min = minimum(f, x, interv)
res_max = maximum(f, x, interv)

Но как задать начальное приближение переменной Х = 1, чтобы результаты получились как в примере -0,452 и -1,162 ?

Заранее благодарю.

Прикреплённый файлы:
attachment Снимок.PNG (122,0 KБ)

Офлайн

  • Пожаловаться

#2 Окт. 17, 2021 16:32:19

экстремум функции

x800
Задался целью изучить Python.
Поставил перед собой задачу – портировать расчет, который используем в работе, из маткада в python.

Зачем? Вот ты теперь сидишь и сделать ничего не можешь с этим. Обучение классное, конечно. Это как мальчишка решил научиться плавать и сиганул на десятиметровую глубину, а там понял, что плавать-то он не умеет, и орёт окружающим, чтобы спасли его, бросили ему спасательный круг.

Хочешь научиться – начинай, как все начинают, с простого чего-нибудь. Если плавать учишься, начинай с лужи, где воды по пояс. А если ты такой умный и тонешь уже в какой-то яме, то тони тихонько, не ори.

Отредактировано py.user.next (Окт. 17, 2021 16:36:47)

Офлайн

  • Пожаловаться

#3 Окт. 17, 2021 18:55:58

экстремум функции

 from scipy import optimize
def f(x):
	return -1*(x ** 7) + 5 * (x **3) - 3 * x
print(optimize.minimize(f,[1]))

       fun: -0.8981283963885099
 hess_inv: array([[0.07846038]])
      jac: array([-1.82539225e-06])
  message: 'Optimization terminated successfully.'
     nfev: 14
      nit: 5
     njev: 7
   status: 0
  success: True
        x: array([0.45161859])
Process finished with exit code 0

Вы чего-нибудь понимаете?…
я нет…

при x = -1

       fun: -1.49841576523223
 hess_inv: array([[0.01842377]])
      jac: array([5.96046448e-08])
  message: 'Optimization terminated successfully.'
     nfev: 16
      nit: 6
     njev: 8
   status: 0
  success: True
        x: array([-1.16240037])
Process finished with exit code 0

Отредактировано xam1816 (Окт. 17, 2021 19:04:46)

Офлайн

  • Пожаловаться

#4 Окт. 17, 2021 19:12:52

экстремум функции

xam1816
x: array(0.45161859)

xam1816
x: array(-1.16240037)

то, что нужно!
спасибо большое!

почитал про optimize.minimize, получается и интервал (как в примере, -2 < x < 0 ) задать можно через параметр bounds!

 x1=[(-2, 0)]
print(optimize.minimize(f,[1], bounds = x1))

то, что искал! спасибо, xam1816, еще раз.

Отредактировано x800 (Окт. 17, 2021 21:25:21)

Офлайн

  • Пожаловаться

  • Начало
  • » Центр помощи
  • » экстремум функции

Добавить комментарий