繁体   English   中英

python中的约束线性优化问题

[英]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]
     ])`

第 1 步:建立数学模型

描述很冗长,而且不是很准确。 因此我不确定这是否是正确的数学模型,但这是我的解释:

在此处输入图片说明

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

当然,在实践中我们会稍微调整一下(单位的变化)。 bx和目标的数字有点大。

约束

假设您指的是以下形式的线性方程组:

轴 = b

其中 a 是代码片段中的值( coefficients )矩阵,而 b 是代码中的常量向量( maxPoints ),那么您正在求解 x。

添加约束在这里没有真正意义,因为系统在以下情况下是可解决的:

  1. coefficients是可逆的(或至少具有伪逆)。
  2. 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算法。 我认为您非常想对矩阵的一部分进行最小二乘解,然后向前迭代。 作为一个合理的算法:

  1. 使用trust-constr解决只有 2 列的问题(对应于 x[4] 和 x[5])。 边界(自由选择BOUND ):
    • x[4]: [-np.inf,BOUND]
    • x[5]: [BOUND, np.inf]
  2. 用 3 列 (x 3 , x[4], x[5]) 再次求解。 边界(用 _p 表示从上一步获得的值):
    • x 3 : [-np.inf,x_p[4]]
    • x[4]: [x_p[4],x_p[5]]
    • x[5]: [x_p[5],np.inf]
  3. 重复类似的操作,直到您解决完整的 6 列。

它不是世界上最好的算法,但是,您的约束将得到满足,并且如果您确实选择了一个错误的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM