Как должна быть реализована интеграция Эйлера в TensorFlow? - PullRequest
0 голосов
/ 11 февраля 2019

Я хочу написать грубую симуляцию Эйлера для набора PDE.Я прочитал учебник по PDE на tenorflow.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_))

Вопрос 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_step и update_step.Если я правильно понимаю, это можно сделать с помощью отдельных вызовов Session.run():

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

Вопрос 2

Кажется разочаровывающим, что нельзя определить одну операцию, которая объединяет оба этапав фиксированном порядке, например,

combined_update = tf.group(delta_step, update_step)    

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

, но согласно ответу на этот поток , tf.group() не гарантирует какой-либо конкретный порядок оценки.Подход, описанный в этом потоке для управления порядком оценки, включает в себя нечто, называемое «зависимостями управления»;можно ли их использовать в этом случае, где мы хотим убедиться, что повторные вычисления двух тензоров выполняются в фиксированном порядке?

Если нет, есть ли другой способ управления порядком оценки этих тензоров, кроме явного использования последовательных вызовов Session.run()?

Обновление (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.Если мы сначала оценим assignB, b сначала обновится до 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}

Как и ожидалось, это последовательно вычисляет шаг интеграции Эйлера правильно.

Я могу понять, почему иногда tf.group(assignA, assignB) выдает ответ, совместимый с запуском assignA, за которым следует assignB, и почему иногда он выдает ответ, совместимый с запуском assignB, за которым следует assignA, но яНе понимаю, почему он обычно дает ответ, который магически правильный (для случая интеграции Эйлера) и не соответствует ни одному из этих порядков.Что происходит?

1 Ответ

0 голосов
/ 11 февраля 2019

Действительно, вы можете убедиться, что все работает в нужном вам порядке, используя управляющие зависимости .В этом случае вам просто нужно убедиться, что 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_ будут вычислены до обновления a и b.

В третьем фрагменте вы используете управляющие зависимости, но не вэффективный способ.Что вы указываете своим кодом, так это то, что групповая операция не должна выполняться до вычисления a_ и b_.Однако это не значит много;групповая операция в значительной степени не работает с зависимостями от ее входов.Управляющие зависимости там влияют только на этот запрет, но не препятствуют выполнению операций присваивания когда-либо преждеКак я и предлагал изначально, вместо этого вы должны поместить операции присваивания в блоке управляющих зависимостей, чтобы убедиться, что присваивания не произойдут раньше, чем должны (в моем фрагменте я также поместил групповую операцию в блок просто для удобства, но этона самом деле не имеет значения, входит это или нет).

...