Программа Mosek не работает, когда в задачу оптимизации добавлены ограничения (переменная 10000, используется Python / cvxpy) - PullRequest
0 голосов
/ 05 декабря 2018

Короче говоря

Приведенная ниже проблема оптимизации объявлена ​​не решаемой при запуске с Mosek, но разрешима (легко и точно) с помощью решателя с открытым исходным кодом ECOS.

Мне интересно: почему такой продвинутый коммерческий решатель, как Мозек, не может решить эту проблему ?

import cvxpy as cvx
import numpy as np


print('cvxpy version:')
print(cvx.__version__)
print('')

np.random.seed(0)

SOLVER = 'ECOS_BB'  # Works fine, sticks to constraint thresholds very precisely
# SOLVER = 'MOSEK'  # Fails when many "sumproduct" constraints are added

def get_objective_function_and_weights(n, means, std_devs):
    weights = cvx.Variable(n)

    # Markowitz-style objective "expectation minus variance" objective function
    objective = cvx.Maximize(
        means * weights
        - cvx.sum_entries(cvx.mul_elemwise(std_devs, weights) ** 2)
    )

    return objective, weights

def calculate_objective_value(weights, means, std_devs):
    expec = weights.T @ means
    cov = np.power(np.diag(std_devs), 2)
    var = weights.T @ cov @ weights
    return float(expec - var)

def get_total_discrepancy(weights, A, b):
    # We want A @ weights <= b
    # Discrepancy is sum of any elements where this is not the case

    values = A @ weights
    assert values.shape == b.shape

    discrepancy_idx = values > b
    discrepancies = values[discrepancy_idx] - b[discrepancy_idx]

    return discrepancies.sum()

def get_weights_gt_0_discrepancy(weights):
    discrepancy_idx = weights < 0
    discrepancies = np.abs(weights[discrepancy_idx])

    return discrepancies.sum()

def get_sum_weights_le_1_discrepancy(weights):
    discrepancy = max(0, weights.sum() - 1)

    return discrepancy

def main():
    n = 10000

    means = np.random.normal(size=n)
    std_devs = np.random.chisquare(df=5, size=n)

    print()
    print(' === BASIC PROBLEM (only slightly constrained) ===')
    print()

    objective, weights = get_objective_function_and_weights(n, means, std_devs)
    constraints = [
        weights >= 0,
        cvx.sum_entries(weights) == 1,
    ]

    # Set up problem and solve
    basic_prob = cvx.Problem(objective, constraints)
    basic_prob.solve(solver=SOLVER, verbose=True)
    basic_weights = np.asarray(weights.value)

    print('Optimal weights')
    print(basic_weights.round(6))
    print('Objective function value:')
    basic_obj_value = calculate_objective_value(basic_weights, means, std_devs)
    print(basic_obj_value)
    print('Discrepancy: all weights > 0')
    print(get_weights_gt_0_discrepancy(basic_weights))
    print('Discrepancy: sum(weights) <= 1')
    print(get_sum_weights_le_1_discrepancy(basic_weights))
    print()
    print()


    print()
    print(' === CONSTRAINED PROBLEM (many added "sumproduct" constraints) ===')
    print()

    objective, weights = get_objective_function_and_weights(n, means, std_devs)

    # We will place all our sumproduct constraints into a single large matrix `A`
    # We want `A` to be a fairly sparse matrix with only a few 1s, mostly 0s
    m = 100  # number of "sumproduct" style constraints
    p = 5  # on average, number of 1s per row in `A`
    A = 1 * (np.random.uniform(size=(m, n)) < p/n)

    # We look at the observed values of A @ weights from the basic
    # problem, and base our constraint on that
    observed_values = (A @ basic_weights).reshape((-1, 1))
    b = observed_values * np.random.uniform(low=0.90, high=1.00, size=(m, 1))

    print('number of 1s in A')
    print(A.sum())

    new_constraints = [
        weights >= 0,
        cvx.sum_entries(weights) == 1,
        A * weights <= b,
    ]

    # Set up problem and solve
    prob = cvx.Problem(objective, new_constraints)
    prob.solve(solver=SOLVER, verbose=True)
    final_weights = np.asarray(weights.value)

    print('Optimal weights')
    print(final_weights.round(6))
    print('Objective function value:')
    constrained_obj_value = calculate_objective_value(final_weights, means, std_devs)
    print(constrained_obj_value)
    print('Difference vs basic')
    print(constrained_obj_value - basic_obj_value)

    # Now calculate the discrepancy - the amount by which the returned
    # optimal weights go beyond the required threshold
    print('Discrepancy: all weights > 0')
    print(get_weights_gt_0_discrepancy(final_weights))
    print('Discrepancy: sum(weights) <= 1')
    print(get_sum_weights_le_1_discrepancy(final_weights))
    print('Discrepancy: sumproduct threshold:')
    print(get_total_discrepancy(final_weights, A, b))


main()

_

Подробнее

Я тестируюнекоторые оптимизаторы, и смотрели на Мосека.Я скачал пробную лицензию и использую Mosek v8.1 и cvxpy 0.4.10.

Я обнаружил, что Mosek, похоже, не очень точно придерживается ограничений или не работает в случаях со многимиограничения.Вот с чем мне бы хотелось помочь - , почему Mosek неточен в этих ограничениях и почему он не работает для четко решаемых задач ?

В приведенном ниже сценарии я решаю простую проблему с помощью толькодва ограничения (все переменные положительные, суммирующие до 1), затем повторно решите его с несколькими добавленными ограничениями, которые я называю «sumproduct» ограничениями.

Эти добавленные ограничения имеют вид "веса для некоторого подмножества переменных должны быть меньше чем b_i" для некоторой константы b_i.Я упаковываю эти ограничения в матричное уравнение A @ weights <= b.

Когда я запускаю скрипт внизу этого поста, используя встроенный решатель ECOS, он легко решает основную проблему, давая оптимальное значение 2,63....:

Objective function value:
2.6338492447784283
Discrepancy: all weights > 0
4.735618828548444e-13
Discrepancy: sum(weights) <= 1
1.3322676295501878e-15

Вы видите, что я также вычисляю то, что я называю несоответствием для каждого ограничения.Это величина, на которую оптимизатор «преодолевает» ограничение в возвращаемых весах.Таким образом, ECOS здесь немного «нарушает правила», определенные ограничениями, но ненамного.

Затем я прошу ECOS решить гораздо более ограниченную проблему с добавлением 100 дополнительных «sumproduct» ограничений.Эти ограничения на конечный продукт имеют вид A @ weights <= b, а A имеет 486, остальные нули.

number of 1s in A
486

Затем я заново решаю проблему и вижу пересмотренный набор оптимальных весов.Оптимальное значение немного меньше, чем было раньше (из-за добавленных ограничений), и ECOS все еще «соблюдает» все ограничения с очень хорошей точностью:

Objective function value:
2.6338405110044203
Difference vs basic
-8.733774008007344e-06
Discrepancy: all weights > 0
5.963041247103521e-12
Discrepancy: sum(weights) <= 1
9.103828801926284e-15
Discrepancy: sumproduct threshold:
0.0

Если я запускаю то же самоеСценарий с Mosek, я нахожу, что по основной проблеме, Mosek решает ее, но уже довольно далеко от одного из ограничений:

Objective function value:
2.633643747862593
Discrepancy: all weights > 0
7.039232392536552e-06
Discrepancy: sum(weights) <= 1
0

Это означает, что у нас есть несколько весов меньше нуля Суммирование в -7e-6, что недостаточно для моего вкуса.

Затем, когда дело доходит до решения более сложной проблемы, Мосек полностью терпит неудачу и объявляет ее PRIMAL_INFEASIBLE.

Может кто-нибудь предложить какие-либо идеи относительно того, почему Мосек терпит неудачу, как это?Я видел, что это было слишком неточно в отношении ограничений и в других случаях.Я пытался повысить точность, используя параметр intpnt_co_tol_pfeas, но всякий раз, когда я делаю это, решатель начинает часто выходить из строя.

Заранее спасибо за любую помощь в этом.Вот мой пример кода.Работа с solver='ECOS' работает, работа с solver='MOSEK' завершается неудачей.

1 Ответ

0 голосов
/ 15 августа 2019

Может быть множество причин, по которым два кода используют разные допуски.Например, возможна ли проблема

1 / x <= 0.0, x> = 0

?Только да, если вы допускаете, что х бесконечен.В других случаях ваша проблема может быть неприятной.

В общем, я бы рекомендовал прочитать

https://docs.mosek.com/9.0/pythonapi/debugging-log.html

и, в частности, краткую информацию о решении.Если это не поможет, отправьте свой вопрос с кратким описанием решения в группу Google Mosek.

...