ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

أداء أفضل مع وظيفة tf

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

في TensorFlow 2 ، يتم تشغيل التنفيذ الحثيث افتراضيًا. واجهة المستخدم سهلة الاستخدام ومرنة (تشغيل العمليات لمرة واحدة أسهل بكثير وأسرع) ، ولكن هذا يمكن أن يأتي على حساب الأداء وقابلية النشر.

يمكنك استخدام tf.function لعمل رسوم بيانية من برامجك. إنها أداة تحويل تقوم بإنشاء رسوم بيانية لتدفق البيانات مستقلة عن Python من كود Python الخاص بك. سيساعدك هذا في إنشاء نماذج عالية الأداء ومحمولة ، ويلزم استخدام SavedModel .

سيساعدك هذا الدليل في تصور كيفية عمل tf.function تحت الغطاء حتى تتمكن من استخدامها بفعالية.

النقاط الرئيسية والتوصيات هي:

  • تصحيح الأخطاء في الوضع @tf.function ، ثم التزيين @tf.function .
  • لا تعتمد على تأثيرات Python الجانبية مثل تحور الكائن أو إلحاق القائمة.
  • تعمل tf.function بشكل أفضل مع عمليات TensorFlow ؛ يتم تحويل استدعاءات 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 داخل 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 أسرع من التعليمات البرمجية المتحمسة ، خاصة بالنسبة للرسوم البيانية التي تحتوي على العديد من العمليات الصغيرة. ولكن بالنسبة للرسوم البيانية التي تحتوي على عدد قليل من العمليات باهظة الثمن (مثل التلافيف) ، فقد لا ترى الكثير من التسريع.

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

اقتفاء أثر

تعني الكتابة الديناميكية في Python أنه يمكنك استدعاء الوظائف بمجموعة متنوعة من أنواع الوسائط ، ويمكن لبايثون القيام بشيء مختلف في كل سيناريو.

ومع ذلك ، لإنشاء رسم بياني TensorFlow ، يلزم وجود أنواع ثابتة dtypes للشكل. tf.function سد هذه الفجوة عن طريق لف دالة Python لإنشاء كائن Function . بناءً على المدخلات المعطاة ، تحدد Function الرسم البياني المناسب للمدخلات المعينة ، مع إعادة تتبع وظيفة Python حسب الضرورة. بمجرد أن تفهم سبب حدوث التتبع ومتى يحدث ذلك ، يصبح استخدام 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 تنشئ طبقة إرسال ديناميكية مخزنة مؤقتًا عبر منطق تتبع الرسم البياني في TensorFlow. لتكون أكثر تحديدًا حول المصطلحات:

  • tf.Graph هو tf.Graph .
  • tf.Graph ConcreteFunction عبارة عن غلاف يتم تنفيذه بشغف حول رسم tf.Graph .
  • تقوم إحدى Function بإدارة ذاكرة التخزين المؤقت Function ConcreteFunction وتختار العنصر المناسب لمدخلاتك.
  • تلتف الدالة tf.function Python ، tf.function كائن 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 ، تمت إزالة حجج Python ببساطة من توقيع الوظيفة الملموسة. بدءًا من TensorFlow 2.3 ، تظل وسيطات Python في التوقيع ، ولكنها مقيدة بأخذ القيمة المحددة أثناء التتبع.

@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.function قبل التزيين tf.function . للمساعدة في عملية التصحيح ، يمكنك استدعاء tf.config.run_functions_eagerly(True) لتعطيل tf.function وإعادة تمكينها tf.function .

عند تعقب المشكلات التي تظهر فقط داخل tf.function ، إليك بعض النصائح:

  • لا يتم تنفيذ مكالمات print Python القديمة البسيطة إلا أثناء التتبع ، مما يساعدك على تعقب (إعادة) تتبع وظيفتك.
  • سيتم تنفيذ مكالمات tf.print كل مرة ، ويمكن أن تساعدك على تعقب القيم الوسيطة أثناء التنفيذ.
  • tf.debugging.enable_check_numerics هي طريقة سهلة لتعقب مكان إنشاء NaNs و Inf.
  • يمكن أن يساعدك pdb على فهم ما يحدث أثناء التتبع. (تحذير: سوف ينقلك PDB إلى كود المصدر المحول AutoGraph.)

تتبع الدلالات

قواعد مفتاح التخزين المؤقت

تحدد Function ما إذا كان سيتم إعادة استخدام دالة ملموسة متتبعة عن طريق حساب مفتاح ذاكرة التخزين المؤقت من args و kwargs للإدخال.

  • المفتاح الذي يتم إنشاؤه لوسيطة tf.Tensor هو شكله ونوعه.
  • بدءًا من TensorFlow 2.3 ، يكون المفتاح الذي تم إنشاؤه tf.Variable هو tf.Variable id() .
  • المفتاح الذي تم إنشاؤه لبايثون البدائي هو قيمته. المفتاح إنشاؤها من أجل متداخلة dict الصورة، list الصورة، tuple ق، namedtuple الصورة، و attr الصورة هي الصفوف (tuple) بالارض. (نتيجة لهذا التسطيح ، فإن استدعاء دالة محددة بهيكل متداخل مختلف عن تلك المستخدمة أثناء التتبع سيؤدي إلى خطأ في النوع).
  • بالنسبة لجميع أنواع Python الأخرى ، تعتمد المفاتيح على id() الكائن id() بحيث يتم تتبع الطرق بشكل مستقل لكل مثيل للفئة.

السيطرة على الاسترداد

يساعد الاستعادة على ضمان أن TensorFlow يقوم بإنشاء الرسوم البيانية الصحيحة لكل مجموعة من المدخلات. ومع ذلك ، فإن البحث عن المفقودين عملية مكلفة! إذا كان لديك 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 للسماح بالمرونة في إعادة استخدام التتبع.

    نظرًا لأن TensorFlow يطابق الموترات بناءً على شكلها ، فإن استخدام بُعد None كحرف بدل سيسمح لـ Function s بإعادة استخدام الآثار لإدخال متغير الحجم. يمكن أن يحدث إدخال متغير الحجم إذا كان لديك تسلسلات ذات أطوال مختلفة ، أو صور بأحجام مختلفة لكل دفعة (انظر دروس 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)

  • حجج بايثون إلى Tensors لتقليل الاسترداد.

    غالبًا ما يتم استخدام وسيطات Python للتحكم في num_layers=10 الرسم البياني - على سبيل المثال ، عدد num_layers=10 أو training=True أو nonlinearity='relu' . لذلك إذا تغيرت حجة بايثون ، فمن المنطقي أن تضطر إلى إعادة تتبع الرسم البياني.

    ومع ذلك ، من المحتمل ألا يتم استخدام وسيطة بايثون للتحكم في إنشاء الرسم البياني. في هذه الحالات ، يمكن للتغيير في قيمة Python أن يؤدي إلى ارتداد غير ضروري. خذ ، على سبيل المثال ، حلقة التدريب هذه ، والتي سيقوم 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 جديدة. كائنات Function المنفصلة مضمونة لعدم مشاركة الآثار.

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

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

الآثار الجانبية للبايثون

تحدث تأثيرات Python الجانبية مثل الطباعة ، والإلحاق بالقوائم ، وتحوير الكرات الأرضية فقط في المرة الأولى التي تقوم فيها باستدعاء Function مع مجموعة من المدخلات. بعد ذلك ، يتم إعادة تنفيذ tf.Graph ، دون تنفيذ كود بايثون.

القاعدة العامة هي استخدام التأثيرات الجانبية لـ Python فقط لتصحيح آثارك. بخلاف ذلك ، فإن عمليات TensorFlow مثل 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

تعتمد العديد من ميزات Python ، مثل المولدات والمكررات ، على وقت تشغيل Python لتتبع الحالة. بشكل عام ، بينما تعمل هذه التركيبات كما هو متوقع في الوضع الحماسي ، يمكن أن تحدث العديد من الأشياء غير المتوقعة داخل 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. راجع قسم تحويلات الرسم البياني التلقائي للحصول على نظرة عامة.

إذا كنت ترغب في تنفيذ كود Python أثناء كل استدعاء Function ، فإن tf.py_function عبارة عن فتحة خروج. عيب tf.py_function هو أنها ليست محمولة أو ذات أداء خاص ، ولا تعمل بشكل جيد في tf.py_function الموزعة (متعددة GPU ، TPU). أيضًا ، نظرًا tf.py_function يجب tf.py_function في الرسم البياني ، فإنه يلقي جميع المدخلات / المخرجات إلى الموترات.

يمكن أن تساعدك واجهات برمجة التطبيقات مثل 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 جديد في دالة. يحمي هذا الخطأ من الاختلاف السلوكي عند الاستدعاءات المتكررة: في الوضع الحثيث ، تنشئ دالة متغيرًا جديدًا مع كل استدعاء ، ولكن في 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)

خطأ آخر قد تواجهه هو متغير جمع البيانات المهملة. على عكس دوال Python العادية ، فإن الوظائف الملموسة تحتفظ فقط بـ WeakRefs للمتغيرات التي تغلق عليها ، لذلك يجب عليك الاحتفاظ بمرجع لأي متغيرات.

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

AutoGraph هي مكتبة تعمل بشكل افتراضي في tf.function ، وتحول مجموعة فرعية من كود Python المتحمس إلى عمليات TensorFlow المتوافقة مع الرسم البياني. يتضمن ذلك التحكم في التدفق مثل if ، for ، while .

تستمر عمليات TensorFlow مثل tf.cond و tf.while_loop في العمل ، ولكن غالبًا ما يكون التحكم في التدفق أسهل في الكتابة والفهم عند كتابته بلغة Python.

# 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> هو موتر. وبخلاف ذلك ، يتم تنفيذ عبارة if كشرط لبايثون.

يتم تنفيذ شرط Python أثناء التتبع ، لذلك سيتم إضافة فرع واحد بالضبط من الشرط إلى الرسم البياني. بدون الرسم البياني التلقائي ، لن يتمكن هذا الرسم البياني المتعقب من أخذ الفرع البديل إذا كان هناك تدفق تحكم يعتمد على البيانات.

يتتبع 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

راجع الوثائق المرجعية للحصول على قيود إضافية على عبارات if المحولة تلقائيًا.

الحلقات

سوف يقوم AutoGraph بتحويل بعض عبارات for and while إلى عمليات تكرار TensorFlow المكافئة ، مثل tf.while_loop . tf.while_loop . إذا لم يتم تحويلها ، يتم تنفيذ حلقة for أو while كحلقة Python.

يتم هذا الاستبدال في الحالات التالية:

  • for x in y : إذا كانت y هي Tensor ، tf.while_loop إلى tf.while_loop . في الحالة الخاصة حيث y هي tf.data.Dataset ، يتم إنشاء مجموعة من tf.data.Dataset ops.
  • while <condition> : إذا كان <condition> هو Tensor ، tf.while_loop إلى tf.while_loop . tf.while_loop .

يتم تنفيذ حلقة Python أثناء التتبع ، مما يضيف عمليات إضافية إلى tf.Graph لكل تكرار للحلقة.

تتعقب حلقة TensorFlow جسم الحلقة وتختار ديناميكيًا عدد التكرارات التي سيتم تشغيلها في وقت التنفيذ. يظهر جسم الحلقة مرة واحدة فقط في tf.Graph إنشاؤه.

راجع الوثائق المرجعية للحصول على قيود إضافية على كشوفات الرسم البياني AutoGraph المحولة for and while .

التكرار على بيانات بايثون

تتمثل إحدى tf.function الشائعة في إجراء حلقة حول بيانات Python / tf.function داخل tf.function . سيتم تنفيذ هذه الحلقة أثناء عملية التتبع ، بإضافة نسخة من نموذجك إلى tf.Graph لكل تكرار للحلقة.

إذا كنت ترغب في التفاف حلقة التدريب بأكملها في tf.function ، فإن الطريقة الأكثر أمانًا للقيام بذلك هي لف بياناتك على هيئة tf.data.Dataset بحيث يقوم tf.data.Dataset حلقة التدريب ديناميكيًا.

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_generator مقابل tf.data.Dataset.from_tensors . سيحتفظ الأول بالبيانات في Python tf.py_function عبر tf.py_function والتي يمكن أن يكون لها آثار على الأداء ، في حين أن الثانية ستجمع نسخة من البيانات tf.constant() واحدة كبيرة في الرسم البياني ، والتي يمكن أن يكون لها آثار على الذاكرة.

قراءة البيانات من الملفات عبر TFRecordDataset / CsvDataset / إلخ. هي الطريقة الأكثر فاعلية لاستهلاك البيانات ، حيث يمكن لـ TensorFlow نفسها إدارة التحميل غير المتزامن والجلب المسبق للبيانات ، دون الحاجة إلى إشراك Python. لمعرفة المزيد ، راجع دليل tf.data .

تراكم القيم في حلقة

النمط الشائع هو تجميع القيم الوسيطة من حلقة. عادة ، يتم تحقيق ذلك عن طريق إلحاق قائمة Python أو إضافة إدخالات إلى قاموس Python. ومع ذلك ، نظرًا لأن هذه آثار جانبية لـ Python ، فلن تعمل كما هو متوقع في حلقة غير متحكم فيها ديناميكيًا. استخدم tf.TensorArray لتجميع النتائج من الحلقة غير 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 . لمعرفة كيفية تحسين مسار البيانات الخاص بك وملف تعريف النموذج الخاص بك ، راجع دليل منشئ ملفات التعريف .