我怎样才能让 sco.minimize 给我一个不是最初猜测的解决方案?

How can I get sco.minimize to give me a solution that isn't the initial guesses?

当我使用 2 个变量(渠道)优化广告预算时,我有一段代码运行良好,但当我添加其他渠道时,它停止了优化,没有错误消息。

import numpy as np
import scipy.optimize as sco

# setup variables
media_budget = 100000 # total media budget
media_labels = ['launchvideoviews', 'conversion', 'traffic', 'videoviews', 'reach'] # channel names
media_coefs = [0.3524764781, 5.606903166, -0.1761937775, 5.678596017, 10.50445914] # 
# model coefficients
media_drs = [-1.15, 2.09, 6.7, -0.201, 1.21] # diminishing returns
const = -243.1018144

# the function for our model
def model_function(x, media_coefs, media_drs, const):
    # transform variables and multiply them by coefficients to get contributions
    channel_1_contrib = media_coefs[0] * x[0]**media_drs[0]
    channel_2_contrib = media_coefs[1] * x[1]**media_drs[1]
    channel_3_contrib = media_coefs[2] * x[2]**media_drs[2]
    channel_4_contrib = media_coefs[3] * x[3]**media_drs[3]
    channel_5_contrib = media_coefs[4] * x[4]**media_drs[4]

    # sum contributions and add constant
    y = channel_1_contrib + channel_2_contrib + channel_3_contrib + channel_4_contrib + channel_5_contrib + const 

    # return negative conversions for the minimize function to work
    return -y 

# set up guesses, constraints and bounds
num_media_vars = len(media_labels)
guesses = num_media_vars*[media_budget/num_media_vars,] # starting guesses: divide budget evenly

args = (media_coefs, media_drs, const) # pass non-optimized values into model_function

con_1 = {'type': 'eq', 'fun': lambda x: np.sum(x) - media_budget} # so we can't go over budget
constraints = (con_1)

bound = (0, media_budget) # spend for a channel can't be negative or higher than budget
bounds = tuple(bound for x in range(5))

# run the SciPy Optimizer
solution = sco.minimize(model_function, x0=guesses, args=args, method='SLSQP', constraints=constraints, bounds=bounds)

# print out the solution
print(f"Spend: ${round(float(media_budget),2)}\n")
print(f"Optimized CPA: ${round(media_budget/(-1 * solution.fun),2)}")
print("Allocation:")
for i in range(len(media_labels)):
    print(f"-{media_labels[i]}: ${round(solution.x[i],2)} ({round(solution.x[i]/media_budget*100,2)}%)")

结果是

Spend: 0000.0

Optimized CPA: $-0.0
Allocation:
  -launchvideoviews: 000.0 (20.0%)
  -conversion: 000.0 (20.0%)
  -traffic: 000.0 (20.0%)
  -videoviews: 000.0 (20.0%)
  -reach: 000.0 (20.0%)

这与初始 guesses 参数相同。

非常感谢!

更新: 跟着@joni 的评论,我显式地传递了梯度函数,但仍然没有结果。 我不知道如何更改约束以测试@chthonicdaemon 还没评论。


import numpy as np
import scipy.optimize as sco

# setup variables
media_budget = 100000 # total media budget
media_labels = ['launchvideoviews', 'conversion', 'traffic', 'videoviews', 'reach'] # channel names
media_coefs = [0.3524764781, 5.606903166, -0.1761937775, 5.678596017, 10.50445914] # 
# model coefficients
media_drs = [-1.15, 2.09, 6.7, -0.201, 1.21] # diminishing returns
const = -243.1018144

# the function for our model
def model_function(x, media_coefs, media_drs, const):
    # transform variables and multiply them by coefficients to get contributions
    channel_1_contrib = media_coefs[0] * x[0]**media_drs[0]
    channel_2_contrib = media_coefs[1] * x[1]**media_drs[1]
    channel_3_contrib = media_coefs[2] * x[2]**media_drs[2]
    channel_4_contrib = media_coefs[3] * x[3]**media_drs[3]
    channel_5_contrib = media_coefs[4] * x[4]**media_drs[4]

    # sum contributions and add constant (objetive function)
    y = channel_1_contrib + channel_2_contrib + channel_3_contrib + channel_4_contrib + channel_5_contrib + const 

    # return negative conversions for the minimize function to work
    return -y

# partial derivative of the objective function
def fun_der(x, media_coefs, media_drs, const):
   d_chan1 = 1
   d_chan2 = 1
   d_chan3 = 1
   d_chan4 = 1
   d_chan5 = 1
   
   return np.array([d_chan1, d_chan2, d_chan3, d_chan4, d_chan5])
    
# set up guesses, constraints and bounds
num_media_vars = len(media_labels)
guesses = num_media_vars*[media_budget/num_media_vars,] # starting guesses: divide budget evenly

args = (media_coefs, media_drs, const) # pass non-optimized values into model_function

con_1 = {'type': 'eq', 'fun': lambda x: np.sum(x) - media_budget} # so we can't go over budget
constraints = (con_1)

bound = (0, media_budget) # spend for a channel can't be negative or higher than budget
bounds = tuple(bound for x in range(5))

# run the SciPy Optimizer
solution = sco.minimize(model_function, x0=guesses, args=args, method='SLSQP', constraints=constraints, bounds=bounds, jac=fun_der)

# print out the solution
print(f"Spend: ${round(float(media_budget),2)}\n")
print(f"Optimized CPA: ${round(media_budget/(-1 * solution.fun),2)}")
print("Allocation:")
for i in range(len(media_labels)):
    print(f"-{media_labels[i]}: ${round(solution.x[i],2)} ({round(solution.x[i]/media_budget*100,2)}%)")

你无法解决这个确切问题的原因是你所拥有的特定系数。对于指定的问题,最优值似乎接近一些支出为零的分配。然而,由于 media_drs 中的负系数,在支出接近零时,objective 函数迅速变为无穷大。我相信这就是导致您遇到问题的原因。通过将系数中的 6.7 设置为 0.7 并设置大于 0 的下限以阻止 objective 函数爆炸,我可以获得 success = True 的解决方案。所以这与其说是一个编程问题,不如说是一个问题表述问题。

我无法想象当你减少特定项目的预算时你会看到更多的回报,所以media_dirs中的所有负面力量似乎给我。

我也会post在这里我在调试这个问题时做了一些改进。请注意,我更多地使用 numpy 数组来使某些函数更易于阅读。还要注意我是如何计算出正确的雅可比矩阵的:

import numpy as np
import scipy.optimize as sco

# setup variables
media_budget = 100000  # total media budget
media_labels = ['launchvideoviews', 'conversion', 'traffic', 'videoviews', 'reach'] # channel names
media_coefs = np.array([0.3524764781, 5.606903166, -0.1761937775, 5.678596017, 10.50445914]) # 
# model coefficients
media_drs = np.array([-1.15, 2.09, 1.7, -0.201, 1.21]) # diminishing returns
const = -243.1018144

# the function for our model
def model_function(x, media_coefs, media_drs, const):
    # transform variables and multiply them by coefficients to get contributions
    channel_contrib = media_coefs * x**media_drs

    # sum contributions and add constant
    y = channel_contrib.sum() + const 

    # return negative conversions for the minimize function to work
    return -y 

def model_function_jac(x, media_coefs, media_drs, const):
    dy_dx = media_coefs * media_drs * x**(media_drs-1)
    return -dy_dx

# set up guesses, constraints and bounds
num_media_vars = len(media_labels)
guesses = num_media_vars*[media_budget/num_media_vars,] # starting guesses: divide budget evenly

args = (media_coefs, media_drs, const) # pass non-optimized values into model_function

con_1 = {'type': 'ineq', 'fun': lambda x: media_budget - sum(x)} # so we can't go over budget
constraints = (con_1,)

bound = (10, media_budget) # spend for a channel can't be negative or higher than budget
bounds = tuple(bound for x in range(5))

# run the SciPy Optimizer
solution = sco.minimize(
    model_function, x0=guesses, args=args, 
    method='SLSQP', 
    jac=model_function_jac,
    constraints=constraints, 
    bounds=bounds
)

# print out the solution
print(solution)
print(f"Spend: ${round(float(media_budget),2)}\n")
print(f"Optimized CPA: ${round(media_budget/(-1 * solution.fun),2)}")
print("Allocation:")
for i in range(len(media_labels)):
    print(f"-{media_labels[i]}: ${round(solution.x[i],2)} ({round(solution.x[i]/media_budget*100,2)}%)")

这个解决方案至少“有效”,因为它报告了一个成功的解决方案并且returns一个不同于最初猜测的答案。