Python GEKKO: оптимизация производительности нелинейной оптимизации

Я использую GEKKO‍ для решения задачи нелинейного программирования. Моя цель - сравнить GEKKO‍ производительность с альтернативой, поэтому я хочу убедиться, что получаю от GEKKO‍ лучшее, что она может предложить.

Существует n двоичных переменных, каждой из них присвоен вес, каждый из весов - это число из интервала [0, 1] (т. Е. Рациональное число < em> w, удовлетворяющий 0 ‹= w‹ = 1). Каждое из ограничений линейно. Целевая функция нелинейна: это произведение весов ненулевых переменных, и цель состоит в том, чтобы максимизировать продукт.

Я начал с определения целевой функции как

m.Obj(-np.prod([1 - variables[i] + weights[i] * variables[i] for i in range(len(variables))]))

но тогда я бы наткнулся на APM model error: string > 15000 characters. Поэтому я переключился на вспомогательные переменные, используя функцию if3 как

aux_variables = [m.if3(variables[i], weights[i], 1) for i in range(len(variables))]
m.Obj(-np.prod(aux_variables))

Единственные глобальные параметры, которые я устанавливаю вручную, находятся в следующем коде.

# initialize model

m = GEKKO(remote=False)


# set global variables

m.options.SOLVER = 1 # APOPT solver
# "APOPT is an MINLP solver"
# "APOPT is also the only solver that handles Mixed Integer problems."

m.options.IMODE = 3 # steady state optimization

m.solver_options = ['minlp_maximum_iterations 500', \
                    # minlp iterations with integer solution
                    'minlp_max_iter_with_int_sol 10', \
                    # treat minlp as nlp
                    'minlp_as_nlp 0', \
                    # nlp sub-problem max iterations
                    'nlp_maximum_iterations 50', \
                    # 1 = depth first, 2 = breadth first
                    'minlp_branch_method 1', \
                    # maximum deviation from whole number
                    'minlp_integer_tol 0.05', \
                    # covergence tolerance
                    'minlp_gap_tol 0.01']

# initialize variables
variables = m.Array(m.Var, (number_of_vars), lb=0, ub=1, integer=True)

# set initial values
for var in variables:
    var.value = 1

Вопрос:

Что еще я могу сделать с точки зрения глобальных параметров и формулировки целевой функции, чтобы оптимизировать производительность GEKKO‍ для этой конкретной проблемы?

В то же время я хотел бы GEKKO‍ получить достойные результаты.


person abebebebahabe    schedule 14.10.2019    source источник
comment
В каком диапазоне может быть вес в вашей задаче?   -  person kutschkem    schedule 14.10.2019
comment
Каждый из весов является константой из интервала [0, 1].   -  person abebebebahabe    schedule 14.10.2019


Ответы (1)


Один из способов переформулировать вашу проблему для увеличения скорости - использовать промежуточные переменные.

Исходный (0,0325 секунды при # Var = 5)

m.Obj(-np.prod([1 - variables[i] + weights[i] * variables[i] \
      for i in range(len(variables))]))

Изменено (0,0156 секунды при # Var = 5)

ival = [m.Intermediate(1 - variables[i] + weights[i] * variables[i]) \
                       for i in range(len(variables))]
m.Obj(-np.prod(ival))

Это также должно помочь вам избежать проблемы с длиной строки, если только у вас number_of_vars не очень большой. Кажется, что оптимальным решением всегда будет variables[i]=1, когда weights[i]=1 и variables[i]=0, когда weights[i]=0. С np.prod это означает, что вся целевая функция равна нулю, если любой из членов продукта равен нулю. Помогло бы установить значения отдельных продуктов равными 1 вместо использования целевой функции для нахождения значений? Одна вещь, которая помогает APOPT найти правильное решение, - это использовать что-то вроде 1.1 в вашем промежуточном объявлении вместо 1.0. Таким образом, когда вы максимизируете, он пытается избежать значений 0.1 в пользу поиска решения, которое дает 1.1.

from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False)
number_of_vars = 5
weights = [0,1,0,1,0]
m.options.IMODE = 3
variables = m.Array(m.Var, (number_of_vars), lb=0, ub=1, integer=True)
for var in variables:
    var.value = 1
ival = [m.Intermediate(1.1 - variables[i] + weights[i] * variables[i]) \
                       for i in range(len(variables))]
# objective function
m.Obj(-np.prod(ival))
# integer solution with APOPT
m.options.SOLVER = 1
m.solver_options = ['minlp_maximum_iterations 500', \
                    # minlp iterations with integer solution
                    'minlp_max_iter_with_int_sol 10', \
                    # treat minlp as nlp
                    'minlp_as_nlp 0', \
                    # nlp sub-problem max iterations
                    'nlp_maximum_iterations 50', \
                    # 1 = depth first, 2 = breadth first
                    'minlp_branch_method 1', \
                    # maximum deviation from whole number
                    'minlp_integer_tol 0.05', \
                    # covergence tolerance
                    'minlp_gap_tol 0.01']
m.solve()
print(variables)

Решателю также намного проще найти решение для суммирования, такого как m.sum(), и оно дает то же variables решение, что и опция np.prod().

# objective function
m.Obj(-m.sum(ival))

Вы можете добавить линию постобработки для восстановления целевой функции продукта, которая будет либо 0, либо 1.

Функция if3 не подходит для вашего приложения, потому что условие переключения равно 0, и небольшие числовые вариации приведут к ненадежным результатам. Решатель считает, что от 0 до 0.05 и от 0.95 до 1 являются целочисленными решениями в соответствии с опцией minlp_integer_tol=0.05. Это опция, которая позволяет принимать целочисленные решения, когда они достаточно близки к целочисленному значению. Если значение variables[i] равно 0.01, тогда функция if3 выберет параметр True, тогда как она должна выбрать параметр False. Вы все равно можете использовать функцию if3, если вы установили точку переключения между двоичными значениями, такими как m.if3(variables[i]-0.5, weights[i], 1). Однако есть более простые способы решить вашу проблему, чем использование функции if3.

person John Hedengren    schedule 15.10.2019
comment
Спасибо за подробный ответ, в частности за пояснения по if3. Похоже, в моем исходном посте я не совсем ясно понял: веса - это рациональные числа от 0 до 1 (включая, но НЕ ограничиваясь, 0 и 1). Таким образом, я не могу использовать суммирование вместо произведения. Количество переменных до ~ 2500. Я немного почитаю об использовании промежуточных переменных и посмотрю, что происходит, когда они используются здесь. - person abebebebahabe; 16.10.2019
comment
Переход на промежуточные переменные немного помог с ограничением длины строки и скорости, а также с точностью результатов по сравнению с результатами при использовании if3. Полученные результаты все еще ужасно далеки от оптимальных, но кажется, что с этим ничего нельзя поделать, так как функция, которую нужно оптимизировать, очень сложна. - person abebebebahabe; 18.10.2019