我有一个线性方程组 Ax=b,A - 矩阵 (m x n),x - (n x 1),b - (m x 1)。矩阵 A 几乎全为 0,只包含 ~ n*m^(1/2) 个非零元素。 m 和 n 可以很大,例如 n=28680,m=22500 或 n=28680,m=62500。很明显,系统的高概率精确解根本不存在。因此,我需要找到实现 sum( (Ax-b)^2 ) 函数的最小值的向量 x0。同时,非常希望向量 x0 仅由 0 和 1 组成。因此,我需要解决问题 sum( (Ax-b)^2 ) -> min, x 来自 {0,1}
你是如何尝试解决这个问题的? 首先,我解决了一个简化的问题。 sum( (Ax-b)^2 ) -> min, x 来自实数 使用tensoflow方法很快就解决了这个问题。我汇总了最终的解决方案,但这导致质量很差。
然后我试图解决完整的问题。向我推荐了 CVXPY 库。通过研究 CVXPY 官方网站,我发现它适合我对混合整数二次类使用求解器。我使用 SCIP 求解器编写了一个程序。由于等待最优解的时间太长,我将求解时间限制在 200 秒。该程序有效,但仅适用于较小的 m 和 n,例如 n=4950,m=1600。如果进一步增加 m 和 n,则 SCIP 会给出一个简单的零向量作为解决方案,尽管实际上任何包含一个 1 的向量都更接近最佳值。这是程序代码:
import cvxpy as cp
import numpy as np
#I tried to simulate my inputs for you.
#Here the problem manifests itself already at m, n = 1600, 4950.
np.random.seed(0)
m, n= 1600, 4950
A = np.round(np.random.rand(m, n)/(1-1/m**(1/2))/2)*255*(m**(1/2)/1800)**1.1
b = 255*(np.random.randn(m))
x = cp.Variable(n, integer=True)
objective = cp.Minimize(cp.sum_squares(A @ x - b))
constraints = []
constraints.extend([x >= 0.0, x <= 1])
prob = cp.Problem(objective,constraints)
prob.solve(solver=cp.SCIP,verbose=True,scip_params={"limits/time": 50})
print("Status: ", prob.status)
print("The optimal value is", prob.value)
print("A solution x is")
print(x.value)
print("Non-zero ",sum([i for i in x.value]))
我的问题如下:
我假设“绝对误差之和”足以作为解决方案而不是 SSE 的质量衡量标准。通过这样做,模型可以很容易地线性化,我认为 MIP 求解器比二次求解器更容易处理。请注意,下面的两个示例都assume有一个good(在这种情况下实际上是精确的)解决方案。如果矩阵基本上是“噪声”并且您正在寻找最合适的矩阵,那么求解器几乎肯定会更加困难,因为问题可能更加退化。
在查看解决方案之前,我认为您对上述
A
的构建存在问题。做一个小的 m x n 矩阵,看看它是否是你所期望的。我得到了很多重复的,所有的正值,所以 x 向量 的“全零”完全有可能是 b
向量的最佳解决方案,应该平均为 ~0。我用正/负值和结构做了一些改变,以获得你想要的密度。
如果您使用我的
cvxpy
shell 并将其切换回 SSE,我很想知道您获得的性能与线性性能相比如何。我无法找到合适的求解器来解决这个问题,因为我不常使用cvxpy
。
cvxpy
对于像这样可以很容易地用矩阵格式表达的问题来说是很自然的。我在 pyomo
中跳过几个圈以在解决之前到达同一点,但它工作得很好。在我 8 岁的时候。旧机器,pyomo
需要大约 200 秒来构建最大尺寸的问题(如您在结果中看到的那样)
my
cvxpy
下面的解决方案适用于较小的实例,但 barfs 对于 20k x 20k 左右的矩阵“不可行”,所以可能有一些我不理解的内部内容,因为它显然是可行的。也许对cvxpy
有更好了解的人可以修补并看看发生了什么。
import cvxpy as cp
import numpy as np
from time import time
#I tried to simulate my inputs for you.
#Here the problem manifests itself already at m, n = 1600, 4950.
np.random.seed(0)
m, n= 5_000, 5_000 # 20_000, 20_000 <--- barfs
A_base = (np.random.randint(-1000, 1000, (m, n)))
marks = np.random.binomial(n=1, p=(n*m)**0.5/(m*n), size=(m, n))
A = np.where(marks, A_base, 0)
true_x = np.random.binomial(n=1, p=0.6, size=n)
b = A @ true_x
tic = time()
x = cp.Variable(n, integer=True)
objective = cp.Minimize(cp.sum(cp.abs(A @ x - b)))
constraints = []
constraints.extend([x >= 0.0, x <= 1])
prob = cp.Problem(objective,constraints)
res=prob.solve() #solver=cp.SCIP,verbose=True,scip_params={"limits/time": 50})
toc = time()
print("Status: ", prob.status)
print("The optimal value is", prob.value)
print("A solution x is")
print(x.value)
print("Non-zero ",sum([i for i in x.value if i is not None ]))
print(f'solver time: {toc-tic : 0.1f} sec.')
Status: optimal
The optimal value is 0.0
A solution x is
[ 1. 1. 1. ... -0. 1. -0.]
Non-zero 1907.0
solver time: 3.5 sec.
[Finished in 5.5s]
import numpy as np
import pyomo.environ as pyo
from time import time
import sys
np.random.seed(0)
m, n = 28_680, 62_500
A_base = (np.random.randint(-1000, 1000, (m, n)))
marks = np.random.binomial(n=1, p=(n*m)**0.5/(m*n), size=(m, n))
A = np.where(marks, A_base, 0)
true_x = np.random.binomial(n=1, p=0.6, size=n)
b = A @ true_x
# b = np.random.randint(-100, 100, n)
# print(b)
tic = time()
model = pyo.ConcreteModel()
model.N = pyo.Set(initialize=range(n))
model.M = pyo.Set(initialize=range(m))
# initializing x only because some values of x[n] may end up unconstrained and this prevents "None" in result
model.x = pyo.Var(model.N, within=pyo.Binary, initialize=0)
model.row_err = pyo.Var(model.M)
model.row_abs_err = pyo.Var(model.M)
# constrain the rowsum/error appropriately
@model.Constraint(model.M)
def rowsum(model, m):
return model.row_err[m] == sum(A[m,n] * model.x[n] for n in model.N if A[m,n]) - b[m]
# constrain the abs error
@model.Constraint(model.M)
def abs_1(model, m):
return model.row_abs_err[m] >= model.row_err[m]
@model.Constraint(model.M)
def abs_2(model, m):
return model.row_abs_err[m] >= -model.row_err[m]
# minimize sum of absolute row errors
model.obj = pyo.Objective(expr=sum(model.row_abs_err[m] for m in model.M))
toc = time()
print(f'starting solver... at time: {toc-tic: 0.1f} sec.')
solver = pyo.SolverFactory('cbc')
res = solver.solve(model)
toc = time()
print(f'total solve time: {toc-tic: 0.1f} sec')
print(res)
# model.x.display()
x = np.array([pyo.value(model.x[n]) for n in model.N], dtype=int)
print(f'verifying solution: {np.sum(A@x - b)}')
starting solver... at time: 228.2 sec.
solver time: 384.8 sec
Problem:
- Name: unknown
Lower bound: 0.0
Upper bound: 0.0
Number of objectives: 1
Number of constraints: 40941
Number of variables: 50488
Number of binary variables: 30839
Number of integer variables: 30839
Number of nonzeros: 20949
Sense: minimize
Solver:
- Status: ok
User time: -1.0
System time: 123.18
Wallclock time: 152.91
Termination condition: optimal
Termination message: Model was solved to optimality (subject to tolerances), and an optimal solution is available.
Statistics:
Branch and bound:
Number of bounded subproblems: 21
Number of created subproblems: 21
Black box:
Number of iterations: 2668
Error rc: 0
Time: 153.19930219650269
Solution:
- number of solutions: 0
number of solutions displayed: 0
verifying solution: 0