مشاهده در 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 ها را سازگارتر می کند - به عنوان مثال، RNN های یکپارچه و بهینه سازهای یکپارچه.
- توابع را به جلسات ترجیح می دهد و با اجرای Eager که به طور پیش فرض فعال شده است همراه با
tf.function
که وابستگی های کنترل خودکار برای نمودارها و کامپایل را فراهم می کند، بهتر با زمان اجرا پایتون ادغام می شود. - مجموعههای نمودار جهانی را منسوخ میکند.
- با استفاده از
ResourceVariables
برReferenceVariables
معنای همزمانی متغیر را تغییر میدهد. - پشتیبانی از جریان کنترل مبتنی بر عملکرد و متمایز (Control Flow v2).
- TensorShape API را برای نگه داشتن
int
s به جای اشیاءtf.compat.v1.Dimension
می کند. - مکانیک برابری تانسور را به روز می کند. در TF1.x عملگر
==
روی تانسورها و متغیرها برابری مرجع شی را بررسی می کند. در TF2 برابری ارزش را بررسی می کند. علاوه بر این، تانسورها/متغیرها دیگر قابل هش نیستند، اما اگر نیاز به استفاده از آنها در مجموعه ها یا به عنوان کلیدهایdict
دارید، می توانید از طریقvar.ref()
ارجاعات اشیاء قابل هش را به آنها دریافت کنید.
بخش های زیر زمینه بیشتری را در مورد تفاوت های بین 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
API های TF2 در نظر گرفته نمی شوند. این فضاهای نام ترکیبی از نمادهای سازگاری و همچنین نقاط پایانی API قدیمی از TF 1.x را نشان میدهند. اینها برای کمک به مهاجرت از TF1.x به TF2 در نظر گرفته شده اند. با این حال، از آنجایی که هیچ یک از این APIهای compat.v1
، APIهای اصطلاحی TF2 نیستند، از آنها برای نوشتن کدهای جدید TF2 استفاده نکنید.
نمادهای جداگانه tf.compat.v1
ممکن است با TF2 سازگار باشند زیرا حتی با فعال بودن رفتارهای TF2 (مانند tf.compat.v1.losses.mean_squared_error
)، به کار خود ادامه می دهند، در حالی که سایر نمادها با TF2 (مانند tf.compat.v1) ناسازگار هستند tf.compat.v1.metrics.accuracy
. دقت). بسیاری از نمادهای compat.v1
(اگرچه نه همه) حاوی اطلاعات مهاجرت اختصاصی در اسناد خود هستند که درجه سازگاری آنها با رفتارهای TF2 و همچنین نحوه انتقال آنها به APIهای TF2 را توضیح می دهد.
اسکریپت ارتقاء TF2 میتواند بسیاری از نمادهای API compat.v1
را به APIهای TF2 معادل در مواردی که نام مستعار هستند یا آرگومانهای یکسان اما با ترتیب متفاوتی دارند، نگاشت. همچنین میتوانید از اسکریپت ارتقا برای تغییر نام خودکار APIهای TF1.x استفاده کنید.
API های دوستان نادرست
مجموعهای از نمادهای "دوست نادرست" در فضای نام TF2 tf
یافت میشود (نه در compat.v1
) که در واقع رفتارهای TF2 در زیر هود را نادیده میگیرند، و/یا کاملاً با مجموعه کامل رفتارهای TF2 سازگار نیستند. به این ترتیب، این APIها احتمالاً به روشهای بیصدا با کد TF2 بد رفتار میکنند.
-
tf.estimator.*
: برآوردگرها نمودارها و جلسات را در زیر هود ایجاد و استفاده می کنند. به این ترتیب، اینها نباید با TF2 سازگار در نظر گرفته شوند. اگر کد شما برآوردگرها را اجرا می کند، از رفتارهای TF2 استفاده نمی کند. -
keras.Model.model_to_estimator(...)
: این یک برآوردگر در زیر هود ایجاد می کند که همانطور که در بالا ذکر شد با TF2 سازگار نیست. -
tf.Graph().as_default()
: رفتارهای نمودار TF1.x را وارد می کند و از رفتارهای استانداردtf.function
TF2 سازگار با TF2 پیروی نمی کند. کدهایی که چنین نمودارهایی را وارد می کنند، معمولاً آنها را از طریق Sessions اجرا می کنند و نباید با 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 از شما میخواهد که بهصورت دستی یک درخت نحو انتزاعی (گراف) را با ایجاد tf.*
فراخوانیهای API و سپس با ارسال مجموعهای از تانسورهای خروجی و تانسورهای ورودی به یک تماس session.run
، درخت نحو انتزاعی را به صورت دستی کامپایل کنید. TF2 با اشتیاق اجرا می شود (مانند پایتون که معمولاً انجام می دهد) و باعث می شود نمودارها و جلسات شبیه جزئیات پیاده سازی شوند.
یکی از محصولات جانبی قابل توجه اجرای مشتاق این است که tf.control_dependencies
دیگر مورد نیاز نیست، زیرا همه خطوط کد به ترتیب اجرا می شوند (در یک tf.function
، کد با عوارض جانبی به ترتیب نوشته شده اجرا می شود).
دیگر جهانی نیست
TF1.x به شدت به فضاهای نام جهانی و مجموعه های ضمنی متکی بود. وقتی tf.Variable
را فراخوانی میکردید، در یک مجموعه در نمودار پیشفرض قرار میگرفت و در آنجا باقی میماند، حتی اگر مسیر متغیر پایتون را که به آن اشاره میکرد از دست بدهید. سپس میتوانید آن tf.Variable
را بازیابی کنید، اما فقط در صورتی که نامی را که با آن ایجاد شده است بدانید. اگر کنترل ایجاد متغیر را نداشتید انجام این کار دشوار بود. در نتیجه، انواع مکانیسمها برای کمک به شما برای یافتن متغیرهای خود و چارچوبها برای یافتن متغیرهای ایجاد شده توسط کاربر گسترش یافتند. برخی از این موارد عبارتند از: دامنه های متغیر، مجموعه های سراسری، روش های کمکی مانند tf.get_global_step
و tf.global_variables_initializer
، بهینه سازهایی که به طور ضمنی گرادیان ها را بر روی همه متغیرهای آموزش پذیر محاسبه می کنند و غیره. TF2 همه این مکانیسم ها ( متغیرهای 2.0 RFC ) را به نفع مکانیزم پیش فرض حذف می کند - شما متغیرهای خود را پیگیری می کنید. اگر مسیر یک tf.Variable
را گم کنید، زباله جمعآوری میشود.
نیاز به ردیابی متغیرها کار اضافی ایجاد میکند، اما با ابزارهایی مانند شیمهای مدلسازی و رفتارهایی مانند مجموعههای متغیر شیگرا ضمنی در tf.Module
s و tf.keras.layers.Layer
، بار به حداقل میرسد.
توابع، نه جلسات
تماس session.run
تقریباً شبیه یک فراخوانی تابع است: ورودی ها و تابعی را که باید فراخوانی شود را مشخص می کنید و مجموعه ای از خروجی ها را برمی گردانید. در TF2، میتوانید یک تابع پایتون را با استفاده از 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)
با قدرت تلاقی آزادانه کد پایتون و تنسورفلو، میتوانید از بیانی بودن پایتون استفاده کنید. با این حال، TensorFlow قابل حمل در زمینههایی بدون مفسر پایتون، مانند موبایل، C++ و جاوا اسکریپت اجرا میشود. برای کمک به جلوگیری از بازنویسی کد خود هنگام اضافه کردن 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
می شود. برای اطلاعات بیشتر در مورد انتقال از APIهایی که با اجرای مشتاقانه و tf.function
ناسازگار هستند به APIهایی که با آنها سازگار هستند، به راهنمای انتقال TF2 مراجعه کنید.
در زیر برخی از الگوهای برنامه معمولی وجود دارد که به یک API مرتبط نیستند که ممکن است هنگام تغییر از tf.Graph
s و tf.compat.v1.Session
به اجرای مشتاق با tf.function
s مشکل ایجاد کند.
الگوی 1: دستکاری شیء پایتون و ایجاد متغیر تنها یک بار انجام می شود و چندین بار اجرا می شود
در برنامه های TF1.x که به نمودارها و جلسات متکی هستند، معمولاً انتظار این است که تمام منطق پایتون در برنامه شما فقط یک بار اجرا شود. با این حال، با اجرای مشتاقانه و tf.function
، منصفانه است که انتظار داشته باشید که منطق پایتون شما حداقل یک بار، اما احتمالاً چندین بار اجرا شود (چه چندین بار مشتاقانه، یا چندین بار در مسیرهای مختلف tf.function
). گاهی اوقات، tf.function
حتی دو بار در یک ورودی یکسان ردیابی می کند و باعث رفتارهای غیرمنتظره می شود (به مثال 1 و 2 مراجعه کنید). برای جزئیات بیشتر به راهنمای tf.function
مراجعه کنید.
مثال 1: ایجاد متغیر
مثال زیر را در نظر بگیرید، جایی که تابع هنگام فراخوانی یک متغیر ایجاد می کند:
def f():
v = tf.Variable(1.0)
return v
with tf.Graph().as_default():
with tf.compat.v1.Session() as sess:
res = f()
sess.run(tf.compat.v1.global_variables_initializer())
sess.run(res)
با این حال، پیچیده کردن ساده تابع فوق که حاوی ایجاد متغیر با tf.function
است مجاز نیست. tf.function
فقط از ایجاد متغیرهای تکی در اولین تماس پشتیبانی می کند. برای اعمال این امر، هنگامی که tf.function ایجاد متغیر را در اولین فراخوانی تشخیص میدهد، سعی میکند دوباره ردیابی کند و در صورت ایجاد متغیر در ردیابی دوم، خطا ایجاد کند.
@tf.function
def f():
print("trace") # This will print twice because the python body is run twice
v = tf.Variable(1.0)
return v
try:
f()
except ValueError as e:
print(e)
یک راه حل، ذخیره کردن و استفاده مجدد از متغیر پس از ایجاد آن در اولین تماس است.
class Model(tf.Module):
def __init__(self):
self.v = None
@tf.function
def __call__(self):
print("trace") # This will print twice because the python body is run twice
if self.v is None:
self.v = tf.Variable(0)
return self.v
m = Model()
m()
مثال 2: تانسورهای خارج از محدوده به دلیل ردیابی tf.function
همانطور که در مثال 1 نشان داده شده است، tf.function
زمانی که ایجاد متغیر را در اولین فراخوانی تشخیص دهد، دوباره دنبال می شود. این می تواند باعث سردرگمی اضافی شود، زیرا دو ردیابی دو نمودار ایجاد می کنند. هنگامی که نمودار دوم از ردیابی مجدد تلاش می کند به یک 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
از عوارض جانبی پایتون مانند ضمیمه کردن لیست یا چک کردن/افزودن به فرهنگ لغت پشتیبانی بسیار ضعیفی دارد. جزئیات بیشتر در "عملکرد بهتر با tf.function" آمده است. در مثال زیر، کد از دیکشنری ها برای ذخیره مجموعه داده ها و تکرارکننده ها استفاده می کند. برای همان کلید، هر فراخوانی به مدل همان تکرار کننده مجموعه داده را برمی گرداند.
class Model(tf.Module):
def __init__(self):
self.datasets = {}
self.iterators = {}
def __call__(self, key):
if key not in self.datasets:
self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])
self.iterators[key] = self.datasets[key].make_initializable_iterator()
return self.iterators[key]
with tf.Graph().as_default():
with tf.compat.v1.Session() as sess:
m = Model()
it = m('a')
sess.run(it.initializer)
for _ in range(3):
print(sess.run(it.get_next())) # prints 1, 2, 3
با این حال، الگوی بالا آنطور که انتظار می رود در tf.function
کار نخواهد کرد. در طول ردیابی، tf.function
عارضه جانبی پایتون از افزودن به دیکشنری ها را نادیده می گیرد. در عوض، فقط ایجاد یک مجموعه داده و تکرار کننده جدید را به خاطر می آورد. در نتیجه، هر فراخوانی به مدل همیشه یک تکرار کننده جدید برمی گرداند. متوجه این موضوع سخت است مگر اینکه نتایج عددی یا عملکرد به اندازه کافی قابل توجه باشد. از این رو، ما به کاربران توصیه می کنیم قبل از قرار دادن tf.function
ساده روی کد پایتون، به دقت در مورد کد فکر کنند.
class Model(tf.Module):
def __init__(self):
self.datasets = {}
self.iterators = {}
@tf.function
def __call__(self, key):
if key not in self.datasets:
self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
self.iterators[key] = iter(self.datasets[key])
return self.iterators[key]
m = Model()
for _ in range(3):
print(next(m('a'))) # prints 1, 1, 1
میتوانیم از tf.init_scope
برای بالا بردن مجموعه دادهها و ایجاد تکرارکننده در خارج از نمودار استفاده کنیم تا به رفتار مورد انتظار دست یابیم:
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
قاعده کلی این است که از تکیه بر عوارض جانبی پایتون در منطق خود اجتناب کنید و فقط از آنها برای رفع اشکال ردیابی خود استفاده کنید.
مثال 4: دستکاری لیست جهانی پایتون
کد TF1.x زیر از یک لیست کلی از ضررها استفاده می کند که فقط برای حفظ لیست ضررهای ایجاد شده توسط مرحله آموزشی فعلی استفاده می کند. توجه داشته باشید که منطق پایتون که ضررها را به لیست اضافه می کند، صرف نظر از اینکه جلسه برای چند مرحله آموزشی اجرا شده است، فقط یک بار فراخوانی می شود.
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(...)
با این حال، اگر این منطق پایتون سادهلوحانه با اجرای مشتاقانه به 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 مجدداً محاسبه شود، هنگام تغییر به Eager به طور تصادفی با مقدار اولیه در حافظه پنهان ذخیره می شود.
این الگو معمولاً باعث میشود کد شما در هنگام اجرای مشتاقانه در خارج از tf.functions بیصدا رفتار نادرست داشته باشد، اما اگر ذخیره مقدار اولیه در داخل یک InaccessibleTensorError
رخ دهد، یک tf.function
. با این حال، توجه داشته باشید که برای جلوگیری از الگوی 1 بالا، اغلب ناخواسته کد خود را به گونهای ساختار میدهید که این ذخیره مقادیر اولیه خارج از هر tf.function
که میتواند خطا ایجاد کند، اتفاق بیفتد. بنابراین، اگر می دانید که برنامه شما ممکن است مستعد این الگو باشد، بیشتر مراقب باشید.
راه حل کلی برای این الگو این است که کد را بازسازی کنید یا در صورت لزوم از فراخوانی های پایتون استفاده کنید تا مطمئن شوید که مقدار هر بار به جای اینکه به طور تصادفی در حافظه پنهان ذخیره شود دوباره محاسبه می شود.
مثال 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: مقدار دهی اولیه اعداد تصادفی نمادین که به عنوان ویژگی های شی اختصاص داده شده و سپس از طریق اشاره گر مجددا استفاده می شوند، به طور تصادفی در حافظه پنهان هنگام تغییر به Eager ذخیره می شوند.
ماژول 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
که بر روی آن منشعب میکنید، استفاده کنید تا فقط مواردی را که واقعاً میخواهید اجرا کنید، اجرا کنید.
حذف مجموعه ها
هنگامی که اجرای مشتاق فعال است، APIهای compat.v1
مربوط به مجموعه گراف (از جمله آنهایی که در مجموعههای زیر سرپوشی مانند tf.compat.v1.trainable_variables
) دیگر در دسترس نیستند. برخی ممکن است ValueError
s را افزایش دهند، در حالی که برخی دیگر ممکن است بی سر و صدا لیست های خالی را برگردانند.
استانداردترین استفاده از مجموعه ها در TF1.x برای حفظ مقداردهی اولیه، گام جهانی، وزن ها، تلفات منظم سازی، تلفات خروجی مدل و به روز رسانی های متغیری است که باید اجرا شوند مانند لایه های BatchNormalization
.
برای رسیدگی به هر یک از این کاربردهای استاندارد:
- Initializers - نادیده گرفته شود. با فعال بودن اجرای مشتاق، مقداردهی اولیه متغیر دستی مورد نیاز نیست.
- مرحله جهانی - برای دستورالعمل های مهاجرت به مستندات
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.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.while_loop
عملیات های سطح پایین درون خطی مانند 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
شد. بنابراین برای دریافت int
نیازی به فراخوانی .value
نیست.
اشیاء 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 و tf.compat.v1.Dimension
None
ندارند. در زیر چند نمونه از این 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 تغییر کردند. علاوه بر این، تانسورها و متغیرها دیگر مستقیماً قابل هش کردن یا استفاده در مجموعهها یا کلیدهای دیکت نیستند، زیرا ممکن است هش کردن آنها بر اساس مقدار ممکن نباشد. در عوض، آنها یک .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 از TF1.x از بخش مهاجرت به TF2 دیدن کنید.
- راهنمای نگاشت مدل را بخوانید تا نقشهبرداری مدلهای TF1.x خود را برای کار مستقیم در TF2 بیشتر بدانید.