ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

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

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

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

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

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

اقامة

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

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

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

أشرطة متدرجة

يوفر TensorFlow واجهة برمجة تطبيقات 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)

للحصول على تدرج y بالنسبة إلى كلا المتغيرين ، يمكنك تمرير كلاهما كمصادر لطريقة 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': tf.Variable(tf.random.normal((3, 2)), name='w'),
    'b': tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')
}

grad = tape.gradient(loss, my_vars)
grad['b']

التدرجات بالنسبة للنموذج

انها مشتركة لجمع tf.Variables إلى tf.Module أو واحدة من الفئات الفرعية ( layers.Layer ، 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 عند إنشاء شريط التدرج. يستخدم هذا الحساب متغيرين ، لكنه يربط فقط التدرج اللوني لأحد المتغيرات:

678533 ميت

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

# dy = 2x * dx
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_dx = 2 * y, where y = x ** 2
print(tape.gradient(z, y).numpy())
18.0

بشكل افتراضي ، يتم تحرير الموارد التي يحتفظ بها شريط GradientTape بمجرد استدعاء طريقة GradientTape.gradient() . لحساب تدرجات متعددة على نفس الحساب ، قم بإنشاء شريط متدرج persistent . يسمح هذا باستدعاءات متعددة إلى طريقة 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 s while s على سبيل المثال) يتم التعامل معه بشكل طبيعي.

هنا يتم استخدام متغير مختلف في كل فرع من فروع 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 .

# The x0 variable has an `int` dtype.
x = tf.Variable([[2, 2],
                 [2, 2]])

with tf.GradientTape() as tape:
  # The path to x1 is blocked by the `int` dtype here.
  y = tf.cast(x, tf.float32)
  y = tf.reduce_sum(x)

print(tape.gradient(y, x))
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 + x2)
None

وبالمثل tf.data.Dataset iterators و tf.queue s ذات tf.queue ، tf.queue جميع التدرجات على الموترات التي تمر عبرها.

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

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

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

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

على سبيل المثال ، tf.image.adjust_contrast الدالة 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)