หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

ประสิทธิภาพที่ดีขึ้นด้วยฟังก์ชัน tf

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดสมุดบันทึก

ใน TensorFlow 2 การดำเนินการอย่างกระตือรือร้นจะเปิดใช้งานโดยค่าเริ่มต้น อินเทอร์เฟซผู้ใช้นั้นใช้งานง่ายและยืดหยุ่น (การใช้งานครั้งเดียวทำได้ง่ายกว่าและเร็วกว่ามาก) แต่อาจมีค่าใช้จ่ายด้านประสิทธิภาพและความสามารถในการปรับใช้

คุณสามารถใช้ tf.function เพื่อสร้างกราฟจากโปรแกรมของคุณ เป็นเครื่องมือแปลงร่างที่สร้างกราฟกระแสข้อมูลที่ไม่ขึ้นกับ Python จากโค้ด Python ของคุณ สิ่งนี้จะช่วยให้คุณสร้างโมเดลที่มีประสิทธิภาพและพกพาได้และจำเป็นต้องใช้ SavedModel

คู่มือนี้จะช่วยให้คุณกำหนดแนวคิดว่า tf.function ทำงานอย่างไรภายใต้ฝากระโปรงเพื่อให้คุณใช้งานได้อย่างมีประสิทธิภาพ

ประเด็นหลักและคำแนะนำคือ:

  • ดีบักในโหมดกระตือรือร้นแล้วตกแต่งด้วย @tf.function
  • อย่าพึ่งพาผลข้างเคียงของ Python เช่นการกลายพันธุ์ของวัตถุหรือรายการต่อท้าย
  • tf.function ทำงานได้ดีที่สุดกับ TensorFlow ops; การเรียก 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 สามารถเร็วกว่าโค้ด eager โดยเฉพาะอย่างยิ่งสำหรับกราฟที่มีตัวเลือกขนาดเล็กจำนวนมาก แต่สำหรับกราฟที่มีตัวเลือกราคาแพง (เช่น Convolutions) คุณอาจไม่เห็นการเร่งความเร็วมากนัก

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 หมายความว่าคุณสามารถเรียกใช้ฟังก์ชันที่มีประเภทอาร์กิวเมนต์ได้หลากหลายและ Python สามารถทำสิ่งที่แตกต่างกันไปในแต่ละสถานการณ์

อย่างไรก็ตามในการสร้าง TensorFlow Graph จำเป็นต้องมี 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 คือการนำเสนอการคำนวณแบบไม่เข้าใจภาษาและแบบพกพาของคุณ
  • ConcreteFunction คือ wrapper ที่ดำเนินการอย่างกระตือรือร้นรอบ ๆ tf.Graph
  • Function จัดการแคชของ ConcreteFunction s และเลือกสิ่งที่เหมาะสมสำหรับอินพุตของคุณ
  • tf.function ล้อมรอบฟังก์ชัน Python โดยส่งคืนวัตถุ 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]

คุณอาจสังเกตเห็นว่าอาร์กิวเมนต์ Python ได้รับการปฏิบัติเป็นพิเศษในลายเซ็นอินพุตของฟังก์ชันที่เป็นรูปธรรม ก่อนหน้า 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.config.run_functions_eagerly(True) เพื่อปิดใช้งานและเปิดใช้งาน tf.function ทั่วโลก

เมื่อติดตามปัญหาที่ปรากฏเฉพาะใน tf.function . นี่คือเคล็ดลับบางประการ:

  • print เรียก print Python แบบเก่าธรรมดาจะดำเนินการระหว่างการติดตามเท่านั้นช่วยให้คุณติดตามเมื่อฟังก์ชันของคุณได้รับการติดตาม (อีกครั้ง)
  • tf.print เรียก tf.print จะดำเนินการทุกครั้งและสามารถช่วยคุณติดตามค่ากลางระหว่างการดำเนินการ
  • tf.debugging.enable_check_numerics เป็นวิธีง่ายๆในการติดตามตำแหน่งที่สร้าง NaNs และ Inf
  • pdb สามารถช่วยให้คุณเข้าใจว่าเกิดอะไรขึ้นระหว่างการติดตาม (ข้อแม้: PDB จะนำคุณไปสู่ซอร์สโค้ดที่แปลงเป็น AutoGraph)

การติดตามความหมาย

กฎคีย์แคช

Function กำหนดว่าจะนำฟังก์ชันคอนกรีตที่ติดตามมาใช้ซ้ำหรือไม่โดยการคำนวณคีย์แคชจาก args และ kwargs ของอินพุต

  • คีย์ที่สร้างขึ้นสำหรับอาร์กิวเมนต์ tf.Tensor คือรูปร่างและ dtype
  • เริ่มต้นใน TensorFlow 2.3 คีย์ที่สร้างขึ้นสำหรับ tf.Variable อาร์กิวเมนต์ tf.Variable คือ id()
  • คีย์ที่สร้างขึ้นสำหรับ Python ดั้งเดิมคือค่าของมัน คีย์ที่สร้างขึ้นสำหรับ dict ซ้อนกัน, list s, tuple s, namedtuple s และ attr s คือทูเพิลแบบแบน (อันเป็นผลมาจากการแบนนี้การเรียกฟังก์ชันคอนกรีตที่มีโครงสร้างการซ้อนที่แตกต่างจากที่ใช้ระหว่างการติดตามจะส่งผลให้เกิด TypeError)
  • สำหรับ Python ประเภทอื่น ๆ ทั้งหมดคีย์จะขึ้นอยู่กับ id() อ็อบเจ็กต์ id() เพื่อให้เมธอดถูกติดตามอย่างอิสระสำหรับแต่ละอินสแตนซ์ของคลาส

การควบคุมการย้อนกลับ

Retracing ช่วยให้มั่นใจได้ว่า 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 จับคู่ Tensors ตามรูปร่างการใช้ None Dimension เป็นสัญลักษณ์แทนจะช่วยให้ 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)

  • ส่งอาร์กิวเมนต์ Python ไปยัง Tensors เพื่อลดการย้อนกลับ

    บ่อยครั้งอาร์กิวเมนต์ Python ถูกใช้เพื่อควบคุม num_layers=10 และโครงสร้างกราฟ - ตัวอย่างเช่น num_layers=10 หรือ training=True หรือ nonlinearity='relu' ดังนั้นหากอาร์กิวเมนต์ Python เปลี่ยนไปก็สมเหตุสมผลที่คุณจะต้องย้อนดูกราฟ

    อย่างไรก็ตามอาจเป็นไปได้ว่าอาร์กิวเมนต์ Python ไม่ได้ถูกใช้เพื่อควบคุมการสร้างกราฟ ในกรณีเหล่านี้การเปลี่ยนแปลงค่า 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

ผลข้างเคียงของ Python เช่นการพิมพ์การต่อท้ายรายการและการกลายพันธุ์ของลูกโลกเกิดขึ้นในครั้งแรกที่คุณเรียกใช้ Function ด้วยชุดอินพุต หลังจากนั้น tf.Graph ติดตามจะถูกเรียกใช้งานอีกครั้งโดยไม่ต้องเรียกใช้โค้ด Python

หลักการทั่วไปคือใช้เฉพาะผลข้างเคียงของ Python เพื่อแก้จุดบกพร่องร่องรอยของคุณ มิฉะนั้น TensorFlow ops เช่น 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

ตัวอย่างหนึ่งการเลื่อนสถานะตัววนซ้ำเป็นผลข้างเคียงของ Python ดังนั้นจึงเกิดขึ้นระหว่างการติดตามเท่านั้น

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 คือไม่สามารถพกพาได้หรือมีประสิทธิภาพโดยเฉพาะและทำงานได้ดีในการตั้งค่าแบบกระจาย (multi-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 ใหม่ในฟังก์ชัน ข้อผิดพลาดนี้ป้องกันความแตกต่างของพฤติกรรมในการเรียกซ้ำ: ในโหมดกระตือรือร้นฟังก์ชันจะสร้างตัวแปรใหม่สำหรับการโทรแต่ละครั้ง แต่ใน 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 เป็นไลบรารีที่เปิดใช้งานโดยค่าเริ่มต้นใน tf.function และแปลงส่วนย่อยของโค้ด Python eager ให้เป็น TensorFlow ops ที่เข้ากันได้กับกราฟ ซึ่งรวมถึงโฟลว์การควบคุมเช่น if , for , while

TensorFlow ops เช่น 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> เป็น Tensor มิฉะนั้นคำสั่ง if จะถูกดำเนินการโดยมีเงื่อนไข Python

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

ดู เอกสารอ้างอิง สำหรับข้อ จำกัด เพิ่มเติมเกี่ยวกับคำสั่ง AutoGraph-modified if

ลูป

AutoGraph จะแปลงคำสั่ง for and while บางคำสั่งเป็น TensorFlow looping ops ที่เทียบเท่าเช่น 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

ลูป Python ดำเนินการระหว่างการติดตามโดยเพิ่มตัวเลือกเพิ่มเติมให้กับ tf.Graph สำหรับการวนซ้ำทุกครั้ง

ลูป TensorFlow ติดตามเนื้อหาของลูปและเลือกจำนวนการวนซ้ำที่จะรันในเวลาดำเนินการแบบไดนามิก ห่วงปรากฏเพียงครั้งเดียวใน tf.Graph สร้างขึ้น

ดู เอกสารอ้างอิง สำหรับข้อ จำกัด เพิ่มเติมเกี่ยวกับคำสั่ง AutoGraph ที่แปลง for และ while

วนซ้ำข้อมูล Python

ข้อผิดพลาดทั่วไปคือการวนซ้ำข้อมูล Python / Numpy ภายใน tf.function ลูปนี้จะดำเนินการในระหว่างขั้นตอนการติดตามโดยเพิ่มสำเนาของโมเดลของคุณลงใน 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 ก่อนหน้านี้จะเก็บข้อมูลไว้ใน Python และดึงข้อมูลผ่าน tf.py_function ซึ่งอาจมีผลกระทบด้านประสิทธิภาพในขณะที่ส่วนหลังจะรวมสำเนาของข้อมูลเป็นโหนด tf.constant() ขนาดใหญ่หนึ่งโหนดในกราฟซึ่งอาจมีผลต่อหน่วยความจำ

การอ่านข้อมูลจากไฟล์ผ่าน TFRecordDataset / CsvDataset / etc เป็นวิธีที่มีประสิทธิภาพสูงสุดในการใช้ข้อมูลเนื่องจาก TensorFlow เองสามารถจัดการการโหลดแบบอะซิงโครนัสและการดึงข้อมูลล่วงหน้าโดยไม่ต้องเกี่ยวข้องกับ Python หากต้องการเรียนรู้เพิ่มเติมโปรดดู คู่มือ tf.data

การสะสมค่าในวง

รูปแบบทั่วไปคือการสะสมค่ากลางจากลูป โดยปกติจะทำได้โดยการต่อท้ายรายการ Python หรือเพิ่มรายการในพจนานุกรม 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.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