ช่วยปกป้อง Great Barrier Reef กับ TensorFlow บน Kaggle เข้าร่วมท้าทาย

TensorFlow 1.x กับ TensorFlow 2 - พฤติกรรมและ API

ดูบน TensorFlow.org ทำงานใน Google Colab ดูบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

ภายใต้ประทุน TensorFlow 2 ดำเนินตามกระบวนทัศน์การเขียนโปรแกรมที่แตกต่างกันโดยพื้นฐานจาก TF1.x

คู่มือนี้อธิบายความแตกต่างพื้นฐานระหว่าง TF1.x และ TF2 ในแง่ของพฤติกรรมและ API และความสัมพันธ์ทั้งหมดนี้กับเส้นทางการย้ายข้อมูลของคุณอย่างไร

สรุประดับสูงของการเปลี่ยนแปลงที่สำคัญ

โดยพื้นฐานแล้ว TF1.x และ TF2 ใช้ชุดพฤติกรรมรันไทม์ที่แตกต่างกันรอบการดำเนินการ (กระตือรือร้นใน TF2) ตัวแปร โฟลว์การควบคุม รูปร่างเทนเซอร์ และการเปรียบเทียบความเท่าเทียมกันของเทนเซอร์ เพื่อให้เข้ากันได้กับ TF2 รหัสของคุณต้องเข้ากันได้กับชุดพฤติกรรม TF2 ทั้งหมด ในระหว่างการโยกย้ายคุณสามารถเปิดหรือปิดการใช้งานส่วนใหญ่ของพฤติกรรมเหล่านี้เป็นรายบุคคลผ่านทาง tf.compat.v1.enable_* หรือ tf.compat.v1.disable_* APIs ข้อยกเว้นประการหนึ่งคือการลบคอลเล็กชัน ซึ่งเป็นผลข้างเคียงของการเปิด/ปิดการดำเนินการที่กระตือรือร้น

ในระดับสูง TensorFlow 2:

ส่วนด้านล่างให้บริบทเพิ่มเติมเกี่ยวกับความแตกต่างระหว่าง TF1.x และ TF2 ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับขั้นตอนการออกแบบที่อยู่เบื้องหลัง TF2 อ่าน RFCs และ เอกสารการออกแบบ

การล้างข้อมูล API

API ที่หลายคนมีทั้งที่ หายไปหรือย้าย ใน TF2 บางส่วนของการเปลี่ยนแปลงที่สำคัญรวมถึงการลบ tf.app , tf.flags และ tf.logging ในความโปรดปรานของตอนนี้มาเปิด absl-PY , rehoming โครงการที่อาศัยอยู่ใน tf.contrib และทำความสะอาดหลัก tf.* namespace โดย ย้ายฟังก์ชั่นที่ใช้น้อยลงในแพคเกจย่อยเช่น tf.math APIs บางคนได้ถูกแทนที่ด้วยเทียบเท่า TF2 ของพวกเขา - tf.summary , tf.keras.metrics และ tf.keras.optimizers

tf.compat.v1 : มรดกและความเข้ากันได้ API อุปกรณ์ปลายทาง

ภายใต้สัญลักษณ์ tf.compat และ tf.compat.v1 namespaces จะไม่ถือว่า TF2 APIs เนมสเปซเหล่านี้แสดงสัญลักษณ์ที่เข้ากันได้ รวมทั้งปลายทาง API ดั้งเดิมจาก TF 1.x สิ่งเหล่านี้มีจุดมุ่งหมายเพื่อช่วยในการย้ายจาก TF1.x ไปยัง TF2 อย่างไรก็ตามในขณะที่ไม่มีของเหล่านี้ compat.v1 APIs มี APIs TF2 สำนวนไม่ใช้พวกเขาสำหรับการเขียนแบรนด์ใหม่รหัส TF2

บุคคล tf.compat.v1 สัญลักษณ์อาจจะ TF2 เข้ากันได้เพราะพวกเขายังคงทำงานแม้จะมีพฤติกรรม TF2 เปิดการใช้งาน (เช่น tf.compat.v1.losses.mean_squared_error ) ในขณะที่คนอื่น ๆ จะเข้ากันกับ TF2 (เช่น tf.compat.v1.metrics.accuracy ) หลาย compat.v1 สัญลักษณ์ ( แต่ไม่ใช่ทั้งหมด) มีข้อมูลการโยกย้ายเฉพาะในเอกสารของพวกเขาที่อธิบายระดับของการเข้ากันได้กับ TF2 พฤติกรรมเช่นเดียวกับวิธีการโยกย้ายให้พวกเขา TF2 APIs

สคริปต์อัพเกรด TF2 สามารถแมหลาย compat.v1 สัญลักษณ์ API เพื่อ APIs TF2 เทียบเท่าในกรณีที่พวกเขาเป็นนามแฝงหรือมีการขัดแย้งกัน แต่มีการสั่งซื้อที่แตกต่างกัน คุณยังสามารถใช้สคริปต์อัปเกรดเพื่อเปลี่ยนชื่อ TF1.x API โดยอัตโนมัติ

API ของเพื่อนปลอม

มีชุดของ "เท็จเพื่อน" สัญลักษณ์ที่พบใน TF2 tf namespace (ไม่อยู่ภายใต้ compat.v1 ) ที่จริงไม่สนใจ TF2 พฤติกรรมภายใต้เครื่องดูดควันและ / หรือไม่ได้อย่างเข้ากันได้กับชุดเต็มรูปแบบของพฤติกรรม TF2 ดังนั้น API เหล่านี้จึงมีแนวโน้มที่จะทำงานผิดปกติกับโค้ด TF2 ซึ่งอาจเกิดขึ้นในลักษณะที่เงียบ

  • tf.estimator.* : ประมาณค่าสร้างและกราฟการใช้งานและการประชุมภายใต้ประทุน ด้วยเหตุนี้ สิ่งเหล่านี้จึงไม่ถือว่าเข้ากันได้กับ TF2 หากโค้ดของคุณใช้ตัวประมาณ แสดงว่าไม่ได้ใช้พฤติกรรม TF2
  • keras.Model.model_to_estimator(...) : นี้จะสร้างประมาณการภายใต้ประทุนซึ่งเป็นที่กล่าวถึงข้างต้นไม่ TF2 ที่เข้ากันได้
  • tf.Graph().as_default() นี้เข้าสู่พฤติกรรมกราฟ TF1.x และไม่เป็นไปตาม TF2 ได้มาตรฐาน tf.function พฤติกรรม รหัสที่เข้าสู่กราฟในลักษณะนี้โดยทั่วไปจะเรียกใช้ผ่านเซสชัน และไม่ควรถือว่าเข้ากันได้กับ TF2
  • tf.feature_column.* API สำหรับคอลัมน์คุณลักษณะทั่วไปพึ่งพา TF1 สไตล์ tf.compat.v1.get_variable สร้างตัวแปรและคิดว่าตัวแปรที่สร้างจะสามารถเข้าถึงได้ผ่านทางคอลเลกชันทั่วโลก เนื่องจาก TF2 ไม่รองรับคอลเลกชัน API อาจทำงานไม่ถูกต้องเมื่อเรียกใช้งานโดยเปิดใช้งานลักษณะการทำงาน TF2

การเปลี่ยนแปลง API อื่นๆ

  • TF2 มีการปรับปรุงที่สำคัญขั้นตอนวิธีการจัดวางอุปกรณ์ที่ทำให้การใช้งานของ tf.colocate_with ที่ไม่จำเป็น ถ้าถอดมันออกมาทำให้เกิดการลดประสิทธิภาพ โปรดยื่นข้อผิดพลาด

  • แทนที่การใช้งานทั้งหมดของ tf.v1.ConfigProto กับฟังก์ชั่นเทียบเท่าจาก tf.config

การดำเนินการอย่างกระตือรือร้น

TF1.x ต้องให้คุณปักครอสติตนเองกัน ต้นไม้ไวยากรณ์นามธรรม (กราฟ) โดยการทำให้ tf.* เรียก API แล้วด้วยตนเองรวบรวมต้นไม้ไวยากรณ์นามธรรมโดยผ่านชุดของเทนเซอร์และเอาท์พุทเทนเซอร์ใส่กับ session.run โทร TF2 ดำเนินการอย่างกระตือรือร้น (เช่นปกติที่ Python ทำ) และทำให้กราฟและเซสชันรู้สึกเหมือนรายละเอียดการใช้งาน

หนึ่งพลอยที่โดดเด่นของการดำเนินการกระตือรือร้นคือ tf.control_dependencies จะต้องไม่เป็นทุกบรรทัดของรหัสที่รันในการสั่งซื้อ (ภายใน tf.function รหัสกับรันผลข้างเคียงในการสั่งซื้อที่เขียน)

ไม่มีโลกาภิวัตน์อีกต่อไป

TF1.x อาศัยเนมสเปซและคอลเล็กชันทั่วโลกโดยนัยอย่างมาก เมื่อคุณเรียก tf.Variable ก็จะใส่ลงไปในคอลเลกชันในกราฟเริ่มต้นและมันจะยังคงมีแม้ถ้าคุณสูญเสียการติดตามของตัวแปรชี้งูหลามกับมัน จากนั้นคุณสามารถกู้คืนที่ tf.Variable แต่ถ้าคุณรู้ชื่อที่จะได้รับการสร้างขึ้นด้วย สิ่งนี้ทำได้ยากหากคุณไม่ได้ควบคุมการสร้างตัวแปร ด้วยเหตุนี้ กลไกทุกประเภทจึงขยายตัวขึ้นเพื่อพยายามช่วยคุณค้นหาตัวแปรอีกครั้ง และสำหรับเฟรมเวิร์กเพื่อค้นหาตัวแปรที่ผู้ใช้สร้างขึ้น บางส่วนของเหล่านี้รวมถึง: ขอบเขตตัวแปรคอลเลกชันทั่วโลกวิธีการช่วยเหลือเช่น tf.get_global_step และ tf.global_variables_initializer เพิ่มประสิทธิภาพโดยปริยายคำนวณการไล่ระดับสีตัวแปรสุวินัยทั้งหมดและอื่น ๆ TF2 ช่วยขจัดของกลไกเหล่านี้ ( ตัวแปร 2.0 RFC ) ในความโปรดปรานของกลไกการเริ่มต้น - คุณสามารถติดตามของตัวแปรของคุณ ถ้าคุณสูญเสียการติดตามของ tf.Variable จะได้รับการเก็บขยะ

ความต้องการในการติดตามตัวแปรที่จะสร้างการทำงานบางอย่างที่พิเศษ แต่ด้วยเครื่องมือเช่น shims การสร้างแบบจำลอง และพฤติกรรมเช่น คอลเลกชันตัวแปรเชิงวัตถุนัยใน tf.Module และ tf.keras.layers.Layer s ภาระจะลดลง

ฟังก์ชั่นไม่ใช่เซสชัน

session.run โทรเป็นเหมือนการเรียกใช้ฟังก์ชัน: คุณระบุปัจจัยการผลิตและฟังก์ชั่นที่จะเรียกและคุณได้รับกลับชุดของเอาท์พุต ใน TF2 คุณสามารถตกแต่งฟังก์ชั่นการใช้หลาม tf.function เพื่อทำเครื่องหมายสำหรับ JIT รวบรวมเพื่อให้ TensorFlow วิ่งมันเป็นกราฟเดียว ( ฟังก์ชั่น 2.0 RFC ) กลไกนี้ช่วยให้ TF2 ได้รับประโยชน์ทั้งหมดจากโหมดกราฟ:

  • ประสิทธิภาพ: ฟังก์ชันนี้สามารถปรับให้เหมาะสมได้ (การตัดโหนด การผสานเคอร์เนล ฯลฯ)
  • Portability: ฟังก์ชั่นที่สามารถส่งออก / reimported ( SavedModel 2.0 RFC ) ช่วยให้คุณสามารถนำมาใช้ใหม่และส่วนแบ่งการแบบแยกส่วนฟังก์ชั่น TensorFlow
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

ด้วยพลังในการกระจายโค้ด Python และ TensorFlow อย่างอิสระ คุณสามารถใช้ประโยชน์จากการแสดงออกของ Python อย่างไรก็ตาม TensorFlow แบบพกพาจะทำงานในบริบทโดยไม่มีตัวแปล Python เช่น อุปกรณ์เคลื่อนที่, C++ และ JavaScript เพื่อหลีกเลี่ยงการเขียนรหัสของคุณเมื่อมีการเพิ่ม tf.function ใช้ ลายเซ็น ในการแปลงย่อยของโครงสร้างหลามเข้าไปเทียบเท่า TensorFlow ของพวกเขา

  • for / while -> tf.while_loop ( break และ continue ได้รับการสนับสนุน)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph รองรับการซ้อนโฟลว์การควบคุมตามอำเภอใจ ซึ่งทำให้สามารถใช้โปรแกรม ML ที่ซับซ้อนจำนวนมากได้อย่างมีประสิทธิภาพและรัดกุม เช่น โมเดลลำดับ การเรียนรู้การเสริมแรง ลูปการฝึกแบบกำหนดเอง และอื่นๆ

การปรับตัวให้เข้ากับ TF 2.x การเปลี่ยนแปลงพฤติกรรม

การโยกย้ายไปยัง TF2 จะสมบูรณ์ก็ต่อเมื่อคุณได้ย้ายไปยังชุดพฤติกรรม TF2 ทั้งหมดแล้ว ชุดเต็มรูปแบบของพฤติกรรมที่สามารถเปิดหรือปิดการใช้งานผ่านทาง tf.compat.v1.enable_v2_behaviors และ tf.compat.v1.disable_v2_behaviors ส่วนด้านล่างจะกล่าวถึงการเปลี่ยนแปลงพฤติกรรมที่สำคัญแต่ละรายการโดยละเอียด

ใช้ tf.function s

การเปลี่ยนแปลงที่ใหญ่ที่สุดในโปรแกรมของคุณในระหว่างการโยกย้ายมีแนวโน้มที่จะมาจากพื้นฐานการเขียนโปรแกรมแบบปรับเปลี่ยนกระบวนทัศน์จากกราฟและการประชุมที่จะมีการกระตือรือร้นและ tf.function อ้างถึง TF2 การโยกย้ายคู่มือ เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการย้ายจาก API ที่ว่าเข้ากันไม่ได้กับการดำเนินความกระตือรือร้นและ tf.function เพื่อ APIs ที่เข้ากันได้กับพวกเขา

ด้านล่างมีบางรูปแบบโปรแกรมทั่วไปไม่ได้ผูกติดอยู่กับคนใดคนหนึ่งของ API ที่อาจก่อให้เกิดปัญหาเมื่อเปลี่ยนจาก tf.Graph และ tf.compat.v1.Session เพื่อดำเนินการกระตือรือร้นกับ tf.function s

รูปแบบที่ 1: การจัดการอ็อบเจ็กต์ Python และการสร้างตัวแปรที่ตั้งใจจะทำเพียงครั้งเดียวถูกเรียกใช้หลายครั้ง

ในโปรแกรม TF1.x ที่อาศัยกราฟและเซสชัน โดยปกติคาดว่าตรรกะของ Python ทั้งหมดในโปรแกรมของคุณจะทำงานเพียงครั้งเดียว แต่ด้วยการดำเนินการและความกระตือรือร้นที่ tf.function มันไม่ยุติธรรมที่จะคาดหวังว่าตรรกะหลามของคุณจะถูกเรียกใช้อย่างน้อยหนึ่งครั้ง แต่ครั้งอาจจะมากกว่า (ทั้งหลายครั้งกระหายหรือหลายครั้งข้ามที่แตกต่างกัน tf.function ร่องรอย) บางครั้ง tf.function แม้จะติดตามสองครั้งในการป้อนข้อมูลเดียวกันที่ก่อให้เกิดพฤติกรรมที่ไม่คาดคิด (ดูตัวอย่างที่ 1 และ 2) อ้างถึง tf.function คู่มือ สำหรับรายละเอียดเพิ่มเติม

ตัวอย่างที่ 1: การสร้างตัวแปร

พิจารณาตัวอย่างด้านล่าง ซึ่งฟังก์ชันสร้างตัวแปรเมื่อเรียก:

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)

แต่ไร้เดียงสาตัดฟังก์ชั่นดังกล่าวข้างต้นที่มีการสร้างตัวแปรกับ tf.function ไม่ได้รับอนุญาต tf.function สนับสนุนเฉพาะ การสร้างสรรค์ตัวแปรเดี่ยวบนสายแรก เพื่อบังคับใช้เมื่อ tf.function ตรวจพบการสร้างตัวแปรในการเรียกครั้งแรก จะพยายามติดตามอีกครั้งและทำให้เกิดข้อผิดพลาดหากมีการสร้างตัวแปรในการติดตามครั้งที่สอง

@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)

วิธีแก้ปัญหาคือการแคชและนำตัวแปรกลับมาใช้ใหม่หลังจากที่สร้างขึ้นในการเรียกครั้งแรก

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()

ตัวอย่างที่ 2: ออกจากขอบเขตเทนเซอร์เนื่องจาก tf.function ย้อนรอย

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

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()

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

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.

วิธีการแก้ปัญหา straightfoward ที่สุดคือการสร้างความมั่นใจว่าการสร้างตัวแปรและการสร้างชุดข้อมูลที่อยู่นอกทั้งสอง tf.funciton โทร ตัวอย่างเช่น:

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()

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

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()

ตัวอย่างที่ 3: การสร้างวัตถุ Tensorflow ที่ไม่คาดคิดขึ้นใหม่เนื่องจากการใช้ dict

tf.function มีการสนับสนุนที่ดีมากสำหรับผลข้างเคียงเช่นงูหลามผนวกกับรายการหรือการตรวจสอบ / การเพิ่มพจนานุกรม รายละเอียดเพิ่มเติมอยู่ใน "ผลการดำเนินงานที่ดีขึ้นด้วย tf.function" ในตัวอย่างด้านล่าง โค้ดใช้พจนานุกรมเพื่อแคชชุดข้อมูลและตัววนซ้ำ สำหรับคีย์เดียวกัน การเรียกใช้โมเดลแต่ละครั้งจะส่งคืนตัววนซ้ำชุดข้อมูลเดียวกัน

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

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

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

เราสามารถใช้ tf.init_scope ยกชุดและภายนอกสร้าง iterator ของกราฟเพื่อให้เกิดพฤติกรรมที่คาดหวัง:

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

กฎทั่วไปคือหลีกเลี่ยงการพึ่งพา Python ผลข้างเคียงในตรรกะของคุณ และใช้เฉพาะเพื่อดีบักการติดตามของคุณ

ตัวอย่างที่ 4: การจัดการรายการ Python ทั่วโลก

รหัส TF1.x ต่อไปนี้ใช้รายการการสูญเสียทั่วโลกที่ใช้เพื่อรักษารายการความสูญเสียที่เกิดจากขั้นตอนการฝึกอบรมปัจจุบันเท่านั้น โปรดทราบว่าลอจิก Python ที่ผนวกการสูญเสียเข้ากับรายการจะถูกเรียกเพียงครั้งเดียวโดยไม่คำนึงถึงขั้นตอนการฝึกอบรมที่เซสชันจะดำเนินการ

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(...)  

อย่างไรก็ตาม หากลอจิก Python นี้ถูกแมปอย่างไร้เดียงสากับ TF2 ด้วยการดำเนินการที่กระตือรือร้น รายการการสูญเสียทั่วโลกจะมีค่าใหม่ผนวกเข้ากับมันในแต่ละขั้นตอนการฝึก ซึ่งหมายความว่ารหัสขั้นตอนการฝึกอบรมซึ่งก่อนหน้านี้คาดว่ารายการจะมีเฉพาะการสูญเสียจากขั้นตอนการฝึกอบรมปัจจุบัน จะเห็นรายการการสูญเสียจากขั้นตอนการฝึกอบรมทั้งหมดที่ดำเนินการอยู่ นี่เป็นการเปลี่ยนแปลงพฤติกรรมโดยไม่ได้ตั้งใจ และรายการจะต้องถูกล้างเมื่อเริ่มต้นแต่ละขั้นตอนหรือกำหนดให้เฉพาะกับขั้นตอนการฝึกอบรม

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)
  ...

รูปแบบที่ 2: เมตริกซ์เชิงสัญลักษณ์หมายถึงการคำนวณใหม่ทุกๆ ขั้นตอนใน TF1.x จะถูกแคชไว้โดยไม่ตั้งใจด้วยค่าเริ่มต้นเมื่อเปลี่ยนไปใช้ความกระตือรือร้น

รูปแบบนี้มักจะทำให้เกิดรหัสของคุณซุกซนอย่างเงียบ ๆ เมื่อมีการดำเนินกระหายนอก tf.functions แต่ยก InaccessibleTensorError ถ้าแคชค่าเริ่มต้นเกิดขึ้นภายในของ tf.function อย่างไรก็ตามทราบว่าเพื่อที่จะหลีกเลี่ยง แบบที่ 1 ข้างต้นคุณมักจะไม่ได้ตั้งใจจะจัดโครงสร้างรหัสของคุณในลักษณะที่ว่านี้แคชค่าเริ่มต้นที่จะเกิดขึ้นนอกใดtf.function ที่จะสามารถที่จะยกข้อผิดพลาด ดังนั้น โปรดใช้ความระมัดระวังเป็นพิเศษหากคุณรู้ว่าโปรแกรมของคุณอาจอ่อนไหวต่อรูปแบบนี้

วิธีแก้ปัญหาทั่วไปสำหรับรูปแบบนี้คือการปรับโครงสร้างโค้ดใหม่หรือใช้ Python callables หากจำเป็น เพื่อให้แน่ใจว่าค่าจะถูกคำนวณใหม่ทุกครั้งแทนที่จะถูกแคชโดยไม่ได้ตั้งใจ

ตัวอย่างที่ 1: อัตราการเรียนรู้/ไฮเปอร์พารามิเตอร์/อื่นๆ กำหนดการที่ขึ้นกับขั้นตอนระดับโลก

ในโค้ดต่อไปนี้ความคาดหวังก็คือว่าเวลาช่วงที่มีการเรียกใช้ล่าสุดทุก global_step ค่าจะถูกอ่านและมีอัตราการเรียนรู้ใหม่จะได้รับการคำนวณ

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(...)

อย่างไรก็ตาม เมื่อพยายามเปลี่ยนไปใช้ความกระตือรือร้น ให้ระวังว่าจะจบลงด้วยการคำนวณอัตราการเรียนรู้เพียงครั้งเดียวแล้วนำกลับมาใช้ใหม่ แทนที่จะทำตามกำหนดเวลา:

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)
  ...

เพราะตัวอย่างที่เฉพาะเจาะจงนี้เป็นรูปแบบที่พบบ่อยและเพิ่มประสิทธิภาพควรจะเริ่มต้นครั้งเดียวมากกว่าในขั้นตอนการฝึกอบรมแต่ละ TF2 เพิ่มประสิทธิภาพสนับสนุน tf.keras.optimizers.schedules.LearningRateSchedule ตารางหรือ callables หลามเป็นข้อโต้แย้งสำหรับอัตราการเรียนรู้และ hyperparameters อื่น ๆ

ตัวอย่างที่ 2: การเริ่มต้นหมายเลขสุ่มเชิงสัญลักษณ์ที่กำหนดเป็นแอตทริบิวต์ของวัตถุ จากนั้นนำกลับมาใช้ใหม่ผ่านตัวชี้จะถูกแคชโดยไม่ตั้งใจเมื่อเปลี่ยนไปใช้โหมดกระตือรือร้น

พิจารณาต่อไปนี้ NoiseAdder โมดูล:

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

การใช้ดังต่อไปนี้ใน TF1.x จะคำนวณเทนเซอร์สัญญาณรบกวนแบบสุ่มใหม่ทุกครั้งที่เรียกใช้เซสชัน:

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(...)

อย่างไรก็ตามในการเริ่มต้น TF2 noise_adder ที่จุดเริ่มต้นจะทำให้ noise_distribution จะได้รับการคำนวณเพียงครั้งเดียวและได้รับการแช่แข็งสำหรับขั้นตอนการฝึกอบรมทั้งหมด:

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

เพื่อแก้ไขปัญหานี้ refactor NoiseAdder จะเรียก tf.random.normal เวลาเมตริกซ์แบบสุ่มใหม่เป็นสิ่งจำเป็นทุกแทนหมายถึงวัตถุเมตริกซ์เดียวกันในแต่ละครั้ง

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

รูปแบบ 3: รหัส TF1.x อาศัยโดยตรงและค้นหาเทนเซอร์ตามชื่อ

เป็นเรื่องปกติสำหรับการทดสอบโค้ด TF1.x ที่ต้องอาศัยการตรวจสอบเทนเซอร์หรือการดำเนินการที่มีอยู่ในกราฟ ในบางกรณีซึ่งพบไม่บ่อย โค้ดการสร้างแบบจำลองจะใช้การค้นหาตามชื่อเหล่านี้ด้วย

ชื่อเมตริกซ์จะไม่ได้สร้างขึ้นเมื่อมีการดำเนินกระหายนอก tf.function ที่ทุกคนเพื่อให้การใช้งานทั้งหมดของ tf.Tensor.name ที่ต้องเกิดขึ้นภายในของ tf.function เก็บไว้ในใจชื่อสร้างที่เกิดขึ้นจริงเป็นอย่างมากมีแนวโน้มที่จะแตกต่างกันระหว่าง TF1.x และ TF2 แม้จะอยู่ในที่เดียวกัน tf.function และการค้ำประกัน API ไม่มั่นใจในเสถียรภาพของชื่อที่สร้างข้ามรุ่น TF

รูปแบบที่ 4: เซสชัน TF1.x เรียกใช้เฉพาะส่วนหนึ่งของกราฟที่สร้างขึ้นเท่านั้น

ใน TF1.x คุณสามารถสร้างกราฟแล้วเลือกเรียกใช้เฉพาะชุดย่อยของกราฟด้วยเซสชันโดยเลือกชุดของอินพุตและเอาต์พุตที่ไม่ต้องการเรียกใช้ทุก op ในกราฟ

ตัวอย่างเช่นคุณอาจมีทั้งเครื่องกำเนิดไฟฟ้าและภายใน discriminator ของกราฟเดียวและใช้แยกต่างหาก tf.compat.v1.Session.run โทรไปสลับกันระหว่างการฝึกอบรมเพียง discriminator หรือเฉพาะการฝึกอบรมเครื่องกำเนิดไฟฟ้า

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

ดังนั้นคุณจะต้องใช้อย่างใดอย่างหนึ่งหลาย tf.function s ที่มีส่วนต่าง ๆ ของโปรแกรมหรืออาร์กิวเมนต์เงื่อนไขกับ tf.function ที่คุณสาขาในเพื่อที่จะดำเนินการเฉพาะในสิ่งที่คุณต้องการจริงที่จะมีการวิ่ง

การลบคอลเลกชัน

เมื่อดำเนินการกระตือรือร้นที่จะเปิดใช้งานกราฟคอลเลกชันที่เกี่ยวข้องกับ compat.v1 APIs (รวมทั้งผู้ที่อ่านหรือเขียนไปยังคอลเลกชันภายใต้ประทุนเช่น tf.compat.v1.trainable_variables ) จะไม่สามารถใช้ได้ บางคนอาจยก ValueError s ขณะที่คนอื่นอาจจะกลับมาอย่างเงียบ ๆ รายการที่ว่างเปล่า

การใช้งานมาตรฐานที่สุดของคอลเลกชันใน TF1.x คือการรักษา initializers ขั้นตอนระดับโลก, น้ำหนัก, การสูญเสียกูสูญเสียรูปแบบการส่งออกและการปรับปรุงตัวแปรที่จะต้องมีการทำงานเช่นจาก BatchNormalization ชั้น

เพื่อจัดการกับการใช้งานมาตรฐานเหล่านี้:

  1. Initializers - ละเว้น ไม่จำเป็นต้องมีการเริ่มต้นตัวแปรด้วยตนเองเมื่อเปิดใช้งานการดำเนินการที่กระตือรือร้น
  2. ขั้นตอนที่ทั่วโลก - ดูเอกสารของ tf.compat.v1.train.get_or_create_global_step สำหรับคำแนะนำการโยกย้าย
  3. น้ำหนัก - โรงแรมแบบจำลองของคุณเพื่อ tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s โดยทำตามคำแนะนำใน คู่มือการทำแผนที่รูปแบบ แล้วใช้กลไกน้ำหนักการติดตามของตนเช่น tf.module.trainable_variables
  4. การสูญเสีย regularization - โรงแรมแบบจำลองของคุณเพื่อ tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s โดยทำตามคำแนะนำในการที่ คู่มือการทำแผนที่รูปแบบ แล้วใช้ tf.keras.losses หรือคุณสามารถติดตามการสูญเสียการทำให้เป็นมาตรฐานได้ด้วยตนเอง
  5. การสูญเสียการส่งออกรุ่น - ใช้ tf.keras.Model กลไกการจัดการความสูญเสียหรือแยกติดตามสูญเสียของคุณโดยไม่ต้องใช้คอลเลกชัน
  6. การอัปเดตน้ำหนัก - ละเว้นคอลเล็กชันนี้ การดำเนินการและความกระตือรือร้นที่ tf.function (พร้อมลายเซ็นและอัตโนมัติควบคุมการอ้างอิง) หมายถึงการปรับปรุงตัวแปรทั้งหมดจะได้รับการทำงานโดยอัตโนมัติ ดังนั้น คุณจะไม่ต้องรันการอัปเดตน้ำหนักทั้งหมดอย่างชัดเจนในตอนท้าย แต่โปรดทราบว่าการอัปเดตน้ำหนักอาจเกิดขึ้นในเวลาที่แตกต่างจากที่ทำในโค้ด TF1.x ของคุณ ขึ้นอยู่กับว่าคุณใช้การขึ้นต่อกันของการควบคุมอย่างไร
  7. สรุป - อ้างถึง การโยกย้ายคู่มือ API สรุป

การใช้คอลเลกชันที่ซับซ้อนมากขึ้น (เช่น การใช้คอลเลกชันที่กำหนดเอง) อาจทำให้คุณต้องจัดโครงสร้างโค้ดใหม่เพื่อรักษาร้านค้าทั่วโลกของคุณเอง หรือเพื่อไม่ให้ต้องพึ่งพาร้านค้าทั่วโลกเลย

ResourceVariables แทน ReferenceVariables

ResourceVariables มีการค้ำประกันที่แข็งแกร่งอ่านเขียนสอดคล้องกว่า ReferenceVariables สิ่งนี้นำไปสู่ความหมายที่คาดเดาได้ง่ายขึ้นและเข้าใจง่ายขึ้นเกี่ยวกับว่าคุณจะสังเกตผลลัพธ์ของการเขียนครั้งก่อนหรือไม่เมื่อใช้ตัวแปรของคุณ การเปลี่ยนแปลงนี้ไม่น่าเป็นไปได้อย่างยิ่งที่จะทำให้โค้ดที่มีอยู่เกิดข้อผิดพลาดหรือทำงานผิดพลาดอย่างเงียบๆ

แต่ก็เป็นไปได้ แต่ไม่น่าที่เหล่านี้แข็งแกร่งค้ำประกันสอดคล้องอาจเพิ่มการใช้งานหน่วยความจำของโปรแกรมเฉพาะของคุณ กรุณายื่น ปัญหา ถ้าคุณพบนี้จะเป็นกรณีที่ นอกจากนี้ หากคุณมีการทดสอบหน่วยโดยอาศัยการเปรียบเทียบสตริงที่แน่นอนกับชื่อตัวดำเนินการในกราฟที่สอดคล้องกับการอ่านตัวแปร โปรดทราบว่าการเปิดใช้งานตัวแปรทรัพยากรอาจเปลี่ยนชื่อของตัวดำเนินการเหล่านี้เล็กน้อย

เพื่อแยกผลกระทบของการเปลี่ยนแปลงพฤติกรรมนี้ในรหัสของคุณถ้าการดำเนินการกระตือรือร้นที่ถูกปิดใช้งานคุณสามารถใช้ tf.compat.v1.disable_resource_variables() และ tf.compat.v1.enable_resource_variables() เพื่อปิดการใช้งานทั่วโลกหรือเปิดใช้งานการเปลี่ยนแปลงพฤติกรรมนี้ ResourceVariables มักจะใช้ในกรณีที่การดำเนินการกระตือรือร้นที่จะเปิดใช้งาน

ควบคุมการไหล v2

ใน TF1.x ควบคุม Ops ไหลเช่น tf.cond และ tf.while_loop อินไลน์ในระดับต่ำ Ops เช่น Switch , Merge ฯลฯ TF2 ให้ปรับปรุงการควบคุมการทำงาน Ops ไหลที่จะดำเนินการแยก tf.function ร่องรอยทุกสาขาและการสนับสนุน ความแตกต่างระดับสูง

เพื่อแยกผลกระทบของการเปลี่ยนแปลงพฤติกรรมนี้ในรหัสของคุณถ้าการดำเนินการกระตือรือร้นที่ถูกปิดใช้งานคุณสามารถใช้ tf.compat.v1.disable_control_flow_v2() และ tf.compat.v1.enable_control_flow_v2() เพื่อปิดการใช้งานทั่วโลกหรือเปิดใช้งานการเปลี่ยนแปลงพฤติกรรมนี้ อย่างไรก็ตาม คุณสามารถปิดใช้งานการควบคุมโฟลว์ v2 ได้ก็ต่อเมื่อการดำเนินการที่กระตือรือร้นนั้นถูกปิดใช้งานด้วย หากเปิดใช้งาน โฟลว์การควบคุม v2 จะถูกใช้เสมอ

การเปลี่ยนแปลงลักษณะการทำงานนี้สามารถเปลี่ยนโครงสร้างของโปรแกรม TF ที่สร้างขึ้นซึ่งใช้โฟลว์การควบคุมได้อย่างมาก เนื่องจากจะมีการติดตามฟังก์ชันที่ซ้อนกันหลายรายการ แทนที่จะเป็นกราฟแบบแบนเดียว ดังนั้น โค้ดใดๆ ก็ตามที่ขึ้นอยู่กับความหมายที่แท้จริงของการติดตามที่ผลิตขึ้นอย่างมาก อาจต้องมีการแก้ไขบางอย่าง ซึ่งรวมถึง:

  • รหัสขึ้นอยู่กับชื่อตัวดำเนินการและเทนเซอร์
  • รหัสที่อ้างถึงเทนเซอร์ที่สร้างขึ้นภายในสาขาโฟลว์การควบคุม TensorFlow จากภายนอกสาขานั้น นี่คือแนวโน้มที่จะผลิต InaccessibleTensorError

การเปลี่ยนแปลงลักษณะการทำงานนี้มีวัตถุประสงค์เพื่อเป็นผลการดำเนินงานที่เป็นกลางเป็นบวก แต่ถ้าคุณทำงานเป็นปัญหาที่ดำเนินการควบคุมการไหล v2 ที่เลวร้ายยิ่งกว่าสำหรับคุณควบคุม TF1.x ไหลแล้วโปรดยื่น ปัญหา ด้วยขั้นตอนการทำสำเนา

การเปลี่ยนแปลงพฤติกรรม TensorShape API

TensorShape ชั้นง่ายที่จะถือ int s แทน tf.compat.v1.Dimension วัตถุ จึงมีความจำเป็นที่จะเรียกไม่มี .value ที่จะได้รับ int

บุคคล tf.compat.v1.Dimension วัตถุยังคงสามารถเข้าถึงได้จาก tf.TensorShape.dims

เพื่อแยกผลกระทบของการเปลี่ยนแปลงพฤติกรรมนี้ในรหัสของคุณคุณสามารถใช้ tf.compat.v1.disable_v2_tensorshape() และ tf.compat.v1.enable_v2_tensorshape() เพื่อปิดการใช้งานทั่วโลกหรือเปิดใช้งานการเปลี่ยนแปลงพฤติกรรมนี้

ต่อไปนี้แสดงให้เห็นถึงความแตกต่างระหว่าง TF1.x และ 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])

หากคุณมีสิ่งนี้ใน TF1.x:

value = shape[i].value

จากนั้นทำสิ่งนี้ใน TF2:

value = shape[i]
value
16

หากคุณมีสิ่งนี้ใน TF1.x:

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

จากนั้นทำสิ่งนี้ใน TF2:

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

หากคุณมีสิ่งนี้ใน TF1.x (หรือใช้วิธีมิติอื่น):

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

จากนั้นทำสิ่งนี้ใน 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

ค่าบูลีนของ tf.TensorShape เป็น True ถ้ายศเป็นที่รู้จักกัน False อย่างอื่น

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

ข้อผิดพลาดที่อาจเกิดขึ้นเนื่องจากการเปลี่ยนแปลง TensorShape

การเปลี่ยนแปลงพฤติกรรม TensorShape ไม่น่าจะทำให้โค้ดของคุณเสียหาย แต่คุณอาจจะเห็นรูปร่างที่เกี่ยวข้องกับรหัสเริ่มต้นที่จะยกระดับ AttributeError s เป็น int และ None s ไม่ได้มีลักษณะเดียวกันที่ tf.compat.v1.Dimension s ทำ ด้านล่างเป็นตัวอย่างบางส่วนของเหล่า AttributeError s:

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'

ความเท่าเทียมกันของเทนเซอร์ตามมูลค่า

ไบนารี == และ != ผู้ประกอบการเกี่ยวกับตัวแปรและเทนเซอร์ที่มีการเปลี่ยนแปลงจะเปรียบเทียบด้วยค่าใน TF2 มากกว่าการเปรียบเทียบโดยอ้างอิงวัตถุเหมือนใน TF1.x. นอกจากนี้ เทนเซอร์และตัวแปรจะไม่สามารถแฮชโดยตรงหรือใช้ในเซ็ตหรือคีย์ dict ได้อีกต่อไป เนื่องจากอาจไม่สามารถแฮชด้วยค่าเหล่านี้ได้ แต่พวกเขาเปิดเผย .ref() วิธีการที่คุณสามารถใช้เพื่อได้รับการอ้างอิง hashable จะเมตริกซ์หรือตัวแปร

เพื่อแยกผลกระทบของการเปลี่ยนแปลงพฤติกรรมนี้คุณสามารถใช้ tf.compat.v1.disable_tensor_equality() และ tf.compat.v1.enable_tensor_equality() เพื่อปิดการใช้งานทั่วโลกหรือเปิดใช้งานการเปลี่ยนแปลงพฤติกรรมนี้

ยกตัวอย่างเช่นใน TF1.x สองตัวแปรที่มีค่าเดียวกันจะกลับเท็จเมื่อคุณใช้ == ผู้ประกอบการ:

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

x == y
False

ในขณะที่ใน TF2 กับเมตริกซ์การตรวจสอบความเสมอภาคเปิดใช้งาน x == y จะกลับมา 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>

ดังนั้นใน TF2 ถ้าคุณต้องการที่จะเปรียบเทียบด้วยวัตถุอ้างอิงให้แน่ใจว่าจะใช้ is และ is not

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

x is y
False

แฮชเทนเซอร์และตัวแปร

กับพฤติกรรมการ TF1.x ที่คุณใช้เพื่อให้สามารถโดยตรงเพิ่มตัวแปรและเทนเซอร์โครงสร้างข้อมูลที่จำเป็นต้องคร่ำเครียดเช่น set และ 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>}

อย่างไรก็ตามใน TF2 มีความเท่าเทียมกันเมตริกซ์เปิดใช้งานเทนเซอร์และตัวแปรที่ทำ unhashable เนื่องจากการ == และ != ความหมายเปลี่ยนไปประกอบการตรวจสอบความเท่าเทียมกันค่า

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.

ดังนั้นใน TF2 ถ้าคุณจำเป็นต้องใช้เมตริกซ์หรือตัวแปรวัตถุเป็นกุญแจหรือ set เนื้อหาคุณสามารถใช้ tensor.ref() ได้รับการอ้างอิง hashable ที่สามารถใช้เป็นคีย์:

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>>}

หากจำเป็นคุณยังสามารถได้รับเมตริกซ์หรือตัวแปรอ้างอิงโดยใช้ reference.deref() :

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

แหล่งข้อมูลและการอ่านเพิ่มเติม