高级功能¶
本教程的这一部分涵盖了CVXPY的功能,面向具有凸优化高级知识的用户。我们推荐Boyd和Vandenberghe的《凸优化》作为参考,以了解您不熟悉的任何术语。
N维表达式¶
在版本1.6中新增。
CVXPY 现在支持 N 维表达式。这使得用户可以定义具有任意维度的变量、参数和常量。 这一新功能使用户能够以更自然的方式对多维数据的问题进行建模。
在下面的例子中,我们考虑一个问题,其目标是优化资源在多个地点、天数和小时的使用。 我们现在能够轻松地在任何维度的组合上形成约束。
# create a 3-dimensional variable (locations, days, hours)
x = cp.Variable((12, 10, 24))
constraints = [
cp.sum(x, axis=(0, 2)) <= 2000, # constrain the daily usage across all locations
x[:, :, :12] <= 100, # constrain the first 12 hours of each day at every location
x[:, 3, :] == 0,] # constrain the usage on the fourth day to be zero
obj = cp.Minimize(cp.sum_squares(x))
prob = cp.Problem(obj, constraints)
prob.solve()
请参考NumPy的优秀参考 关于N维数组和数组API标准以获取更多详细信息 关于如何操作N维数组。我们的目标是尽可能接近NumPy API。
警告
N维支持仍然是实验性的,可能不适用于所有CVXPY功能。 如果您遇到任何问题或功能缺失,请在GitHub issues上报告。
对偶变量¶
你可以使用CVXPY来找到问题的最优对偶变量。当你调用prob.solve()时,解决方案中的每个对偶变量都存储在其对应的约束的dual_value字段中。
import cvxpy as cp
# Create two scalar optimization variables.
x = cp.Variable()
y = cp.Variable()
# Create two constraints.
constraints = [x + y == 1,
x - y >= 1]
# Form objective.
obj = cp.Minimize((x - y)**2)
# Form and solve problem.
prob = cp.Problem(obj, constraints)
prob.solve()
# The optimal dual variable (Lagrange multiplier) for
# a constraint is stored in constraint.dual_value.
print("optimal (x + y == 1) dual variable", constraints[0].dual_value)
print("optimal (x - y >= 1) dual variable", constraints[1].dual_value)
print("x - y value:", (x - y).value)
optimal (x + y == 1) dual variable 6.47610300459e-18
optimal (x - y >= 1) dual variable 2.00025244976
x - y value: 0.999999986374
x - y >= 1 的对偶变量是 2。根据互补性,这意味着 x - y 是 1,我们可以看到这是正确的。对偶变量非零的事实还告诉我们,如果我们收紧 x - y >= 1(即增加右侧的值),问题的最优值将会增加。
转换¶
变换提供了除了原子函数之外的额外方式来操作CVXPY对象。例如,indicator 变换将一系列约束转换为一个表达式,表示当约束成立时取值为0的凸函数,当约束被违反时取值为\(\infty\)。
x = cp.Variable()
constraints = [0 <= x, x <= 1]
expr = cp.transforms.indicator(constraints)
x.value = .5
print("expr.value = ", expr.value)
x.value = 2
print("expr.value = ", expr.value)
expr.value = 0.0
expr.value = inf
完整的变换集在Transforms中讨论。
问题算术¶
为了方便,算术操作已被重载用于问题和目标。 问题算术非常有用,因为它允许你将一个问题写成多个较小问题的总和。 下面给出了目标相加、相减和相乘的规则。
# Addition and subtraction.
Minimize(expr1) + Minimize(expr2) == Minimize(expr1 + expr2)
Maximize(expr1) + Maximize(expr2) == Maximize(expr1 + expr2)
Minimize(expr1) + Maximize(expr2) # Not allowed.
Minimize(expr1) - Maximize(expr2) == Minimize(expr1 - expr2)
# Multiplication (alpha is a positive scalar).
alpha*Minimize(expr) == Minimize(alpha*expr)
alpha*Maximize(expr) == Maximize(alpha*expr)
-alpha*Minimize(expr) == Maximize(-alpha*expr)
-alpha*Maximize(expr) == Minimize(-alpha*expr)
添加和乘法问题的规则同样简单明了:
# Addition and subtraction.
prob1 + prob2 == Problem(prob1.objective + prob2.objective,
prob1.constraints + prob2.constraints)
prob1 - prob2 == Problem(prob1.objective - prob2.objective,
prob1.constraints + prob2.constraints)
# Multiplication (alpha is any scalar).
alpha*prob == Problem(alpha*prob.objective, prob.constraints)
请注意,+ 运算符会连接约束列表,因为这是 Python 列表的默认行为。
就地运算符 +=、-= 和 *= 也支持目标和问题,并遵循与上述相同的规则。
获取标准形式¶
如果您对获取CVXPY为问题生成的标准形式感兴趣,您可以使用get_problem_data方法。当问题被解决时,SolvingChain会将与目标求解器兼容的低级表示传递给求解器,求解器会解决问题。此方法返回该低级表示,以及用于将解解包到问题中的SolvingChain和元数据。此低级表示与提供给求解器的参数非常相似,但并不完全相同。
可以通过调用返回的求解链的solve_via_data方法,从数据中获得等效低级问题的解决方案,这是一个围绕CVXPY外部代码的薄包装,进一步处理和解决问题。调用unpack_results方法以恢复原始问题的解决方案。
例如:
problem = cp.Problem(objective, constraints)
data, chain, inverse_data = problem.get_problem_data(cp.SCS)
# calls SCS using `data`
soln = chain.solve_via_data(problem, data)
# unpacks the solution returned by SCS into `problem`
problem.unpack_results(soln, chain, inverse_data)
或者,此方法返回的data字典包含足够的信息,可以绕过CVXPY并直接调用求解器。
例如:
problem = cp.Problem(objective, constraints)
probdata, _, _ = problem.get_problem_data(cp.SCS)
import scs
data = {
'A': probdata['A'],
'b': probdata['b'],
'c': probdata['c'],
}
cone_dims = probdata['dims']
cones = {
"f": cone_dims.zero,
"l": cone_dims.nonpos,
"q": cone_dims.soc,
"ep": cone_dims.exp,
"s": cone_dims.psd,
}
soln = scs.solve(data, cones)
CVXPY 返回的数据字典的结构取决于求解器。有关详细信息,请打印字典,或查阅 cvxpy/reductions/solvers 中的求解器接口。
规范化后端¶
用户可以通过向.solve()调用添加canon_backend关键字参数来选择多个规范化后端,例如problem.solve(canon_backend=cp.SCIPY_CANON_BACKEND)(在CVXPY 1.3中引入)。
这可以显著加快某些问题的规范化时间。
目前,支持以下规范化后端:
CPP(默认):原始的C++实现,也称为CVXCORE。
- SCIPY: 基于SciPy稀疏模块的纯Python实现。对于已经向量化的问题通常速度较快。
NUMPY: 纯NumPy的参考实现。对于一些小型或密集问题速度较快。