Hadiri Simposium Women in ML pada 7 Desember Daftar sekarang

TensorFlow 1.x vs TensorFlow 2 - Perilaku dan API

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Lihat di TensorFlow.org Jalankan di Google Colab Lihat di GitHub Unduh buku catatan

Di bawah tenda, TensorFlow 2 mengikuti paradigma pemrograman yang berbeda secara fundamental dari TF1.x.

Panduan ini menjelaskan perbedaan mendasar antara TF1.x dan TF2 dalam hal perilaku dan API, dan bagaimana semua ini terkait dengan perjalanan migrasi Anda.

Ringkasan tingkat tinggi dari perubahan besar

Pada dasarnya, TF1.x dan TF2 menggunakan serangkaian perilaku runtime yang berbeda seputar eksekusi (bersemangat dalam TF2), variabel, aliran kontrol, bentuk tensor, dan perbandingan kesetaraan tensor. Agar kompatibel dengan TF2, kode Anda harus kompatibel dengan set lengkap perilaku TF2. Selama migrasi, Anda dapat mengaktifkan atau menonaktifkan sebagian besar perilaku ini satu per satu melalui tf.compat.v1.enable_* atau tf.compat.v1.disable_* API. Satu-satunya pengecualian adalah penghapusan koleksi, yang merupakan efek samping dari mengaktifkan/menonaktifkan eksekusi yang bersemangat.

Pada level tinggi, TensorFlow 2:

  • Menghapus API yang berlebihan .
  • Membuat API lebih konsisten - misalnya, RNN Terpadu dan Pengoptimal Terpadu .
  • Lebih menyukai fungsi daripada sesi dan terintegrasi lebih baik dengan runtime Python dengan eksekusi Eager diaktifkan secara default bersama dengan tf.function yang menyediakan dependensi kontrol otomatis untuk grafik dan kompilasi.
  • Menghentikan koleksi grafik global .
  • Mengubah semantik konkurensi Variabel dengan menggunakan ResourceVariables di atas ReferenceVariables .
  • Mendukung aliran kontrol berbasis fungsi dan terdiferensiasi (Alur Kontrol v2).
  • Menyederhanakan TensorShape API untuk menampung int s alih-alih objek tf.compat.v1.Dimension .
  • Memperbarui mekanisme kesetaraan tensor. Di TF1.x operator == pada tensor dan variabel memeriksa kesetaraan referensi objek. Di TF2 ia memeriksa kesetaraan nilai. Selain itu, tensor/variabel tidak lagi hashable, tetapi Anda bisa mendapatkan referensi objek hashable melalui var.ref() jika Anda perlu menggunakannya dalam set atau sebagai kunci dict .

Bagian di bawah ini memberikan lebih banyak konteks tentang perbedaan antara TF1.x dan TF2. Untuk mempelajari lebih lanjut tentang proses desain di balik TF2, baca RFC dan dokumen desain .

pembersihan API

Banyak API hilang atau dipindahkan di TF2. Beberapa perubahan besar termasuk menghapus tf.app , tf.flags , dan tf.logging demi absl-py yang sekarang open-source, merombak kembali proyek yang ada di tf.contrib , dan membersihkan namespace tf.* utama dengan memindahkan fungsi yang lebih jarang digunakan ke dalam subpaket seperti tf.math . Beberapa API telah diganti dengan TF2 yang setara - tf.summary , tf.keras.metrics , dan tf.keras.optimizers .

tf.compat.v1 : Titik Akhir API Lama dan Kompatibilitas

Simbol di bawah ruang nama tf.compat dan tf.compat.v1 tidak dianggap sebagai API TF2. Ruang nama ini menampilkan campuran simbol kompatibilitas, serta titik akhir API lama dari TF 1.x. Ini dimaksudkan untuk membantu migrasi dari TF1.x ke TF2. Namun, karena tidak satu pun dari API compat.v1 ini adalah API TF2 idiomatis, jangan gunakan untuk menulis kode TF2 baru.

Simbol tf.compat.v1 individu mungkin kompatibel dengan TF2 karena mereka terus bekerja bahkan dengan perilaku TF2 diaktifkan (seperti tf.compat.v1.losses.mean_squared_error ), sementara yang lain tidak kompatibel dengan TF2 (seperti tf.compat.v1.metrics.accuracy . akurasi ). Banyak simbol compat.v1 (meskipun tidak semua) berisi informasi migrasi khusus dalam dokumentasinya yang menjelaskan tingkat kompatibilitasnya dengan perilaku TF2, serta cara memigrasikannya ke API TF2.

Skrip pemutakhiran TF2 dapat memetakan banyak simbol API compat.v1 ke API TF2 yang setara jika mereka adalah alias atau memiliki argumen yang sama tetapi dengan urutan yang berbeda. Anda juga dapat menggunakan skrip pemutakhiran untuk secara otomatis mengganti nama TF1.x API.

API teman palsu

Ada satu set simbol "teman palsu" yang ditemukan di TF2 tf namespace (tidak di bawah compat.v1 ) yang sebenarnya mengabaikan perilaku TF2 di bawah tenda, dan/atau tidak sepenuhnya kompatibel dengan set lengkap perilaku TF2. Dengan demikian, API ini cenderung berperilaku tidak semestinya dengan kode TF2, berpotensi secara diam-diam.

  • tf.estimator.* : Penaksir membuat dan menggunakan grafik dan sesi di bawah tenda. Dengan demikian, ini tidak boleh dianggap kompatibel dengan TF2. Jika kode Anda menjalankan estimator, itu tidak menggunakan perilaku TF2.
  • keras.Model.model_to_estimator(...) : Ini membuat Estimator di bawah tenda, yang seperti disebutkan di atas tidak kompatibel dengan TF2.
  • tf.Graph().as_default() : Ini memasukkan perilaku grafik TF1.x dan tidak mengikuti perilaku tf.function standar yang kompatibel dengan TF2. Kode yang memasuki grafik seperti ini umumnya akan menjalankannya melalui Sesi, dan tidak boleh dianggap kompatibel dengan TF2.
  • tf.feature_column.* API kolom fitur umumnya mengandalkan pembuatan variabel tf.compat.v1.get_variable gaya TF1 dan menganggap bahwa variabel yang dibuat akan diakses melalui koleksi global. Karena TF2 tidak mendukung koleksi, API mungkin tidak berfungsi dengan benar saat menjalankannya dengan perilaku TF2 yang diaktifkan.

Perubahan API lainnya

  • TF2 menampilkan peningkatan signifikan pada algoritme penempatan perangkat yang menjadikan penggunaan tf.colocate_with tidak perlu. Jika menghapusnya menyebabkan penurunan kinerja, harap laporkan bug .

  • Ganti semua penggunaan tf.v1.ConfigProto dengan fungsi yang setara dari tf.config .

Eksekusi bersemangat

TF1.x mengharuskan Anda untuk secara manual menggabungkan pohon sintaksis abstrak (grafik) dengan membuat panggilan API tf.* dan kemudian secara manual mengompilasi pohon sintaksis abstrak dengan meneruskan satu set tensor output dan tensor input ke panggilan session.run . TF2 dijalankan dengan penuh semangat (seperti yang biasa dilakukan Python) dan membuat grafik dan sesi terasa seperti detail implementasi.

Salah satu produk sampingan yang menonjol dari eksekusi yang bersemangat adalah bahwa tf.control_dependencies tidak lagi diperlukan, karena semua baris kode dieksekusi secara berurutan (dalam tf.function , kode dengan efek samping dieksekusi dalam urutan yang tertulis).

Tidak ada lagi global

TF1.x sangat bergantung pada ruang nama dan koleksi global implisit. Ketika Anda memanggil tf.Variable , itu akan dimasukkan ke dalam koleksi di grafik default, dan itu akan tetap ada, bahkan jika Anda kehilangan jejak variabel Python yang menunjuk ke sana. Anda kemudian dapat memulihkan tf.Variable , tetapi hanya jika Anda tahu nama yang digunakan untuk membuatnya. Ini sulit dilakukan jika Anda tidak mengendalikan pembuatan variabel. Akibatnya, segala macam mekanisme berkembang biak untuk mencoba membantu Anda menemukan variabel Anda lagi, dan untuk kerangka kerja untuk menemukan variabel yang dibuat pengguna. Beberapa di antaranya termasuk: cakupan variabel, koleksi global, metode pembantu seperti tf.get_global_step dan tf.global_variables_initializer , pengoptimal yang secara implisit menghitung gradien di atas semua variabel yang dapat dilatih, dan seterusnya. TF2 menghilangkan semua mekanisme ini ( Variabel 2.0 RFC ) mendukung mekanisme default - Anda melacak variabel Anda. Jika Anda kehilangan jejak tf.Variable , itu akan mengumpulkan sampah.

Persyaratan untuk melacak variabel menciptakan beberapa pekerjaan tambahan, tetapi dengan alat seperti shim pemodelan dan perilaku seperti kumpulan variabel berorientasi objek implisit di tf.Module s dan tf.keras.layers.Layer s , beban diminimalkan.

Fungsi, bukan sesi

Panggilan session.run hampir seperti panggilan fungsi: Anda menentukan input dan fungsi yang akan dipanggil, dan Anda mendapatkan kembali satu set output. Di TF2, Anda dapat mendekorasi fungsi Python menggunakan tf.function untuk menandainya untuk kompilasi JIT sehingga TensorFlow menjalankannya sebagai grafik tunggal ( Functions 2.0 RFC ). Mekanisme ini memungkinkan TF2 untuk mendapatkan semua manfaat dari mode grafik:

  • Performa: Fungsi dapat dioptimalkan (pemangkasan node, fusi kernel, dll.)
  • Portabilitas: Fungsi ini dapat diekspor/diimpor ulang ( SavedModel 2.0 RFC ), memungkinkan Anda untuk menggunakan kembali dan berbagi fungsi TensorFlow modular.
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

Dengan kemampuan untuk menyisipkan kode Python dan TensorFlow secara bebas, Anda dapat memanfaatkan ekspresifitas Python. Namun, TensorFlow portabel dijalankan dalam konteks tanpa penerjemah Python, seperti seluler, C++, dan JavaScript. Untuk membantu menghindari penulisan ulang kode Anda saat menambahkan tf.function , gunakan AutoGraph untuk mengonversi subset konstruksi Python menjadi setara dengan TensorFlow:

  • for / while -> tf.while_loop ( break dan continue didukung)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph mendukung kumpulan aliran kontrol yang arbitrer, yang memungkinkan penerapan banyak program ML yang kompleks secara kinerja dan ringkas seperti model urutan, pembelajaran penguatan, loop pelatihan khusus, dan banyak lagi.

Beradaptasi dengan TF 2.x Perubahan Perilaku

Migrasi Anda ke TF2 hanya selesai setelah Anda bermigrasi ke set lengkap perilaku TF2. Kumpulan perilaku lengkap dapat diaktifkan atau dinonaktifkan melalui tf.compat.v1.enable_v2_behaviors dan tf.compat.v1.disable_v2_behaviors . Bagian di bawah ini membahas setiap perubahan perilaku utama secara rinci.

Menggunakan tf.function s

Perubahan terbesar pada program Anda selama migrasi kemungkinan besar berasal dari perubahan paradigma model pemrograman mendasar dari grafik dan sesi ke eksekusi dan tf.function yang bersemangat. Lihat panduan migrasi TF2 untuk mempelajari lebih lanjut tentang berpindah dari API yang tidak kompatibel dengan eksekusi bersemangat dan tf.function ke API yang kompatibel dengannya.

Di bawah ini adalah beberapa pola program umum yang tidak terikat pada salah satu API yang dapat menyebabkan masalah saat beralih dari tf.Graph s dan tf.compat.v1.Session s ke eksekusi bersemangat dengan tf.function s.

Pola 1: Manipulasi objek Python dan pembuatan variabel dimaksudkan untuk dilakukan hanya sekali dijalankan beberapa kali

Dalam program TF1.x yang mengandalkan grafik dan sesi, harapannya biasanya semua logika Python dalam program Anda hanya akan berjalan satu kali. Namun, dengan eksekusi dan tf.function yang bersemangat, wajar untuk mengharapkan bahwa logika Python Anda akan dijalankan setidaknya sekali, tetapi mungkin lebih sering (baik beberapa kali dengan penuh semangat, atau beberapa kali di berbagai jejak tf.function berbeda). Terkadang, tf.function bahkan akan melacak dua kali pada input yang sama, menyebabkan perilaku yang tidak diharapkan (lihat Contoh 1 dan 2). Lihat panduan tf.function untuk lebih jelasnya.

Contoh 1: Pembuatan variabel

Perhatikan contoh di bawah ini, di mana fungsi membuat variabel saat dipanggil:

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

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    res = f()
    sess.run(tf.compat.v1.global_variables_initializer())
    sess.run(res)

Namun, secara naif membungkus fungsi di atas yang berisi pembuatan variabel dengan tf.function tidak diperbolehkan. tf.function hanya mendukung pembuatan variabel tunggal pada panggilan pertama . Untuk menegakkan ini, ketika tf.function mendeteksi pembuatan variabel di panggilan pertama, ia akan mencoba untuk melacak lagi dan menimbulkan kesalahan jika ada pembuatan variabel di jejak kedua.

@tf.function
def f():
  print("trace") # This will print twice because the python body is run twice
  v = tf.Variable(1.0)
  return v

try:
  f()
except ValueError as e:
  print(e)

Solusinya adalah melakukan caching dan menggunakan kembali variabel setelah dibuat di panggilan pertama.

class Model(tf.Module):
  def __init__(self):
    self.v = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    return self.v

m = Model()
m()

Contoh 2: Tensor di luar cakupan karena penelusuran tf.function

Seperti yang ditunjukkan pada Contoh 1, tf.function akan menelusuri kembali ketika mendeteksi pembuatan Variabel dalam panggilan pertama. Ini dapat menyebabkan kebingungan ekstra, karena dua penelusuran akan membuat dua grafik. Saat grafik kedua dari penelusuran ulang mencoba mengakses Tensor dari grafik yang dihasilkan selama penelusuran pertama, Tensorflow akan memunculkan kesalahan yang mengeluhkan bahwa Tensor berada di luar cakupan. Untuk mendemonstrasikan skenario, kode di bawah ini membuat kumpulan data pada panggilan tf.function pertama. Ini akan berjalan seperti yang diharapkan.

class Model(tf.Module):
  def __init__(self):
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print once: only traced once
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return next(it)

m = Model()
m()

Namun, jika kita juga mencoba membuat variabel pada panggilan tf.function pertama, kode akan memunculkan kesalahan yang mengeluhkan bahwa kumpulan data berada di luar cakupan. Ini karena dataset berada di grafik pertama, sedangkan grafik kedua juga mencoba mengaksesnya.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
try:
  m()
except TypeError as e:
  print(e) # <tf.Tensor ...> is out of scope and cannot be used here.

Solusi paling mudah adalah memastikan bahwa pembuatan variabel dan pembuatan kumpulan data berada di luar panggilan tf.funciton . Sebagai contoh:

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    if self.v is None:
      self.v = tf.Variable(0)

  @tf.function
  def __call__(self):
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Namun, terkadang membuat variabel di tf.function tidak dapat dihindari (seperti variabel slot di beberapa TF keras optimizer ). Namun, kita cukup memindahkan pembuatan kumpulan data di luar panggilan tf.function . Alasan kami dapat mengandalkan ini adalah karena tf.function akan menerima dataset sebagai input implisit dan kedua grafik dapat mengaksesnya dengan benar.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])

  @tf.function
  def __call__(self):
    if self.v is None:
      self.v = tf.Variable(0)
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Contoh 3: Pembuatan ulang objek Tensorflow yang tidak terduga karena penggunaan dict

tf.function memiliki dukungan yang sangat buruk untuk efek samping python seperti menambahkan ke daftar, atau memeriksa/menambahkan ke kamus. Detail lebih lanjut ada di "Kinerja yang lebih baik dengan tf.function" . Pada contoh di bawah, kode menggunakan kamus untuk menyimpan dataset dan iterator dalam cache. Untuk kunci yang sama, setiap panggilan ke model akan mengembalikan iterator yang sama dari kumpulan data.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = self.datasets[key].make_initializable_iterator()
    return self.iterators[key]

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    m = Model()
    it = m('a')
    sess.run(it.initializer)
    for _ in range(3):
      print(sess.run(it.get_next())) # prints 1, 2, 3

Namun, pola di atas tidak akan berfungsi seperti yang diharapkan di tf.function . Selama penelusuran, tf.function akan mengabaikan efek samping python dari penambahan kamus. Sebaliknya, itu hanya mengingat pembuatan set data dan iterator baru. Akibatnya, setiap panggilan ke model akan selalu mengembalikan iterator baru. Masalah ini sulit untuk diperhatikan kecuali jika hasil numerik atau kinerja cukup signifikan. Oleh karena itu, kami menyarankan pengguna untuk memikirkan kode dengan hati-hati sebelum membungkus tf.function naif ke kode python.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 1, 1

Kita dapat menggunakan tf.init_scope untuk mengangkat kumpulan data dan pembuatan iterator di luar grafik, untuk mencapai perilaku yang diharapkan:

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      # Lifts ops out of function-building graphs
      with tf.init_scope():
        self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
        self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 2, 3

Aturan umum adalah untuk menghindari mengandalkan efek samping Python dalam logika Anda dan hanya menggunakannya untuk men-debug jejak Anda.

Contoh 4: Memanipulasi daftar Python global

Kode TF1.x berikut menggunakan daftar kerugian global yang digunakan untuk hanya mempertahankan daftar kerugian yang dihasilkan oleh langkah pelatihan saat ini. Perhatikan bahwa logika Python yang menambahkan kerugian ke daftar hanya akan dipanggil sekali terlepas dari berapa banyak langkah pelatihan sesi dijalankan.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

g = tf.Graph()
with g.as_default():
  ...
  # initialize all objects
  model = Model()
  optimizer = ...
  ...
  # train step
  model(...)
  total_loss = tf.reduce_sum(all_losses)
  optimizer.minimize(total_loss)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)  

Namun, jika logika Python ini secara naif dipetakan ke TF2 dengan eksekusi yang bersemangat, daftar kerugian global akan memiliki nilai baru yang ditambahkan ke dalamnya di setiap langkah pelatihan. Ini berarti kode langkah pelatihan yang sebelumnya diharapkan daftar hanya berisi kerugian dari langkah pelatihan saat ini sekarang benar-benar melihat daftar kerugian dari semua langkah pelatihan yang berjalan sejauh ini. Ini adalah perubahan perilaku yang tidak diinginkan, dan daftar tersebut perlu dihapus pada awal setiap langkah atau dibuat lokal untuk langkah pelatihan.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

# initialize all objects
model = Model()
optimizer = ...

def train_step(...)
  ...
  model(...)
  total_loss = tf.reduce_sum(all_losses) # global list is never cleared,
  # Accidentally accumulates sum loss across all training steps
  optimizer.minimize(total_loss)
  ...

Pola 2: Tensor simbolis yang dimaksudkan untuk dihitung ulang setiap langkah di TF1.x secara tidak sengaja di-cache dengan nilai awal saat beralih ke bersemangat.

Pola ini biasanya menyebabkan kode Anda berperilaku tidak baik secara diam-diam saat mengeksekusi dengan penuh semangat di luar tf.functions, tetapi memunculkan InaccessibleTensorError jika caching nilai awal terjadi di dalam tf.function . Namun, perlu diketahui bahwa untuk menghindari Pola 1 di atas Anda akan sering secara tidak sengaja menyusun kode Anda sedemikian rupa sehingga caching nilai awal ini akan terjadi di luar tf.function apa pun yang dapat menimbulkan kesalahan. Jadi, berhati-hatilah jika Anda tahu program Anda mungkin rentan terhadap pola ini.

Solusi umum untuk pola ini adalah merestrukturisasi kode atau menggunakan callable Python jika perlu untuk memastikan nilai dihitung ulang setiap kali alih-alih di-cache secara tidak sengaja.

Contoh 1: Kecepatan pembelajaran/hiperparameter/dll. jadwal yang bergantung pada langkah global

Dalam cuplikan kode berikut, harapannya adalah setiap kali sesi dijalankan, nilai global_step terbaru akan dibaca dan kecepatan pembelajaran baru akan dihitung.

g = tf.Graph()
with g.as_default():
  ...
  global_step = tf.Variable(0)
  learning_rate = 1.0 / global_step
  opt = tf.compat.v1.train.GradientDescentOptimizer(learning_rate)
  ...
  global_step.assign_add(1)
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Namun, ketika mencoba beralih ke bersemangat, berhati-hatilah karena berakhir dengan tingkat pembelajaran yang hanya dihitung sekali kemudian digunakan kembali, daripada mengikuti jadwal yang dimaksudkan:

global_step = tf.Variable(0)
learning_rate = 1.0 / global_step # Wrong! Only computed once!
opt = tf.keras.optimizers.SGD(learning_rate)

def train_step(...):
  ...
  opt.apply_gradients(...)
  global_step.assign_add(1)
  ...

Karena contoh khusus ini adalah pola umum dan pengoptimal hanya boleh diinisialisasi sekali, bukan pada setiap langkah pelatihan, pengoptimal TF2 mendukung jadwal tf.keras.optimizers.schedules.LearningRateSchedule atau callable Python sebagai argumen untuk kecepatan pembelajaran dan hyperparameter lainnya.

Contoh 2: Inisialisasi angka acak simbolik yang ditetapkan sebagai atribut objek kemudian digunakan kembali melalui pointer secara tidak sengaja di-cache saat beralih ke bersemangat

Perhatikan modul NoiseAdder berikut:

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution + input) * self.trainable_scale

Menggunakannya sebagai berikut di TF1.x akan menghitung tensor noise acak baru setiap kali sesi dijalankan:

g = tf.Graph()
with g.as_default():
  ...
  # initialize all variable-containing objects
  noise_adder = NoiseAdder(shape, mean)
  ...
  # computation pass
  x_with_noise = noise_adder.add_noise(x)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Namun, dalam TF2 menginisialisasi noise_adder di awal akan menyebabkan noise_distribution hanya dihitung sekali dan dibekukan untuk semua langkah pelatihan:

...
# initialize all variable-containing objects
noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!
...
# computation pass
x_with_noise = noise_adder.add_noise(x)
...

Untuk memperbaikinya, refactor NoiseAdder untuk memanggil tf.random.normal setiap kali tensor acak baru diperlukan, alih-alih merujuk ke objek tensor yang sama setiap kali.

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = lambda: tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution() + input) * self.trainable_scale

Pola 3: Kode TF1.x secara langsung mengandalkan dan mencari tensor berdasarkan nama

Biasanya pengujian kode TF1.x mengandalkan pemeriksaan tensor atau operasi apa yang ada dalam grafik. Dalam beberapa kasus yang jarang terjadi, kode pemodelan juga akan bergantung pada pencarian ini berdasarkan nama.

Nama tensor tidak dihasilkan saat mengeksekusi dengan penuh semangat di luar tf.function sama sekali, jadi semua penggunaan tf.Tensor.name harus terjadi di dalam tf.function . Ingatlah bahwa nama yang dihasilkan sebenarnya sangat mungkin berbeda antara TF1.x dan TF2 bahkan dalam tf.function yang sama, dan jaminan API tidak memastikan stabilitas nama yang dihasilkan di seluruh versi TF.

Pola 4: Sesi TF1.x secara selektif hanya menjalankan sebagian dari grafik yang dihasilkan

Di TF1.x, Anda dapat membuat grafik dan kemudian memilih untuk hanya menjalankan secara selektif hanya sebagian darinya dengan sesi dengan memilih satu set input dan output yang tidak memerlukan menjalankan setiap op dalam grafik.

Misalnya, Anda mungkin memiliki generator dan diskriminator di dalam satu grafik, dan menggunakan panggilan tf.compat.v1.Session.run terpisah untuk bergantian antara hanya melatih diskriminator atau hanya melatih generator.

Di TF2, karena dependensi kontrol otomatis di tf.function dan eksekusi bersemangat, tidak ada pemangkasan selektif dari jejak tf.function . Grafik lengkap yang berisi semua pembaruan variabel akan dijalankan meskipun, misalnya, hanya keluaran dari diskriminator atau generator yang dikeluarkan dari tf.function .

Jadi, Anda perlu menggunakan beberapa tf.function s yang berisi bagian-bagian berbeda dari program, atau argumen bersyarat ke tf.function yang Anda cabangkan sehingga hanya menjalankan hal-hal yang sebenarnya ingin Anda jalankan.

Penghapusan Koleksi

Saat eksekusi bersemangat diaktifkan, API compat.v1 terkait koleksi grafik (termasuk yang membaca atau menulis ke koleksi di bawah tenda seperti tf.compat.v1.trainable_variables ) tidak lagi tersedia. Beberapa mungkin menaikkan ValueError s, sementara yang lain mungkin diam-diam mengembalikan daftar kosong.

Penggunaan paling standar dari koleksi di TF1.x adalah untuk mempertahankan inisialisasi, langkah global, bobot, kerugian regularisasi, kerugian keluaran model, dan pembaruan variabel yang perlu dijalankan seperti dari lapisan BatchNormalization .

Untuk menangani setiap penggunaan standar ini:

  1. Inisialisasi - Abaikan. Inisialisasi variabel manual tidak diperlukan dengan eksekusi bersemangat diaktifkan.
  2. Langkah global - Lihat dokumentasi tf.compat.v1.train.get_or_create_global_step untuk petunjuk migrasi.
  3. Bobot - Petakan model Anda ke tf.Module s/ tf.keras.layers.Layer s/ tf.keras.Model s dengan mengikuti panduan dalam panduan pemetaan model dan kemudian gunakan mekanisme pelacakan bobot masing-masing seperti tf.module.trainable_variables .
  4. Kehilangan regularisasi - Petakan model Anda ke tf.Module s/ tf.keras.layers.Layer s/ tf.keras.Model s dengan mengikuti panduan dalam panduan pemetaan model dan kemudian gunakan tf.keras.losses . Atau, Anda juga dapat melacak kerugian regularisasi Anda secara manual.
  5. Kehilangan keluaran model - Gunakan mekanisme manajemen kerugian tf.keras.Model atau lacak kerugian Anda secara terpisah tanpa menggunakan koleksi.
  6. Pembaruan berat badan - Abaikan koleksi ini. Eksekusi yang bersemangat dan tf.function (dengan autograph dan auto-control-dependencies) berarti semua pembaruan variabel akan dijalankan secara otomatis. Jadi, Anda tidak perlu menjalankan semua pembaruan bobot secara eksplisit di akhir, tetapi perhatikan bahwa ini berarti pembaruan bobot dapat terjadi pada waktu yang berbeda dari yang terjadi pada kode TF1.x Anda, bergantung pada cara Anda menggunakan dependensi kontrol.
  7. Ringkasan - Lihat panduan API ringkasan migrasi .

Penggunaan koleksi yang lebih kompleks (seperti menggunakan koleksi khusus) mungkin mengharuskan Anda untuk memfaktorkan ulang kode Anda untuk mempertahankan toko global Anda sendiri, atau membuatnya tidak bergantung pada toko global sama sekali.

ResourceVariables bukan ReferenceVariables

ResourceVariables memiliki jaminan konsistensi baca-tulis yang lebih kuat daripada ReferenceVariables . Ini mengarah pada semantik yang lebih dapat diprediksi, lebih mudah dipahami tentang apakah Anda akan mengamati hasil penulisan sebelumnya saat menggunakan variabel Anda atau tidak. Perubahan ini sangat tidak mungkin menyebabkan kode yang ada memunculkan kesalahan atau rusak secara diam-diam.

Namun, mungkin meskipun tidak mungkin bahwa jaminan konsistensi yang lebih kuat ini dapat meningkatkan penggunaan memori dari program spesifik Anda. Silakan ajukan masalah jika menurut Anda ini masalahnya. Selain itu, jika Anda memiliki pengujian unit yang mengandalkan perbandingan string yang tepat terhadap nama operator dalam grafik yang sesuai dengan pembacaan variabel, ketahuilah bahwa mengaktifkan variabel sumber daya mungkin sedikit mengubah nama operator ini.

Untuk mengisolasi dampak perubahan perilaku ini pada kode Anda, jika eksekusi bersemangat dinonaktifkan, Anda dapat menggunakan tf.compat.v1.disable_resource_variables() dan tf.compat.v1.enable_resource_variables() untuk menonaktifkan atau mengaktifkan perubahan perilaku ini secara global. ResourceVariables akan selalu digunakan jika eksekusi bersemangat diaktifkan.

Kontrol aliran v2

Di TF1.x, operasi kontrol aliran seperti tf.cond dan tf.while_loop inline operasi tingkat rendah seperti Switch , Merge , dll. TF2 menyediakan operasi aliran kontrol fungsional yang ditingkatkan yang diimplementasikan dengan tf.function terpisah untuk setiap cabang dan dukungan diferensiasi tingkat tinggi.

Untuk mengisolasi dampak perubahan perilaku ini pada kode Anda, jika eksekusi bersemangat dinonaktifkan, Anda dapat menggunakan tf.compat.v1.disable_control_flow_v2() dan tf.compat.v1.enable_control_flow_v2() untuk menonaktifkan atau mengaktifkan perubahan perilaku ini secara global. Namun, Anda hanya dapat menonaktifkan aliran kontrol v2 jika eksekusi bersemangat juga dinonaktifkan. Jika diaktifkan, aliran kontrol v2 akan selalu digunakan.

Perubahan perilaku ini dapat secara dramatis mengubah struktur program TF yang dihasilkan yang menggunakan aliran kontrol, karena mereka akan berisi beberapa jejak fungsi bersarang daripada satu grafik datar. Jadi, kode apa pun yang sangat bergantung pada semantik yang tepat dari jejak yang dihasilkan mungkin memerlukan beberapa modifikasi. Ini termasuk:

  • Kode mengandalkan nama operator dan tensor
  • Kode yang mengacu pada tensor yang dibuat dalam cabang aliran kontrol TensorFlow dari luar cabang tersebut. Ini kemungkinan akan menghasilkan InaccessibleTensorError

Perubahan perilaku ini dimaksudkan agar kinerja netral menjadi positif, tetapi jika Anda mengalami masalah di mana aliran kontrol v2 berkinerja lebih buruk untuk Anda daripada aliran kontrol TF1.x, silakan ajukan masalah dengan langkah-langkah reproduksi.

Perubahan perilaku API TensorShape

Kelas TensorShape disederhanakan untuk menampung int s, bukan objek tf.compat.v1.Dimension . Jadi tidak perlu memanggil .value untuk mendapatkan int .

Objek tf.compat.v1.Dimension individu masih dapat diakses dari tf.TensorShape.dims .

Untuk mengisolasi dampak perubahan perilaku ini pada kode Anda, Anda dapat menggunakan tf.compat.v1.disable_v2_tensorshape() dan tf.compat.v1.enable_v2_tensorshape() untuk menonaktifkan atau mengaktifkan perubahan perilaku ini secara global.

Berikut ini menunjukkan perbedaan antara TF1.x dan TF2.

import tensorflow as tf
# Create a shape and choose an index
i = 0
shape = tf.TensorShape([16, None, 256])
shape
TensorShape([16, None, 256])

Jika Anda memiliki ini di TF1.x:

value = shape[i].value

Kemudian lakukan ini di TF2:

value = shape[i]
value
16

Jika Anda memiliki ini di TF1.x:

for dim in shape:
    value = dim.value
    print(value)

Kemudian, lakukan ini di TF2:

for value in shape:
  print(value)
16
None
256

Jika Anda memiliki ini di TF1.x (atau menggunakan metode dimensi lain):

dim = shape[i]
dim.assert_is_compatible_with(other_dim)

Kemudian lakukan ini di TF2:

other_dim = 16
Dimension = tf.compat.v1.Dimension

if shape.rank is None:
  dim = Dimension(None)
else:
  dim = shape.dims[i]
dim.is_compatible_with(other_dim) # or any other dimension method
True
shape = tf.TensorShape(None)

if shape:
  dim = shape.dims[i]
  dim.is_compatible_with(other_dim) # or any other dimension method

Nilai boolean dari tf.TensorShape adalah True jika peringkatnya diketahui, False jika tidak.

print(bool(tf.TensorShape([])))      # Scalar
print(bool(tf.TensorShape([0])))     # 0-length vector
print(bool(tf.TensorShape([1])))     # 1-length vector
print(bool(tf.TensorShape([None])))  # Unknown-length vector
print(bool(tf.TensorShape([1, 10, 100])))       # 3D tensor
print(bool(tf.TensorShape([None, None, None]))) # 3D tensor with no known dimensions
print()
print(bool(tf.TensorShape(None)))  # A tensor with unknown rank.
True
True
True
True
True
True

False

Potensi kesalahan karena perubahan TensorShape

Perubahan perilaku TensorShape tidak mungkin memecahkan kode Anda secara diam-diam. Namun, Anda mungkin melihat kode terkait bentuk mulai memunculkan AttributeError s karena int s dan None s tidak memiliki atribut yang sama dengan yang tf.compat.v1.Dimension s. Di bawah ini adalah beberapa contoh dari AttributeError ini:

try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  value = shape[0].value
except AttributeError as e:
  # 'int' object has no attribute 'value'
  print(e)
'int' object has no attribute 'value'
try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  dim = shape[1]
  other_dim = shape[2]
  dim.assert_is_compatible_with(other_dim)
except AttributeError as e:
  # 'NoneType' object has no attribute 'assert_is_compatible_with'
  print(e)
'NoneType' object has no attribute 'assert_is_compatible_with'

Persamaan Tensor berdasarkan Nilai

Operator biner == dan != pada variabel dan tensor diubah untuk membandingkan berdasarkan nilai di TF2 daripada membandingkan berdasarkan referensi objek seperti di TF1.x. Selain itu, tensor dan variabel tidak lagi dapat di-hash secara langsung atau dapat digunakan dalam set atau kunci dict, karena mungkin tidak dapat di-hash berdasarkan nilai. Sebagai gantinya, mereka mengekspos metode .ref() yang bisa Anda gunakan untuk mendapatkan referensi hashable ke tensor atau variabel.

Untuk mengisolasi dampak dari perubahan perilaku ini, Anda dapat menggunakan tf.compat.v1.disable_tensor_equality() dan tf.compat.v1.enable_tensor_equality() untuk menonaktifkan atau mengaktifkan perubahan perilaku ini secara global.

Misalnya, di TF1.x, dua variabel dengan nilai yang sama akan menghasilkan false saat Anda menggunakan operator == :

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
False

Saat di TF2 dengan pemeriksaan kesetaraan tensor diaktifkan, x == y akan mengembalikan True .

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
<tf.Tensor: shape=(), dtype=bool, numpy=True>

Jadi, di TF2, jika Anda perlu membandingkan dengan referensi objek, pastikan untuk menggunakannya is is not

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x is y
False

Hashing tensor dan variabel

Dengan perilaku TF1.x, Anda dulu dapat langsung menambahkan variabel dan tensor ke struktur data yang memerlukan hashing, seperti kunci set dan dict .

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
set([x, tf.constant(2.0)])
{<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.0>}

Namun, di TF2 dengan kesetaraan tensor diaktifkan, tensor dan variabel dibuat tidak dapat di-hash karena semantik operator == dan != berubah menjadi pemeriksaan kesetaraan nilai.

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

try:
  set([x, tf.constant(2.0)])
except TypeError as e:
  # TypeError: Variable is unhashable. Instead, use tensor.ref() as the key.
  print(e)
Variable is unhashable. Instead, use tensor.ref() as the key.

Jadi, di TF2 jika Anda perlu menggunakan tensor atau objek variabel sebagai kunci atau set konten, Anda dapat menggunakan tensor.ref() untuk mendapatkan referensi hashable yang dapat digunakan sebagai kunci:

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

tensor_set = set([x.ref(), tf.constant(2.0).ref()])
assert x.ref() in tensor_set

tensor_set
{<Reference wrapping <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>>,
 <Reference wrapping <tf.Tensor: shape=(), dtype=float32, numpy=2.0>>}

Jika diperlukan, Anda juga bisa mendapatkan tensor atau variabel dari referensi dengan menggunakan reference.deref() :

referenced_var = x.ref().deref()
assert referenced_var is x
referenced_var
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>

Sumber daya dan bacaan lebih lanjut

  • Kunjungi bagian Migrasi ke TF2 untuk membaca selengkapnya tentang migrasi ke TF2 dari TF1.x.
  • Baca panduan pemetaan model untuk mempelajari lebih lanjut pemetaan model TF1.x Anda untuk bekerja di TF2 secara langsung.