下面的优化问题在与Mosek一起运行时被宣布为不可行,但使用开源求解器ECOS可以(轻松且准确地)解决。
我想知道:为什么像Mosek这样的高级商业解决方案无法解决此问题?
import cvxpy as cvx
import numpy as np
print('cvxpy version:')
print(cvx.__version__)
print('')
np.random.seed(0)
SOLVER = 'ECOS_BB' # Works fine, sticks to constraint thresholds very precisely
# SOLVER = 'MOSEK' # Fails when many "sumproduct" constraints are added
def get_objective_function_and_weights(n, means, std_devs):
weights = cvx.Variable(n)
# Markowitz-style objective "expectation minus variance" objective function
objective = cvx.Maximize(
means * weights
- cvx.sum_entries(cvx.mul_elemwise(std_devs, weights) ** 2)
)
return objective, weights
def calculate_objective_value(weights, means, std_devs):
expec = weights.T @ means
cov = np.power(np.diag(std_devs), 2)
var = weights.T @ cov @ weights
return float(expec - var)
def get_total_discrepancy(weights, A, b):
# We want A @ weights <= b
# Discrepancy is sum of any elements where this is not the case
values = A @ weights
assert values.shape == b.shape
discrepancy_idx = values > b
discrepancies = values[discrepancy_idx] - b[discrepancy_idx]
return discrepancies.sum()
def get_weights_gt_0_discrepancy(weights):
discrepancy_idx = weights < 0
discrepancies = np.abs(weights[discrepancy_idx])
return discrepancies.sum()
def get_sum_weights_le_1_discrepancy(weights):
discrepancy = max(0, weights.sum() - 1)
return discrepancy
def main():
n = 10000
means = np.random.normal(size=n)
std_devs = np.random.chisquare(df=5, size=n)
print()
print(' === BASIC PROBLEM (only slightly constrained) ===')
print()
objective, weights = get_objective_function_and_weights(n, means, std_devs)
constraints = [
weights >= 0,
cvx.sum_entries(weights) == 1,
]
# Set up problem and solve
basic_prob = cvx.Problem(objective, constraints)
basic_prob.solve(solver=SOLVER, verbose=True)
basic_weights = np.asarray(weights.value)
print('Optimal weights')
print(basic_weights.round(6))
print('Objective function value:')
basic_obj_value = calculate_objective_value(basic_weights, means, std_devs)
print(basic_obj_value)
print('Discrepancy: all weights > 0')
print(get_weights_gt_0_discrepancy(basic_weights))
print('Discrepancy: sum(weights) <= 1')
print(get_sum_weights_le_1_discrepancy(basic_weights))
print()
print()
print()
print(' === CONSTRAINED PROBLEM (many added "sumproduct" constraints) ===')
print()
objective, weights = get_objective_function_and_weights(n, means, std_devs)
# We will place all our sumproduct constraints into a single large matrix `A`
# We want `A` to be a fairly sparse matrix with only a few 1s, mostly 0s
m = 100 # number of "sumproduct" style constraints
p = 5 # on average, number of 1s per row in `A`
A = 1 * (np.random.uniform(size=(m, n)) < p/n)
# We look at the observed values of A @ weights from the basic
# problem, and base our constraint on that
observed_values = (A @ basic_weights).reshape((-1, 1))
b = observed_values * np.random.uniform(low=0.90, high=1.00, size=(m, 1))
print('number of 1s in A')
print(A.sum())
new_constraints = [
weights >= 0,
cvx.sum_entries(weights) == 1,
A * weights <= b,
]
# Set up problem and solve
prob = cvx.Problem(objective, new_constraints)
prob.solve(solver=SOLVER, verbose=True)
final_weights = np.asarray(weights.value)
print('Optimal weights')
print(final_weights.round(6))
print('Objective function value:')
constrained_obj_value = calculate_objective_value(final_weights, means, std_devs)
print(constrained_obj_value)
print('Difference vs basic')
print(constrained_obj_value - basic_obj_value)
# Now calculate the discrepancy - the amount by which the returned
# optimal weights go beyond the required threshold
print('Discrepancy: all weights > 0')
print(get_weights_gt_0_discrepancy(final_weights))
print('Discrepancy: sum(weights) <= 1')
print(get_sum_weights_le_1_discrepancy(final_weights))
print('Discrepancy: sumproduct threshold:')
print(get_total_discrepancy(final_weights, A, b))
main()
我正在测试一些优化器,并且一直在研究Mosek。我已经下载了试用许可证,并且正在使用Mosek v8.1和cvxpy 0.4.10。
我发现Mosek似乎并不太准确地遵守约束,或者在有很多约束的情况下失败。这就是我想要的帮助-为什么Mosek在这些约束条件上不够精确,为什么它因可解决的问题而失败?
在下面的脚本中,我用两个约束(所有变量均为正,总和为1)解决了一个简单的问题,然后用几个附加的约束(称为“ sumproduct”约束)重新解决了这个问题。
对于某些常数b_i,这些添加的约束全部为“某些变量子集的权重之和必须小于b_i”的形式。我将这些约束打包到矩阵方程A @ weights <= b
中。
当我使用内置的求解器ECOS在本文底部运行脚本时,它可以轻松解决基本问题,给出的最佳值为2.63 ...:
Objective function value:
2.6338492447784283
Discrepancy: all weights > 0
4.735618828548444e-13
Discrepancy: sum(weights) <= 1
1.3322676295501878e-15
您可以看到,我还在计算每个约束的差异。这是优化器“遍历”返回权重中的约束的量。因此,这里的ECOS稍微有点违反了约束所定义的规则,但并没有太多。
然后我要求ECOS用100个附加的“ sumproduct”约束来解决一个更受约束的问题。这些和积约束的形式为A @ weights <= b
,A
有486个,其余为零。
number of 1s in A
486
然后,我重新解决该问题,并看到一组修改后的最佳权重。最佳值比以前要小一些(由于增加了约束),ECOS仍在“服从”所有约束以达到非常好的准确性:
Objective function value:
2.6338405110044203
Difference vs basic
-8.733774008007344e-06
Discrepancy: all weights > 0
5.963041247103521e-12
Discrepancy: sum(weights) <= 1
9.103828801926284e-15
Discrepancy: sumproduct threshold:
0.0
如果我用Mosek运行相同的脚本,我发现在基本问题上,Mosek可以解决它,但在其中一个约束条件上已经相距甚远了
Objective function value:
2.633643747862593
Discrepancy: all weights > 0
7.039232392536552e-06
Discrepancy: sum(weights) <= 1
0
这意味着我们有几个权重小于零总计为-7e-6,这不足以满足我的喜好。
然后,在解决更受限制的问题时,Mosek完全失败并宣布为PRIMAL_INFEASIBLE
。
对于Mosek为何如此失败,有人可以提供任何想法吗?在其他情况下,我也看到它在约束方面极其不准确。我尝试使用参数intpnt_co_tol_pfeas
来提高精度,但是每当执行此操作时,求解器就会开始经常出现故障。
在此先感谢您的帮助。这是我的示例代码。使用solver='ECOS'
运行有效,使用solver='MOSEK'
运行失败。
答案 0 :(得分:0)
可能有很多原因导致两个代码使用不同的公差。例如是问题
1 / x <= 0.0,x> = 0
可行吗?如果您允许x为无限,则仅是。在其他情况下,您的问题可能令人讨厌。
一般来说,我建议阅读
https://docs.mosek.com/9.0/pythonapi/debugging-log.html
,尤其是有关解决方案摘要。如果这样做没有帮助,请在Mosek google组中发布您的问题以及解决方案摘要。