Compute_totals занимает больше времени с аналитическим градиентом по сравнению со сложным шагом

ниже приведен фрагмент кода для проблемы с одним компонентом.

setting self.flag --> 1  uses complex step    
setting self.flag --> 0  uses analytical gradients 

для аппроксимации / вычисления частных производных.

Вычислительное время для вычисления полных производных с опцией 1 требуется около 20 секунд, а с опцией 0 - около 60 секунд.

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

Я проверил вызовы функций, и они кажутся правильными. Я проверил аналитические данные с помощью «cs», они тоже кажутся правильными.

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

import time
import numpy as np
dim1,dim2,dim3=10,40,30
ran1=np.random.random([dim1,dim2,dim3])*5
ran2=np.random.random([dim1,dim2,dim3])*10

from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent

class FDPartialComp(ExplicitComponent):

    def setup(self):
        dim1,dim2,dim3=10,40,30
        self.add_input('var1', val=np.ones([dim1,dim2,dim3]))
        self.add_input('var2', val=np.ones([dim1,dim2,dim3]))
        self.add_output('f', shape=(dim1,))
        self.flag=0 
        self.cou=0
        self.partcou=0
        if self.flag:
            self.declare_partials('*', '*', method='cs')
        else:
            self.declare_partials('f', 'var1',cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
            self.declare_partials('f', 'var2' ,cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))

    def compute(self, inputs, outputs):
        self.cou+=1
        print(self.cou)
        var1 = inputs['var1']
        var2 = inputs['var2']
        m=3
        outputs['f'] = np.sum((var2*var1**m),axis=(1,2))        

    def compute_partials(self, inputs, partials):
        if self.flag:
            pass
        else:
            m=3
            var1 = inputs['var1']
            var2 = inputs['var2']        
            partials['f','var1'] =(var1**m*m*var2/var1).flatten()
            partials["f","var2" ]= (var1**m).flatten()
            self.partcou+=1
            print(self.partcou)

model = Group()
comp = IndepVarComp()

comp.add_output('var1', ran1)
comp.add_output('var2', ran2)
#comp.add_output('var1', np.ones([dim1,dim2,dim3])*5)
#comp.add_output('var2', np.ones([dim1,dim2,dim3])*10)
model.add_subsystem('input', comp,promotes=['*'])
model.add_subsystem('example', FDPartialComp(),promotes=['*'])

problem = Problem(model=model)
problem.setup(check=True)
#problem.run_model()
st=time.time()
totals = problem.compute_totals(['f'], ['var1','var2'])
#problem.setup(force_alloc_complex=True)
#problem.check_partials(compact_print=True,method='cs')
print(time.time()-st)

ПОСЛЕ ОТВЕТА я добавил снимок вычислительного времени, потраченного на РАЗЛИЧНЫЕ ЧАСТИ КОДА.

Compute_totals занимает больше времени с аналитическим градиентом по сравнению со сложным шагом

Стоит ли изучать 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
0
132
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Разница в производительности, которую вы видите, связана с внутренними структурами данных в OpenMDAO. Ваша модель, когда даны аналитические производные, указывается с использованием разреженного формата (это хорошо, поскольку оно очень разреженное!). Но чтобы действительно воспользоваться этим, вам нужно использовать формат собранная матрица для хранения частных производных данных и прямой решатель для вычисления разреженной факторизации LU. После того, как вы добавите эти функции в свою модель, аналитика станет лучше, чем с CS.

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

Вот обновленный скрипт, который показывает правильную производительность (я сделал ввод меньшего размера, чтобы он работал быстрее)

import time
import numpy as np

# dim1,dim2,dim3=10,40,30
dim1,dim2,dim3=10,40,5

ran1=np.random.random([dim1,dim2,dim3])*5
ran2=np.random.random([dim1,dim2,dim3])*10

from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent, DirectSolver

class FDPartialComp(ExplicitComponent):

    def setup(self):

        self.add_input('var1', val=np.ones([dim1,dim2,dim3]))
        self.add_input('var2', val=np.ones([dim1,dim2,dim3]))
        self.add_output('f', shape=(dim1,))
        self.flag=0
        self.cou=0
        self.partcou=0

        if self.flag:
            self.declare_partials('*', '*', method='cs')
        else:
            self.declare_partials('f', 'var1',cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))
            self.declare_partials('f', 'var2' ,cols=np.arange(dim2*dim3*dim1),rows=np.repeat(np.arange(dim1),dim2*dim3))

    def compute(self, inputs, outputs):
        self.cou+=1
        # print(self.cou)
        var1 = inputs['var1']
        var2 = inputs['var2']
        m=3
        outputs['f'] = np.sum((var2*var1**m),axis=(1,2))

    def compute_partials(self, inputs, partials):
        if self.flag:
            pass
        else:
            m=3
            var1 = inputs['var1']
            var2 = inputs['var2']
            partials['f','var1'] = (var1**m*m*var2/var1).flatten()
            partials['f','var2' ]= (var1**m).flatten()
            self.partcou+=1
            # print(self.partcou)

model = Group()
comp = IndepVarComp()

comp.add_output('var1', ran1)
comp.add_output('var2', ran2)
#comp.add_output('var1', np.ones([dim1,dim2,dim3])*5)
#comp.add_output('var2', np.ones([dim1,dim2,dim3])*10)
model.add_subsystem('input', comp,promotes=['*'])
model.add_subsystem('example', FDPartialComp(),promotes=['*'])


model.linear_solver = DirectSolver(assemble_jac=True)

problem = Problem(model=model)
problem.setup(check=True, mode='fwd')

problem.final_setup()

# exit()
#problem.run_model()
st=time.time()
totals = problem.compute_totals(['f'], ['var1','var2'])
#problem.check_partials(compact_print=True,method='cs')
print(time.time()-st)
print(problem._mode)

По-прежнему требуется меньше времени с CS с настройкой по умолчанию (без прямого решателя, с однократным использованием линейного прогона) Как аналитический, так и CS занимает одинаковое время, см. Недавно добавленное изображение (что означает, что CS требуется меньше времени для решения фактической матрицы с учетом compute метод вызывается много раз)

user2375049 28.12.2018 07:55
Ответ принят как подходящий

Я взглянул на код OpenMDAO, чтобы понять, почему CS без прямого решателя работает так же быстро, как аналитические производные без прямого решателя. В процессе этого я обнаружил несколько мест, где мы использовали вызовы numpy.add.at внутри себя, и эти вызовы были довольно медленными. Я заменил эти звонки намного более быстрыми звонками на numpy.bincount. Показанные здесь числа используют те улучшения кода, которые теперь были объединены в основную ветку OpenMDAO после фиксации 7f13fda. Эти улучшения будут выпущены в V2.9.

После недавних изменений в OpenMDAO я получаю следующие тайминги:

analytical derivs w/o direct solver (fwd mode):  13.55 s
analytical derivs with direct solver (fwd mode): 27.02 s
CS w/o direct solver (fwd mode):                 15.76 s

Обратите внимание, что теперь аналитические производные без DirectSolver на самом деле быстрее, чем CS, но если мы посмотрим немного глубже с помощью профилировщика, мы увидим кое-что интересное.

solve_linear time (analytical):  12.65000 s 
linearize time (analytical):    + 0.00195 s   (1 call to compute_partials)
                                 ----------
                                 12.65195 s


solve_linear time (CS):           9.63 s 
linearize time (CS):            + 4.81 s    (24,000 compute calls)
                                 -------
                                 14.44 s

Таким образом, detect_linear все еще быстрее в CS. Причина этого в том, что для CS партиалы объявляются плотными (в настоящее время это единственный способ сделать это, поскольку мы еще не поддерживаем объявление разреженных партиалов при использовании FD или CS). Когда частичные элементы объявлены как плотные, произведения матрица-вектор, внутренние для решения_linear, выполняются с использованием быстрого вызова numpy.dot, но когда частичные элементы объявлены как разреженные, как в вашем примере при использовании аналитических производных, тогда мы используем более медленную функцию. В то время, когда вы определяли тайминги, мы использовали numpy.add.at, который, как упоминалось выше, очень медленный. Сейчас мы используем numpy.bincount, который намного быстрее, но все же не так быстро, как numpy.dot, так что в этом разница.

Кроме того, поскольку ваш общий якобиан в этом случае имеет форму (10 x 24000), я настоятельно рекомендую использовать режим rev вместо режима fwd, поэтому вы будете выполнять 10 линейных решений вместо 24000. Когда я это сделаю, , Я получаю эти тайминги:

analytical derivs w/o direct solver (rev mode):  0.01 s
analytical derivs with direct solver (rev mode): 0.04 s
CS w/o direct solver (rev mode):                 4.86 s

Случай с аналитической производной теперь явно является победителем.

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

Спасибо за подробный анализ и ответ на наши вопросы. Также здорово, что вы упомянули файл rev / fwd. Думаю, во время тестирования я забыл обратить на это внимание и оставил как есть.

user2375049 12.09.2019 15:01

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