Lihat di TensorFlow.org | Jalankan di Google Colab | Lihat sumber di GitHub | Unduh buku catatan |
Di TensorFlow 2, eksekusi bersemangat diaktifkan secara default. Antarmuka pengguna intuitif dan fleksibel (menjalankan operasi satu kali jauh lebih mudah dan lebih cepat), tetapi ini dapat mengorbankan kinerja dan kemampuan penerapan.
Anda dapat menggunakan tf.function
untuk membuat grafik dari program Anda. Ini adalah alat transformasi yang membuat grafik aliran data Python-independen dari kode Python Anda. Ini akan membantu Anda membuat model yang berkinerja dan portabel, dan itu diperlukan untuk menggunakan SavedModel
.
Panduan ini akan membantu Anda mengkonseptualisasikan bagaimana tf.function
bekerja di bawah tenda, sehingga Anda dapat menggunakannya secara efektif.
Takeaways utama dan rekomendasi adalah:
- Debug dalam mode bersemangat, lalu hiasi dengan
@tf.function
. - Jangan mengandalkan efek samping Python seperti mutasi objek atau penambahan daftar.
-
tf.function
berfungsi paling baik dengan operasi TensorFlow; Panggilan NumPy dan Python dikonversi ke konstanta.
Mempersiapkan
import tensorflow as tf
Tentukan fungsi pembantu untuk menunjukkan jenis kesalahan yang mungkin Anda temui:
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))
Dasar-dasar
Penggunaan
Function
yang Anda tentukan (misalnya dengan menerapkan dekorator @tf.function
) sama seperti operasi inti TensorFlow: Anda dapat menjalankannya dengan penuh semangat; Anda dapat menghitung gradien; dan seterusnya.
@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.]]
<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>
Anda dapat menggunakan Function
s di dalam Function
s lainnya.
@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 bisa lebih cepat daripada kode bersemangat, terutama untuk grafik dengan banyak operasi kecil. Tetapi untuk grafik dengan beberapa operasi mahal (seperti konvolusi), Anda mungkin tidak melihat banyak percepatan.
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.006058974999177735 Function conv: 0.005791576000774512 Note how there's not much difference in performance for convolutions
Pelacakan
Bagian ini memaparkan bagaimana Function
bekerja di bawah tenda, termasuk detail implementasi yang mungkin berubah di masa mendatang . Namun, begitu Anda memahami mengapa dan kapan penelusuran terjadi, akan jauh lebih mudah untuk menggunakan tf.function
secara efektif!
Apa itu "menelusuri"?
Function
menjalankan program Anda dalam Grafik TensorFlow . Namun, tf.Graph
tidak dapat mewakili semua hal yang akan Anda tulis dalam program TensorFlow yang bersemangat. Misalnya, Python mendukung polimorfisme, tetapi tf.Graph
membutuhkan inputnya untuk memiliki tipe dan dimensi data yang ditentukan. Atau Anda dapat melakukan tugas sampingan seperti membaca argumen baris perintah, memunculkan kesalahan, atau bekerja dengan objek Python yang lebih kompleks; tidak satu pun dari hal-hal ini dapat berjalan di tf.Graph
.
Function
menjembatani kesenjangan ini dengan memisahkan kode Anda dalam dua tahap:
1) Pada tahap pertama, disebut sebagai " tracing ", Function
membuat tf.Graph
baru. Kode Python berjalan normal, tetapi semua operasi TensorFlow (seperti menambahkan dua Tensor) ditangguhkan : mereka ditangkap oleh tf.Graph
dan tidak dijalankan.
2) Pada tahap kedua, tf.Graph
yang berisi semua yang ditangguhkan pada tahap pertama dijalankan. Tahap ini jauh lebih cepat daripada tahap tracing.
Bergantung pada inputnya, Function
tidak akan selalu menjalankan tahap pertama saat dipanggil. Lihat "Aturan penelusuran" di bawah untuk mendapatkan pemahaman yang lebih baik tentang bagaimana hal itu membuat penentuan itu. Melewati tahap pertama dan hanya menjalankan tahap kedua akan memberi Anda kinerja tinggi TensorFlow.
Ketika Function
memutuskan untuk melacak, tahap penelusuran segera diikuti oleh tahap kedua, jadi memanggil Function
akan membuat dan menjalankan tf.Graph
. Nanti Anda akan melihat bagaimana Anda hanya dapat menjalankan tahap penelusuran dengan get_concrete_function
.
Saat Anda meneruskan argumen dari berbagai jenis ke dalam Function
, kedua tahapan dijalankan:
@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)
Perhatikan bahwa jika Anda berulang kali memanggil Function
dengan tipe argumen yang sama, TensorFlow akan melewati tahap penelusuran dan menggunakan kembali grafik yang dilacak sebelumnya, karena grafik yang dihasilkan akan identik.
# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)
Anda dapat menggunakan pretty_printed_concrete_signatures()
untuk melihat semua jejak yang tersedia:
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=()
Sejauh ini, Anda telah melihat bahwa tf.function
membuat lapisan pengiriman dinamis yang di-cache di atas logika pelacakan grafik TensorFlow. Untuk lebih spesifik tentang terminologi:
-
tf.Graph
adalah representasi mentah, agnostik bahasa, portabel dari komputasi TensorFlow. - Sebuah
ConcreteFunction
membungkustf.Graph
. - Sebuah
Function
mengelola cache dariConcreteFunction
s dan memilih yang tepat untuk masukan Anda. -
tf.function
membungkus fungsi Python, mengembalikan objekFunction
. - Tracing membuat
tf.Graph
dan membungkusnya dalamConcreteFunction
, juga dikenal sebagai trace.
Aturan pelacakan
Function
menentukan apakah akan menggunakan kembali ConcreteFunction
yang dilacak dengan menghitung kunci cache dari argumen dan kwargs input. Kunci cache adalah kunci yang mengidentifikasi ConcreteFunction
berdasarkan argumen input dan kwargs dari pemanggilan Function
, menurut aturan berikut (yang dapat berubah):
- Kunci yang dihasilkan untuk
tf.Tensor
adalah bentuk dan tipenya. - Kunci yang dihasilkan untuk
tf.Variable
adalah id variabel unik. - Kunci yang dihasilkan untuk primitif Python (seperti
int
,float
,str
) adalah nilainya. - Kunci yang dihasilkan untuk nested
dict
s,list
s,tuple
s,namedtuple
s, danattr
s adalah tuple yang diratakan dari leaf-keys (lihatnest.flatten
). (Sebagai hasil dari perataan ini, memanggil fungsi konkret dengan struktur bersarang yang berbeda dari yang digunakan selama penelusuran akan menghasilkan TypeError). - Untuk semua jenis Python lainnya, kuncinya unik untuk objek. Dengan cara ini fungsi atau metode dilacak secara independen untuk setiap instance yang dipanggil.
Mengontrol penelusuran kembali
Penelusuran ulang, yaitu saat Function
Anda membuat lebih dari satu jejak, membantu memastikan bahwa TensorFlow menghasilkan grafik yang benar untuk setiap rangkaian input. Namun, penelusuran adalah operasi yang mahal! Jika Function
Anda menelusuri kembali grafik baru untuk setiap panggilan, Anda akan menemukan bahwa kode Anda dieksekusi lebih lambat daripada jika Anda tidak menggunakan tf.function
.
Untuk mengontrol perilaku penelusuran, Anda dapat menggunakan teknik berikut:
- Tentukan
input_signature
ditf.function
untuk membatasi pelacakan.
@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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/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)).
Tentukan dimensi [None] di
tf.TensorSpec
untuk memungkinkan fleksibilitas dalam penggunaan kembali pelacakan.Karena TensorFlow cocok dengan tensor berdasarkan bentuknya, menggunakan dimensi
None
sebagai karakter pengganti akan memungkinkanFunction
s menggunakan kembali jejak untuk input berukuran bervariasi. Input dengan ukuran yang bervariasi dapat terjadi jika Anda memiliki urutan dengan panjang yang berbeda, atau gambar dengan ukuran berbeda untuk setiap batch (Lihat tutorial Transformer dan Deep Dream misalnya).
@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)
Keluarkan argumen Python ke Tensor untuk mengurangi penelusuran ulang.
Seringkali, argumen Python digunakan untuk mengontrol hiperparameter dan konstruksi grafik - misalnya,
num_layers=10
atautraining=True
ataunonlinearity='relu'
. Jadi, jika argumen Python berubah, masuk akal jika Anda harus menelusuri kembali grafik.Namun, ada kemungkinan bahwa argumen Python tidak digunakan untuk mengontrol konstruksi grafik. Dalam kasus ini, perubahan nilai Python dapat memicu penelusuran yang tidak perlu. Ambil, misalnya, loop pelatihan ini, yang AutoGraph akan dibuka secara dinamis. Meskipun banyak jejak, grafik yang dihasilkan sebenarnya identik, jadi penelusuran ulang tidak diperlukan.
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
Jika Anda perlu memaksa penelusuran ulang, buat Function
baru. Objek Function
terpisah dijamin tidak akan berbagi jejak.
def f():
print('Tracing!')
tf.print('Executing')
tf.function(f)()
tf.function(f)()
Tracing! Executing Tracing! Executing
Mendapatkan fungsi konkrit
Setiap kali suatu fungsi ditelusuri, fungsi konkret baru dibuat. Anda bisa langsung mendapatkan fungsi konkret, dengan menggunakan 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")))
tf.Tensor(b'cc', shape=(), dtype=string)
Mencetak ConcreteFunction
menampilkan ringkasan argumen inputnya (dengan tipe) dan tipe outputnya.
print(double_strings)
ConcreteFunction double(a) Args: a: string Tensor, shape=() Returns: string Tensor, shape=()
Anda juga dapat langsung mengambil tanda tangan fungsi konkret.
print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {}) Tensor("Identity:0", shape=(), dtype=string)
Menggunakan jejak beton dengan tipe yang tidak kompatibel akan menimbulkan kesalahan
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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/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]
Anda mungkin memperhatikan bahwa argumen Python diberikan perlakuan khusus dalam tanda tangan input fungsi konkret. Sebelum TensorFlow 2.3, argumen Python dihapus begitu saja dari tanda tangan fungsi konkret. Dimulai dengan TensorFlow 2.3, argumen Python tetap ada di tanda tangan, tetapi dibatasi untuk mengambil nilai yang ditetapkan selama penelusuran.
@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 1765, in _call_with_flat_signature raise TypeError(f"{self._flat_signature_summary()} got unexpected " 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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/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.
Mendapatkan grafik
Setiap fungsi konkret adalah pembungkus yang dapat dipanggil di sekitar tf.Graph
. Meskipun mengambil objek tf.Graph
yang sebenarnya bukanlah sesuatu yang biasanya perlu Anda lakukan, Anda dapat memperolehnya dengan mudah dari fungsi konkret apa pun.
graph = double_strings.graph
for node in graph.as_graph_def().node:
print(f'{node.input} -> {node.name}')
[] -> a ['a', 'a'] -> add ['add'] -> Identity
Men-debug
Secara umum, kode debug lebih mudah dalam mode bersemangat daripada di dalam tf.function
. Anda harus memastikan bahwa kode Anda dijalankan tanpa kesalahan dalam mode bersemangat sebelum mendekorasi dengan tf.function
. Untuk membantu proses debug, Anda dapat memanggil tf.config.run_functions_eagerly(True)
untuk menonaktifkan dan mengaktifkan kembali tf.function
secara global.
Saat melacak masalah yang hanya muncul dalam tf.function
, berikut adalah beberapa tip:
- Panggilan
print
Python lama biasa hanya dijalankan selama pelacakan, membantu Anda melacak ketika fungsi Anda dilacak (kembali). - panggilan
tf.print
akan dijalankan setiap saat, dan dapat membantu Anda melacak nilai perantara selama eksekusi. -
tf.debugging.enable_check_numerics
adalah cara mudah untuk melacak di mana NaN dan Inf dibuat. -
pdb
( debug Python ) dapat membantu Anda memahami apa yang terjadi selama penelusuran. (Peringatan:pdb
akan memasukkan Anda ke dalam kode sumber yang diubah AutoGraph.)
Transformasi AutoGraph
AutoGraph adalah library yang aktif secara default di tf.function
, dan mengubah subset kode bersemangat Python menjadi operasi TensorFlow yang kompatibel dengan grafik. Ini termasuk aliran kontrol seperti if
, for
, while
.
Operasi TensorFlow seperti tf.cond
dan tf.while_loop
terus bekerja, tetapi aliran kontrol seringkali lebih mudah untuk ditulis dan dipahami jika ditulis dengan 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.666458249 0.713946581 0.723879576 0.330758929 0.184087753] [0.582645297 0.613145649 0.619306684 0.319202513 0.182036072] [0.524585426 0.546337605 0.550645113 0.308785647 0.18005164] [0.481231302 0.497770309 0.501003504 0.299331933 0.178130865] [0.447229207 0.460361809 0.462906033 0.290701121 0.176270396] [0.419618756 0.430379033 0.432449728 0.282779962 0.174467146] [0.396609187 0.405638 0.407366514 0.275476 0.172718227] [0.377043903 0.384762734 0.386234313 0.268712848 0.17102097] [0.360137492 0.366836458 0.368109286 0.262426734 0.169372901] [0.345335096 0.351221472 0.352336824 0.256563932 0.167771652] [0.332231969 0.337458342 0.338446289 0.251078814 0.166215062] [0.320524871 0.325206399 0.326089561 0.24593246 0.164701089] [0.309981436 0.314206958 0.31500268 0.241091311 0.163227797] [0.300420195 0.304259449 0.304981351 0.236526251 0.161793426] [0.291697085 0.295205742 0.295864582 0.232211992 0.160396278] [0.283696055 0.286919087 0.287523568 0.228126258 0.159034774] [0.276322395 0.279296666 0.27985391 0.224249557 0.157707423] [0.269497961 0.272254 0.272769839 0.220564634 0.15641281] [0.263157606 0.265720904 0.266200244 0.21705614 0.155149609] [0.257246554 0.259638608 0.260085613 0.213710397 0.153916568] [0.251718313 0.25395745 0.254375577 0.210515186 0.152712509] [0.246533215 0.248635098 0.249027327 0.207459539 0.151536316] [0.241657034 0.243635193 0.244004101 0.204533577 0.15038693] [0.237060249 0.238926381 0.239274174 0.201728329 0.149263337] [0.232717097 0.234481394 0.234810054 0.199035719 0.148164615] [0.228605017 0.230276451 0.230587661 0.196448416 0.147089839] [0.224704206 0.226290658 0.22658591 0.193959698 0.14603813] [0.220997125 0.222505584 0.222786173 0.191563457 0.145008713] <tf.Tensor: shape=(5,), dtype=float32, numpy= array([0.21746822, 0.21890487, 0.21917202, 0.18925412, 0.14400077], dtype=float32)>
Jika Anda penasaran, Anda dapat memeriksa kode yang dihasilkan tanda tangan.
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)
bersyarat
AutoGraph akan mengonversi beberapa pernyataan if <condition>
menjadi panggilan tf.cond
yang setara. Substitusi ini dilakukan jika <condition>
adalah Tensor. Jika tidak, pernyataan if
dieksekusi sebagai kondisional Python.
Kondisional Python dijalankan selama penelusuran, jadi tepat satu cabang kondisional akan ditambahkan ke grafik. Tanpa AutoGraph, grafik yang dilacak ini tidak akan dapat mengambil cabang alternatif jika ada aliran kontrol yang bergantung pada data.
tf.cond
melacak dan menambahkan kedua cabang kondisional ke grafik, secara dinamis memilih cabang pada waktu eksekusi. Menelusuri dapat memiliki efek samping yang tidak diinginkan; lihat efek tracing AutoGraph untuk informasi lebih lanjut.
@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
Lihat dokumentasi referensi untuk pembatasan tambahan pada pernyataan if yang dikonversi-Otomatis.
loop
AutoGraph akan mengonversi beberapa pernyataan for
dan while
menjadi operasi perulangan TensorFlow yang setara, seperti tf.while_loop
. Jika tidak dikonversi, loop for
atau while
dieksekusi sebagai loop Python.
Penggantian ini dilakukan dalam situasi berikut:
-
for x in y
: jikay
adalah Tensor, konversikan ketf.while_loop
. Dalam kasus khusus di manay
adalahtf.data.Dataset
, kombinasi operasitf.data.Dataset
dihasilkan. -
while <condition>
: jika<condition>
adalah Tensor, konversikan ketf.while_loop
.
Loop Python dijalankan selama penelusuran, menambahkan operasi tambahan ke tf.Graph
untuk setiap iterasi loop.
Loop TensorFlow melacak isi loop, dan secara dinamis memilih berapa banyak iterasi yang akan dijalankan pada waktu eksekusi. Badan loop hanya muncul sekali dalam tf.Graph
yang dihasilkan.
Lihat dokumentasi referensi untuk pembatasan tambahan pada pernyataan for
dan while
yang dikonversi-Otomatis.
Mengulangi data Python
Perangkap umum adalah mengulang data Python/NumPy dalam tf.function
. Loop ini akan dijalankan selama proses penelusuran, menambahkan salinan model Anda ke tf.Graph
untuk setiap iterasi loop.
Jika Anda ingin membungkus seluruh loop pelatihan dalam tf.function
, cara teraman untuk melakukannya adalah dengan membungkus data Anda sebagai tf.data.Dataset
sehingga AutoGraph akan membuka gulungan pelatihan secara dinamis.
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
Saat membungkus data Python/NumPy dalam Dataset, perhatikan tf.data.Dataset.from_generator
versus tf.data.Dataset.from_tensors
. Yang pertama akan menyimpan data dalam Python dan mengambilnya melalui tf.py_function
yang dapat memiliki implikasi kinerja, sedangkan yang kedua akan menggabungkan salinan data sebagai satu simpul tf.constant()
besar dalam grafik, yang dapat memiliki implikasi memori.
Membaca data dari file melalui TFRecordDataset
, CsvDataset
, dll. adalah cara paling efektif untuk menggunakan data, karena TensorFlow sendiri dapat mengelola pemuatan asinkron dan pengambilan data sebelumnya, tanpa harus melibatkan Python. Untuk mempelajari lebih lanjut, lihat tf.data
: Membuat panduan pipeline input TensorFlow .
Mengumpulkan nilai dalam satu lingkaran
Pola yang umum adalah mengakumulasi nilai antara dari sebuah loop. Biasanya, ini dilakukan dengan menambahkan ke daftar Python atau menambahkan entri ke kamus Python. Namun, karena ini adalah efek samping Python, mereka tidak akan berfungsi seperti yang diharapkan dalam loop yang dibuka secara dinamis. Gunakan tf.TensorArray
untuk mengumpulkan hasil dari loop yang dibuka secara dinamis.
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.06309307, 0.9938811 , 0.90789986, 0.42136216], [0.44997275, 1.9107027 , 1.0716251 , 0.717237 ], [0.6026064 , 2.1622117 , 1.4164022 , 1.4153863 ]], [[0.04946005, 0.69127274, 0.56848884, 0.22406638], [0.8148316 , 1.0278493 , 0.6207781 , 1.1935129 ], [0.9178308 , 1.320889 , 0.989761 , 2.0120025 ]]], dtype=float32)>
Keterbatasan
TensorFlow Function
memiliki beberapa batasan berdasarkan desain yang harus Anda perhatikan saat mengonversi fungsi Python ke Function
.
Menjalankan efek samping Python
Efek samping, seperti mencetak, menambahkan daftar, dan mengubah global, dapat berperilaku tidak terduga di dalam Function
, terkadang dijalankan dua kali atau tidak semuanya. Itu hanya terjadi saat pertama kali Anda memanggil Function
dengan satu set input. Setelah itu, tf.Graph
yang dilacak dieksekusi ulang, tanpa mengeksekusi kode Python.
Aturan umum adalah untuk menghindari mengandalkan efek samping Python dalam logika Anda dan hanya menggunakannya untuk men-debug jejak Anda. Jika tidak, API TensorFlow seperti tf.data
, tf.print
, tf.summary
, tf.Variable.assign
, dan tf.TensorArray
adalah cara terbaik untuk memastikan kode Anda akan dieksekusi oleh runtime TensorFlow dengan setiap panggilan.
@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
Jika Anda ingin mengeksekusi kode Python selama setiap pemanggilan Function
, tf.py_function
adalah pintu keluar. Kelemahan dari tf.py_function
adalah tidak portabel atau sangat berkinerja, tidak dapat disimpan dengan SavedModel, dan tidak berfungsi dengan baik dalam pengaturan terdistribusi (multi-GPU, TPU). Selain itu, karena tf.py_function
harus disambungkan ke dalam grafik, tf.py_function mentransmisikan semua input/output ke tensor.
Mengubah variabel global dan bebas Python
Mengubah variabel global dan bebas Python dianggap sebagai efek samping Python, jadi itu hanya terjadi selama penelusuran.
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
Terkadang perilaku yang tidak terduga sangat sulit untuk diperhatikan. Dalam contoh di bawah ini, counter
dimaksudkan untuk melindungi kenaikan suatu variabel. Namun karena ini adalah bilangan bulat python dan bukan objek TensorFlow, nilainya ditangkap selama pelacakan pertama. Ketika tf.function
digunakan, assign_add
akan direkam tanpa syarat di grafik yang mendasarinya. Oleh karena itu v
akan bertambah 1, setiap kali fungsi tf.function
dipanggil. Masalah ini umum terjadi di antara pengguna yang mencoba memigrasikan kode Tensorflow mode Grpah ke Tensorflow 2 menggunakan dekorator tf.function
, ketika efek samping python ( counter
dalam contoh) digunakan untuk menentukan operasi apa yang akan dijalankan ( assign_add
dalam contoh ). Biasanya, pengguna menyadari hal ini hanya setelah melihat hasil numerik yang mencurigakan, atau kinerja yang jauh lebih rendah dari yang diharapkan (misalnya jika operasi yang dijaga sangat mahal).
class Model(tf.Module):
def __init__(self):
self.v = tf.Variable(0)
self.counter = 0
@tf.function
def __call__(self):
if self.counter == 0:
# A python side-effect
self.counter += 1
self.v.assign_add(1)
return self.v
m = Model()
for n in range(3):
print(m().numpy()) # prints 1, 2, 3
1 2 3
Solusi untuk mencapai perilaku yang diharapkan adalah menggunakan tf.init_scope
untuk mengangkat operasi di luar grafik fungsi. Ini memastikan bahwa kenaikan variabel hanya dilakukan satu kali selama waktu penelusuran. Perlu dicatat init_scope
memiliki efek samping lain termasuk aliran kontrol yang dibersihkan dan pita gradien. Terkadang penggunaan init_scope
bisa menjadi terlalu rumit untuk dikelola secara realistis.
class Model(tf.Module):
def __init__(self):
self.v = tf.Variable(0)
self.counter = 0
@tf.function
def __call__(self):
if self.counter == 0:
# Lifts ops out of function-building graphs
with tf.init_scope():
self.counter += 1
self.v.assign_add(1)
return self.v
m = Model()
for n in range(3):
print(m().numpy()) # prints 1, 1, 1
1 1 1
Singkatnya, sebagai aturan praktis, Anda harus menghindari mutasi objek python seperti bilangan bulat atau wadah seperti daftar yang berada di luar Function
. Sebagai gantinya, gunakan argumen dan objek TF. Misalnya, bagian "Mengumpulkan nilai dalam satu lingkaran" memiliki satu contoh bagaimana operasi seperti daftar dapat diimplementasikan.
Anda dapat, dalam beberapa kasus, menangkap dan memanipulasi status jika itu adalah tf.Variable
. Ini adalah bagaimana bobot model Keras diperbarui dengan panggilan berulang ke ConcreteFunction
yang sama.
Menggunakan iterator dan generator Python
Banyak fitur Python, seperti generator dan iterator, mengandalkan runtime Python untuk melacak status. Secara umum, sementara konstruksi ini bekerja seperti yang diharapkan dalam mode bersemangat, mereka adalah contoh efek samping Python dan karena itu hanya terjadi selama penelusuran.
@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
Sama seperti bagaimana TensorFlow memiliki tf.TensorArray
khusus untuk konstruksi daftar, TensorFlow memiliki tf.data.Iterator
khusus untuk konstruksi iterasi. Lihat bagian tentang transformasi AutoGraph untuk gambaran umum. Selain itu, tf.data
API dapat membantu mengimplementasikan pola generator:
@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
Semua output dari tf.function harus mengembalikan nilai
Dengan pengecualian tf.Variable
s, tf.function harus mengembalikan semua outputnya. Mencoba mengakses tensor apa pun secara langsung dari suatu fungsi tanpa melalui nilai pengembalian menyebabkan "kebocoran".
Misalnya, fungsi di bawah ini "membocorkan" tensor a
melalui Python global x
:
x = None
@tf.function
def leaky_function(a):
global x
x = a + 1 # Bad - leaks local tensor
return a + 2
correct_a = leaky_function(tf.constant(1))
print(correct_a.numpy()) # Good - value obtained from function's returns
try:
x.numpy() # Bad - tensor leaked from inside the function, cannot be used here
except AttributeError as expected:
print(expected)
3 'Tensor' object has no attribute 'numpy'
Ini benar bahkan jika nilai yang bocor juga dikembalikan:
@tf.function
def leaky_function(a):
global x
x = a + 1 # Bad - leaks local tensor
return x # Good - uses local tensor
correct_a = leaky_function(tf.constant(1))
print(correct_a.numpy()) # Good - value obtained from function's returns
try:
x.numpy() # Bad - tensor leaked from inside the function, cannot be used here
except AttributeError as expected:
print(expected)
@tf.function
def captures_leaked_tensor(b):
b += x # Bad - `x` is leaked from `leaky_function`
return b
with assert_raises(TypeError):
captures_leaked_tensor(tf.constant(2))
2 'Tensor' object has no attribute 'numpy' Caught expected exception <class 'TypeError'>: Traceback (most recent call last): File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/566849597.py", line 21, in <module> captures_leaked_tensor(tf.constant(2)) TypeError: Originated from a graph execution error. The graph execution error is detected at a node built at (most recent call last): >>> File /usr/lib/python3.7/runpy.py, line 193, in _run_module_as_main >>> File /usr/lib/python3.7/runpy.py, line 85, in _run_code >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py, line 16, in <module> >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/traitlets/config/application.py, line 846, in launch_instance >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelapp.py, line 677, in start >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tornado/platform/asyncio.py, line 199, in start >>> File /usr/lib/python3.7/asyncio/base_events.py, line 534, in run_forever >>> File /usr/lib/python3.7/asyncio/base_events.py, line 1771, in _run_once >>> File /usr/lib/python3.7/asyncio/events.py, line 88, in _run >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 457, in dispatch_queue >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 446, in process_one >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 353, in dispatch_shell >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 648, in execute_request >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/ipkernel.py, line 353, in do_execute >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/zmqshell.py, line 533, in run_cell >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 2902, in run_cell >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 2947, in _run_cell >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/async_helpers.py, line 68, in _pseudo_sync_runner >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3173, in run_cell_async >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3364, in run_ast_nodes >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3444, in run_code >>> File /tmp/ipykernel_26244/566849597.py, line 7, in <module> >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 910, in __call__ >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 958, in _call >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 781, in _initialize >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3157, in _get_concrete_function_internal_garbage_collected >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3557, in _maybe_define_function >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3402, in _create_graph_function >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 1143, in func_graph_from_py_func >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 672, in wrapped_fn >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 1125, in autograph_handler >>> File /tmp/ipykernel_26244/566849597.py, line 4, in leaky_function >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/math_ops.py, line 1383, in binary_op_wrapper >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py, line 1096, in op_dispatch_handler >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/math_ops.py, line 1737, in _add_dispatch >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/gen_math_ops.py, line 476, in add_v2 >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/op_def_library.py, line 746, in _apply_op_helper >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 691, in _create_op_internal >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/ops.py, line 3705, in _create_op_internal >>> File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/ops.py, line 2101, in __init__ Error detected in node 'add' defined at: File "/tmp/ipykernel_26244/566849597.py", line 4, in leaky_function TypeError: tf.Graph captured an external symbolic tensor. The symbolic tensor 'add:0' created by node 'add' is captured by the tf.Graph being executed as an input. But a tf.Graph is not allowed to take symbolic tensors from another graph as its inputs. Make sure all captured inputs of the executing tf.Graph are not symbolic tensors. Use return values, explicit Python locals or TensorFlow collections to access it. Please see https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values for more information.
Biasanya, kebocoran seperti ini terjadi saat Anda menggunakan pernyataan atau struktur data Python. Selain membocorkan tensor yang tidak dapat diakses, pernyataan seperti itu juga kemungkinan salah karena dianggap sebagai efek samping Python, dan tidak dijamin untuk dijalankan pada setiap pemanggilan fungsi.
Cara umum untuk membocorkan tensor lokal juga termasuk mengubah koleksi Python eksternal, atau objek:
class MyClass:
def __init__(self):
self.field = None
external_list = []
external_object = MyClass()
def leaky_function():
a = tf.constant(1)
external_list.append(a) # Bad - leaks tensor
external_object.field = a # Bad - leaks tensor
Fungsi tf.rekursif tidak didukung
Function
Rekursif s tidak didukung dan dapat menyebabkan loop tak terbatas. Sebagai contoh,
@tf.function
def recursive_fn(n):
if n > 0:
return recursive_fn(n - 1)
else:
return 1
with assert_raises(Exception):
recursive_fn(tf.constant(5)) # Bad - maximum recursion error.
Caught expected exception <class 'Exception'>: Traceback (most recent call last): File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/2233998312.py", line 9, in <module> recursive_fn(tf.constant(5)) # Bad - maximum recursion error. tensorflow.python.autograph.impl.api.StagingError: in user code: File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn * return recursive_fn(n - 1) File "/tmp/ipykernel_26244/2233998312.py", line 3, in recursive_fn * if n > 0: File "/usr/lib/python3.7/abc.py", line 139, in __instancecheck__ return _abc_instancecheck(cls, instance) RecursionError: maximum recursion depth exceeded while calling a Python object
Bahkan jika Function
rekursif tampaknya berfungsi, fungsi python akan dilacak beberapa kali dan dapat memiliki implikasi kinerja. Sebagai contoh,
@tf.function
def recursive_fn(n):
if n > 0:
print('tracing')
return recursive_fn(n - 1)
else:
return 1
recursive_fn(5) # Warning - multiple tracings
tracing tracing tracing tracing tracing <tf.Tensor: shape=(), dtype=int32, numpy=1>
Masalah Dikenal
Jika Function
Anda tidak mengevaluasi dengan benar, kesalahan dapat dijelaskan oleh masalah yang diketahui ini yang direncanakan untuk diperbaiki di masa mendatang.
Bergantung pada variabel global dan bebas Python
Function
membuat ConcreteFunction
baru ketika dipanggil dengan nilai baru dari argumen Python. Namun, itu tidak melakukannya untuk penutupan Python, global, atau nonlokal dari Function
itu. Jika nilainya berubah di antara panggilan ke Function
, Function
akan tetap menggunakan nilai yang mereka miliki saat dilacak. Ini berbeda dari cara kerja fungsi Python biasa.
Untuk alasan itu, Anda harus mengikuti gaya pemrograman fungsional yang menggunakan argumen alih-alih menutup nama luar.
@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)
Cara lain untuk memperbarui nilai global adalah dengan membuatnya menjadi tf.Variable
dan menggunakan metode Variable.assign
sebagai gantinya.
@tf.function
def variable_add():
return 1 + foo
foo = tf.Variable(1)
print("Variable:", variable_add())
Variable: tf.Tensor(2, shape=(), dtype=int32)
print("Updating the value of `foo` to 100!")
foo.assign(100)
print("Variable:", variable_add())
Updating the value of `foo` to 100! Variable: tf.Tensor(101, shape=(), dtype=int32)
Bergantung pada objek Python
Rekomendasi untuk meneruskan objek Python sebagai argumen ke tf.function
memiliki sejumlah masalah yang diketahui, yang diharapkan dapat diperbaiki di masa mendatang. Secara umum, Anda dapat mengandalkan pelacakan yang konsisten jika Anda menggunakan struktur primitif Python atau yang kompatibel dengan tf.nest
sebagai argumen atau meneruskan instance objek yang berbeda ke dalam Function
. Namun, Function
tidak akan membuat jejak baru saat Anda melewati objek yang sama dan hanya mengubah atributnya .
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)
Menggunakan Function
yang sama untuk mengevaluasi instance model yang diperbarui akan bermasalah karena model yang diperbarui memiliki kunci cache yang sama dengan model aslinya.
Oleh karena itu, Anda disarankan untuk menulis Function
agar tidak bergantung pada atribut objek yang dapat diubah atau membuat objek baru.
Jika itu tidak memungkinkan, satu solusinya adalah membuat Function
s baru setiap kali Anda memodifikasi objek Anda untuk memaksa penelusuran ulang:
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)
Karena penelusuran ulang bisa mahal , Anda dapat menggunakan tf.Variable
s sebagai atribut objek, yang dapat dimutasi (tetapi tidak diubah, hati-hati!) untuk efek serupa tanpa perlu penelusuran ulang.
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)
Membuat tf.Variabel
Function
hanya mendukung tf.Variable
s tunggal yang dibuat sekali pada panggilan pertama, dan digunakan kembali di seluruh panggilan fungsi berikutnya. Cuplikan kode di bawah ini akan membuat tf.Variable
baru di setiap panggilan fungsi, yang menghasilkan pengecualian ValueError
.
Contoh:
@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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/3018268426.py", line 7, in <module> f(1.0) ValueError: in user code: File "/tmp/ipykernel_26244/3018268426.py", line 3, in f * v = tf.Variable(1.0) ValueError: tf.function only supports singleton tf.Variables created on the first call. Make sure the tf.Variable is only created once or created outside tf.function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.
Pola umum yang digunakan untuk mengatasi batasan ini adalah memulai dengan nilai Python None, lalu membuat tf.Variable
jika nilainya 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)
Menggunakan dengan beberapa pengoptimal Keras
Anda mungkin menemukan ValueError: tf.function only supports singleton tf.Variables created on the first call.
saat menggunakan lebih dari satu pengoptimal Keras dengan tf.function
. Kesalahan ini terjadi karena pengoptimal membuat tf.Variables
secara internal saat mereka menerapkan gradien untuk pertama kalinya.
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_26244/3551158538.py", line 8, in assert_raises yield File "/tmp/ipykernel_26244/3167358578.py", line 18, in <module> train_step(w, x, y, opt2) ValueError: in user code: File "/tmp/ipykernel_26244/3167358578.py", line 9, in train_step * optimizer.apply_gradients(zip(gradients, [w])) File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 639, in apply_gradients ** self._create_all_weights(var_list) File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 828, in _create_all_weights _ = self.iterations File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 835, in __getattribute__ return super(OptimizerV2, self).__getattribute__(name) File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 995, in iterations aggregation=tf.VariableAggregation.ONLY_FIRST_REPLICA) File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 1202, in add_weight aggregation=aggregation) File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/base_layer_utils.py", line 129, in make_variable shape=variable_shape if variable_shape else None) ValueError: tf.function only supports singleton tf.Variables created on the first call. Make sure the tf.Variable is only created once or created outside tf.function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.
Jika Anda perlu mengubah pengoptimal selama pelatihan, solusinya adalah membuat Function
baru untuk setiap pengoptimal, dengan memanggil ConcreteFunction
secara langsung.
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.
Menggunakan dengan beberapa model Keras
Anda mungkin juga menemukan ValueError: tf.function only supports singleton tf.Variables created on the first call.
ketika meneruskan contoh model yang berbeda ke Function
yang sama.
Kesalahan ini terjadi karena model Keras (yang bentuk inputnya tidak ditentukan ) dan lapisan Keras membuat tf.Variables
s saat pertama kali dipanggil. Anda mungkin mencoba menginisialisasi variabel-variabel tersebut di dalam Function
, yang telah dipanggil. Untuk menghindari kesalahan ini, coba panggil model.build(input_shape)
untuk menginisialisasi semua bobot sebelum melatih model.
Bacaan lebih lanjut
Untuk mempelajari tentang cara mengekspor dan memuat Function
, lihat panduan Model Tersimpan . Untuk mempelajari lebih lanjut tentang pengoptimalan grafik yang dilakukan setelah pelacakan, lihat panduan Grappler . Untuk mempelajari cara mengoptimalkan saluran data dan membuat profil model Anda, lihat panduan Profiler .