این صفحه به‌وسیله ‏Cloud Translation API‏ ترجمه شده است.
Switch to English

عملکرد بهتر با عملکرد tf

مشاهده در TensorFlow.org در Google Colab اجرا کنید مشاهده منبع در GitHub دانلود دفترچه یادداشت

در TensorFlow 2 ، اعدام مشتاق به طور پیش فرض روشن می شود. رابط کاربری بصری و انعطاف پذیر است (اجرای عملیات یکبار مصرف بسیار راحت تر و سریعتر است) ، اما این می تواند به ازای عملکرد و قابلیت استفاده باشد.

برای تهیه نمودار از برنامه های خود می توانید از tf.function استفاده کنید. این یک ابزار تحول است که نمودارهای گردش داده داده مستقل از پایتون را از کد پایتون شما ایجاد می کند. این به شما کمک می کند تا مدل های قابل اجرا و قابل حمل ایجاد کنید ، و استفاده از SavedModel .

این راهنما به شما کمک می کند تا نحوه عملکرد tf.function در زیر کاپوت را مفهوم سازی کنید تا بتوانید به طور م effectivelyثر از آن استفاده کنید.

اقدامات اصلی و توصیه های اصلی عبارتند از:

  • در حالت اشتیاق اشکال زدایی کنید ، سپس با @tf.function تزئین کنید.
  • به عوارض جانبی پایتون مانند جهش جسم یا ضمیمه لیست اعتماد نکنید.
  • tf.function با tf.function به بهترین وجه کار می کند. تماس های NumPy و Python به ثابت تبدیل می شوند.

برپایی

import tensorflow as tf

برای نشان دادن انواع خطاهایی که ممکن است با آنها روبرو شوید ، یک تابع کمکی را تعریف کنید:

import traceback
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}:'.format(error_class))
    traceback.print_exc(limit=2)
  except Exception as e:
    raise e
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))

اصول

استفاده

Function که تعریف می کنید دقیقاً مانند یک عملیات اصلی TensorFlow است: می توانید با اشتیاق آن را اجرا کنید. می توانید شیب ها را محاسبه کنید. و غیره

@tf.function
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>
v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

شما می توانید از Function s در داخل سایر Function ها استفاده کنید.

@tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

Function s می تواند سریعتر از کد مشتاق باشد ، به خصوص برای نمودارهایی که دارای بسیاری از عملیات کوچک هستند. اما برای نمودارهای دارای چند اپلیکیشن گران قیمت (مانند پیچ ​​های پیچشی) ، ممکن است سرعت زیادی نبینید.

import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)

@tf.function
def conv_fn(image):
  return conv_layer(image)

image = tf.zeros([1, 200, 200, 100])
# warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")

Eager conv: 0.0026629049998518894
Function conv: 0.0034744160000172997
Note how there's not much difference in performance for convolutions

ردیابی

تایپ پویای پایتون به این معنی است که شما می توانید توابع را با انواع مختلف آرگومان فراخوانی کنید و پایتون می تواند در هر سناریو کار متفاوتی انجام دهد.

با این حال ، برای ایجاد نمودار dtypes ، انواع مختلف استاتیک و ابعاد شکل مورد نیاز است. tf.function با بسته بندی یک تابع Python برای ایجاد یک شی Function این پل را از بین می برد. بر اساس ورودی های داده شده ، Function نمودار مناسب را برای ورودی های داده شده انتخاب می کند و در صورت لزوم عملکرد پایتون را بازیابی می کند. وقتی tf.function که چرا و وقتی ردیابی اتفاق می افتد ، استفاده از tf.function موثر بسیار آسان تر است!

برای دیدن این رفتار چند شکل در عمل می توانید یک Function با آرگومانهای از انواع مختلف فراخوانی کنید.

@tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()

Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)


توجه داشته باشید که اگر شما مرتباً یک Function با همان نوع آرگومان را فراخوانی کنید ، TensorFlow از گرافی که قبلاً ردیابی شده استفاده می کند ، زیرا نمودار ایجاد شده یکسان است.

# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)

(تغییر زیر در TensorFlow هر شب موجود است و در TensorFlow 2.3 در دسترس خواهد بود.)

برای دیدن همه ردیابی های موجود می توانید از pretty_printed_concrete_signatures() استفاده کنید:

print(double.pretty_printed_concrete_signatures())
double(a)
  Args:
    a: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 Tensor, shape=()

double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

تاکنون مشاهده کرده اید که tf.function یک لایه اعزام پویا ، پنهان شده و منطق ردیابی نمودار tf.function ایجاد می کند. برای اینکه در مورد اصطلاحات دقیق تر باشیم:

  • tf.Graph خام ، زبان-اگنوستیک و قابل حمل از محاسبات شما است.
  • ConcreteFunction یک بسته بندی مشتاقانه اطراف یک tf.Graph .
  • یک Function حافظه نهان ConcreteFunction مدیریت می کند و ورودی مناسب را برای ورودی شما انتخاب می کند.
  • tf.function تابع Python را می پیچد ، و یک شی Function tf.function .

دستیابی به توابع بتن

هر بار که یک تابع ردیابی می شود ، یک تابع بتن جدید ایجاد می شود. با استفاده از get_concrete_function می توانید مستقیماً یک عملکرد بتن بدست get_concrete_function .

print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))

Obtaining concrete trace
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)

# You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))
Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'cc', shape=(), dtype=string)

(تغییر زیر در TensorFlow هر شب موجود است و در TensorFlow 2.3 در دسترس خواهد بود.)

چاپ ConcreteFunction خلاصه ای از آرگومان های ورودی آن (با انواع) و نوع خروجی آن را نمایش می دهد.

print(double_strings)
ConcreteFunction double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

همچنین می توانید امضای یک عملکرد بتن را مستقیماً بازیابی کنید.

print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)

استفاده از ردیابی بتونی با انواع ناسازگار خطایی ایجاد می کند

with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-15-e4e2860a4364>", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_168 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_168]

ممکن است متوجه شوید که استدلالهای پایتون در امضای ورودی یک تابع بتن رفتار ویژه ای دارند. قبل از TensorFlow 2.3 ، استدلال های پایتون به سادگی از امضای عملکرد بتن حذف شدند. با شروع TensorFlow 2.3 ، آرگومان های پایتون در امضا باقی می مانند ، اما محدود به گرفتن مقدار تعیین شده در هنگام ردیابی هستند.

@tf.function
def pow(a, b):
  return a ** b

square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)
ConcreteFunction pow(a, b=2)
  Args:
    a: float32 Tensor, shape=<unknown>
  Returns:
    float32 Tensor, shape=<unknown>

assert square(tf.constant(10.0)) == 100

with assert_raises(TypeError):
  square(tf.constant(10.0), b=3)
Caught expected exception 
  <class 'TypeError'>:

Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1669, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1714, in _call_with_flat_signature
    self._flat_signature_summary(), ", ".join(sorted(kwargs))))
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-17-d163f3d206cb>", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3

به دست آوردن نمودارها

هر عملکرد بتن یک بسته بندی قابل تماس با یک tf.Graph . اگرچه بازیابی شی واقعی tf.Graph کاری نیست که به طور معمول باید انجام دهید ، اما می توانید آن را به راحتی از هر عملکرد بتنی بدست آورید.

graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')

[] -> a
['a', 'a'] -> add
['add'] -> Identity

اشکال زدایی

به طور کلی ، کد اشکال زدایی در حالت اشتیاق نسبت به tf.function آسان تر است. قبل از تزئین با tf.function باید اطمینان حاصل کنید که کد شما بدون خطا در حالت مشتاق اجرا می شود. برای کمک به روند اشکال زدایی ، می توانید با tf.config.run_functions_eagerly(True) تماس بگیرید تا tf.config.run_functions_eagerly(True) در سطح جهانی غیرفعال و دوباره فعال tf.function .

هنگام ردیابی مواردی که فقط در tf.function نظر می tf.function ، در اینجا چند نکته وجود دارد:

  • تماس های print قدیمی پایتون فقط در هنگام ردیابی اجرا می شوند ، به شما کمک می کند وقتی عملکرد شما ردیابی می شود ردیابی کنید.
  • تماس های tf.print هر بار اجرا می شوند و می توانند به شما کمک کنند مقادیر متوسط ​​را در حین اجرا ردیابی کنید.
  • tf.debugging.enable_check_numerics راهی آسان برای ردیابی مکان ایجاد NaN و Inf است.
  • pdb می تواند به شما کمک کند تا در جریان ردیابی چه خبر است. (Caveat: PDB شما را در کد منبع تبدیل شده توسط AutoGraph رها می کند.)

ردیابی معناشناسی

قوانین کلیدی حافظه پنهان

یک Function تعیین می کند که با استفاده از محاسبه یک کلید حافظه نهان از کمان ها و کوارگ های ورودی ، از تابع بتن ردیابی استفاده مجدد کنیم.

  • کلید تولید شده برای استدلال tf.Tensor شکل و نوع آن است.
  • با شروع در TensorFlow 2.3 ، کلیدی که برای یک tf.Variable استدلال tf.Variable id() آن است id() .
  • کلیدی که برای یک اولیه پایتون تولید می شود مقدار آن است. کلیدی که برای s ، namedtuple dict ، list s ، tuple s ، namedtuple s و attr شود ، تاپل مسطح است. (در نتیجه این پهن شدن ، فراخوانی عملکرد بتنی با ساختار تودرتو متفاوت از عملکردی که در حین ردیابی استفاده شده منجر به ایجاد TypeError خواهد شد).
  • برای همه انواع دیگر پایتون ، کلیدها براساس شی id() تا روش ها به طور مستقل برای هر نمونه از یک کلاس ردیابی شوند.

کنترل مجدد

جمع آوری مجدد به شما اطمینان می دهد که TensorFlow نمودارهای صحیحی برای هر مجموعه ورودی تولید می کند. با این حال ردیابی عملیاتی پرهزینه است! اگر Function شما نمودار جدیدی را برای هر تماس tf.function کند ، متوجه می شوید که کد شما با کندی بیشتری اجرا می شود تا اینکه از tf.function استفاده tf.function .

برای کنترل رفتار ردیابی ، می توانید از تکنیک های زیر استفاده کنید:

  • input_signature در tf.function برای محدود کردن ردیابی مشخص کنید.
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(x % 2 == 0, x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# We specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# We specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([1.0, 2.0]))

Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'ValueError'>:
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-19-20f544b8adbf>", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))
Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-19-20f544b8adbf>", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor([1. 2.], shape=(2,), dtype=float32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))

  • برای ایجاد انعطاف پذیری در استفاده مجدد از tf.TensorSpec ابعاد [None] را در tf.TensorSpec مشخص کنید.

    از آنجا که TensorFlow بر اساس شکل آنها با سنسورها مطابقت دارد ، استفاده از بعد None بعنوان کارت حافظه به Function اجازه می دهد تا از ردیابی ها برای ورودی متغیر استفاده کند. اگر توالی هایی با طول متفاوت ، یا تصاویر با اندازه های مختلف برای هر دسته داشته باشید ، ورودی به اندازه متغیر می تواند رخ دهد (برای مثال به آموزشهای Transformer و Deep Dream مراجعه کنید).

@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
  print('Tracing with', x)
  return x

# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))

Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)

  • استدلال های Python را برای کاهش بازگرداندن به تنورها ارائه دهید.

    غالباً ، از آرگومان های پایتون برای کنترل num_layers=10 پارامترها و ساختارهای نمودار استفاده می شود - به عنوان مثال ، num_layers=10 یا training=True یا nonlinearity='relu' . بنابراین اگر آرگومان پایتون تغییر کند ، منطقی است که مجبور شوید نمودار را دوباره تهیه کنید.

    با این حال ، ممکن است که از آرگومان پایتون برای کنترل ساخت نمودار استفاده نشده باشد. در این موارد ، تغییر در مقدار پایتون می تواند باعث بازگرداندن بدون نیاز شود. به عنوان مثال ، این حلقه آموزش را در نظر بگیرید ، که AutoGraph به صورت پویا آن را باز می کند. با وجود ردپاهای متعدد ، نمودار ایجاد شده در واقع یکسان است ، بنابراین بازگرداندن آن غیر ضروری است.

def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = ", num_steps)
  tf.print("Executing with num_steps = ", num_steps)
  for _ in tf.range(num_steps):
    train_one_step()

print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)

print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
Retracing occurs for different Python arguments.
Tracing with num_steps =  10
Executing with num_steps =  10
Tracing with num_steps =  20
Executing with num_steps =  20

Traces are reused for Tensor arguments.
Tracing with num_steps =  Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps =  10
Executing with num_steps =  20

اگر نیاز به بازگرداندن مجدد دارید ، یک Function جدید ایجاد کنید. اشیاction Function جداگانه تضمین می کنند که ردیابی نمی کنند.

def f():
  print('Tracing!')
  tf.print('Executing')

tf.function(f)()
tf.function(f)()
Tracing!
Executing
Tracing!
Executing

عوارض جانبی پایتون

عوارض جانبی پایتون مانند چاپ ، ضمیمه کردن لیست ها و جهش جهانی فقط اولین باری است که شما یک Function با مجموعه ای از ورودی فراخوانی می کنید. پس از آن ، tf.Graph ردیابی شده بدون اجرای کد پایتون دوباره اجرا می شود.

قانون کلی این است که فقط از عوارض جانبی پایتون برای اشکال زدایی از ردیابی های خود استفاده کنید. در غیر این صورت ، tf.Variable.assign های tf.Variable.assign مانند tf.Variable.assign ، tf.print و tf.summary بهترین راه برای اطمینان از ردیابی و اجرای کد شما توسط زمان اجرا TensorFlow با هر تماس هستند.

@tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)

Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

بسیاری از ویژگی های پایتون ، مانند مولد ها و تکرارکننده ها ، برای پیگیری وضعیت ، به زمان اجرای پایتون متکی هستند. به طور کلی ، در حالی که این سازه ها همانطور که انتظار می رود در حالت اشتیاق کار می کنند ، اما بسیاری از اتفاقات غیرمنتظره در داخل یک Function می تواند رخ دهد.

برای ذکر یک مثال ، پیشرفت حالت تکرار کننده یک عارضه جانبی پایتون است و بنابراین فقط در هنگام ردیابی اتفاق می افتد.

external_var = tf.Variable(0)
@tf.function
def buggy_consume_next(iterator):
  external_var.assign_add(next(iterator))
  tf.print("Value of external_var:", external_var)

iterator = iter([0, 1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)

Value of external_var: 0
Value of external_var: 0
Value of external_var: 0

برخی از سازه های تکرار از طریق AutoGraph پشتیبانی می شوند. برای نمای کلی به بخش تغییرات خودکار نمودار مراجعه کنید.

اگر می خواهید در هر فراخوانی یک Function کد پایتون را اجرا کنید ، tf.py_function یک دریچه خروج است. اشکال tf.py_function این است که قابل حمل نیست و به ویژه عملکردی ندارد و همچنین در تنظیمات توزیع شده (چند GPU ، TPU) به خوبی کار نمی کند. همچنین ، از آنجا که tf.py_function باید به نمودار وصل شود ، تمام ورودی / خروجی ها را به تنسورها منتقل می کند.

API هایی مانند tf.gather ، tf.stack و tf.TensorArray می توانند به شما در اجرای الگوهای معمول حلقه زدن در TensorFlow بومی کمک کنند.

external_list = []

def side_effect(x):
  print('Python side effect')
  external_list.append(x)

@tf.function
def f(x):
  tf.py_function(side_effect, inp=[x], Tout=[])

f(1)
f(1)
f(1)
# The list append happens all three times!
assert len(external_list) == 3
# The list contains tf.constant(1), not 1, because py_function casts everything to tensors.
assert external_list[0].numpy() == 1

Python side effect
Python side effect
Python side effect

متغیرها

هنگام ایجاد tf.Variable جدید ممکن است با خطایی روبرو tf.Variable در یک تابع. این خطا از واگرایی رفتار در تماسهای مکرر جلوگیری می کند: در حالت اشتیاق ، یک تابع با هر تماس متغیر جدیدی ایجاد می کند ، اما در یک Function ، به دلیل استفاده مجدد از ردیف ، ممکن است متغیر جدید ایجاد نشود.

@tf.function
def f(x):
  v = tf.Variable(1.0)
  v.assign_add(x)
  return v

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-26-73e410646579>", line 8, in <module>
    f(1.0)
ValueError: in user code:

    <ipython-input-26-73e410646579>:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:262 __call__  **
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:256 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/eager/def_function.py:702 invalid_creator_scope
        "tf.function-decorated function tried to create "

    ValueError: tf.function-decorated function tried to create variables on non-first call.


می توانید متغیرهایی را در داخل یک Function ایجاد کنید به شرطی که این متغیرها فقط در اولین بار اجرای تابع ایجاد شوند.

class Count(tf.Module):
  def __init__(self):
    self.count = None
  
  @tf.function
  def __call__(self):
    if self.count is None:
      self.count = tf.Variable(0)
    return self.count.assign_add(1)

c = Count()
print(c())
print(c())
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

خطای دیگری که ممکن است با آن روبرو شوید متغیر جمع آوری زباله است. برخلاف توابع عادی پایتون ، توابع بتن فقط ضعف ریف ها را در متغیرهایی که بسته می شوند حفظ می کنند ، بنابراین باید به هر متغیری اشاره داشته باشید.

external_var = tf.Variable(3)
@tf.function
def f(x):
  return x * external_var

traced_f = f.get_concrete_function(4)
print("Calling concrete function...")
print(traced_f(4))

del external_var
print()
print("Calling concrete function after garbage collecting its closed Variable...")
with assert_raises(tf.errors.FailedPreconditionError):
  traced_f(4)
Calling concrete function...
tf.Tensor(12, shape=(), dtype=int32)

Calling concrete function after garbage collecting its closed Variable...
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.FailedPreconditionError'>:

Traceback (most recent call last):
  File "<ipython-input-3-73d0ca52e838>", line 8, in assert_raises
    yield
  File "<ipython-input-28-304a18524b57>", line 14, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-28-304a18524b57>:4) ]]
  (1) Failed precondition:  Error while reading resource variable _AnonymousVar4 from Container: localhost. This could mean that the variable was uninitialized. Not found: Resource localhost/_AnonymousVar4/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at <ipython-input-28-304a18524b57>:4) ]]
     [[ReadVariableOp/_2]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_514]

Function call stack:
f -> f


تحولات خودکار نمودار

AutoGraph کتابخانه ای است که به طور پیش فرض در tf.function و زیرمجموعه ای از کد مشتاق Python را به گزینه های سازگار با نمودار tf.function تبدیل می کند. این شامل جریان کنترل مانند if ، for ، while .

گزینه های tf.cond مانند tf.cond و tf.while_loop به کار خود ادامه می دهند ، اما نوشتن و فهم جریان کنترل اغلب هنگام نوشتن در پایتون راحت تر است.

# Simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))
[0.592976809 0.128855109 0.13305068 0.379838109 0.429846764]
[0.532033205 0.128146663 0.132271096 0.362566859 0.405193239]
[0.486933738 0.127449796 0.131505072 0.347472966 0.384383708]
[0.451779395 0.126764178 0.130752221 0.334132522 0.366508394]
[0.423360586 0.126089528 0.130012169 0.322229117 0.35093388]
[0.399757773 0.125425547 0.129284561 0.311521083 0.337203503]
[0.379741699 0.124771953 0.128569037 0.301820248 0.324978501]
[0.362483114 0.124128476 0.127865285 0.292977482 0.31400153]
[0.347399354 0.123494864 0.127172977 0.284872979 0.304073036]
[0.334067136 0.12287087 0.1264918 0.277409106 0.295035541]
[0.322170526 0.122256249 0.125821471 0.270505458 0.286762893]
[0.311468184 0.12165077 0.125161693 0.264095098 0.279152662]
[0.301772147 0.121054202 0.124512196 0.258121818 0.272120655]
[0.292933524 0.120466337 0.123872712 0.252537966 0.265597]
[0.284832567 0.119886965 0.123243 0.247302905 0.259523094]
[0.277371794 0.119315878 0.122622795 0.242381677 0.253849417]

<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.27047086, 0.11875288, 0.12201188, 0.23774408, 0.24853374],
      dtype=float32)>

اگر کنجکاو هستید می توانید کد تولید شده خودنویس را بررسی کنید.

print(tf.autograph.to_code(f.python_function))
def tf__f(x):
    with ag__.FunctionScope('f', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (x,)

        def set_state(vars_):
            nonlocal x
            (x,) = vars_

        def loop_body():
            nonlocal x
            ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
            x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)

        def loop_test():
            return (ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1)
        ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
        try:
            do_return = True
            retval_ = ag__.ld(x)
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)


مشروط

AutoGraph برخی از دستورات if <condition> را به تماس های معادل tf.cond می کند. اگر <condition> یک Tensor باشد این تعویض انجام می <condition> . در غیر این صورت ، دستور if به صورت شرطی پایتون اجرا می شود.

شرط Python در حین ردیابی اجرا می شود ، بنابراین دقیقاً یک شاخه از شرط به نمودار اضافه می شود. بدون وجود AutoGraph ، در صورت وجود جریان کنترل وابسته به داده ، این نمودار پیگیری شده قادر به گرفتن شاخه جایگزین نخواهد بود.

tf.cond ردیابی می کند و هر دو شاخه شرطی را به نمودار اضافه می کند ، یک شاخه را در زمان اجرا به صورت پویا انتخاب می کند. ردیابی می تواند عوارض جانبی ناخواسته داشته باشد. برای اطلاعات بیشتر به جلوه های ردیابی خودکار نمودار مراجعه کنید.

@tf.function
def fizzbuzz(n):
  for i in tf.range(1, n + 1):
    print('Tracing for loop')
    if i % 15 == 0:
      print('Tracing fizzbuzz branch')
      tf.print('fizzbuzz')
    elif i % 3 == 0:
      print('Tracing fizz branch')
      tf.print('fizz')
    elif i % 5 == 0:
      print('Tracing buzz branch')
      tf.print('buzz')
    else:
      print('Tracing default branch')
      tf.print(i)

fizzbuzz(tf.constant(5))
fizzbuzz(tf.constant(20))
Tracing for loop
Tracing fizzbuzz branch
Tracing fizz branch
Tracing buzz branch
Tracing default branch
1
2
fizz
4
buzz
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

برای محدودیت های اضافی در صورت استفاده از دستورات ، به اسناد مرجع مراجعه کنید.

حلقه ها

AutoGraph برخی از عبارات for و while را به گزینه های معادل حلقه TensorFlow تبدیل می کند ، مانند tf.while_loop . اگر تبدیل نشود ، حلقه for یا while به عنوان حلقه Python اجرا می شود.

این تعویض در شرایط زیر انجام می شود:

  • for x in y : اگر y یک Tensor است ، به tf.while_loop تبدیل کنید. در حالت خاص که y یک tf.data.Dataset ، ترکیبی از tf.data.Dataset های tf.data.Dataset ایجاد می شود.
  • while <condition> : اگر <condition> Tensor است ، به tf.while_loop تبدیل کنید.

یک حلقه پایتون در حین ردیابی اجرا می شود و گزینه های اضافی را به tf.Graph اضافه می کند. tf.Graph برای هر تکرار حلقه.

یک حلقه TensorFlow بدن حلقه را ردیابی می کند و به صورت پویا تعداد تکرارها را برای اجرا در زمان اجرا انتخاب می کند. بدنه حلقه فقط یک بار در tf.Graph تولید شده tf.Graph .

را ببینید اسناد مرجع برای محدودیت های بیشتر در دستخط-تبدیل for و while اظهارات.

حلقه زدن روی داده های پایتون

یک دام مشترک این است که داده های Python / Numpy را در یک tf.function . tf.function . این حلقه در طول فرایند ردیابی اجرا می شود و یک کپی از مدل خود را به tf.Graph کند. tf.Graph برای هر تکرار حلقه.

اگر می خواهید کل حلقه آموزش را در tf.function ، مطمئن ترین راه برای انجام این کار بسته بندی داده های خود به صورت tf.data.Dataset تا AutoGraph به صورت پویا حلقه آموزش را باز کند.

def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 3
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))
train([(1, 1), (1, 1), (1, 1)]) contains 11 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 8 nodes in its graph

هنگام بسته بندی داده های Python / Numpy در یک مجموعه داده ، مراقب tf.data.Dataset.from_generator در مقابل tf.data.Dataset.from_tensors . مورد اول داده ها را در پایتون نگه داشته و از طریق tf.py_function که می تواند پیامدهای عملکردی داشته باشد واکشی می کند ، در حالی که دومی نسخه ای از داده را به عنوان یک گره tf.constant() بزرگ در نمودار قرار می دهد که می تواند پیامدهای حافظه داشته باشد.

خواندن داده ها از طریق TFRecordDataset / CsvDataset / و غیره م effectiveثرترین روش برای مصرف داده است ، زیرا در این صورت TensorFlow خودش می تواند بارگیری و پیش بارگیری همزمان داده ها را بدون نیاز به پایتون مدیریت کند. برای کسب اطلاعات بیشتر ، به راهنمای tf.data مراجعه کنید .

مقادیر جمع شده در یک حلقه

یک الگوی معمول جمع آوری مقادیر میانی از یک حلقه است. به طور معمول ، این کار با الحاق به لیست پایتون یا اضافه کردن ورودی به فرهنگ لغت پایتون انجام می شود. با این حال ، از آنجا که اینها عوارض جانبی پایتون هستند ، در یک حلقه باز نشده پویا مطابق انتظار عمل نخواهند کرد. برای جمع آوری نتایج حاصل از یک حلقه باز نشده به صورت پویا ، از tf.TensorArray استفاده کنید.

batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])
  
dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))
<tf.Tensor: shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.3888433 , 0.2078135 , 0.3843341 , 0.5707482 ],
        [1.1050591 , 0.5968373 , 0.982028  , 0.7987355 ],
        [1.614423  , 0.8602725 , 1.4517052 , 1.6631885 ]],

       [[0.64148533, 0.67286134, 0.07972229, 0.9772469 ],
        [1.5103817 , 0.8244705 , 0.747108  , 1.7505025 ],
        [2.5020413 , 1.5157869 , 0.8045732 , 2.4830637 ]]], dtype=float32)>

خواندن بیشتر

برای کسب اطلاعات در مورد نحوه صادرات و بارگذاری یک Function ، به راهنمای SavedModel مراجعه کنید . برای کسب اطلاعات بیشتر در مورد بهینه سازی نمودار که پس از ردیابی انجام می شود ، به راهنمای Grappler مراجعه کنید. برای یادگیری نحوه بهینه سازی خط لوله داده و نمایه سازی مدل خود ، به راهنمای Profiler مراجعه کنید.