GEKKO 中的约束非线性优化,objective 函数与预期解不匹配

Constrained non-linear optimisation in GEKKO, objective function not matching expected solution

我有一个相当简单的约束非线性优化问题,最大化收入,给定一些支出限制(保持总支出不变,increase/decrease 每个渠道的支出最多增加 50%)和 objective 函数 revenue = spend * roi(其中 roi 是使用每个通道的对数对数模型系数计算的)。

首先,该解决方案与我认为的最佳解决方案不符。此外,当采用 GEKKO 建议的最佳支出值并将其放入 objective 函数时,这也不符合 GEKKO 给出的最佳 objective 函数值。

如果上面的问题没有很好地表达出来,下面的示例代码应该可以解决这个问题...

有人知道我错过了什么吗?

代码如下:

from gekko import GEKKO
import numpy as np
import pandas as pd

df1 = pd.DataFrame({'channel': ['fb', 'tv', 'ppc'],
                    'value': [5000, 5000, 5000],                    
                    'alpha': [1.00, 1.00, 1.00],
                    'beta': [0.03, 0.02, 0.01]
})

total_spend = df1['value'].sum()

m = GEKKO()

# Constraint parameters
spend_inc = 0.00
spend_dec = 0.00
channel_inc = 0.50
channel_dec = 0.50

channels = len(df1)

# Initialise decision variables
x = m.Array(m.Var, (channels), integer=True)
i = 0
for xi in x:
    xi.value = df1['value'][i]
    xi.lower = df1['value'][i] * (1 - channel_dec)
    xi.upper = df1['value'][i] * (1 + channel_inc)
    i += 1

# Initialise alpha
a = m.Array(m.Param, (channels))
i = 0
for ai in a:
    ai.value = df1['alpha'][i]
    i += 1
    
# Initialise beta
b = m.Array(m.Param, (channels))
i = 0
for bi in b:
    bi.value = df1['beta'][i]
    i += 1
    
# Initial global contraints
spend_min = total_spend * (1 - spend_dec)
spend_max = total_spend * (1 + spend_dec)
    
# Print out variabales
print(f'spend min: {spend_min}')
print(f'spend max: {spend_max}')
print('')
for i in range(0, channels):
    print(f'x{i+1} value: {str(x[i].value)}')    
    print(f'x{i+1} lower: {str(x[i].lower)}')
    print(f'x{i+1} upper: {str(x[i].upper)}')
    print(f'x{i+1} alpha: {str(a[i].value)}')
    print(f'x{i+1} beta: {str(b[i].value)}')
    print('')

# Constraints
m.Equation(sum(x) >= spend_min)
m.Equation(sum(x) <= spend_max)

# Log-Log model
def roi(a, b, x):
    roi = a + b * m.log(x[0])
    roi = m.exp(roi[0])
    return roi

# Objective function
m.Maximize(m.sum(x * roi(a, b, x)))
m.options.IMODE = 3
m.solve()

for i in range(0, channels):
    print(f'x{i+1}: {str(x[i].value[0])}')
print('')
print(f'optimal solution: {str(m.options.objfcnval)}')

# THIS DOESN'T MATCH THE OBJECTIVE FUNCTION VALUE SUGGESTED BY GEKKO
opt = 0
for i in range(0, channels):
    opt += x[i].value[0] * (np.exp((a[0].value[0] + b[i].value[0] * np.log(x[i].value[0]))))
print(f'optimal solution: {opt}')

# THIS IS THE EXPECETD SOLUTION, WHICH ALSO DOESN'T MATCH THE OBJECTIVE FUNCTION VALUE SUGGESTED BY GEKKO
7500 * np.exp(1.00 + 0.03 * np.log(7500)) + 5000 * np.exp(1.00 + 0.02 * np.log(5000)) + 2500 * np.exp(1.00 + 0.01 * np.log(2500))

objective 函数的定义方式存在问题。解决该问题的方法是在函数中一次使用一个 a[i]b[i]x[i]。 Gekko 允许多个 objective 函数定义。最大化时,它转换为 min = -max 的最小化问题。因此,最大化的 objective 是 -m.options.OBJFCNVAL.

# Log-Log model
def roi(ai, bi, xi):
    return m.exp(ai + bi * m.log(xi))

# Objective function
for i,xi in enumerate(x):
    m.Maximize(xi * roi(a[i], b[i], x[i]))

它还有助于定义一个新变量 sum_x,上限 spend_max 和下限 spend_min

# Constraints
sum_x = m.Var(lb=spend_min,ub=spend_max)
m.Equation(sum_x==m.sum(x))

这是完整的脚本。

from gekko import GEKKO
import numpy as np
import pandas as pd

df1 = pd.DataFrame({'channel': ['fb', 'tv', 'ppc'],
                    'value': [5000, 5000, 5000],                    
                    'alpha': [1.00, 1.00, 1.00],
                    'beta': [0.03, 0.02, 0.01]
})

total_spend = df1['value'].sum()

m = GEKKO()

# Constraint parameters
spend_inc = 0.00
spend_dec = 0.00
channel_inc = 0.50
channel_dec = 0.50

channels = len(df1)

# Initialise decision variables
x = m.Array(m.Var, (channels), integer=True)
i = 0
for xi in x:
    xi.value = df1['value'][i]
    xi.lower = df1['value'][i] * (1 - channel_dec)
    xi.upper = df1['value'][i] * (1 + channel_inc)
    i += 1

# Initialise alpha
a = m.Array(m.Param, (channels))
i = 0
for ai in a:
    ai.value = df1['alpha'][i]
    i += 1
    
# Initialise beta
b = m.Array(m.Param, (channels))
i = 0
for bi in b:
    bi.value = df1['beta'][i]
    i += 1
    
# Initial global contraints
spend_min = total_spend * (1 - spend_dec)
spend_max = total_spend * (1 + spend_dec)
    
# Print out variabales
print(f'spend min: {spend_min}')
print(f'spend max: {spend_max}')
print('')
for i in range(0, channels):
    print(f'x{i+1} value: {str(x[i].value)}')    
    print(f'x{i+1} lower: {str(x[i].lower)}')
    print(f'x{i+1} upper: {str(x[i].upper)}')
    print(f'x{i+1} alpha: {str(a[i].value)}')
    print(f'x{i+1} beta: {str(b[i].value)}')
    print('')

# Constraints
sum_x = m.Var(lb=spend_min,ub=spend_max)
m.Equation(sum_x==m.sum(x))

# Log-Log model
def roi(ai, bi, xi):
    return m.exp(ai + bi * m.log(xi))

# Objective function
for i,xi in enumerate(x):
    m.Maximize(xi * roi(a[i], b[i], x[i]))
m.options.IMODE = 3
m.solve()

for i in range(0, channels):
    print(f'x{i+1}: {str(x[i].value[0])}')
print('')
print(f'optimal solution: {str(-m.options.objfcnval)}')

opt = 0
for i in range(0, channels):
    opt += x[i].value[0] * (np.exp((a[i].value[0] \
                                    + b[i].value[0] \
                                    * np.log(x[i].value[0]))))
print(f'optimal solution: {opt}')

# THIS IS THE EXPECTED SOLUTION
# IT NOW MATCHES THE OBJECTIVE FUNCTION VALUE SUGGESTED BY GEKKO
sol = 7500 * np.exp(1.00 + 0.03 * np.log(7500)) \
      + 5000 * np.exp(1.00 + 0.02 * np.log(5000)) \
      + 2500 * np.exp(1.00 + 0.01 * np.log(2500))
print(f'expected optimal solution: {sol}')

解决方法如下图

EXIT: Optimal Solution Found.
 
 The solution was found.
 
 The final value of the objective function is   -50108.7613900549     
 
 ---------------------------------------------------
 Solver         :  IPOPT (v3.12)
 Solution time  :   9.499999985564500E-003 sec
 Objective      :   -50108.7611889392     
 Successful solution
 ---------------------------------------------------
 
x1: 7500.0
x2: 4999.9999497
x3: 2500.0

optimal solution: 50108.761189
optimal solution: 50108.7611890521
expected optimal solution: 50108.76135441651