Друзья, если вам нужно перепечатать, пожалуйста, указывайте источник:blog.CSDN.net/Генерал сказал, о...
В tensorflow1.x методом выполнения кода по умолчанию является выполнение графа (выполнение графа), а начиная с tensorflow2.0, он изменен на нетерпеливое выполнение (исполнение голодания). Как означает перевод, энергичное выполнение будет выполнять каждый шаг кода немедленно, очень голодно. А графовое выполнение объединит весь код в граф (граф) и затем выполнит его. Вот неуместная аналогия, чтобы всем было понятно: страстное исполнение — это как секс на одну ночь, и «исполняется» сразу после знакомства, а графовое исполнение — как брак.После длительного периода «накопления», "выполнить" его еще раз.
В активном режиме написание кода становится естественным и легким, а отладка становится удобной, поскольку код выполняется немедленно. В режиме графа эффективность выполнения кода выше, а поскольку граф фактически представляет собой структуру данных, состоящую из инструкций по выполнению операций и данных, граф можно легко экспортировать и сохранять, а позже даже запускать в другом, отличном от Python. , (поскольку граф — это структура данных, которая определяет некоторые рабочие инструкции и данные, поэтому, если эти операции и данные могут быть объяснены в любом месте, тогда модель может быть запущена); это также потому, что граф — это структура данных, такая другая. Операционная среда может интерпретировать операции и данные в ней в соответствии с вашими предпочтениями, таким образом, код, сгенерированный после интерпретации, будет больше соответствовать текущей операционной среде, а эффективность выполнения кода будет выше.
Некоторые учащиеся могут не понять вышеупомянутое «граф — это структура данных...». Здесь я привожу аналогию, чтобы помочь вам понять. Предположим, граф содержит два данных x и y, а также содержит инструкцию операции «сложить x и y». Когда среда C++ хочет запустить график, операция «добавление x и y» будет переведена в соответствующий код C++, а когда график необходимо запустить в среде Java, он будет интерпретирован как соответствующий код Java. . На графике только некоторые данные и инструкции, способ выполнения команды зависит от текущей рабочей среды.
В дополнение к вышесказанному, граф имеет множество внутренних механизмов, позволяющих сделать код более эффективным. Короче говоря, выполнение графа может заставить модель тензорного потока работать быстрее, эффективнее, более распараллеленной и лучше адаптироваться к различным операционным средам и операционным устройствам.
Хотя граф работает очень эффективно, код не такой лаконичный, чтобы учесть преимущества двух режимов, появляется tf.function. Используйте tf.function для инкапсуляции нетерпеливого кода в граф одним щелчком мыши.
Поскольку он инкапсулирован в граф, почему в названии используется слово function, а не tf.graph? Потому что роль tf.function заключается в преобразовании функции Python в функцию тензорного потока, включающую график. Таким образом, использование слова «функция» также имеет смысл. Следующий код может помочь вам лучше понять.
import tensorflow as tf
import timeit
from datetime import datetime
# 定义一个 Python function.
def a_regular_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# `a_function_that_uses_a_graph` 是一个 TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)
# 定义一些tensorflow tensors.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)
orig_value = a_regular_function(x1, y1, b1).numpy()
# 在python中可以直接调用tenforflow Function。就像使用python自己的function一样。
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)
tf.function работает не только с функциями Python верхнего уровня, но и со встроенными функциями Python. Посмотрите на код ниже, и вы все поймете.
def inner_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# 使用tf.function将`outer_function`变成一个tensorflow `Function`。注意,之前的代码是将tf.function当作是函数来使用,这样是被当作了修饰符来使用。这两种方式都是被支持的。
@tf.function
def outer_function(x):
y = tf.constant([[2.0], [3.0]])
b = tf.constant(4.0)
return inner_function(x, y, b)
# tf.function构建的graph中不仅仅包含了 `outer_function`还包含了它里面调用的`inner_function`。
outer_function(tf.constant([[1.0, 2.0]])).numpy()
Выходной результат:
array([[12.]], dtype=float32)
Если вы раньше использовали tenforflow 1.x, вы заметите, что tf.Session и Placeholder больше не нужны для построения графиков в 2.x. Делает код намного проще.
Мы часто смешиваем код Python с кодом tensorflow в нашем коде. При использовании tf.function для преобразования графа код тензорного потока будет преобразован напрямую, а код python будет преобразован библиотекой AutoGraph (tf.autograph).
Одна и та же функция тензорного потока может генерировать разные графики. Поскольку тип ввода каждого tf.Graph должен быть фиксированным, если при вызове функции tensorflow передается новый тип данных, этот вызов создаст новый график. Тип и размерность входных данных называются сигнатурами.Функция тензорного потока генерирует графики на основе сигнатур, а новые графики генерируются при обнаружении новых сигнатур. Код ниже может помочь вам понять.
@tf.function
def my_relu(x):
return tf.maximum(0., x)
# 下面对`my_relu` 的3次调用的数据类型都不同,所以生成了3个graph。这3个graph都被保存在my_relu这个tenforflow function中。
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1])) #python数组
print(my_relu(tf.constant([3., -3.]))) # tf数组
Выходной результат:
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)
Если вызывается тот же тип ввода, новый тип не будет регенерирован.
# 下面这两个调用就不会生成新的graph.
print(my_relu(tf.constant(-2.5))) # 这个数据类型与上面的 `tf.constant(5.5)`一样.
print(my_relu(tf.constant([-1., 1.]))) # 这个数据类型与上面的 `tf.constant([3., -3.])`一样。
Поскольку функция тензорного потока может содержать несколько графиков, функция тензорного потока является полиморфной. Благодаря этому полиморфизму функция тензорного потока может произвольно поддерживать разные типы ввода, что очень гибко, а поскольку для каждого типа ввода создается отдельный граф, это также сделает выполнение кода более эффективным!
Код ниже выводит 3 разные подписи
print(my_relu.pretty_printed_concrete_signatures())
Выходной результат:
my_relu(x)
Args:
x: float32 Tensor, shape=()
Returns:
float32 Tensor, shape=()
my_relu(x=[1, -1])
Returns:
float32 Tensor, shape=(2,)
my_relu(x)
Args:
x: float32 Tensor, shape=(2,)
Returns:
float32 Tensor, shape=(2,)
Выше вы узнали, как использовать tf.function для преобразования функции python в функцию tenforflow. Но чтобы правильно использовать tf.function в реальной разработке, необходимо изучить больше знаний. Ниже я покажу вам, как их изучить. Братья 88-й дивизии, не отступать, за мной и спешить!
По умолчанию код в функции tenforflow будет выполняться в графическом режиме, но их также можно выполнять в нетерпеливом режиме. Посмотрите на код ниже.
@tf.function
def get_MSE():
print("Calculating MSE!")
#这条语句就是让下面的代码以eager的模式来执行
tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
#这条代码就是取消前面的设置
tf.config.run_functions_eagerly(False)
В некоторых случаях одна и та же функция тензорного потока будет иметь разные эффекты работы в графическом и энергичном режимах. Функция печати Python — один из таких особых случаев. См. код ниже.
@tf.function
def get_MSE(y_true, y_pred):
print("Calculating MSE!")
sq_diff = tf.pow(y_true - y_pred, 2)
return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Выходной результат:
Calculating MSE!
Вы удивлены, увидев результат? get_MSE вызывается 3 раза, но функция печати Python внутри выполняется только один раз. Почему это? Поскольку функция печати python выполняется только при создании графа, а типы входных параметров одинаковы в трех вызовах выше, только один граф создается один раз, поэтому функция печати python будет вызываться только один раз.
Чтобы сравнить график и нетерпеливый, давайте посмотрим на вывод в нетерпеливом режиме.
# 开启强制eager模式
tf.config.run_functions_eagerly(True)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
# 取消eager模式
tf.config.run_functions_eagerly(False)
Выходной результат:
Calculating MSE!
Calculating MSE!
Calculating MSE!
Смотреть! В активном режиме печать выполняется 3 раза. PS: Если вы используете tf.print, он будет печатать 3 раза как в графическом, так и в нетерпеливом режиме.
Еще одна особенность режима выполнения графа заключается в том, что он не будет выполнять этот бесполезный код. См. код ниже.
def unused_return_eager(x):
# 当传入的x只包含一个元素时,下面的代码会报错,因为下面的代码是要获取x的第二个元素。PS:索引是从0开始的,1代表第二个元素
tf.gather(x, [1]) # unused
return x
try:
print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
print(f'{type(e).__name__}: {e}')
Приведенный выше код работает в активном режиме, поэтому каждая строка кода будет выполнена, поэтому вышеуказанное исключение возникнет и будет перехвачено. Следующий код выполняется в графическом режиме, и об исключении не сообщается. Поскольку строка кода tf.gather(x, [1]) на самом деле бесполезна (она просто получает второй элемент x без присвоения или изменения каких-либо переменных), она вообще не выполняется в графическом режиме, поэтому никакие исключения не будут выполняться. сообщать.
@tf.function
def unused_return_graph(x):
tf.gather(x, [1])
return x
try:
print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
print(f'{type(e).__name__}: {e}')
Ранее мы говорили, что эффективность выполнения графа будет выше, чем у энтузиаста, насколько она выше? На самом деле, мы можем использовать следующий код, чтобы вычислить, насколько эффективность графического режима может повыситься по сравнению с нетерпеливым режимом.
x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)
def power(x, y):
result = tf.eye(10, dtype=tf.dtypes.int32)
for _ in range(y):
result = tf.matmul(x, result)
return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Выходной результат:
Eager execution: 1.8983725069999764
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Выходной результат:
Graph execution: 0.5891194120000023
Из приведенного выше кода видно, что время выполнения графа почти в 3 раза меньше, чем у нетерпеливого. Конечно, в зависимости от конкретного содержания расчета степень повышения эффективности также различна.
Хотя граф может повысить эффективность работы, при преобразовании графа также возникают затраты. Для некоторого кода преобразование графа может занять больше времени, чем запуск графа. Поэтому старайтесь избегать повторного преобразования графа при написании кода. Если вы обнаружите, что ваша модель неэффективна, вы можете проверить наличие повторяющихся преобразований. Вы можете определить, есть ли повторяющиеся преобразования, добавив функцию печати (помните, что мы говорили ранее, что функция печати вызывается каждый раз при преобразовании графика). См. код ниже.
@tf.function
def a_function_with_python_side_effect(x):
print("Tracing!") # An eager-only side effect.
return x * x + tf.constant(2)
print(a_function_with_python_side_effect(tf.constant(2)))
print(a_function_with_python_side_effect(tf.constant(3)))
Выходной результат:
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
Можно видеть, что, поскольку типы параметров двух вышеприведенных вызовов одинаковы, graph преобразуется только один раз, а print вызывается только один раз.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Выходной результат:
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)
Надпечатка вызывается 2 раза. А? Зачем? Вы можете быть озадачены ~~ Типы двух вышеуказанных параметров одинаковы, почему вы вызываете print дважды. Поскольку входной параметр имеет тип python, каждый раз создается новый график для нового типа python. Так что лучше всего использовать тип данных tenforflow в качестве входного параметра функции.
Наконец, я даю несколько предложений, связанных с tf.function:
-
Когда необходимо переключиться между нетерпеливым и графическим режимами, следует использовать tf.config.run_functions_eagerly для очевидной аннотации.
-
Переменные Tenforflow (tf.Variables) должны быть созданы вне функции python, а их значения изменены внутри. Этот совет также относится к другим объектам tenforflow, которые используют tf.Variables (например, keras.layers, keras.Models, tf.optimizers).
-
Избегайте функций, которые зависят от внешних переменных Python.
-
Вы должны попытаться включить больше вычислительного кода в одну tf.function вместо нескольких tf.functions, чтобы максимизировать эффективность выполнения кода.
-
В качестве входного параметра функции лучше всего использовать тип данных tenforflow.