Python GEKKO: оптимизация производительности нелинейной оптимизации - PullRequest
2 голосов
/ 14 октября 2019

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

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

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

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‍ приносил достойные результаты.

1 Ответ

1 голос
/ 16 октября 2019

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

Оригинал (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.

...