如何在TensorFlow中实现Euler集成?

问题描述 投票:2回答:1

我想写一组PDE的粗略欧拉模拟。我读了PDE tutorial on tensorflow.org,我对如何正确地做这件事感到有些困惑。我有两个具体的问题,但如果有任何我忽略或误解的话,我会欢迎进一步的反馈。

以下代码来自教程:

# Discretized PDE update rules
U_ = U + eps * Ut
Ut_ = Ut + eps * (laplace(U) - damping * Ut)

# Operation to update the state
step = tf.group(
  U.assign(U_),
  Ut.assign(Ut_))

Question 1

这里有没有错误?一旦对U.assign(U_)进行了评估,Ut_的下一次评估肯定会使用U的更新值而不是同一时间段的值?我原以为这样做的正确方法如下:

delta_U = tf.Variable(dU_init)
delta_Ut = tf.Variable(dUt_init)

delta_step = tf.group(    
    delta_U.assign(Ut)
    delta_Ut.assign(laplace(U) - damping * Ut)
)

update_step = tf.group(
    U.assign_add(eps * delta_U),
    Ut.assign_add(eps * delta_Ut)
)    

然后我们可以通过交替评估delta_stepupdate_step来运行Euler积分步骤。如果我理解正确,可以通过单独调用Session.run()来完成:

with tf.Session() as sess:
    ...
    for i in range(1000):
        sess.run(delta_step)
        sess.run(update_step)

Question 2

令人沮丧的是,无法定义单个操作,其以固定顺序组合两个步骤,例如,

combined_update = tf.group(delta_step, update_step)    

with tf.Session() as sess:
    ...
    for i in range(1000):    
        sess.run(combined_update)

但根据this thread的回答,tf.group()不保证任何特定的评估订单。在该线程上描述的用于控制评估顺序的方法涉及称为“控制依赖性”的东西;他们是否可以在这种情况下使用,我们希望确保以固定顺序对两个张量的重复评估?

如果没有,除了明确使用连续的Session.run()调用之外,还有另一种方法来控制这些张量的评估顺序吗?

Update (12/02/2019)

更新:根据jdehesa的回答,我进行了更详细的调查。结果支持我原来的直觉,即PDE教程中存在一个错误,由于tf.assign()调用的评估顺序不一致而产生错误的结果;使用控件依赖项无法解决此问题。但是,PDE教程中的方法通常会产生正确的结果,我不明白为什么。

我使用以下代码检查了以明确顺序运行赋值操作的结果:

import tensorflow as tf
import numpy as np

# define two variables a and b, and the PDEs that govern them
a = tf.Variable(0.0)
b = tf.Variable(1.0)
da_dt_ = b * 2
db_dt_ = 10 - a * b

dt = 0.1 # integration step size

# after one step of Euler integration, we should have
#   a = 0.2 [ = 0.0 + (1.0 * 2) * 0.1 ]
#   b = 2.0 [ = 1.0 + (10 - 0.0 * 1.0) * 0.1 ]

# using the method from the PDE tutorial, define updated values for a and b

a_ = a + da_dt_ * dt
b_ = b + db_dt_ * dt

# and define the update operations

assignA = a.assign(a_)
assignB = b.assign(b_)

# define a higher-order function that runs a particular simulation n times
# and summarises the results

def summarise(simulation, n=500):
  runs = np.array( [ simulation() for i in range(n) ] )

  summary = dict( { (tuple(run), 0) for run in np.unique(runs, axis=0) } )

  for run in runs:
    summary[tuple(run)] += 1

  return summary  

# check the results of running the assignment operations in an explicit order

def explicitOrder(first, second):
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(first)
    sess.run(second)
    return (sess.run(a), sess.run(b))

print( summarise(lambda: explicitOrder(assignA, assignB)) ) 
# prints {(0.2, 1.98): 500}

print( summarise(lambda: explicitOrder(assignB, assignA)) ) 
# prints {(0.4, 2.0): 500}

正如所料,如果我们首先评估assignA然后a更新为0.2,然后使用此更新值将b更新为1.98。如果我们首先评估assignBb首先更新为2.0,然后使用此更新值将a更新为0.4。这些都是欧拉积分的错误答案:我们应该得到的是a = 0.2,b = 2.0。

我测试了当我们允许tf.group()隐式控制评估顺序时发生的情况,而不使用控制依赖性。

noCDstep = tf.group(assignA, assignB)

def implicitOrder():  
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(noCDstep)
    return (sess.run(a), sess.run(b))


print( summarise(lambda: implicitOrder()) ) 
# prints, e.g. {(0.4, 2.0): 37, (0.2, 1.98): 1, (0.2, 2.0): 462}  

偶尔,这会产生与评估assignB,然后是assignA,或(更少见)评估assignA,然后是assignB相同的结果。但大多数时候,有一个完全出乎意料的结果:欧拉积分步骤的正确答案。这种行为既不一致又令人惊讶。

我试图通过引入jdehesa建议的控件依赖性来解决这种不一致的行为,使用以下代码:

with tf.control_dependencies([a_, b_]):
  cdStep = tf.group(assignA, assignB)

def cdOrder():   
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(cdStep)
    return (sess.run(a), sess.run(b))

print( summarise(lambda: cdOrder()) )  
# prints, e.g. {(0.4, 2.0): 3, (0.2, 1.98): 3, (0.2, 2.0): 494}

似乎控制依赖关系不能解决这种不一致性,并且它们根本不存在任何差异尚不清楚。然后我尝试实现我的问题中最初建议的方法,该方法使用其他变量来强制独立地计算增量和更新:

da_dt = tf.Variable(0.0)
db_dt = tf.Variable(0.0)

assignDeltas = tf.group( da_dt.assign(da_dt_), db_dt.assign(db_dt_) )
assignUpdates = tf.group( a.assign_add(da_dt * dt), b.assign_add(db_dt * dt) )

def explicitDeltas():
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(assignDeltas)
    sess.run(assignUpdates)
    return (sess.run(a), sess.run(b))

print( summarise(lambda: explicitDeltas()) )
# prints {(0.2, 2.0): 500}

正如预期的那样,这一致地正确地计算了Euler积分步骤。

我能理解为什么有时候tf.group(assignA, assignB)会产生一个与运行assignA然后assignB一致的答案,以及为什么它有时会产生一个与运行assignB然后assignA一致的答案,但我不明白为什么它通常产生一个神奇正确的答案(对于Euler整合案例)并且与这些订单都不一致。到底是怎么回事?

python tensorflow operator-precedence
1个回答
0
投票

实际上,您可以确保使用control dependencies以您想要的顺序运行。在这种情况下,您只需要确保在执行赋值操作之前计算U_Ut_。我认为(虽然我不是很确定)教程中的代码可能是正确的,并且要使用更新的Ut_计算U,您需要具有以下内容:

U_ = U + eps * Ut
U = U.assign(U_)
Ut_ = Ut + eps * (laplace(U) - damping * Ut)
step = Ut.assign(Ut_)

但是,只要您想确保某些事物在另一个事物之前执行,您就可以明确地编写依赖项:

# Discretized PDE update rules
U_ = U + eps * Ut
Ut_ = Ut + eps * (laplace(U) - damping * Ut)

# Operation to update the state
with tf.control_dependencies([U_, Ut_]):
    step = tf.group(
      U.assign(U_),
      Ut.assign(Ut_))

这将确保在执行任何赋值操作之前,首先计算U_Ut_

编辑:关于新片段的一些额外说明。

在更新的第一个代码段(12/02/2019)中,代码首先运行一个分配,然后运行下一个分配。如你所说,这显然是错误的,因为第二次更新将使用另一个变量的已更新值。

第二个片段,如果我没有弄错(纠正我,如果我错了)是教程提出的,将分配操作分组。既然你说你已经看到过这种情况会产生错误的结果,我想这样评估它并不总是安全的。但是,您经常得到正确的结果并不奇怪。这里TensorFlow将计算更新两个变量所需的所有值。由于评估顺序不是确定性的(当没有明确的依赖关系时),可能会发生a的更新发生在计算b_之前,例如,在这种情况下,您将得到错误的结果。但是,在a_b_更新之前,可以预期很多次ab将被计算是合理的。

在第三个代码段中,您使用控件依赖项,但不是以有效的方式。您在代码中指出的是,在计算a_b_之前,不应运行组操作。但是,这并不意味着什么;组操作几乎是一个与其输入相关的无操作。那里的控制依赖关系只会影响这个no-op,但不会阻止赋值操作在以前运行。正如我最初建议的那样,您应该将赋值操作放在控件依赖项块中,以确保分配不会比它们应该更早发生(在我的代码片段中,我还将组操作放在块中只是为了方便,但它这是不是真的无关紧要)。

© www.soinside.com 2019 - 2024. All rights reserved.