فهم مصمم TensorFlow @ tf.function

المُقدّمة

يمكن أن يؤدي تحسين أداء حلقة التدريب إلى توفير ساعات من وقت الحوسبة عند تدريب نماذج التعلم الآلي. تتمثل إحدى طرق تحسين أداء كود TensorFlow في استخدام امتداد tf.function() decorator - تغيير بسيط من سطر واحد يمكن أن يجعل وظائفك تعمل بشكل أسرع.

في هذا الدليل المختصر ، سنشرح كيف tf.function() يحسن الأداء وإلقاء نظرة على بعض أفضل الممارسات.

بايثون ديكورات و وظيفة tf ()

في لغة بايثون ، المصمم هو وظيفة تعدل سلوك الوظائف الأخرى. على سبيل المثال ، افترض أنك تستدعي الوظيفة التالية في خلية دفتر ملاحظات:

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)

ومع ذلك ، إذا مررنا الوظيفة المكلفة إلى ملف 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() ؟

كيف يمكننا جعل وظائف معينة تعمل 2-3x بشكل أسرع؟

يمكن تشغيل كود TensorFlow في وضعين: الوضع الحريص و وضع الرسم البياني. الوضع الحماسي هو الطريقة القياسية والتفاعلية لتشغيل التعليمات البرمجية: في كل مرة تستدعي فيها وظيفة ، يتم تنفيذها.

ومع ذلك ، فإن وضع الرسم البياني مختلف قليلاً. في وضع الرسم البياني ، قبل تنفيذ الوظيفة ، ينشئ TensorFlow رسمًا بيانيًا حسابيًا ، وهو عبارة عن بنية بيانات تحتوي على العمليات المطلوبة لتنفيذ الوظيفة. يسمح الرسم البياني الحسابي لـ TensorFlow بتبسيط العمليات الحسابية وإيجاد فرص للتوازي. يعزل الرسم البياني أيضًا الوظيفة عن كود Python العلوي ، مما يسمح بتشغيلها بكفاءة على العديد من الأجهزة المختلفة.

وظيفة مزينة بـ @tf.function يتم تنفيذه على خطوتين:

  1. في الخطوة الأولى ، ينفذ TensorFlow كود Python للوظيفة ويجمع رسمًا بيانيًا حسابيًا ، مما يؤخر تنفيذ أي عملية TensorFlow.
  2. بعد ذلك ، يتم تشغيل الرسم البياني للحساب.

ملحوظة: تُعرف الخطوة الأولى بـ "اقتفاء أثر".

سيتم تخطي الخطوة الأولى إذا لم تكن هناك حاجة لإنشاء رسم بياني حسابي جديد. يعمل هذا على تحسين أداء الوظيفة ولكنه يعني أيضًا أن الوظيفة لن يتم تنفيذها مثل كود Python العادي (حيث يتم تنفيذ كل سطر قابل للتنفيذ). على سبيل المثال ، دعنا نعدل وظيفتنا السابقة:

@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!

print() يتم تنفيذه مرة واحدة فقط أثناء خطوة التتبع ، وهي عندما يتم تشغيل كود Python العادي. تقوم الاستدعاءات التالية للوظيفة بتنفيذ عمليات 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 ، مع أفضل الممارسات ، والمعايير المقبولة في الصناعة ، وورقة الغش المضمنة. توقف عن أوامر Googling Git وفي الواقع تعلم ذلك!

Prints every time!
Prints every time!

يتضمن TensorFlow ملفات tf.print() في الرسم البياني للحساب حيث إنها عملية TensorFlow - وليست دالة Python عادية.

: تحذير لا يتم تنفيذ جميع تعليمات Python البرمجية في كل استدعاء لوظيفة مزينة بها @tf.function. بعد التتبع ، يتم تشغيل العمليات من الرسم البياني الحسابي فقط ، مما يعني أنه يجب توخي بعض الحذر في الكود الخاص بنا.

أفضل الممارسات مع @tf.function

كتابة التعليمات البرمجية باستخدام عمليات TensorFlow

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

أفضل طريقة لاستخدامها 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]

إحدى الأدوات الملائمة لإصلاح كلا النوعين من الأخطاء هي مصحح أخطاء Python التفاعلي ، والذي يمكنك الاتصال به تلقائيًا في دفتر ملاحظات Jupyter باستخدام %pdb. باستخدام ذلك ، يمكنك ترميز وظيفتك وتشغيلها من خلال بعض حالات الاستخدام الشائعة. إذا كان هناك خطأ ، يتم فتح موجه تفاعلي. تسمح لك هذه المطالبة بالانتقال إلى أعلى وأسفل طبقات التجريد في التعليمات البرمجية الخاصة بك والتحقق من قيم وأنواع وأشكال متغيرات TensorFlow.

وفي الختام

لقد رأينا كيف تعمل TensorFlow tf.function() يجعل وظيفتك أكثر كفاءة ، وكيف أن @tf.function يقوم المصمم بتطبيق الوظيفة على خاصتك.

هذا التسريع مفيد في الوظائف التي سيتم استدعاؤها عدة مرات ، مثل خطوات التدريب المخصصة لنماذج التعلم الآلي.

الطابع الزمني:

اكثر من ستاكابوز