درک TensorFlow's @tf.function Decorator

معرفی

بهبود عملکرد یک حلقه آموزشی می تواند ساعت ها در زمان محاسباتی هنگام آموزش مدل های یادگیری ماشین صرفه جویی کند. یکی از راه های بهبود عملکرد کد TensorFlow استفاده از tf.function() دکوراتور - یک تغییر ساده و تک خطی که می تواند عملکردهای شما را به میزان قابل توجهی سریعتر کند.

در این راهنمای کوتاه، نحوه انجام آن را توضیح خواهیم داد tf.function() عملکرد را بهبود می بخشد و به برخی از بهترین شیوه ها نگاهی بیندازید.

دکوراتورهای پایتون و tf.function()

در پایتون، دکوراتور تابعی است که رفتار سایر توابع را تغییر می دهد. به عنوان مثال، فرض کنید تابع زیر را در یک سلول نوت بوک فراخوانی می کنید:

import tensorflow as tf

x = tf.random.uniform(shape=[100, 100], minval=-1, maxval=1, dtype=tf.dtypes.float32)

def some_costly_computation(x):
    aux = tf.eye(100, dtype=tf.dtypes.float32)
    result = tf.zeros(100, dtype = tf.dtypes.float32)
    for i in range(1,100):
        aux = tf.matmul(x,aux)/i
        result = result + aux
    return result

%timeit some_costly_computation(x)
16.2 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

با این حال، اگر تابع پرهزینه را به a منتقل کنیم tf.function():

quicker_computation = tf.function(some_costly_computation)
%timeit quicker_computation(x)

ما گرفتیم quicker_computation() - یک عملکرد جدید که بسیار سریعتر از عملکرد قبلی انجام می شود:

4.99 ms ± 139 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

پس tf.function() تغییر some_costly_computation() و خروجی می دهد quicker_computation() تابع. دکوراتورها نیز عملکردها را تغییر می دهند، بنابراین ساختن آن طبیعی بود tf.function() یک دکوراتور نیز

استفاده از نماد دکوراتور مانند فراخوانی است tf.function(function):

@tf.function
def quick_computation(x):
  aux = tf.eye(100, dtype=tf.dtypes.float32)
  result = tf.zeros(100, dtype = tf.dtypes.float32)
  for i in range(1,100):
    aux = tf.matmul(x,aux)/i
    result = result + aux
  return result

%timeit quick_computation(x)
5.09 ms ± 283 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

چطور؟ tf.function() کار کن

چگونه می‌توانیم عملکردهای خاصی را ۲ تا ۳ برابر سریع‌تر اجرا کنیم؟

کد TensorFlow را می توان در دو حالت اجرا کرد: حالت مشتاق و حالت نمودار. حالت اشتیاق روش استاندارد و تعاملی برای اجرای کد است: هر بار که یک تابع را فراخوانی می کنید، اجرا می شود.

حالت نمودار، با این حال، کمی متفاوت است. در حالت گراف، قبل از اجرای تابع، TensorFlow یک نمودار محاسباتی ایجاد می کند که یک ساختار داده حاوی عملیات مورد نیاز برای اجرای تابع است. نمودار محاسباتی به TensorFlow اجازه می دهد تا محاسبات را ساده کند و فرصت هایی برای موازی سازی پیدا کند. این نمودار همچنین تابع را از کد پایتون پوشاننده جدا می کند و به آن اجازه می دهد تا به طور موثر در بسیاری از دستگاه های مختلف اجرا شود.

یک تابع تزئین شده با @tf.function در دو مرحله اجرا می شود:

  1. در مرحله اول، TensorFlow کد پایتون را برای تابع اجرا می کند و یک نمودار محاسباتی را کامپایل می کند و اجرای هر عملیات TensorFlow را به تاخیر می اندازد.
  2. سپس نمودار محاسباتی اجرا می شود.

توجه داشته باشید: مرحله اول به عنوان شناخته شده است "ردیابی".

اگر نیازی به ایجاد یک نمودار محاسباتی جدید نباشد، مرحله اول نادیده گرفته می شود. این عملکرد عملکرد را بهبود می بخشد، اما همچنین به این معنی است که تابع مانند کد معمولی پایتون (که در آن هر خط اجرایی اجرا می شود) اجرا نمی شود. به عنوان مثال، اجازه دهید عملکرد قبلی خود را اصلاح کنیم:

@tf.function
def quick_computation(x):
  print('Only prints the first time!')
  aux = tf.eye(100, dtype=tf.dtypes.float32)
  result = tf.zeros(100, dtype = tf.dtypes.float32)
  for i in range(1,100):
    aux = tf.matmul(x,aux)/i
    result = result + aux
  return result

quick_computation(x)
quick_computation(x)

این نتیجه در:

Only prints the first time!

La print() فقط یک بار در مرحله ردیابی اجرا می شود، یعنی زمانی که کد معمولی پایتون اجرا می شود. فراخوانی های بعدی تابع فقط عملیات TenforFlow را از نمودار محاسباتی (عملیات TensorFlow) اجرا می کند.

با این حال اگر استفاده کنیم tf.print() بجای:

@tf.function
def quick_computation_with_print(x):
  tf.print("Prints every time!")
  aux = tf.eye(100, dtype=tf.dtypes.float32)
  result = tf.zeros(100, dtype = tf.dtypes.float32)
  for i in range(1,100):
    aux = tf.matmul(x,aux)/i
    result = result + aux
  return result

quick_computation_with_print(x)
quick_computation_with_print(x)

راهنمای عملی و عملی ما برای یادگیری Git را با بهترین روش ها، استانداردهای پذیرفته شده در صنعت و برگه تقلب شامل بررسی کنید. دستورات Google Git را متوقف کنید و در واقع یاد گرفتن آی تی!

Prints every time!
Prints every time!

TensorFlow شامل tf.print() در نمودار محاسباتی آن به عنوان یک عملیات TensorFlow - نه یک تابع معمولی پایتون.

هشدار: همه کدهای پایتون در هر فراخوانی به تابعی که با آن تزئین شده است اجرا نمی شود @tf.function. پس از ردیابی، فقط عملیات از نمودار محاسباتی اجرا می شود، به این معنی که باید در کد ما کمی دقت شود.

بهترین تمرین ها با @tf.function

نوشتن کد با عملیات TensorFlow

همانطور که نشان دادیم، برخی از بخش‌های کد توسط نمودار محاسباتی نادیده گرفته می‌شوند. این امر پیش‌بینی رفتار تابع را در هنگام کدنویسی با کد پایتون «عادی» دشوار می‌کند، همانطور که قبلاً مشاهده کردیم. print(). برای جلوگیری از رفتار غیرمنتظره، بهتر است عملکرد خود را در صورت امکان با عملیات TensorFlow کدنویسی کنید.

به عنوان مثال، for و while حلقه ها ممکن است به حلقه TensorFlow معادل تبدیل شوند یا نه. بنابراین، بهتر است در صورت امکان حلقه "for" خود را به صورت عملیات برداری شده بنویسید. این کار عملکرد کد شما را بهبود می بخشد و اطمینان حاصل می کند که عملکرد شما به درستی ردیابی می شود.

به عنوان نمونه موارد زیر را در نظر بگیرید:

x = tf.random.uniform(shape=[100, 100], minval=-1, maxval=1, dtype=tf.dtypes.float32)

@tf.function
def function_with_for(x):
    summ = float(0)
    for row in x:
      summ = summ + tf.reduce_mean(row)
    return summ

@tf.function
def vectorized_function(x):
  result = tf.reduce_mean(x, axis=0)
  return tf.reduce_sum(result)


print(function_with_for(x))
print(vectorized_function(x))

%timeit function_with_for(x)
%timeit vectorized_function(x)
tf.Tensor(0.672811, shape=(), dtype=float32)
tf.Tensor(0.67281103, shape=(), dtype=float32)
1.58 ms ± 177 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
440 µs ± 8.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

کد با عملیات TensorFlow بسیار سریعتر است.

از ارجاع به متغیرهای جهانی خودداری کنید

کد زیر را در نظر بگیرید:

x = tf.Variable(2, dtype=tf.dtypes.float32)
y = 2

@tf.function
def power(x):
  return tf.pow(x,y)

print(power(x))

y = 3

print(power(x))
tf.Tensor(4.0, shape=(), dtype=float32)
tf.Tensor(4.0, shape=(), dtype=float32)

اولین بار عملکرد تزئین شده است power() فراخوانی شد، مقدار خروجی 4 مورد انتظار بود. اما، بار دوم، تابع نادیده گرفت که مقدار y تغییر کرد. این به این دلیل است که مقدار متغیرهای جهانی پایتون برای تابع پس از ردیابی ثابت می شود.

یک راه بهتر استفاده از آن خواهد بود tf.Variable() برای همه متغیرهای خود و هر دو را به عنوان آرگومان به تابع خود ارسال کنید.

x = tf.Variable(2, dtype=tf.dtypes.float32)
y = tf.Variable(2, dtype = tf.dtypes.float32)

@tf.function
def power(x,y):
  return tf.pow(x,y)

print(power(x,y))

y.assign(3)

print(power(x,y))
tf.Tensor(4.0, shape=(), dtype=float32)
tf.Tensor(8.0, shape=(), dtype=float32)

اشکال زدایی [ایمیل محافظت شده]_s

به طور کلی، شما می خواهید عملکرد خود را در حالت مشتاق اشکال زدایی کنید و سپس آنها را با آن تزئین کنید @tf.function پس از اینکه کد شما به درستی اجرا شد زیرا پیام های خطا در حالت مشتاق آموزنده تر هستند.

برخی از مشکلات رایج عبارتند از خطاهای نوع و اشکال. خطاهای نوع زمانی اتفاق می افتد که در نوع متغیرهای درگیر در یک عملیات عدم تطابق وجود داشته باشد:

x = tf.Variable(1, dtype = tf.dtypes.float32)
y = tf.Variable(1, dtype = tf.dtypes.int32)

z = tf.add(x,y)
InvalidArgumentError: cannot compute AddV2 as input #1(zero-based) was expected to be a float tensor but is a int32 tensor [Op:AddV2]

خطاهای نوع به راحتی وارد می شوند و به راحتی می توان با فرستادن یک متغیر به نوع دیگری آن را برطرف کرد:

y = tf.cast(y, tf.dtypes.float32)
z = tf.add(x, y) 
tf.print(z) 

خطاهای شکل زمانی اتفاق می‌افتند که تانسورهای شما شکل مورد نیاز عملیات شما را نداشته باشند:

x = tf.random.uniform(shape=[100, 100], minval=-1, maxval=1, dtype=tf.dtypes.float32)
y = tf.random.uniform(shape=[1, 100], minval=-1, maxval=1, dtype=tf.dtypes.float32)

z = tf.matmul(x,y)
InvalidArgumentError: Matrix size-incompatible: In[0]: [100,100], In[1]: [1,100] [Op:MatMul]

یکی از ابزارهای مناسب برای رفع هر دو نوع خطا، دیباگر تعاملی پایتون است که می‌توانید به‌طور خودکار در نوت‌بوک Jupyter با استفاده از آن تماس بگیرید. %pdb. با استفاده از آن، می توانید تابع خود را کدنویسی کرده و آن را از طریق برخی موارد استفاده رایج اجرا کنید. اگر خطایی وجود داشته باشد، یک اعلان تعاملی باز می شود. این اعلان به شما اجازه می دهد تا لایه های انتزاعی را در کد خود بالا و پایین کنید و مقادیر، انواع و اشکال متغیرهای TensorFlow خود را بررسی کنید.

نتیجه

ما دیدیم که تنسورفلو چگونه است tf.function() عملکرد شما را کارآمدتر می کند و چگونه @tf.function دکوراتور عملکرد را به خودتان اعمال می کند.

این افزایش سرعت در عملکردهایی که بارها فراخوانی می شوند، مانند مراحل آموزش سفارشی برای مدل های یادگیری ماشین مفید است.

تمبر زمان:

بیشتر از Stackabuse