لدي سؤال؟ تواصل مع المجتمع في منتدى زيارة منتدى TensorFlow

مقدمة في التدرجات والتمايز التلقائي

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

التفاضل التلقائي والتدرجات

التمايز التلقائي مفيد لتنفيذ خوارزميات التعلم الآلي مثل backpropagation لتدريب الشبكات العصبية.

في هذا الدليل ، سوف تستكشف طرقًا لحساب التدرجات باستخدام TensorFlow ، خاصةً في التنفيذ الحثيث.

يثبت

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

تدرجات الحوسبة

للتمييز تلقائيًا ، يحتاج TensorFlow إلى تذكر العمليات التي تحدث بأي ترتيب أثناء التمرير الأمامي . ثم ، أثناء التمرير الخلفي ، يجتاز TensorFlow قائمة العمليات هذه بترتيب عكسي لحساب التدرجات اللونية.

أشرطة متدرجة

يوفرtf.GradientTape واجهة برمجة تطبيقاتtf.GradientTape التلقائي ؛ أي حساب التدرج tf.Variable حسابية فيما يتعلق ببعض المدخلات ، عادةً tf.Variable s. يسجل TensorFlow العمليات ذات الصلة المنفذة داخل سياقtf.GradientTape على "شريط". ثم يستخدم TensorFlow هذا الشريط لحساب تدرجات الحساب "المسجل" باستخدام تمايز الوضع العكسي .

اليك مثال بسيط:

x = tf.Variable(3.0)

with tf.GradientTape() as tape:
  y = x**2

بمجرد تسجيل بعض العمليات ، استخدم GradientTape.gradient(target, sources) لحساب التدرج GradientTape.gradient(target, sources) لبعض الأهداف (غالبًا ما تكون خسارة) بالنسبة إلى مصدر ما (غالبًا متغيرات النموذج):

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
6.0

يستخدم المثال أعلاهtf.GradientTape ، لكنtf.GradientTape يعمل بسهولة على أي موتر:

w = tf.Variable(tf.random.normal((3, 2)), name='w')
b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')
x = [[1., 2., 3.]]

with tf.GradientTape(persistent=True) as tape:
  y = x @ w + b
  loss = tf.reduce_mean(y**2)

للحصول على تدرج loss بالنسبة لكلا المتغيرين ، يمكنك تمرير كلاهما كمصادر لطريقة gradient . الشريط مرن حول كيفية تمرير المصادر وسيقبل أي مجموعة متداخلة من القوائم أو القواميس tf.nest التدرج منظمًا بنفس الطريقة (انظر tf.nest ).

[dl_dw, dl_db] = tape.gradient(loss, [w, b])

التدرج بالنسبة لكل مصدر له شكل المصدر:

print(w.shape)
print(dl_dw.shape)
(3, 2)
(3, 2)

ها هو حساب التدرج مرة أخرى ، وهذه المرة تمرير قاموس للمتغيرات:

my_vars = {
    'w': w,
    'b': b
}

grad = tape.gradient(loss, my_vars)
grad['b']
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([ 2.5342524, -3.8607523], dtype=float32)>

التدرجات فيما يتعلق بالنموذج

من الشائع تجميع tf.Variables في وحدة tf.Module أو إحدى layers.Layer الفرعية ( layers.Layer ، keras.Model ، keras.Model ) من أجل نقاط التفتيش والتصدير .

في معظم الحالات ، ستحتاج إلى حساب التدرجات فيما يتعلق بمتغيرات النموذج القابلة للتدريب. نظرًا لأن جميع الفئات الفرعية لـ tf.Module تجمع متغيراتها في الخاصية Module.trainable_variables ، يمكنك حساب هذه التدرجات في بضعة أسطر من التعليمات البرمجية:

layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]])

with tf.GradientTape() as tape:
  # Forward pass
  y = layer(x)
  loss = tf.reduce_mean(y**2)

# Calculate gradients with respect to every trainable variable
grad = tape.gradient(loss, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad):
  print(f'{var.name}, shape: {g.shape}')
dense/kernel:0, shape: (3, 2)
dense/bias:0, shape: (2,)

السيطرة على ما يشاهده الشريط

السلوك الافتراضي هو تسجيل جميع العمليات بعد الوصول إلى tf.Variable للتدريب. أسباب ذلك هي:

  • يحتاج الشريط إلى معرفة العمليات التي يجب تسجيلها في التمرير الأمامي لحساب التدرجات اللونية في التمرير الخلفي.
  • يحتوي الشريط على مراجع لمخرجات وسيطة ، لذلك لا تريد تسجيل العمليات غير الضرورية.
  • تتضمن حالة الاستخدام الأكثر شيوعًا حساب التدرج اللوني للخسارة فيما يتعلق بجميع متغيرات النموذج القابلة للتدريب.

على سبيل المثال ، يفشل ما يلي في حساب التدرج tf.Tensor لأن tf.Tensor غير "مراقب" افتراضيًا ، tf.Variable غير قابل للتدريب:

# A trainable variable
x0 = tf.Variable(3.0, name='x0')
# Not trainable
x1 = tf.Variable(3.0, name='x1', trainable=False)
# Not a Variable: A variable + tensor returns a tensor.
x2 = tf.Variable(2.0, name='x2') + 1.0
# Not a variable
x3 = tf.constant(3.0, name='x3')

with tf.GradientTape() as tape:
  y = (x0**2) + (x1**2) + (x2**2)

grad = tape.gradient(y, [x0, x1, x2, x3])

for g in grad:
  print(g)
tf.Tensor(6.0, shape=(), dtype=float32)
None
None
None

يمكنك سرد المتغيرات التي يراقبها الشريط باستخدام طريقة GradientTape.watched_variables :

[var.name for var in tape.watched_variables()]
['x0:0']

tf.GradientTape يوفر خطافات تمنح المستخدم التحكم فيما يتم مشاهدته أو لا يتم مشاهدته.

لتسجيل التدرجات بالنسبة إلى tf.Tensor ، تحتاج إلى استدعاء GradientTape.watch(x) :

x = tf.constant(3.0)
with tf.GradientTape() as tape:
  tape.watch(x)
  y = x**2

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
print(dy_dx.numpy())
6.0

على العكس من ذلك ، لتعطيل السلوك الافتراضي لمشاهدة جميع tf.Variables ، قم بتعيين watch_accessed_variables=False عند إنشاء شريط التدرج. يستخدم هذا الحساب متغيرين ، ولكنه يربط فقط التدرج اللوني لأحد المتغيرات:

x0 = tf.Variable(0.0)
x1 = tf.Variable(10.0)

with tf.GradientTape(watch_accessed_variables=False) as tape:
  tape.watch(x1)
  y0 = tf.math.sin(x0)
  y1 = tf.nn.softplus(x1)
  y = y0 + y1
  ys = tf.reduce_sum(y)

نظرًا لعدم استدعاء GradientTape.watch على x0 ، لم يتم حساب أي تدرج فيما يتعلق به:

# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1)
grad = tape.gradient(ys, {'x0': x0, 'x1': x1})

print('dy/dx0:', grad['x0'])
print('dy/dx1:', grad['x1'].numpy())
dy/dx0: None
dy/dx1: 0.9999546

نتائج متوسطة

يمكنك أيضًا طلب تدرجات المخرجات فيما يتعلق بالقيم الوسيطة المحسوبة داخل سياقtf.GradientTape .

x = tf.constant(3.0)

with tf.GradientTape() as tape:
  tape.watch(x)
  y = x * x
  z = y * y

# Use the tape to compute the gradient of z with respect to the
# intermediate value y.
# dz_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())
18.0

بشكل افتراضي ، يتم تحرير الموارد التي يحتفظ بها شريط GradientTape بمجرد استدعاء طريقة GradientTape.gradient . لحساب تدرجات متعددة على نفس الحساب ، قم بإنشاء شريط متدرج مع persistent=True . يسمح هذا باستدعاءات متعددة لطريقة gradient حيث يتم تحرير الموارد عندما يتم جمع كائن الشريط غير المرغوب فيه. على سبيل المثال:

x = tf.constant([1, 3.0])
with tf.GradientTape(persistent=True) as tape:
  tape.watch(x)
  y = x * x
  z = y * y

print(tape.gradient(z, x).numpy())  # 108.0 (4 * x**3 at x = 3)
print(tape.gradient(y, x).numpy())  # 6.0 (2 * x)
[  4. 108.]
[2. 6.]
del tape   # Drop the reference to the tape

ملاحظات على الأداء

  • هناك حمل صغير مرتبط بإجراء العمليات داخل سياق شريط التدرج. لن تكون هذه تكلفة ملحوظة في معظم عمليات التنفيذ الحثيثة ، ولكن لا يزال يتعين عليك استخدام سياق الشريط حول المناطق التي تكون مطلوبة فيها فقط.

  • تستخدم أشرطة التدرج الذاكرة لتخزين النتائج الوسيطة ، بما في ذلك المدخلات والمخرجات ، لاستخدامها أثناء التمرير للخلف.

    من أجل الكفاءة ، لا تحتاج بعض العمليات (مثل ReLU ) إلى الاحتفاظ بنتائجها الوسيطة ويتم تقليمها أثناء التمريرة الأمامية. ومع ذلك ، إذا كنت تستخدم persistent=True على الشريط الخاص بك ، فلن يتم تجاهل أي شيء وسيكون استخدامك للذاكرة أعلى.

تدرجات الأهداف غير العددية

التدرج اللوني هو في الأساس عملية على سلمي.

x = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
  y0 = x**2
  y1 = 1 / x

print(tape.gradient(y0, x).numpy())
print(tape.gradient(y1, x).numpy())
4.0
-0.25

وبالتالي ، إذا طلبت التدرج اللوني لأهداف متعددة ، فإن النتيجة لكل مصدر هي:

  • التدرج اللوني لمجموع الأهداف ، أو ما يعادله
  • مجموع التدرجات لكل هدف.
x = tf.Variable(2.0)
with tf.GradientTape() as tape:
  y0 = x**2
  y1 = 1 / x

print(tape.gradient({'y0': y0, 'y1': y1}, x).numpy())
3.75

وبالمثل ، إذا لم يكن الهدف (الأهداف) عدديًا ، فسيتم حساب تدرج المجموع:

x = tf.Variable(2.)

with tf.GradientTape() as tape:
  y = x * [3., 4.]

print(tape.gradient(y, x).numpy())
7.0

هذا يجعل من السهل أخذ التدرج اللوني لمجموع مجموعة الخسائر ، أو التدرج اللوني لمجموع حساب الخسارة من حيث العنصر.

إذا كنت بحاجة إلى تدرج لوني منفصل لكل عنصر ، فارجع إلى اليعاقبة .

في بعض الحالات يمكنك تخطي اليعقوبي. لحساب العناصر ، يعطي التدرج اللوني المجموع مشتقًا لكل عنصر فيما يتعلق بعنصر الإدخال ، لأن كل عنصر مستقل:

x = tf.linspace(-10.0, 10.0, 200+1)

with tf.GradientTape() as tape:
  tape.watch(x)
  y = tf.nn.sigmoid(x)

dy_dx = tape.gradient(y, x)
plt.plot(x, y, label='y')
plt.plot(x, dy_dx, label='dy/dx')
plt.legend()
_ = plt.xlabel('x')

بي إن جي

تدفق التحكم

نظرًا لأن شريط التدرج يسجل العمليات أثناء تنفيذها ، يتم التعامل مع تدفق التحكم في Python بشكل طبيعي (على سبيل المثال ، عبارات if and while )

هنا يتم استخدام متغير مختلف في كل فرع من فروع if . لا يتصل التدرج اللوني إلا بالمتغير الذي تم استخدامه:

x = tf.constant(1.0)

v0 = tf.Variable(2.0)
v1 = tf.Variable(2.0)

with tf.GradientTape(persistent=True) as tape:
  tape.watch(x)
  if x > 0.0:
    result = v0
  else:
    result = v1**2 

dv0, dv1 = tape.gradient(result, [v0, v1])

print(dv0)
print(dv1)
tf.Tensor(1.0, shape=(), dtype=float32)
None

فقط تذكر أن عبارات التحكم نفسها غير قابلة للتفاضل ، لذا فهي غير مرئية لمحسّني التحسين المستند إلى التدرج.

اعتمادًا على قيمة x في المثال أعلاه ، إما أن يسجل الشريط result = v0 أو result = v1**2 . التدرج فيما يتعلق x هو دائما None .

dx = tape.gradient(result, x)

print(dx)
None

الحصول على تدرج None

عندما لا يكون الهدف متصلاً بمصدر ، ستحصل على تدرج None .

x = tf.Variable(2.)
y = tf.Variable(3.)

with tf.GradientTape() as tape:
  z = y * y
print(tape.gradient(z, x))
None

من الواضح أن z هنا غير متصل بـ x ، ولكن هناك عدة طرق أقل وضوحًا يمكن من خلالها فصل التدرج اللوني.

1. استبدال متغير بموتر

في قسم "التحكم في ما tf.Variable الشريط" رأيت أن الشريط tf.Variable تلقائيًا tf.Variable ولكن ليس tf.Tensor .

أحد الأخطاء الشائعة هو استبدال tf.Variable عن غير قصد بـ tf.Tensor ، بدلاً من استخدام Variable.assign لتحديث Variable.assign tf.Variable . هنا مثال:

x = tf.Variable(2.0)

for epoch in range(2):
  with tf.GradientTape() as tape:
    y = x+1

  print(type(x).__name__, ":", tape.gradient(y, x))
  x = x + 1   # This should be `x.assign_add(1)`
ResourceVariable : tf.Tensor(1.0, shape=(), dtype=float32)
EagerTensor : None

2. إجراء حسابات خارج TensorFlow

لا يمكن للشريط تسجيل مسار التدرج إذا خرج الحساب من TensorFlow. على سبيل المثال:

x = tf.Variable([[1.0, 2.0],
                 [3.0, 4.0]], dtype=tf.float32)

with tf.GradientTape() as tape:
  x2 = x**2

  # This step is calculated with NumPy
  y = np.mean(x2, axis=0)

  # Like most ops, reduce_mean will cast the NumPy array to a constant tensor
  # using `tf.convert_to_tensor`.
  y = tf.reduce_mean(y, axis=0)

print(tape.gradient(y, x))
None

3. أخذ التدرجات من خلال عدد صحيح أو سلسلة

لا يمكن التمييز بين الأعداد الصحيحة والسلاسل. إذا كان مسار الحساب يستخدم أنواع البيانات هذه ، فلن يكون هناك تدرج.

لا يتوقع أحد أن تكون السلاسل قابلة dtype ، ولكن من السهل إنشاء ثابت أو متغير int عن طريق الخطأ إذا لم تحدد نوع dtype .

x = tf.constant(10)

with tf.GradientTape() as g:
  g.watch(x)
  y = x * x

print(g.gradient(y, x))
WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32
WARNING:tensorflow:The dtype of the target tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
None

لا يتم إرسال TensorFlow تلقائيًا بين الأنواع ، لذلك ، من الناحية العملية ، ستحصل غالبًا على خطأ في النوع بدلاً من التدرج اللوني المفقود.

4. أخذ التدرجات من خلال كائن ذي حالة

الدولة توقف التدرجات. عندما تقرأ من كائن ذي حالة ، يمكن للشريط ملاحظة الحالة الحالية فقط ، وليس التاريخ الذي يؤدي إليها.

tf.Tensor غير قابل للتغيير. لا يمكنك تغيير موتر بمجرد إنشائه. لها قيمة ، ولكن ليس لها دولة . جميع العمليات التي تمت مناقشتها حتى الآن هي أيضًا بدون حالة: إخراج tf.matmul يعتمد فقط على مدخلاته.

tf.Variable له حالة داخلية - قيمته. عند استخدام المتغير ، تتم قراءة الحالة. من الطبيعي حساب التدرج اللوني فيما يتعلق بالمتغير ، لكن حالة المتغير تمنع حسابات التدرج من العودة بعيدًا. على سبيل المثال:

x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)

with tf.GradientTape() as tape:
  # Update x1 = x1 + x0.
  x1.assign_add(x0)
  # The tape starts recording from x1.
  y = x1**2   # y = (x1 + x0)**2

# This doesn't work.
print(tape.gradient(y, x0))   #dy/dx0 = 2*(x1 + x0)
None

وبالمثل ، فإن مكرراتtf.data.Dataset و tf.queue s ذات tf.queue ، tf.queue كل التدرجات على الموترات التي تمر من خلالها.

لم يتم تسجيل التدرج

بعض tf.Operation يتم تسجيل الصورة على أنها غير تفاضل وستعود None . آخرون ليس لديهم تدرج مسجل .

tf.raw_ops صفحة tf.raw_ops العمليات ذات المستوى المنخفض التي تم تسجيل التدرجات اللونية لها.

إذا حاولت أن تأخذ التدرج من خلال المرجع تعويم التي لا يوجد لديه التدرج مسجل سوف الشريط رمي خطأ بدلا من العودة بصمت None . بهذه الطريقة تعرف أن شيئًا ما قد حدث خطأ.

على سبيل المثال ، tf.image.adjust_contrast الدالة raw_ops.AdjustContrastv2 ، والتي يمكن أن تحتوي على تدرج ولكن لم يتم تنفيذ التدرج:

image = tf.Variable([[[0.5, 0.0, 0.0]]])
delta = tf.Variable(0.1)

with tf.GradientTape() as tape:
  new_image = tf.image.adjust_contrast(image, delta)

try:
  print(tape.gradient(new_image, [image, delta]))
  assert False   # This should not happen.
except LookupError as e:
  print(f'{type(e).__name__}: {e}')
LookupError: gradient registry has no entry for: AdjustContrastv2

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

أصفار بدلاً من لا شيء

في بعض الحالات ، قد يكون من الملائم الحصول على 0 بدلاً من None للتدرجات غير المتصلة. يمكنك تحديد ما تريد إرجاعه عندما يكون لديك تدرجات غير متصلة باستخدام وسيطة unconnected_gradients :

x = tf.Variable([2., 2.])
y = tf.Variable(3.)

with tf.GradientTape() as tape:
  z = y**2
print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))
tf.Tensor([0. 0.], shape=(2,), dtype=float32)