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_* ข้อยกเว้นประการหนึ่งคือการลบคอลเล็กชัน ซึ่งเป็นผลข้างเคียงของการเปิด/ปิดการดำเนินการที่กระตือรือร้น

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

  • ลบ API ที่ซ้ำซ้อน
  • ทำให้ API มีความสอดคล้องกันมากขึ้น - ตัวอย่างเช่น Unified RNNs และ Unified Optimizers
  • ชอบ ฟังก์ชันมากกว่าเซสชัน และรวมเข้ากับรันไทม์ของ Python ได้ดียิ่งขึ้นโดยเปิดใช้งาน การดำเนินการอย่างกระตือรือร้น โดยค่าเริ่มต้นพร้อมกับ tf.function ที่ให้การพึ่งพาการควบคุมอัตโนมัติสำหรับกราฟและการรวบรวม
  • เลิกใช้ คอลเลกชัน กราฟทั่วโลก
  • เปลี่ยนความหมายการทำงานพร้อมกันของตัวแปรโดยใช้ ResourceVariables เหนือ ReferenceVariables
  • รองรับโฟลว์การควบคุม ตามฟังก์ชัน และดิฟเฟอเรนทิเอ เบิ ล (Control Flow v2)
  • ลดความซับซ้อนของ TensorShape API เพื่อเก็บ int s แทน tf.compat.v1.Dimension
  • อัปเดตกลไกความเท่าเทียมกันของเทนเซอร์ ใน TF1.x ตัวดำเนินการ == บนเทนเซอร์และตัวแปรจะตรวจสอบความเท่าเทียมกันในการอ้างอิงวัตถุ ใน TF2 จะตรวจสอบความเท่าเทียมกันของมูลค่า นอกจากนี้ เทนเซอร์/ตัวแปรจะไม่สามารถแฮชได้อีกต่อไป แต่คุณสามารถขอรับการอ้างอิงอ็อบเจ็กต์ที่แฮชได้ผ่านทาง var.ref() หากคุณต้องการใช้ในเซ็ตหรือเป็นคีย์ dict

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • ประสิทธิภาพ: ฟังก์ชันนี้สามารถปรับให้เหมาะสมได้ (การตัดโหนด การผสานเคอร์เนล ฯลฯ)
  • การพกพา: ฟังก์ชันนี้สามารถส่งออก/นำเข้าใหม่ได้ ( 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 ให้ใช้ AutoGraph เพื่อแปลงชุดย่อยของโครงสร้าง Python ให้เทียบเท่ากับ 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 ไปยัง API ที่เข้ากันได้กับ API

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

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

ในโปรแกรม TF1.x ที่อาศัยกราฟและเซสชัน โดยปกติคาดว่าตรรกะของ Python ทั้งหมดในโปรแกรมของคุณจะทำงานเพียงครั้งเดียว อย่างไรก็ตาม ด้วยการดำเนินการอย่างกระตือรือร้นและ tf.function การคาดหวังว่าลอจิก Python ของคุณจะทำงานอย่างน้อยหนึ่งครั้ง แต่อาจมีมากกว่าหนึ่ง 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 รองรับเฉพาะการ สร้างตัวแปร singleton ในการโทรครั้งแรก เพื่อบังคับใช้เมื่อ 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 retracing

ดังที่แสดงไว้ในตัวอย่างที่ 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.

วิธีแก้ปัญหาที่ตรงไปตรงมาที่สุดคือการทำให้แน่ใจว่าทั้งการสร้างตัวแปรและการสร้างชุดข้อมูลนั้นอยู่นอกการเรียก 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 รองรับผลข้างเคียงของ python ได้แย่มาก เช่น การต่อท้ายรายการ หรือการตรวจสอบ/เพิ่มลงในพจนานุกรม รายละเอียดเพิ่มเติมอยู่ใน "ประสิทธิภาพที่ดีขึ้นด้วย 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 จะละเว้นผลข้างเคียงของ python จากการเพิ่มเติมพจนานุกรม แต่จะจำเฉพาะการสร้างชุดข้อมูลและตัววนซ้ำเท่านั้น ด้วยเหตุนี้ การเรียกใช้โมเดลแต่ละครั้งจะส่งคืนตัววนซ้ำใหม่เสมอ ปัญหานี้สังเกตได้ยาก เว้นแต่ผลลัพธ์ที่เป็นตัวเลขหรือประสิทธิภาพจะมีนัยสำคัญเพียงพอ ดังนั้น เราแนะนำให้ผู้ใช้คิดเกี่ยวกับโค้ดอย่างรอบคอบก่อนที่จะ 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 เพื่อยกชุดข้อมูลและการสร้างตัววนซ้ำนอกกราฟ เพื่อให้ได้พฤติกรรมที่คาดไว้:

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 หรือ Python callables เป็นอาร์กิวเมนต์สำหรับอัตราการเรียนรู้และไฮเปอร์พารามิเตอร์อื่นๆ

ตัวอย่างที่ 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)
...

ในการแก้ไขปัญหานี้ ให้ปรับโครงสร้าง 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 ที่ต้องอาศัยการตรวจสอบเทนเซอร์หรือการดำเนินการที่มีอยู่ในกราฟ ในบางกรณีซึ่งพบไม่บ่อย โค้ดการสร้างแบบจำลองจะใช้การค้นหาตามชื่อเหล่านี้ด้วย

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

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

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

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

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

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

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

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

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

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

  1. ตัวเริ่มต้น - ละเว้น ไม่จำเป็นต้องมีการเริ่มต้นตัวแปรด้วยตนเองเมื่อเปิดใช้งานการดำเนินการที่กระตือรือร้น
  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. การสูญเสียการทำให้เป็นมาตรฐาน - แมปแบบจำลองของคุณกับ 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 inline 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 s และ None แอตทริบิวต์เดียวกันกับที่ tf.compat.v1.Dimension มี ด้านล่างนี้คือตัวอย่างบางส่วนของ AttributeError เหล่านี้:

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() ที่คุณสามารถใช้เพื่อรับการอ้างอิงที่แฮชได้ไปยังเทนเซอร์หรือตัวแปร

เพื่อแยกผลกระทบของการเปลี่ยนแปลงพฤติกรรมนี้ คุณสามารถใช้ 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>
ตัวยึดตำแหน่ง42

ดังนั้น ใน 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>}
ตัวยึดตำแหน่ง46

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

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() เพื่อรับการอ้างอิงที่แฮชได้ซึ่งสามารถใช้เป็นคีย์ได้:

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>
ตัวยึดตำแหน่ง52

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

  • ไปที่ส่วน โยกย้ายไปยัง TF2 เพื่ออ่านเพิ่มเติมเกี่ยวกับการโยกย้ายไปยัง TF2 จาก TF1.x
  • อ่าน คู่มือการแมปแบบจำลอง เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการแมปโมเดล TF1.x ของคุณเพื่อทำงานใน TF2 โดยตรง