compute_totals 与复杂步骤相比,分析梯度需要更长的时间
compute_totals takes longer with analytical gradient vs complex step
下面是单个组件问题的代码片段。
setting self.flag --> 1 uses complex step
setting self.flag --> 0 uses analytical gradients
到approximate/compute偏导数。
计算时间
使用选项 1 计算总导数所需的时间约为 20 秒,选项 0 约为 60 秒。
我的预期恰恰相反,因为有数以千计的 'compute' 函数调用具有复杂的步骤。
我检查了函数调用,它们似乎是正确的。我用 '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)
根据答案,我添加了一个快照,用于记录在代码的各个部分所花费的计算时间
您看到的性能差异与 OpenMDAO 中的内部数据结构有关。当给出解析导数时,您的模型是使用稀疏格式指定的(这很好,因为它非常稀疏!)。但要真正利用它,您需要使用 assembled matrix format for the partial derivative data storage and a direct solver 来计算稀疏 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)
我查看了 OpenMDAO 代码,以了解为什么没有直接求解器的 CS 运行 与没有直接求解器的解析导数一样快。在这样做的过程中,我发现了一些我们在内部使用 numpy.add.at
调用的地方,而且这些调用非常慢。我用更快的 numpy.bincount
调用替换了这些调用。此处显示的数字使用了那些代码改进,这些改进现已合并到 OpenMDAO master b运行ch 中,截至提交 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
所以solve_linear在CS下还是比较快的。原因是对于 CS,部分被声明为密集(这是目前唯一的方法,因为我们还不支持在使用 FD 或 CS 时声明稀疏部分)。当部分被声明为密集时,solve_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 非线性求解的数量始终是确定的按部分雅可比矩阵中的列数。
下面是单个组件问题的代码片段。
setting self.flag --> 1 uses complex step
setting self.flag --> 0 uses analytical gradients
到approximate/compute偏导数。
计算时间 使用选项 1 计算总导数所需的时间约为 20 秒,选项 0 约为 60 秒。
我的预期恰恰相反,因为有数以千计的 'compute' 函数调用具有复杂的步骤。
我检查了函数调用,它们似乎是正确的。我用 '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)
根据答案,我添加了一个快照,用于记录在代码的各个部分所花费的计算时间
您看到的性能差异与 OpenMDAO 中的内部数据结构有关。当给出解析导数时,您的模型是使用稀疏格式指定的(这很好,因为它非常稀疏!)。但要真正利用它,您需要使用 assembled matrix format for the partial derivative data storage and a direct solver 来计算稀疏 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)
我查看了 OpenMDAO 代码,以了解为什么没有直接求解器的 CS 运行 与没有直接求解器的解析导数一样快。在这样做的过程中,我发现了一些我们在内部使用 numpy.add.at
调用的地方,而且这些调用非常慢。我用更快的 numpy.bincount
调用替换了这些调用。此处显示的数字使用了那些代码改进,这些改进现已合并到 OpenMDAO master b运行ch 中,截至提交 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
所以solve_linear在CS下还是比较快的。原因是对于 CS,部分被声明为密集(这是目前唯一的方法,因为我们还不支持在使用 FD 或 CS 时声明稀疏部分)。当部分被声明为密集时,solve_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 非线性求解的数量始终是确定的按部分雅可比矩阵中的列数。