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

问题描述

我有一个相当简单的约束非线性优化问题,在给定一些支出限制(保持总体支出不变,并将每个渠道的支出增加/减少多达 50%)和一个目标函数,即收入的情况下,最大化收入= 花费 * roi(其中 roi 是使用每个通道的 log-log 模型系数计算的)。

首先,该解决方案与我认为的最佳解决方案不符。另外,当将GEKKO建议的最优花费值放入目标函数时,这也与GEKKO给出的最优目标函数值不符。

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

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

代码如下:

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,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))

解决方法

目标函数的定义方式存在问题。该问题的解决方法是在函数中一次使用 a[i]b[i]x[i] 一个。 Gekko 允许多个目标函数定义。最大化时,它转换为 min = -max 的最小化问题。因此最大化的目标是 -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,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