Рекомендации (функции/решения) для применения в OpenMDAO вместо логических условий (if/else)

Я работаю в течение нескольких месяцев с OpenMDAO, и я обнаружил, что борюсь со своим кодом, когда хочу наложить условия для попытки воспроизвести физическое/техническое поведение.

Я пытался использовать сигмовидные функции, но я все еще не уверен в этом из-за сложности компромисса между чувствительностью и числовой стабилизацией. В большинстве случаев я находил переполнения в exp, поэтому я в конечном итоге включал другие условные выражения (например, np.where), теряя линейность.

outputs['sigmoid'] = 1 / (1 + np.exp(-x))

Я искал другой вид ступенчатой ​​функции или что-то в этом роде, способный сохранить линейность и выводимость для простоты оптимизации. Я не знаю, существует ли что-то подобное или есть ли какая-то стратегия, которая может мне помочь. Если это поможет, я работаю с эталонным тестом OpenConcept, который использует векторизованные вычисления и числовое интегрирование правила Симпсона.

Большое спасибо.

PD: Это мой первый вопрос в stackoverflow, поэтому я хотел бы заранее извиниться за любую ошибку или неверную практику. Надеюсь в конечном итоге сотрудничать и стать активным в сообществе.

Обновление после ответа Джастина:

Я воспользуюсь возможностью, чтобы немного больше определить мою проблему и стратегию, которую я пробовал. Я пытаюсь отслеживать и контролировать термодинамические условия внутри резервуара. Одна из вещей - предпринять действия, когда давление P1 достигает определенного порога P2, для определения этого:

eval= (inputs['P1'] - inputs['P2']) / (inputs['P1'] + inputs['P2'])

# P2 = threshold [Pa]
# P1 = calculated pressure [Pa]

k=100  #steepness control
outputs['sigmoid'] = (1 / (1 + np.exp(-eval * k)))

eval был определен, чтобы избежать переполнения, нормализующего значения, поэтому при повторном кэшировании порога вносятся исправления. Очень похожим образом я определил функцию для проверки наличия еще массы (чтобы потоки могли продолжаться между системами):

eval= inputs['mass']/inputs['max'] 
k=50
outputs['sigmoid'] = (1 / (1 + np.exp(-eval*k)))**3

max также используется для нормализации значения, а показатель степени добавляется для достижения нуля перед входом в отрицательную область.

СЮЖЕТ (извините, я пока не могу публиковать изображения для своей репутации)

Может быть важно подчеркнуть, что и mass, и pressure рассчитываются из связанного интегрирования ОДУ, в котором принимают участие эти функции активации. Я предполагаю, что природа OpenConcept «исследует» множество возможных значений, прежде чем прийти к решению, поэтому в большинстве случаев дает отрицательные недостижимые значения для mass и pressure и создает переполнения. Для этого иногда я пытаюсь включить:

eval[np.where(eval > 1.5)] = 1.5
    eval[np.where(eval < -1.5)] = -1.5

Это не красивое, но иногда эффективное решение. Я стараюсь не использовать его, так как чувствую, что это ограничивает работу решателя и оптимизатора.

Я думаю, что масса и давление - это состояния в вашей ОДУ. Я не уверен на 100%, как работает OpenConcept, но аналогичное программное обеспечение Dymos позволит вам установить некоторые жесткие ограничения для переменных состояния, чтобы они никогда не были меньше нуля. Я подозреваю, что OpenConcept может иметь аналогичные возможности.

Justin Gray 03.02.2023 14:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я обычно обращался к таблице функций активации в Википедии: https://en.wikipedia.org/wiki/Activation_function

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

Dymos использует векторизацию, которая, как мне кажется, похожа на OpenConcept, и я также добился успеха с numpy.where, предоставляя производные для каждой возможной «ветки». Это правда, что у вас могут возникнуть проблемы с несоответствием производных, если у вас есть точка анализа прямо на переходе, но часто, несмотря на это, я добивался успеха. Если производная на переходе становится помехой, то более подходящей является реализация сигмоиды или relu.

Если x имеет такую ​​величину, что может вызвать переполнение, рассмотрите возможность применения единиц или масштабирования, чтобы поместить его в разумные пределы, если вы не можете напрямую связать его.

Пожалуйста. Вероятно, нам следует добавить некоторые стандартные функции активации к нашим стандартным компонентам.

Rob Falck 02.02.2023 18:50

Большое спасибо за ваш добрый ответ, Роб, это было очень полезно. Было бы здорово иметь такие стандартные функции :)

rquiben 02.02.2023 18:54
Ответ принят как подходящий

Я мог бы дать вам более полный ответ, если бы вы перевели свой вопрос на конкретный пример кода функции, с которой вы боретесь, и ее ожидаемый диапазон ввода. Если вы предоставите этот образец кода, я обновлю свой ответ.

В целом, это обычная проблема при использовании оптимизации на основе градиента. Вы хотите, чтобы какое-то поведение, такое как условие, включало/выключало что-то, и во многих случаях это принципиально прерывистая функция.

Чтобы обойти это, мы часто используем сигмовидные функции, но они имеют некоторые числовые проблемы, на которые вы указали. В качестве альтернативы вы можете попробовать гиперболический тангенс, хотя он может столкнуться с теми же проблемами.

Я дам вам два широких варианта:

Опция 1

иногда нормально (даже если не идеально) оставить в коде чисто дискретное условное выражение. Допустим, вы хотели представить простую кусочную функцию:

y = 2x; x>=0 

y = 0; x < 0

В этой функции есть острый угол прямо в 0. Этот угол не дифференцируем, но в остальном функция в порядке. На практике это очень похоже на функцию абсолютного значения, хотя вы можете не провести аналогию, глядя на кусочное определение функции, потому что кусочная природа abs часто скрыта от вас.

Если вы знаете (или, по крайней мере, можете проверить постфактум), что ваш окончательный ответ не будет лежать прямо или очень близко к этому разрыву C1, то, вероятно, можно оставить код таким, какой он есть. Ваши производные будут хорошо определены везде, кроме 0, и вы можете просто выбрать левый или правильный ответ для 0.

Это не совсем математически правильно, но работает нормально, пока вы не застряли прямо здесь.

Вариант 2

Примените функцию сглаживания. Это может быть сигмоида или простой многочлен. Точная природа функции сглаживания сильно зависит от типа разрыва, который вы пытаетесь аппроксимировать.

В случае кусочной функции выше у вас может возникнуть соблазн определить эту функцию как:

2x*sig(x)

Это дало бы вам примерно правильное поведение и было бы везде дифференцируемым. Но вольфрам альфа показывает, что на самом деле немного недотягивает. Это, вероятно, нежелательно, поэтому вы можете увеличить показатель степени, чтобы смягчить это. Однако именно здесь вы начинаете получать проблемы с недостатком и переполнением.

Таким образом, чтобы обойти это и сделать функцию с лучшим поведением, вы могли бы вместо этого определить кусочный полином из трех частей:

y = 2x; x>=a 

y = c0 + c1*x + c2*x**2; -a <= x < a

y = 0 x < -a

вы можете найти коэффициенты как функцию a (пожалуйста, дважды проверьте мою алгебру, прежде чем использовать это!):

c0 = 1.5a

c1 = 2 

c2 = 1/(2a)

Преимущество этого подхода в том, что он никогда не выйдет за рамки допустимого и не станет отрицательным. Вы также можете сделать a достаточно маленьким и получить приличные числа. Но если вы попытаетесь сделать его слишком маленьким, c2 явно взорвется.

В общем, я считаю сигмовидную функцию несколько тупым инструментом. Во многих случаях он отлично работает, но если вы попытаетесь слишком приблизить его к ступенчатой ​​функции, это станет кошмаром. Если вы хотите представить физические процессы, я считаю, что функции полиномиального скругления работают лучше.

Требуется немного усилий, чтобы вывести этот многочлен, потому что вы хотите, чтобы он был непрерывным c1 на обеих сторонах кривой. Таким образом, вы должны построить систему уравнений для ее решения в зависимости от полиномиального порядка и конкретной релаксации, которую вы хотите (здесь 0,1).

Спасибо за ваш добрый и исчерпывающий ответ, Джастин, он был очень полезным, и я уверен, что он также просветит больше людей. Я обязательно попробую с полиномиальными функциями скругления. В любом случае, я приму ваше приглашение и представлю один пример моей проблемы с обновлением вопроса.

rquiben 03.02.2023 11:42

Другие вопросы по теме