Часто задаваемые вопросы
|
Страница в процессе перевода. |
Похоже, что в ходе оптимизации решатель нарушает ограничения, из-за чего возникают ошибки DomainError. Что с этим делать?
В процессе оптимизации оптимизаторы используют ослабляющие переменные, чтобы ослабить ограничения с целью получения решения. По этой причине в случае с произвольным оптимизатором нет гарантии, что в ходе оптимизации все шаги будут удовлетворять ограничениям. Это часто может приводить к выдаче ошибки DomainError кодом целевой функции из-за выхода за пределы приемлемой области во время вычисления. Например, log(-1) выдаст следующее:
julia> log(-1) ERROR: DomainError with -1.0: log will only return a complex result if called with a complex argument. Try log(Complex(x)).
С учетом этого не следует предполагать, что переменные будут удовлетворять ограничениям на каждом шаге. Есть три основных способа справиться с этой проблемой:
-
Используйте NaNMath.jl.
-
Обрабатывайте переменные перед вызовами с ограниченной областью определения.
-
Используйте преобразование области определения.
NaNMath.jl предоставляет альтернативные реализации стандартных математических функций, таких как log и sqrt, в форме, которая не выдает DomainError, а возвращает NaN. Оптимизаторы могут корректно обрабатывать значения NaN и продолжать работу, что позволяет справляться с многими из подобных случаев без дополнительных изменений. Обратите внимание, что это делается внутри JuMP.jl, поэтому если в конкретном случае JuMP работает правильно, а Optimization.jl нет, причина может крыться в этом.
В качестве альтернативы можно предварительно обработать значения напрямую. Например, log(abs(x)) точно будет работать. Если идти по этому пути, нужно учитывать два момента. Во-первых, решение само по себе не преобразуется, поэтому преобразование необходимо также применить к sol.u. Например, если решением является оптимум для x = -2 и в целевой функции используется версия abs, значение следует вручную изменить на x = 2. Обратите внимание, что в случае со многими функциями при этом возникнет разрыв в производной, что может повлиять на процесс оптимизации.
Наконец, есть еще один сходный способ: можно реализовать оптимизацию с преобразованиями области определения, чтобы оптимизация была возможна во всем множестве вещественных значений. Например, вместо оптимизации x in [0,Inf] можно оптимизировать exp(x) in [0,Inf], так что x in [-Inf, Inf] будет допустимо без каких-либо ограничений. Для этого достаточно добавить преобразования в начало целевой функции:
function my_objective(u)
x = exp(u[1])
# ... используем x
end
После завершения оптимизации sol.u[1] будет exp(x), таким образом, log(sol.u[1]) будет оптимальным значением для x. В экосистеме Julia есть пакеты, которые упрощают отслеживание таких преобразований области определения и их обратных преобразований для более общих областей определения. Высокоуровневые интерфейсы для этой цели — TransformVariables.jl и Bijectors.jl.
Хотя это позволяет переписать оптимизацию с ограничениями как оптимизацию без ограничений, следует отметить, что при этом могут измениться численные свойства решения, что может либо улучшить, либо ухудшить численную устойчивость в каждом конкретном случае. Таким образом, хотя это и является решением, следует помнить, что в некоторых ситуациях оно может затруднить оптимизацию.
Каковы преимущества и недостатки использования ModelingToolkit.jl или других символьных интерфейсов (JuMP)?
Чисто числовые интерфейсы функций Optimization.jl имеют свои плюсы и минусы. Главное преимущество прямого интерфейса Optimization.jl в том, что он может принимать произвольные программы Julia. Если для программы определена оптимизация, например нейронное обыкновенное дифференциальное уравнение или некий код, который обращается к веб-серверам, такие расширенные конфигурации редко работают в специализированных символьных средах для оптимизации. Для такого рода задач предпочтительнее использовать Optimization.jl напрямую, что и делается обычно в экосистеме Julia по причине простоты этого подхода.
Однако символьные интерфейсы обладают интеллектуальными возможностями и могут лучше знать, как ускорить оптимизацию. И символьные интерфейсы готовы выполнять «рутинную работу» ради того, чтобы сделать оптимизацию более эффективной. Например, интеграция ModelingToolkit с Optimization.jl позволяет выполнять множество упрощений при вызове structural_simplify. Одно из них — развертывание ограничений. Чтобы понять, как работает развертывание, предположим, что у нас есть нелинейные ограничения следующего вида:
0 ~ u1 - sin(u5) * h,
0 ~ u2 - cos(u1),
0 ~ u3 - hypot(u1, u2),
0 ~ u4 - hypot(u2, u3),
При таких ограничениях можно записать u1 = sin(u5) * h и подставить это значение вместо u1 в целевой функции. В таком случае не придется решать задачу с учетом u1, и при оптимизации будет на одну переменную состояния и на одно ограничение меньше. Этот процесс можно распространить на множество функций:
u1 = f1(u5)
u2 = f2(u1)
u3 = f3(u1, u2)
u4 = f4(u2, u3)
Таким образом, если целевая функция является функцией этих 5 переменных и 4 ограничений, ModelingToolkit.jl преобразует ее в систему с 1 переменной и без ограничений, что позволит выполнить оптимизацию без ограничений для меньшей системы. Это будет и быстрее, и проще с численной точки зрения.
JuMP.jl — еще один символьный интерфейс. Хотя он не предусматривает развертывания и символьных упрощений, он включает возможность специализировать процесс решения. Например, он может обрабатывать задачи линейной оптимизации, квадратичной оптимизации, выпуклой оптимизации и т. д. особыми способами, которые более эффективны по сравнению с общим нелинейным интерфейсом. Дополнительные сведения о типах специальных решений, допускаемых в JuMP, см. на этой странице.