Python中的约束线性优化问题

时间:2019-12-28 13:01:57

标签: python numpy optimization

目标-使用给定的最大值和系数,在约束内求解线性方程式

问题-定义约束

代码:

 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列乘32行,系数是6列乘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]
     ])`

2 个答案:

答案 0 :(得分:1)

第1步:建立数学模型

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

enter image description here

r可以解释为残差。我认为问题中提到的最大值意味着b>=Ax或我所说的r>=0。当然,很容易取消r>=0限制。

这是一个最小二乘问题,带有一些侧面约束。它被表述为二次规划(QP)问题。

请注意,也可以用线性目标来表述:只需最小化r的总和即可。那会给你一个LP问题。

第2步:实施,即编写一些代码

有了数学模型,编写一些代码非常容易:

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和物镜的数字有点大。

答案 1 :(得分:0)

约束

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

ax = b

其中a是代码段中的值矩阵(coefficients,而b是代码中的常数矢量(maxPoints),则您要求解x。

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

  1. coefficients是可逆的(或至少具有伪逆)。
  2. maxPoints的所有元素都是有限的。

LinAlgError

实质上,您可以想到numpy.linalg.solve(a,b)会执行以下操作:

解决:ax = b for x

从某种意义上讲,numpy可以计算矩阵a的逆。这里,numpy提出了LinAlgError,因为矩阵不是正方形的,因此逆定义不明确。 numpy实际上在幕后做了一些其他工作(请参阅LAPACK),但这已经足够在此处进行讨论了。

我认为可能是问题所在

您遇到以下问题:

  • coefficients中的列数为6,行数为32。
  • maxPoints中的行数是32。
  • 这是一个无法确定的问题。

第一步是使用numpy.linalg.lstsq(请参阅here)。您可以使用完全相同的语法。

x_ls = np.linalg.lstsq(coefficients, maxPoints)

此求解器给出的答案使平方的欧几里德2范数最小。 相反您要使用x[j] <= x[j+1]对所有j < len(x)的约束来最小化。

一些解决方案的步骤

我可能稍后再使用Github代码段进行编辑,因为这是一个非常有趣的问题。

编辑: trust-constr似乎比SLSQP更好。

我认为您应该进一步了解scipy中的optimization documentation,尤其是trust-constr算法。我认为您非常想对矩阵的一部分做一个最小二乘解,然后向前迭代。作为一种合理的算法:

  1. 仅在两列(对应于x [4]和x [5])的问题上使用trust-constr。界限(自由选择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()

enter image description here

通常,numpy.linalg.lstsq可以很好地解决此问题(这只是线性回归问题),但是它没有所需的约束。但是,总的来说,这种算法是可以的-对于我们选择的numpy.linalg.lstsq,数据仅比BOUND差40%。

通过这种粗略搜索,最佳值由以下给出:

>>> my_sol = solutions[errors.index(min(errors))]
>>> print(my_sol) 
array([-186017.00778511,    6680.13364168,  184099.89046232,
        220587.55247874,  257275.09670101,  257275.09670101])