عرض على 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
.
للتعامل مع كل من هذه الاستخدامات القياسية:
- المبدئ - تجاهل. التهيئة اليدوية المتغيرة غير مطلوبة مع تمكين التنفيذ الحثيث.
- الخطوة العامة - راجع وثائق
tf.compat.v1.train.get_or_create_global_step
للحصول على إرشادات الترحيل. - الأوزان - قم بتعيين النماذج الخاصة بك إلى
tf.Module
s /tf.keras.layers.Layer
s /tf.keras.Model
s باتباع الإرشادات الواردة في دليل تعيين النموذج ثم استخدام آليات تتبع الوزن الخاصة بها مثلtf.module.trainable_variables
. - خسائر التنظيم - قم بتعيين النماذج الخاصة بك إلى
tf.Module
s /tf.keras.layers.Layer
s /tf.keras.Model
s باتباع الإرشادات الواردة في دليل تعيين النموذج ثم استخدمtf.keras.losses
. بدلاً من ذلك ، يمكنك أيضًا تتبع خسائر التسوية يدويًا. - خسائر مخرجات النموذج - استخدم آليات إدارة خسارة
tf.keras.Model
أو تتبع خسائرك بشكل منفصل دون استخدام المجموعات. - تحديثات الوزن - تجاهل هذه المجموعة. يعني التنفيذ الحريص ووظيفة tf (مع توقيعات
tf.function
التحكم التلقائي) أن جميع التحديثات المتغيرة سيتم تشغيلها تلقائيًا. لذلك ، لن تضطر إلى تشغيل جميع تحديثات الوزن بشكل صريح في النهاية ، ولكن لاحظ أن هذا يعني أن تحديثات الوزن قد تحدث في وقت مختلف عما حدث في رمز TF1.x الخاص بك ، اعتمادًا على كيفية استخدامك لتتبعيات التحكم. - الملخصات - راجع دليل ترحيل موجز 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>
الموارد والقراءات الإضافية
- قم بزيارة قسم الترحيل إلى TF2 لقراءة المزيد حول الترحيل إلى TF2 من TF1.x.
- اقرأ دليل تعيين النموذج لمعرفة المزيد عن تعيين نماذج TF1.x للعمل في TF2 مباشرةً.