[英]Constrained Linear Optimization problem in python
目标 - 使用给定的最大值和系数,在约束范围内求解线性方程
问题 - 定义约束
代码:
import numpy as np
coefficients = np.array([
[0, 9, 6, 9, 4, 0 ],
[0, 9, 7, 7, 3, 2 ],
[0, 9, 5, 9, 3, 2 ],
[0, 11, 2, 6, 4, 5],
[0, 11, 1, 7, 2, 7],
[1, 10, 1, 5, 3, 8]
])
maxPoints = np.array([
[4239100],
[4204767],
[4170434],
[4136101],
[4101768],
[4067435]
])
x = np.linalg.solve(coefficients, maxPoints)
print(x)
输出
[[256694.51339286]
[213778.26339286]
[140820.63839286]
[123654.13839286]
[89321.13839286]
[80737.88839286]]
问题是我想应用一个约束,使其:
x[0] <= x[1] <= x[2] <= x[3] <= x[4] <= x[5]
另一个问题是,这目前只能解决这个较小的矩阵,我需要它来处理更大的矩阵,因为我的 maxPoints 是 1 列 x 32 行,而我的系数是 6 列 x 32 行。 使用上面的 linalg 方法不能解决这个问题。
这是错误消息:
Traceback (most recent call last):
File "Untitled-1.py", line 74, in <module>
X = np.linalg.solve(coefficients, maxPoints)
File "<__array_function__ internals>", line 6, in solve
File "/home/comfortablynumb/.local/lib/python3.7/site-packages/numpy/linalg/linalg.py", line 390, in solve
_assertNdSquareness(a)
File "/home/comfortablynumb/.local/lib/python3.7/site- packages/numpy/linalg/linalg.py", line 213, in _assertNdSquareness
raise LinAlgError('Last 2 dimensions of the array must be square')
numpy.linalg.LinAlgError: Last 2 dimensions of the array must be square
谢谢您的帮助。
编辑:
这是我正在使用的完整数据集
`maxPoints = np.array([
[4239100],
[4204767],
[4170434],
[4136101],
[4101768],
[4067435],
[4033102],
[3998769],
[3964436],
[3930103],
[3895770],
[3861437],
[3827104],
[3792771],
[3758438],
[3724105],
[3689772],
[3655439],
[3621106],
[3586773],
[3552440],
[3518107],
[3483774],
[3449441],
[3415108],
[3380775],
[3346442],
[3312109],
[3277776],
[3243443],
[3209110],
[3174777]])`
`coefficients = np.array([
[0, 9, 6, 9, 4, 0 ],
[0, 9, 7, 7, 3, 2 ],
[0, 9, 5, 9, 3, 2 ],
[0, 11, 2, 6, 4, 5],
[0, 11, 1, 7, 2, 7],
[1, 10, 1, 5, 3, 8],
[2, 9, 1, 5, 2, 9 ],
[2, 8, 2, 4, 3, 9 ],
[2, 8, 2, 3, 4, 9 ],
[2, 8, 1, 4, 1, 12],
[3, 6, 1, 5, 1, 12],
[4, 5, 1, 5, 0, 13],
[5, 4, 1, 5, 0, 13],
[5, 4, 0, 5, 1, 13],
[5, 4, 1, 4, 1, 13],
[5, 4, 2, 3, 1, 13],
[5, 4, 2, 3, 1, 13],
[6, 3, 2, 3, 1, 13],
[6, 3, 2, 2, 1, 14],
[6, 3, 2, 1, 2, 14],
[6, 4, 1, 1, 2, 14],
[6, 4, 1, 1, 0, 16],
[6, 3, 2, 1, 0, 16],
[6, 4, 1, 1, 0, 16],
[6, 4, 1, 1, 0, 16],
[6, 4, 1, 1, 0, 16],
[6, 4, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16]
])`
描述很冗长,而且不是很准确。 因此我不确定这是否是正确的数学模型,但这是我的解释:
r
可以解释为残差。 我认为问题中对最大值的引用意味着b>=Ax
或正如我所说的: r>=0
。 当然,去掉r>=0
限制很容易。
这是一个带有一些边约束的最小二乘问题。 它被表述为二次规划 (QP) 问题。
请注意,也可以用线性目标来表述它:只需最小化 r 的总和。 那会给你一个LP问题。
有了带下的数学模型,写一些代码就很容易了:
import numpy as np
import cvxpy as cp
import pandas as pd
b = np.array([[4239100],[4204767],[4170434],[4136101],[4101768],[4067435],[4033102],[3998769],[3964436],[3930103],
[3895770],[3861437],[3827104],[3792771],[3758438],[3724105],[3689772],[3655439],[3621106],[3586773],[3552440],
[3518107],[3483774],[3449441],[3415108],[3380775],[3346442],[3312109],[3277776],[3243443],[3209110],[3174777]])
A = np.array([[0, 9, 6, 9, 4, 0 ],[0, 9, 7, 7, 3, 2 ],[0, 9, 5, 9, 3, 2 ],[0, 11, 2, 6, 4, 5],[0, 11, 1, 7, 2, 7],
[1, 10, 1, 5, 3, 8],[2, 9, 1, 5, 2, 9 ],[2, 8, 2, 4, 3, 9 ],[2, 8, 2, 3, 4, 9 ],[2, 8, 1, 4, 1, 12],
[3, 6, 1, 5, 1, 12],[4, 5, 1, 5, 0, 13],[5, 4, 1, 5, 0, 13],[5, 4, 0, 5, 1, 13],[5, 4, 1, 4, 1, 13],
[5, 4, 2, 3, 1, 13],[5, 4, 2, 3, 1, 13],[6, 3, 2, 3, 1, 13],[6, 3, 2, 2, 1, 14],[6, 3, 2, 1, 2, 14],
[6, 4, 1, 1, 2, 14],[6, 4, 1, 1, 0, 16],[6, 3, 2, 1, 0, 16],[6, 4, 1, 1, 0, 16],[6, 4, 1, 1, 0, 16],
[6, 4, 1, 1, 0, 16],[6, 4, 1, 1, 0, 16],[7, 3, 1, 1, 0, 16],[7, 3, 1, 1, 0, 16],[7, 3, 1, 1, 0, 16],
[7, 3, 1, 1, 0, 16],[7, 3, 1, 1, 0, 16]])
m,n = np.shape(A)
print("m,n=",m,n)
x = cp.Variable((n,1))
r = cp.Variable((m,1),nonneg=True)
ordered = [x[i] >= x[i-1] for i in range(1,n)]
prob = cp.Problem(cp.Minimize(cp.sum_squares(r)),
[r == b-A@x] + ordered)
prob.solve(verbose=True)
print("x:\n",pd.DataFrame(x.value))
print("r:\n",pd.DataFrame(r.value))
CVXPY 模型由默认 QP 求解器:OSQP 求解。 这是一个相当新的、开源的、一阶算法。 求解器日志如下所示:
-----------------------------------------------------------------
OSQP v0.6.0 - Operator Splitting QP Solver
(c) Bartolomeo Stellato, Goran Banjac
University of Oxford - Stanford University 2019
-----------------------------------------------------------------
problem: variables n = 38, constraints m = 69
nnz(P) + nnz(A) = 278
settings: linear system solver = qdldl,
eps_abs = 1.0e-05, eps_rel = 1.0e-05,
eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
rho = 1.00e-01 (adaptive),
sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
check_termination: on (interval 25),
scaling: on, scaled_termination: off
warm start: on, polish: on, time_limit: off
iter objective pri res dua res rho time
1 0.0000e+00 4.24e+06 1.18e+10 1.00e-01 5.06e-04s
200 5.6400e+11 3.43e+01 9.03e+00 1.03e+00 1.68e-03s
225 5.6410e+11 1.06e+01 2.79e+00 1.03e+00 2.50e-03s
plsh 5.6415e+11 2.79e-09 1.77e-08 -------- 3.22e-03s
status: solved
solution polish: successful
number of iterations: 225
optimal objective: 564145476298.7255
run time: 3.22e-03s
optimal rho estimate: 1.44e+00
解向量x
看起来像:
x:
0
0 -101723.089140
1 60977.386991
2 174769.759793
3 189344.863121
4 208736.990006
5 208736.990006
当然,在实践中我们会稍微调整一下(单位的变化)。 b
、 x
和目标的数字有点大。
约束
假设您指的是以下形式的线性方程组:
轴 = b
其中 a 是代码片段中的值( coefficients
)矩阵,而 b 是代码中的常量向量( maxPoints
),那么您正在求解 x。
添加约束在这里没有真正意义,因为系统在以下情况下是可解决的:
coefficients
是可逆的(或至少具有伪逆)。maxPoints
所有元素都是有限的。LinAlgError
本质上,你可以想到numpy.linalg.solve(a,b)
执行以下操作:
求解: ax = b
for x
从某种意义上说,可以将其视为计算矩阵 a 的逆的numpy
。 在这里, numpy
引发了LinAlgError
因为矩阵不是方阵,因此逆矩阵的定义很差。 numpy
实际上在幕后做了一些其他的工作(参见 LAPACK),但这已经足够了,可以在这里讨论。
我认为可能是问题所在
您有以下问题:
coefficients
的列coefficients
为6,行数为32。maxPoints
的行maxPoints
为 32。 第一步是使用numpy.linalg.lstsq
(请参阅此处)。 您可以使用完全相同的语法。
x_ls = np.linalg.lstsq(coefficients, maxPoints)
这个求解器产生一个最小化平方欧几里得 2-范数的答案。 相反,您希望使用x[j] <= x[j+1]
对所有j < len(x)
的约束最小化。
解决方案的一些步骤
稍后我可能会回来并使用 Github 片段对其进行编辑,因为这是一个非常有趣的问题。
编辑: trust-constr
似乎比 SLSQP 更有效。
我认为您应该进一步阅读scipy
中的优化文档,尤其是trust-constr
scipy
算法。 我认为您非常想对矩阵的一部分进行最小二乘解,然后向前迭代。 作为一个合理的算法:
trust-constr
解决只有 2 列的问题(对应于 x[4] 和 x[5])。 边界(自由选择BOUND
):
[-np.inf,BOUND]
[BOUND, np.inf]
[-np.inf,x_p[4]]
[x_p[4],x_p[5]]
[x_p[5],np.inf]
它不是世界上最好的算法,但是,您的约束将得到满足,并且如果您确实选择了一个错误的BOUND
值,那么迭代性质会稍微纠正一点。 如果您希望获得全局解决方案,您可以对许多不同的 bound 值重复,并查看在最终的trust-constr
拟合中哪个返回最低的错误。
编辑:对于实现:
import numpy as np
from scipy.optimize import minimize
from scipy.optimize import Bounds
# Set up bounds and make a,b objects
a,b = coefficients, maxPoints[:,0]
# Set acceptable bounds to be whatever you like here.
# I've chosen -100000 to 100000 in steps of 10000.
iter_of_acceptable_bounds = range(-100000,100000,10000)
def e2n(x,*args):
'Compute the sum of the residuals (Euclidean 2-norm).'
a,b = args
return np.inner(b-a@x,b-a@x)
solutions, errors = [], []
for BOUND in iter_of_acceptable_bounds:
# Repeat the algorithm for each initial choice of bounds.
# Generate an array for the bounds, based no the initial choice.
bounds_arr = np.concatenate(([-np.inf],[BOUND,BOUND],[np.inf]))
for i in range(2,a.shape[1]+1):
# Initial guess of 0.
x0 = [0 for j in range(i)]
# Start with 2 columns, end at a.shape[1]
# (a.shape[1]==6 for the example).
bounds = Bounds(
*bounds_arr.reshape(i,2).T.tolist()
)
# Slice up a accordingly
A = a[:,-i:]
# Minimize using trust-constr, and unpack the solution
xr = minimize(e2n,x0,
args=(A,b),
bounds=bounds,
method='trust-constr').x
# Generate a new array for the bounds, dynamically based on the previous choices.
bounds_arr = np.concatenate(
[[-np.inf]] + [(xr[j],xr[j]) for j in range(i)] + [[np.inf]]
)
# Save the solution and error from the full matrix
solutions.append(xr)
errors.append(np.sqrt(e2n(xr,a,b)))
这似乎表现得相当好。 如果我们快速绘制一个图:
import matplotlib.pyplot as plt
# lstsq, for comparison (no constraints)
lstsq_fit, lstsq_res, *_ = np.linalg.lstsq(a,b)
lstsq_err = np.sqrt(lstsq_res)
# Plot errors
plt.plot(iter_of_acceptable_bounds,100*(errors-lstsq_err)/lstsq_err)
plt.xlabel('Starting choice of BOUND for algorithm')
plt.ylabel('Increase in error compared to numpy.linalg.lstsq')
plt.show()
一般来说, numpy.linalg.lstsq
可以很好地解决这个问题(它只是一个线性回归问题),但是它没有所需的约束。 然而,一般来说,这个算法是可以的——对于我们的一些BOUND
选择,数据拟合仅比numpy.linalg.lstsq
差 40% 左右。
从这个粗略搜索中,最佳值由以下给出:
>>> my_sol = solutions[errors.index(min(errors))]
>>> print(my_sol)
array([-186017.00778511, 6680.13364168, 184099.89046232,
220587.55247874, 257275.09670101, 257275.09670101])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.