У меня есть система линейных уравнений 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. Очевидно, что точного решения системы с большой вероятностью просто не существует. Поэтому мне нужно найти такой вектор x0, при котором реализуется минимум функции sum( (Ax-b)^2 ). При этом крайне желательно, чтобы вектор x0 состоял только из 0 и 1. Таким образом, мне нужно решить задачу сумма( (Ax-b)^2 ) -> мин, х из {0,1}
Как вы пытались решить проблему? Сначала я решил упрощенную задачу. sum( (Ax-b)^2 ) -> min, x от реального Проблема была быстро решена с помощью методов тензопотока. Я округлил полученный раствор, но это вылилось в очень плохое качество.
Затем я попытался решить полную проблему. Мне предложили библиотеку CVXPY. Изучив официальный веб-сайт CVXPY, я обнаружил, что мне подойдет решатель для квадратичного класса Mixed-Integer. Я написал программу, используя решатель SCIP. Поскольку ждать оптимального решения было бы слишком долго, я ограничил время решения 200 секундами. Программа работает, но только для малых m и n, например n=4950, m=1600. Если еще увеличить m и n, то SCIP дает в качестве решения простой нулевой вектор, хотя практически любой вектор даже с одной единицей ближе к оптимуму. Вот код программы:
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]))
Мои вопросы следующие:
Обновлять: Я думал, что будет лучше избегать функций с разрывной производной, но «сумма абсолютных ошибок», кажется, работает лучше, чем сумма квадратов, по крайней мере, в моем примере. Что касается матрицы А, то все в порядке. И, к сожалению, точного решения моей проблемы нет. Однако матрица A все же не является шумом, как в примере, и, надеюсь, может дать решение, достаточно близкое к точному. При этом я на 100% уверен, что нулевой вектор крайне далек от оптимального решения, так как некоторые векторы, состоящие из нулей, я проверял с одной "1" и ~80% из них были ближе к оптимальному. Код AirSquid CVXPY у меня работает даже с матрицами до 27000х23000 и без точного решения! Далее у меня просто заканчивается оперативная память... На моих реальных данных сработал вариант с суммой абсолютных ошибок — больше не возвращает нулевой вектор. Но есть еще одна проблема - слишком много 1... Теперь сумма (abs (Ax-b)) меньше с вектором 0, чем с решением, которое дает мне решатель! Я не понимаю, почему это происходит.
Также: насколько важно, чтобы x
был целым? Есть намного, намного более быстрые линейные решатели наименьших квадратов, если это сделать непрерывным.
@Reinderien Как оказалось - очень важно. Я написал, что пробовал использовать сплошной раствор и результат меня не устроил.
Re: обновление: использование ошибки абс позволяет обрабатывать модель как линейную, что дает много преимуществ по сравнению с квадратичной, если вам не нужен SSE. Производные не используются. Решение, приведенное в приведенных ниже примерах, является оптимальным решением. Нулевой вектор является наиболее вероятным решением для созданного вами примера данных, которые искажены. Что касается вашего вопроса о решении для реальных данных, невозможно устранить неполадки без кода, используемого для решения, и вашего контроля качества решения. Если вы можете предоставить образец матрицы в формате таблицы или csv, который может продемонстрировать, что вы получаете...
неоптимальное решение, когда решатель сообщает «оптимальное» (сомнительное), на это было бы интересно посмотреть.
@AirSquid, извините за неточность, решатель не сообщает «оптимально», потому что я останавливаю его через 100 секунд. Я использовал только матрицу 4900x9730 и ожидал получить более оптимальное решение за это время. Еще раз, где я вижу проблему: полученное решение ОЧЕНЬ плохое, оно хуже нулевого вектора. Вы написали, что с моей матрицей нормально получить много нулей в решении. Но за 100 секунд решатель выдал мне вектор с 7000 "1" из 9730.
Вы критичны к этому времени? Мой пример pyomo ниже решен до оптимальности с полноразмерной задачей за 150 секунд. В зависимости от того, какую предварительную обработку должен выполнить решатель, 100 секунд могут быть очень коротким пределом.
@AirSquid Я занимаюсь довольно бесполезной вещью для собственного развлечения, поэтому было бы утомительно ждать полдня решения: D Я только что установил pyomo и CBC, я проверю, как это работает. Но по опыту cvxpy с моими данными задача решается гораздо дольше, чем с "хорошими" данными из ваших примеров.
@AirSquid Я протестировал pyomo с CBC, и он оказался намного лучше, чем что-либо прежде! За 150 секунд на маленькой матрице я получил что-то вроде неплохого результата.
@AirSquid Думаю, моя проблема решена, спасибо!
Я исходил из предположения, что «сумма абсолютных ошибок» достаточно хороша в качестве меры качества решения, а не SSE. Таким образом, модель может быть легко линеаризована, и я думаю, что гораздо более податливым является решатель MIP, а не квадратичный решатель. Обратите внимание, что в обоих приведенных ниже примерах предполагается, что существует хорошее (фактически точное в данном случае) решение. Если матрицы в основном «шумовые», и вы ищете наилучшее соответствие, то решатели почти наверняка будут испытывать гораздо большие трудности, поскольку проблема, вероятно, будет более вырожденной.
Прежде чем взглянуть на решение, я думаю, что есть проблемы с вашей конструкцией A
выше. Создайте небольшую матрицу m x n и посмотрите, соответствует ли она вашим ожиданиям. Я получаю много дубликатов, все положительные значения, поэтому вполне возможно, что «все нули» для вектора x являются оптимальным решением для вектора b
, который должен в среднем ~ 0. Я немного изменил значения положительных/отрицательных значений и конструкцию, чтобы получить желаемую плотность.
Если вы возьмете мою оболочку cvxpy
и переключите ее обратно на SSE, мне будет любопытно посмотреть, какую производительность вы получите по сравнению с линейной. Я не мог заставить подходящий решатель работать для этого, так как я не так часто использую cvxpy
.
cvxpy
является естественным для таких задач, которые легко могут быть выражены в матричном формате. Я прыгаю через несколько обручей в pyomo
, чтобы добраться до той же точки перед решением, но это работает довольно хорошо. В мои 8 лет. старая машина, pyomo
требуется около 200 секунд, чтобы построить задачу максимального размера (как вы видите в результатах)
мое 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
Благодарю за ваш ответ! Мой ответ не помещается в комментарий, поэтому я обновил вопрос. Короче появилась обратная проблема.
Важно ли в вашем приложении использовать сумму квадратов ошибок в качестве меры качества? Если вы можете принять сумму абсолютной ошибки, это можно сделать линейным и (вероятно) будет намного более производительным.