TensorFlow 1.x مقابل TensorFlow 2 - السلوكيات وواجهات برمجة التطبيقات

عرض على TensorFlow.org تشغيل في Google Colab عرض على جيثب تحميل دفتر

تحت الغطاء ، يتبع TensorFlow 2 نموذج برمجة مختلفًا اختلافًا جذريًا عن TF1.x.

يصف هذا الدليل الاختلافات الأساسية بين TF1.x و TF2 من حيث السلوكيات وواجهات برمجة التطبيقات ، وكيف ترتبط كل هذه الاختلافات برحلة الترحيل الخاصة بك.

ملخص رفيع المستوى للتغييرات الرئيسية

بشكل أساسي ، يستخدم TF1.x و TF2 مجموعة مختلفة من سلوكيات وقت التشغيل حول التنفيذ (متحمس في TF2) ، والمتغيرات ، وتدفق التحكم ، وأشكال الموتر ، ومقارنات مساواة الموتر. لكي تكون متوافقًا مع TF2 ، يجب أن يكون الرمز الخاص بك متوافقًا مع المجموعة الكاملة لسلوكيات TF2. أثناء الترحيل ، يمكنك تمكين أو تعطيل معظم هذه السلوكيات بشكل فردي عبر tf.compat.v1.enable_* أو tf.compat.v1.disable_* واجهات برمجة التطبيقات. الاستثناء الوحيد هو إزالة المجموعات ، وهو أحد الآثار الجانبية لتمكين / تعطيل التنفيذ الحثيث.

على مستوى عالٍ ، TensorFlow 2:

  • يزيل واجهات برمجة التطبيقات المكررة .
  • يجعل واجهات برمجة التطبيقات أكثر اتساقًا - على سبيل المثال ، RNNs الموحدة و Unified Optimizers .
  • يفضل الوظائف على الجلسات ويتكامل بشكل أفضل مع وقت تشغيل Python مع تمكين تنفيذ Eager افتراضيًا جنبًا إلى جنب مع 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 ، اقرأ RFCs ومستندات التصميم .

تنظيف API

العديد من واجهات برمجة التطبيقات إما اختفت أو تم نقلها في TF2. تتضمن بعض التغييرات الرئيسية إزالة tf.app و tf.flags و tf.logging لصالح absl-py المفتوحة المصدر الآن ، وإعادة توجيه المشاريع التي عاشت في tf.contrib ، وتنظيف مساحة الاسم tf.* الرئيسية بواسطة نقل الوظائف الأقل استخدامًا إلى حزم فرعية مثل tf.math . تم استبدال بعض واجهات برمجة التطبيقات بمكافئات TF2 - tf.summary و tf.keras.metrics و tf.keras.optimizers .

tf.compat.v1 : نقاط نهاية واجهة برمجة التطبيقات القديمة والتوافق

لا تعتبر الرموز الموجودة ضمن مساحات الأسماء tf.compat و tf.compat.v1 بمثابة واجهات برمجة تطبيقات TF2. تعرض مساحات الأسماء هذه مزيجًا من رموز التوافق ، بالإضافة إلى نقاط نهاية API القديمة من TF 1.x. تهدف هذه إلى المساعدة في الهجرة من TF1.x إلى TF2. ومع ذلك ، نظرًا لأن أيا من واجهات برمجة التطبيقات compat.v1 هذه هي واجهات برمجة تطبيقات TF2 اصطلاحية ، فلا تستخدمها لكتابة رمز TF2 جديد تمامًا.

قد تكون رموز tf.compat.v1 الفردية متوافقة مع TF2 لأنها تستمر في العمل حتى مع تمكين سلوكيات TF2 (مثل tf.compat.v1.losses.mean_squared_error ) ، بينما لا تتوافق الرموز الأخرى مع TF2 (مثل tf.compat.v1.metrics.accuracy . دقة). تحتوي العديد من الرموز compat.v1 مع v1 (وإن لم تكن كلها) على معلومات ترحيل مخصصة في وثائقها تشرح درجة توافقها مع سلوكيات TF2 ، بالإضافة إلى كيفية ترحيلها إلى واجهات برمجة تطبيقات TF2.

يمكن للبرنامج النصي للترقية TF2 تعيين العديد من رموز API compat.v1 مع واجهات برمجة تطبيقات TF2 المكافئة في الحالة التي تكون فيها أسماء مستعارة أو لها نفس الوسيطات ولكن بترتيب مختلف. يمكنك أيضًا استخدام البرنامج النصي للترقية لإعادة تسمية TF1.x APIs تلقائيًا.

واجهات برمجة تطبيقات صديق زائف

توجد مجموعة من رموز "الصديق الزائف" موجودة في مساحة اسم TF2 tf (ليست ضمن compat.v1 ) تتجاهل في الواقع سلوكيات TF2 تحت الغطاء ، و / أو لا تتوافق تمامًا مع المجموعة الكاملة لسلوكيات TF2. على هذا النحو ، من المحتمل أن تتصرف واجهات برمجة التطبيقات مع كود TF2 ، ربما بطرق صامتة.

  • tf.estimator.* : ينشئ المقدّرون ويستخدمون الرسوم البيانية والجلسات تحت الغطاء. على هذا النحو ، لا ينبغي اعتبار هذه متوافقة مع TF2. إذا كانت التعليمات البرمجية الخاصة بك تستخدم المقدرات ، فإنها لا تستخدم سلوكيات TF2.
  • keras.Model.model_to_estimator(...) : يؤدي هذا إلى إنشاء مقدر تحت الغطاء ، والذي كما ذكر أعلاه غير متوافق مع TF2.
  • tf.Graph().as_default() : هذا يدخل سلوكيات الرسم البياني tf.function ولا يتبع سلوكيات وظيفة tf القياسية المتوافقة مع TF2. ستقوم الكود الذي يدخل الرسوم البيانية مثل هذا بتشغيلها بشكل عام عبر الجلسات ، ولا يجب اعتبارها متوافقة مع TF2.
  • tf.feature_column.* tf.compat.v1.get_variable نظرًا لأن TF2 لا يدعم المجموعات ، فقد لا تعمل واجهات برمجة التطبيقات بشكل صحيح عند تشغيلها مع تمكين سلوكيات 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 ، سيتم وضعه في مجموعة في الرسم البياني الافتراضي ، وسيبقى هناك ، حتى إذا فقدت مسار متغير 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 ، يتم تقليل العبء إلى الحد الأدنى.

وظائف وليس جلسات

تشبه استدعاء session.run استدعاء دالة تقريبًا: فأنت تحدد المدخلات والوظيفة المراد استدعاؤها ، وستحصل على مجموعة من المخرجات. في TF2 ، يمكنك تزيين دالة Python باستخدام وظيفة tf.function من أجل تجميع JIT بحيث يقوم TensorFlow بتشغيلها كرسم بياني واحد ( وظائف 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 ، مثل mobile و C ++ و JavaScript. للمساعدة في تجنب إعادة كتابة التعليمات البرمجية الخاصة بك عند إضافة وظيفة tf ، استخدم tf.function لتحويل مجموعة فرعية من بنيات Python إلى مكافئاتها في TensorFlow:

  • for / while -> tf. tf.while_loop (يتم continue break ومتابعة)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce مجموعة البيانات

يدعم AutoGraph التداخل التعسفي لتدفق التحكم ، مما يجعل من الممكن تنفيذ العديد من برامج ML المعقدة بأداء ودقة مثل نماذج التسلسل والتعلم المعزز وحلقات التدريب المخصصة والمزيد.

التكيف مع تغيرات سلوك فريق العمل 2.x

يكتمل الترحيل إلى TF2 فقط بمجرد انتقالك إلى المجموعة الكاملة من سلوكيات TF2. يمكن تمكين أو تعطيل مجموعة السلوكيات الكاملة عبر tf.compat.v1.enable_v2_behaviors و tf.compat.v1.disable_v2_behaviors . تناقش الأقسام أدناه كل تغيير رئيسي في السلوك بالتفصيل.

باستخدام tf.function

من المحتمل أن تأتي أكبر التغييرات التي تم إجراؤها على برامجك أثناء الترحيل من التحول النموذجي لنموذج البرمجة الأساسي من الرسوم البيانية والجلسات إلى التنفيذ tf.function . راجع أدلة ترحيل TF2 لمعرفة المزيد حول الانتقال من واجهات برمجة التطبيقات غير المتوافقة مع التنفيذ tf.function إلى واجهات برمجة التطبيقات المتوافقة معها.

فيما يلي بعض أنماط البرامج الشائعة غير المرتبطة بواجهة برمجة تطبيقات واحدة والتي قد تسبب مشاكل عند التبديل من tf.Graph s و tf.compat.v1.Session s إلى التنفيذ الجاد باستخدام tf.function .

النمط 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 يدعم فقط إنشاءات المتغير الفردي في المكالمة الأولى . لفرض هذا ، عندما تكتشف 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 خارج النطاق بسبب تصحيح وظيفة tf

كما هو موضح في المثال 1 ، سوف tf.function عندما تكتشف إنشاء متغير في الاستدعاء الأول. يمكن أن يسبب هذا مزيدًا من الارتباك ، لأن التتبعين سينشئان رسمين بيانيين. عندما يحاول الرسم البياني الثاني من الاسترداد الوصول إلى Tensor من الرسم البياني الذي تم إنشاؤه أثناء التتبع الأول ، سيرفع Tensorflow خطأ يشكو من أن Tensor خارج النطاق. لتوضيح السيناريو ، يُنشئ الكود أدناه مجموعة بيانات في أول استدعاء 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 بسبب استخدام الدكت

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 التأثير الجانبي للغة الثعبان للإضافة إلى القواميس. بدلاً من ذلك ، فإنه يتذكر فقط إنشاء مجموعة بيانات ومكرر جديد. ونتيجة لذلك ، فإن كل استدعاء للنموذج سيعيد دائمًا مكررًا جديدًا. يصعب ملاحظة هذه المشكلة ما لم تكن النتائج الرقمية أو الأداء كبيرًا بدرجة كافية. وبالتالي ، نوصي المستخدمين بالتفكير في الكود بعناية قبل تغليف tf.function بسذاجة في كود 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

يمكننا استخدام 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: التلاعب بقائمة بايثون عالمية

يستخدم رمز 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 ، ولكنه يثير خطأ InaccessibleTensorError إذا حدث التخزين المؤقت للقيمة الأولية داخل tf.function . ومع ذلك ، يجب أن تدرك أنه من أجل تجنب النمط 1 أعلاه ، ستقوم في كثير من الأحيان عن غير قصد ببناء الكود الخاص بك بطريقة تجعل التخزين المؤقت للقيمة الأولية هذا سيحدث خارج أي tf.function قد تكون قادرة على رفع خطأ. لذا ، توخ مزيدًا من الحذر إذا كنت تعلم أن برنامجك قد يكون عرضة لهذا النمط.

يتمثل الحل العام لهذا النمط في إعادة هيكلة الكود أو استخدام أدوات استدعاء Python إذا لزم الأمر للتأكد من إعادة حساب القيمة في كل مرة بدلاً من تخزينها مؤقتًا عن طريق الخطأ.

مثال 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 كوسائط لمعدل التعلم والمعلمات الفائقة الأخرى.

مثال 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 على التحقق من الموترات أو العمليات الموجودة في الرسم البياني. في بعض الحالات النادرة ، سيعتمد رمز النمذجة أيضًا على عمليات البحث هذه بالاسم.

لا يتم إنشاء أسماء الموتر عند التنفيذ بفارغ الصبر خارج tf.function على الإطلاق ، لذلك يجب أن تحدث جميع استخدامات tf.Tensor.name داخل tf.function . ضع في اعتبارك أن الأسماء التي تم إنشاؤها فعليًا من المحتمل جدًا أن تختلف بين TF1.x و TF2 حتى داخل نفس tf.function ، ولا تضمن ضمانات واجهة برمجة التطبيقات (API) استقرار الأسماء التي تم إنشاؤها عبر إصدارات TF.

النمط 4: تعمل جلسة TF1.x بشكل انتقائي فقط على جزء من الرسم البياني الذي تم إنشاؤه

في TF1.x ، يمكنك إنشاء رسم بياني ثم اختيار التشغيل الانتقائي فقط لمجموعة فرعية منه بجلسة باختيار مجموعة من المدخلات والمخرجات التي لا تتطلب تشغيل كل عملية تشغيل في الرسم البياني.

على سبيل المثال ، قد يكون لديك مولد ومميز داخل رسم بياني واحد ، وتستخدم استدعاءات tf.compat.v1.Session.run منفصلة للتبديل بين تدريب أداة التمييز فقط أو تدريب المولد فقط.

في TF2 ، بسبب تبعيات التحكم الآلي في tf.function والتنفيذ الحثيث ، لا يوجد تقليم انتقائي tf.function . سيتم تشغيل رسم بياني كامل يحتوي على جميع التحديثات المتغيرة حتى إذا كان ، على سبيل المثال ، خرج فقط من أداة التمييز أو المولد من tf.function .

لذلك ، قد تحتاج إما إلى استخدام عدة tf.function تحتوي على أجزاء مختلفة من البرنامج ، أو وسيطة tf.function التي تتفرع إليها لتنفيذ الأشياء التي تريد بالفعل تشغيلها فقط.

إزالة المجموعات

عند تمكين التنفيذ الحثيث ، لم تعد واجهات برمجة التطبيقات compat.v1 مع مجموعة الرسوم البيانية (بما في ذلك تلك التي تقرأ أو تكتب إلى المجموعات تحت الغطاء مثل tf.compat.v1.trainable_variables ) متاحة. قد يرفع البعض 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 (مع توقيعات 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 ، عمليات التحكم في التدفق مثل tf.cond و tf. tf.while_loop inline low-level ops مثل Switch ، Merge إلخ. يوفر TF2 عمليات تدفق تحكم وظيفية محسّنة يتم تنفيذها من خلال تتبعات 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.TensorShape.dims tf.compat.v1.Dimension

لعزل تأثير تغيير السلوك هذا على التعليمات البرمجية الخاصة بك ، يمكنك استخدام 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 مثل int s و None لا تحتوي على نفس السمات التي tf.compat.v1.Dimension s. فيما يلي بعض الأمثلة على 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'
l10n-placeholder37 l10n-placeholder38l10n-placeholder35 l10n-placeholder36
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. بالإضافة إلى ذلك ، لم تعد الموترات والمتغيرات قابلة للتجزئة أو الاستخدام بشكل مباشر في مجموعات أو مفاتيح ديكت ، لأنه قد لا يكون من الممكن تجزئتها حسب القيمة. بدلاً من ذلك ، يعرضون طريقة .ref() التي يمكنك استخدامها للحصول على مرجع قابل للتجزئة للموتر أو المتغير.

لعزل تأثير هذا التغيير في السلوك ، يمكنك استخدام tf.compat.v1.disable_tensor_equality() و tf.compat.v1.enable_tensor_equality() لتعطيل هذا التغيير في السلوك أو تمكينه بشكل عام.

على سبيل المثال ، في TF1.x ، سيعود متغيرين بنفس القيمة إلى false عند استخدام عامل التشغيل == :

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 مع تمكين مساواة الموتر ، تصبح الموترات والمتغيرات غير قابلة للتجزئة بسبب تغيير دلالات المشغل == و != إلى فحوصات مساواة القيمة.

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>

الموارد والقراءات الإضافية