Я хочу написать грубую симуляцию Эйлера для набора 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
, но яНе понимаю, почему он обычно дает ответ, который магически правильный (для случая интеграции Эйлера) и не соответствует ни одному из этих порядков.Что происходит?