ביצועים טובים יותר עם פונקצית tf

צפה ב- TensorFlow.org הפעל ב- Google Colab צפה במקור ב- GitHub הורד מחברת

בשנת TensorFlow 2, ביצוע להוט מופעל כברירת מחדל. ממשק המשתמש הוא אינטואיטיבי וגמיש (הפעלת פעולות חד פעמיות היא הרבה יותר קלה ומהירה), אבל זה יכול לבוא על חשבון הביצועים והפריסה.

אתה יכול להשתמש tf.function לעשות גרפים מתוך התוכניות שלך. זהו כלי טרנספורמציה היוצר גרפי זרימת נתונים בלתי תלויים ב- Python מתוך קוד ה- Python שלך. זה יעזור לך ליצור מודלים performant ונייד, והוא נדרש להשתמש SavedModel .

מדריך זה יעזור לך להמשיג איך tf.function פועל מאחורי הקלעים, כך שאתה יכול להשתמש בו ביעילות.

ההמלצות העיקריות וההמלצות הן:

  • Debug במצב להוט, ואז לקשט עם @tf.function .
  • אל תסמוך על תופעות לוואי של פייתון כמו מוטציה של אובייקטים או הוספת רשימה.
  • tf.function עובד הכי טוב עם ops 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 שאתה מגדיר (למשל על ידי יישום @tf.function המעצב) הוא בדיוק כמו מבצע TensorFlow ליבה: אתה יכול לבצע אותו בשקיקה; אתה יכול לחשב שיפוע; וכן הלאה.

@tf.function  # The decorator converts `add` into a `Function`.
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
2021-08-19 01:26:31.359247: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.367277: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.368263: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.370408: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-19 01:26:31.371033: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.372158: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.373202: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.003755: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.004773: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.005698: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.006564: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
2021-08-19 01:26:32.343820: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 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 של יכול להיות מהיר יותר מאשר קוד להוט, במיוחד עבור גרפים עם ops קטן רב. אבל עבור גרפים עם כמה אופציות יקרות (כמו התפתלויות), ייתכן שלא תראה מהירות רבה.

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")
2021-08-19 01:26:35.135723: I tensorflow/stream_executor/cuda/cuda_dnn.cc:369] Loaded cuDNN version 8100
Eager conv: 0.004266158999996605
Function conv: 0.004805293000003985
Note how there's not much difference in performance for convolutions
2021-08-19 01:26:40.338397: I tensorflow/core/platform/default/subprocess.cc:304] Start cannot spawn child process: No such file or directory

מעקב

סעיף זה חושף כיצד Function פועל מאחורי הקלעים, כולל פרטים יישום אשר עשוי להשתנות בעתיד. עם זאת, ברגע שאתה מבין למה ומתי התחקות קורה, זה הרבה יותר קל להשתמש tf.function ביעילות!

מהו "מעקב"?

Function מפעילה תוכנית שלך גרף TensorFlow . עם זאת, tf.Graph לא יכול לייצג את כל הדברים שאתה רוצה לכתוב בתוכנית TensorFlow להוט. למשל, Python תומך פולימורפיזם, אבל tf.Graph דורש תשומות שלה כדי להיות בעלת סוג נתונים שצוינו מימד. או שתוכל לבצע משימות צד כמו קריאת ארגומנטים בשורת הפקודה, העלאת שגיאה או עבודה עם אובייקט Python מורכב יותר; אף אחד מהדברים האלה יכולים לרוץ בתוך tf.Graph .

Function מגשר על הפער הזה על ידי הפרדת הקוד שלך בשני שלבים:

1) בשלב הראשון, המכונה "התחקות", Function יוצרת חדש tf.Graph . קוד Python פועל בדרך כלל, אבל כל הפעולות TensorFlow (כמו הוספת שתי tensors) נדחות: הם נלכדים על ידי tf.Graph ולא לרוץ.

2) בשלב השני, tf.Graph אשר מכיל את כול מה נדחה בשלב הראשון מנוהל. שלב זה מהיר בהרבה משלב המעקב.

בהתאם תשומותיו, Function תמיד לא תפעל בשלב הראשון כאשר הוא נקרא. ראה "כללים של התחקות" למטה כדי לקבל תחושה טובה יותר של איך זה עושה את זה נחישות. דילוג על השלב הראשון ורק ביצוע השלב השני הוא זה שנותן לך את הביצועים הגבוהים של TensorFlow.

כאשר Function מחליטה להתחקות, בשלב התחקות ומיד אחריו את השלב השני, כך לקרוא Function הן יוצרת ומפעילה את tf.Graph . בהמשך תוכלו לראות איך אתה יכול להריץ רק בשלב התחקות עם get_concrete_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)

אתה יכול להשתמש pretty_printed_concrete_signatures() כדי לראות את כול עקבות הזמינות:

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

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

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

עד כה, ראיתם כי tf.function יוצר במטמון, שכבת דינאמית שלוח מעל הגרף של TensorFlow התחקות ההיגיון. כדי להיות יותר ספציפי לגבי המינוח:

  • tf.Graph הוא הגלם, אגנוסטי בשפה, הייצוג הנייד של חישוב TensorFlow.
  • ConcreteFunction עוטפת tf.Graph .
  • Function מנהלת מטמון של ConcreteFunction ים ומרימה את האדם הנכון עבור התשומות שלך.
  • tf.function עוטפת פונקצית Python, חזרת Function אובייקט.
  • איתור יוצר tf.Graph ועוטף אותו ConcreteFunction , הידוע גם בשם זכר.

כללי מעקב

Function קובעת אם לעשות שימוש חוזר לייחס ConcreteFunction ידי מחשוב מפתח מטמון מתוך ארגומנטים ו kwargs של קלט. מפתח מטמון הוא מפתח מזהה ConcreteFunction מבוסס על קלט ארגומנטים ו kwargs של Function השיחה, על פי הכללים הבאים (היכולה להשתנות):

  • המפתח שנוצר עבור tf.Tensor הוא הצורה ואת dtype.
  • המפתח שנוצר עבור tf.Variable הוא מזהה משתנה ייחודי.
  • המפתח שנוצר עבור פרימיטיבי Python (כמו int , float , str ) הערך שלה.
  • המפתח שנוצר עבור מקוננות dict ים, list של, tuple ים, namedtuple ים, ו attr s הוא tuple משוטח עלה-מפתחות (ראה nest.flatten ). (כתוצאה מהשטחה זו, קריאה לפונקציית בטון בעלת מבנה קינון שונה מזה שמשתמשים בו במהלך המעקב תגרום ל- TypeError).
  • עבור כל שאר סוגי Python המפתח ייחודי לאובייקט. בדרך זו מעקב אחר פונקציה או שיטה באופן עצמאי עבור כל מופע שהוא נקרא איתו.

שליטה בשחזור

Retracing, וזה כאשר שלך Function יוצרת יותר עקבות אחד, מסייעת מבטיח כי 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])))
# You 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]]))

# You 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 "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/1851403433.py", 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 "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/1851403433.py", 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 תואם tensors מבוסס על הצורה שלהם, באמצעות None ממד בתור תו יאפשר Function של עד עקבות חוזרות עבור קלט variably בגודל. קלט variably בגודל יכול להתרחש אם יש לך רצפים באורך שונה, או תמונות בגדלים שונים עבור כל אצווה (עיין Transformer ו- Deep חלום הדרכות למשל).

@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 משמשים hyperparameters המלא ומבנים גרף - למשל, num_layers=10 או training=True או nonlinearity='relu' . לכן, אם הארגומנט של פייתון משתנה, הגיוני שתצטרך לחזור על הגרף.

    עם זאת, ייתכן שלא נעשה שימוש בארגומנט Python לשליטה על בניית הגרפים. במקרים אלה, שינוי בערך Python יכול לעורר חזרה מיותרת. קח, למשל, את לולאת האימון הזו, שאוטוגרף תפתח באופן דינמי. למרות עקבות מרובים, הגרף שנוצר הוא למעשה זהה, ולכן החזרה לא נחוצה.

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

קבלת פונקציות בטון

בכל פעם שמתחקים אחר פונקציה, נוצרת פונקציה קונקרטית חדשה. אתה יכול להשיג פונקצית בטון ישירות, באמצעות 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)

הדפסת 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 "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3196284684.py", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_162 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_162]

ייתכן שתבחין כי טיעוני פייתון ניתנים לטיפול מיוחד בחתימת הקלט של פונקציה קונקרטית. לפני 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.7/site-packages/tensorflow/python/eager/function.py", line 1721, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py", line 1766, 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 "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/2310937119.py", 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

קבלת גרפים

כל פונקציה בטון הוא מעטפת callable סביב 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.function .

כאשר להתחקות אחר סוגיות המופיעות רק בתוך tf.function , הנה כמה טיפים:

  • זקן Python מישור print שיחות רק לבצע במהלך מעקב, עוזרות לך לעקוב אחר מטה כאשר הפונקציה שלך מקבלת (מחדש) לייחס.
  • tf.print שיחות תבצענה בכול פעם, והוא יכול לעזור לך לאתר את ערכי ביניים במהלך ביצוע.
  • tf.debugging.enable_check_numerics היא דרך קלה לעקוב אחר מטה איפה NaNs ו Inf נוצרים.
  • pdb (את הבאגים Python ) יכול לעזור לך להבין מה קורה במהלך מעקב. (אזהרה: pdb ירד לך לתוך קוד מקור-טרנספורמציה חתימה.)

טרנספורמציות של AutoGraph

חתימה היא ספרייה אשר מופעלת כברירת מחדל ב tf.function , והופכת משנה של קוד להוט Python לתוך ops TensorFlow תואם גרף. זה כולל בקרת זרימה כמו if , for , while .

Ops TensorFlow כמו tf.cond ו tf.while_loop להמשיך לעבוד, אך בקרת זרימה הוא בדרך כלל קל יותר לכתוב ולהבין כאשר כתוב Python.

# A 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.680332422 0.0791739225 0.0669257641 0.701964378 0.535618901]
[0.591735482 0.0790088922 0.0668260083 0.605613232 0.489664495]
[0.531142652 0.0788449 0.0667267069 0.541031778 0.453950107]
[0.486254066 0.078681916 0.0666278452 0.493768573 0.425140589]
[0.451238334 0.0785199404 0.0665294155 0.457202375 0.40125224]
[0.422916353 0.0783589631 0.0664314255 0.427801341 0.38101992]
[0.399384439 0.0781989694 0.0663338676 0.403482229 0.363592863]
[0.379422128 0.0780399516 0.0662367418 0.382924527 0.348374784]
[0.362205505 0.0778819 0.0661400482 0.365244567 0.3349334]
[0.347155213 0.0777248144 0.0660437644 0.349825174 0.322946697]
[0.333850235 0.0775686726 0.0659479052 0.336220473 0.312168896]
[0.321976125 0.0774134621 0.0658524558 0.324099 0.302408934]
[0.311292648 0.0772591755 0.0657574236 0.31320855 0.293515563]
[0.301612616 0.0771058127 0.0656628 0.30335319 0.28536731]
[0.292787671 0.0769533589 0.0655685887 0.294378221 0.27786532]
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.28469858, 0.07680181, 0.06547478, 0.28615952, 0.27092823],
      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)

תנאים

החתימה תמיר חלק if <condition> דוחות למקבילת tf.cond השיחות. החלפה זו מתבצעת אם <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

עיין בתיעוד העזר עבור מגבלות נוספות על אוטוגרף המרה אם הצהרות.

לולאות

חתימה תמיר חלק for ו while הצהרות לתוך TensorFlow המקבילה looping ops, כמו tf.while_loop . אם לא המיר את for או while הלולאה מבוצעת כמו לולאת Python.

החלפה זו מתבצעת במצבים הבאים:

  • for x in y : אם y הוא מותח, להמיר tf.while_loop . במקרה המיוחד שבו y הוא tf.data.Dataset , שילוב של tf.data.Dataset ops נוצר.
  • while <condition> : אם <condition> הוא מותח, להמיר tf.while_loop .

פייתון עוסקת בביצוע לולאה במהלך עקיבה, הוספת ops נוסף tf.Graph לכול איטרציה של הלולאה.

לולאת TensorFlow עוקבת אחר גוף הלולאה, ובוחרת באופן דינמי כמה איטרציות להריץ בזמן הביצוע. גוף הלולאה מופיע רק פעם אחת שנוצר tf.Graph .

עיין בתיעוד העזר עבור מגבלות נוספות על אוטוגרף מרה for ו while הצהרות.

לולאה על נתוני Python

נופל בפח הוא לולאה מעל נתון numpy / Python בתוך tf.function . לולאה זו תבצע במהלך תהליך התחקות, הוספת עותק של המודל שלך אל tf.Graph עבור כל איטרציה של הלולאה.

אם אתה רוצה לעטוף את לולאת אימונים השלמה tf.function , הדרך הבטוחה ביותר לעשות זאת היא לעטוף את נתון שלך בתור 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 6 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 6 nodes in its graph

כאשר גלישת נתונים Python / numpy של מערך נתונים, להיות מודע tf.data.Dataset.from_generator לעומת tf.data.Dataset.from_tensors . הראשון ישמור על נתון ב Python ותחזיר אותו באמצעות tf.py_function אשר יכולה להיות שלכות ביצועים, בעוד שהאחרון יהיה צרור עותק של הנתונים כמו גדול אחד tf.constant() צומת בגרף, אשר יכולה להיות שלכות זיכרון.

קריאת נתונים מקבצים באמצעות TFRecordDataset , CsvDataset , וכו 'היא הדרך האפקטיבית ביותר לצרוך נתונים, כמו אז TensorFlow עצמו יכול לנהל את הטעינה אסינכרוני הכנה מקדימה של נתונים, מבלי לערב Python. כדי ללמוד עוד, ראה tf.data : צינורות הזנה Build TensorFlow להנחות.

צבירת ערכים בלולאה

דפוס נפוץ הוא לצבור ערכי ביניים מלולאה. בדרך כלל, הדבר מתבצע על ידי הוספת לרשימת Python או הוספת ערכים למילון Python. עם זאת, מכיוון שמדובר בתופעות לוואי של פייתון, הן לא יפעלו כצפוי בלולאה שנפרסה באופן דינמי. השתמש 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.9311646 , 0.23201597, 0.27760983, 0.50749147],
        [1.9157217 , 0.73632634, 1.2418742 , 1.3936795 ],
        [2.2432408 , 0.80746305, 1.2652718 , 2.0350993 ]],

       [[0.6350479 , 0.57744336, 0.1134994 , 0.49145043],
        [1.4704436 , 1.2057934 , 0.6300938 , 1.1418717 ],
        [2.2552116 , 1.7597941 , 1.3745583 , 1.9007651 ]]], dtype=float32)>

מגבלות

TensorFlow Function יש מספר מגבלות על ידי עיצוב שאתה צריך להיות מודע בעת המרת פונקצית פיתון על Function .

ביצוע תופעות לוואי של פייתון

תופעות לוואי, כמו הדפסה, צירוף לרשימות, ו מוטציה globals, יכול להתנהג באופן בלתי צפוי בתוך Function , לפעמים ביצוע פעמיים או לא בכלל. הם רק לקרות בפעם הראשונה שאתם קוראים Function עם סט של כניסות. לאחר מכן, לייחס tf.Graph הוא reexecuted, ללא צורך בהפעלת קוד פיתון.

כלל האצבע הכללי הוא להימנע מהסתמכות על תופעות לוואי של פייתון בהגיון שלך ולהשתמש בהן רק כדי לאתר באגים אחר עקבותיך. אחרת, APIs TensorFlow כמו tf.data , tf.print , tf.summary , tf.Variable.assign , ו tf.TensorArray הם הדרך הטובה ביותר להבטיח את הקוד שלך יבוצע על ידי ריצה 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 , tf.py_function מהווה צוהר יציאה. החיסרון של tf.py_function היא שזה לא נייד או במיוחד performant, לא ניתן לשמור עם SavedModel, ואינו פועל היטב מופץ הגדרות (-GPU רב, TPU). כמו כן, מאז tf.py_function יש חוטית לתוך הגרף, זה מטיל את כל כניסות / יציאות כדי tensors.

שינוי משתנים גלובליים וחופשיים של פייתון

שינוי הגלובלי Python משתנה חינם ספירה כתופעת לוואי Python, אז זה קורה רק במהלך מעקב.

external_list = []

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

side_effect(1)
side_effect(1)
side_effect(1)
# The list append only happened once!
assert len(external_list) == 1
Python side effect

אתה צריך להימנע מוטציה מכולות כמו רשימות, dicts, אובייקטים אחרים שחיים מחוץ Function . במקום זאת, השתמש בארגומנטים ובאובייקטים של TF. לדוגמה, בפרק "צבירת ערכי בתוך לולאה" יש דוגמה אחת לאופן רשימה דמוי פעולות יכול להיות מיושם.

אתה יכול, במקרים מסוימים, ללכוד ולטפל מדינה אם מדובר tf.Variable . כך משקלות דגמי Keras מתעדכנים עם קריאות חוזרות ונשנות באותו ConcreteFunction .

שימוש באיטרטורים ובמחוללי פייתון

תכונות רבות של פייתון, כגון גנרטורים ואיטרטורים, מסתמכות על זמן הריצה של פייתון כדי לעקוב אחר המצב. באופן כללי, בעוד שמבנים אלה פועלים כצפוי במצב להוט, הם מהווים דוגמאות לתופעות לוואי של פייתון ולכן הן מתרחשות רק במהלך מעקב.

@tf.function
def buggy_consume_next(iterator):
  tf.print("Value:", next(iterator))

iterator = iter([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: 1
Value: 1
Value: 1

בדיוק כמו איך TensorFlow יש לו התמחה tf.TensorArray עבור מבני רשימה, יש לו התמחה tf.data.Iterator עבור איטרציה מבנים. עיין בסעיף על טרנספורמציות חתימה עבור סקירה. כמו כן, tf.data API יכול לעזור ליישם דפוסי גנרטור:

@tf.function
def good_consume_next(iterator):
  # This is ok, iterator is a tf.data.Iterator
  tf.print("Value:", next(iterator))

ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
iterator = iter(ds)
good_consume_next(iterator)
good_consume_next(iterator)
good_consume_next(iterator)
Value: 1
Value: 2
Value: 3

מחיקת tf.Variables בין Function שיחות

שגיאה נוספת שאתה עלול להיתקל בה היא משתנה שנאסף לאשפה. ConcreteFunction זה רק לשמור 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))

# The original variable object gets garbage collected, since there are no more
# references to it.
external_var = tf.Variable(4)
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 "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3862898592.py", line 16, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Could not find variable _AnonymousVar3. This could mean that the variable has been deleted. In TF1, it can also mean the variable is uninitialized. Debug info: container=localhost, status=Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at tmp/ipykernel_25646/3862898592.py:4) ]]
  (1) Failed precondition:  Could not find variable _AnonymousVar3. This could mean that the variable has been deleted. In TF1, it can also mean the variable is uninitialized. Debug info: container=localhost, status=Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at tmp/ipykernel_25646/3862898592.py:4) ]]
     [[ReadVariableOp/_2]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_772]

Function call stack:
f -> f

בעיות ידועות

אם שלך Function לא מעריכים כראוי, את השגיאה יכולה להיות מוסברת על ידי בעיות ידועות אלה אשר מתוכננים להיות קבוע בעתיד.

תלוי במשתנים הגלובליים והחינמיים של פייתון

Function יוצרת חדשה ConcreteFunction כאשר נקרא עם ערך חדש של טיעון Python. עם זאת, זה לא עושה את זה בשביל סגירת Python, globals, או nonlocals של אותה Function . אם ערכם משתנה בין שיחות Function , את Function עדיין תשתמש הערכים הם היו כשזה אותר. זה שונה מאיך שפונקציות Python רגילות עובדות.

מסיבה זו, אנו ממליצים על סגנון תכנות פונקציונלי המשתמש בטיעונים במקום לסגור מעל שמות חיצוניים.

@tf.function
def buggy_add():
  return 1 + foo

@tf.function
def recommended_add(foo):
  return 1 + foo

foo = 1
print("Buggy:", buggy_add())
print("Correct:", recommended_add(foo))
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(2, shape=(), dtype=int32)
print("Updating the value of `foo` to 100!")
foo = 100
print("Buggy:", buggy_add())  # Did not change!
print("Correct:", recommended_add(foo))
Updating the value of `foo` to 100!
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(101, shape=(), dtype=int32)

אתה יכול לסגור מעל שמות חיצוניים, כל עוד אתה לא מעדכן את הערכים שלהם.

תלוי באובייקטים של פייתון

ההמלצה להעביר חפצים Python כטיעונים לתוך tf.function יש מספר בעיות ידועות, אשר צפויים להיות קבוע בעתיד. באופן כללי, אתה יכול לסמוך על מעקב עקבי אם אתה משתמש Python פרימיטיבי או tf.nest מבנה -compatible כטיעון או לעבור במופע אחר של אובייקט לתוך Function . עם זאת, Function לא תיצור עקבות חדשות כאשר אתה עובר את אותו אובייקט ורק לשנות התכונות שלה.

class SimpleModel(tf.Module):
  def __init__(self):
    # These values are *not* tf.Variables.
    self.bias = 0.
    self.weight = 2.

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

simple_model = SimpleModel()
x = tf.constant(10.)
print(evaluate(simple_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
simple_model.bias += 5.0
print(evaluate(simple_model, x))  # Didn't change :(
Adding bias!
tf.Tensor(20.0, shape=(), dtype=float32)

שימוש באותה Function להעריך את המופע המעודכן של המודל יהיה מרכבה מאז המודל המעודכן יש את אותו מפתח המטמון כמו הדגם המקורי.

מסיבה זו, אנו ממליצים לכתוב שלך Function להימנע תלוי תכונות אובייקט משתנות או ליצור אובייקטים חדשים.

אם זה לא אפשרי, מעקף אחד היא להפוך חדשה Function s בכול פעם שתשנה האובייקט שלך כדי לשחזר את הכח:

def evaluate(model, x):
  return model.weight * x + model.bias

new_model = SimpleModel()
evaluate_no_bias = tf.function(evaluate).get_concrete_function(new_model, x)
# Don't pass in `new_model`, `Function` already captured its state during tracing.
print(evaluate_no_bias(x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
new_model.bias += 5.0
# Create new Function and ConcreteFunction since you modified new_model.
evaluate_with_bias = tf.function(evaluate).get_concrete_function(new_model, x)
print(evaluate_with_bias(x)) # Don't pass in `new_model`.
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

כפי retracing יכול להיות יקר , אתה יכול להשתמש tf.Variable ים כמו תכונות אובייקט, אשר יכול להיות מוטציה (אך לא השתנו,! זהיר) עבור אפקט דומה ללא צורך retrace.

class BetterModel:

  def __init__(self):
    self.bias = tf.Variable(0.)
    self.weight = tf.Variable(2.)

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

better_model = BetterModel()
print(evaluate(better_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
better_model.bias.assign_add(5.0)  # Note: instead of better_model.bias += 5
print(evaluate(better_model, x))  # This works!
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

יצירת tf.Variables

Function תומכת רק סינגלטון tf.Variable s נוצר פעם על השיחה הראשונה, ולעשות בהם שימוש חוזר ברחבי שיחות פונקציה עוקבות. קטע הקוד מתחת תיצור חדש tf.Variable בכל קריאה לפונקציה, שתוצאתה ValueError החריג.

דוגמה:

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

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3018268426.py", line 7, in <module>
    f(1.0)
ValueError: in user code:

    /tmp/ipykernel_25646/3018268426.py:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:268 __call__  **
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:262 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py:765 invalid_creator_scope
        "tf.function-decorated function tried to create "

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

דפוס נפוץ המשמש לעקוף מגבלה זו היא להתחיל עם ערך אף Python, אז באופן מותנה ליצור את tf.Variable אם הערך הוא None:

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)

שימוש עם מספר אופטימיזציות של Keras

אתה עלול להיתקל ValueError: tf.function only supports singleton tf.Variables created on the first call. כשמשתמשים יותר האופטימיזציה אחד Keras עם tf.function . שגיאה זו מתרחשת משום אופטימיזציה פנימית ליצור tf.Variables כשהם חלים הדרגתיים בפעם הראשונה.

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

@tf.function
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

train_step(w, x, y, opt1)
print("Calling `train_step` with different optimizer...")
with assert_raises(ValueError):
  train_step(w, x, y, opt2)
Calling `train_step` with different optimizer...
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3167358578.py", line 18, in <module>
    train_step(w, x, y, opt2)
ValueError: in user code:

    /tmp/ipykernel_25646/3167358578.py:9 train_step  *
        optimizer.apply_gradients(zip(gradients, [w]))
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:628 apply_gradients  **
        self._create_all_weights(var_list)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:813 _create_all_weights
        _ = self.iterations
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:820 __getattribute__
        return super(OptimizerV2, self).__getattribute__(name)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:980 iterations
        aggregation=tf.VariableAggregation.ONLY_FIRST_REPLICA)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:1186 add_weight
        aggregation=aggregation)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/training/tracking/base.py:818 _add_variable_with_custom_getter
        **kwargs_for_getter)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/base_layer_utils.py:129 make_variable
        shape=variable_shape if variable_shape else None)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:266 __call__
        return cls._variable_v1_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:227 _variable_v1_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py:765 invalid_creator_scope
        "tf.function-decorated function tried to create "

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

אם אתה צריך לשנות את האופטימיזציה במהלך האימון, ניתן לעקוף את הבעיה על מנת ליצור חדש Function עבור כל האופטימיזציה, לקרוא ConcreteFunction ישירות.

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

# Not a tf.function.
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

# Make a new Function and ConcreteFunction for each optimizer.
train_step_1 = tf.function(train_step).get_concrete_function(w, x, y, opt1)
train_step_2 = tf.function(train_step).get_concrete_function(w, x, y, opt2)
for i in range(10):
  if i % 2 == 0:
    train_step_1(w, x, y) # `opt1` is not used as a parameter. 
  else:
    train_step_2(w, x, y) # `opt2` is not used as a parameter.

שימוש עם מספר דגמי Keras

ייתכן גם מפגש ValueError: tf.function only supports singleton tf.Variables created on the first call. כאשר עוברים מקרי מודל שונים לאותה Function .

שגיאה זו מתרחשת משום מודלי Keras (אשר אין צורת הקלט שלהם מוגדרת ) ושכבות Keras ליצור tf.Variables ים כשהם נקראים ראשונים. ייתכן שאתה מנסה לאתחל אלה משתנים בתוך Function , אשר כבר נקרא. כדי למנוע שגיאה זו, נסה להתקשר model.build(input_shape) כדי לאתחל את כל המשקולות לפני אימון המודל.

קריאה נוספת

כדי ללמוד על איך לייצא לטעון Function , לראות את מדריך SavedModel . כדי ללמוד עוד על אופטימיזציות גרף המבוצעות לאחר התחקות, לראות את מדריך grappler . כדי ללמוד כיצד לייעל צינור הנתונים שלך ופרופיל המודל שלך, לראות את מדריך Profiler .