عرض على 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
s. TensorFlow "يسجل" العمليات ذات الصلة المنفذة داخل سياق tf.GradientTape
على "شريط". ثم يستخدم TensorFlow هذا الشريط لحساب تدرجات الحساب "المسجل" باستخدام تمايز الوضع العكسي .
اليك مثال بسيط:
x = tf.Variable(3.0)
with tf.GradientTape() as tape:
y = x**2
بمجرد تسجيل بعض العمليات ، استخدم GradientTape.gradient(target, sources)
لحساب التدرج اللوني لبعض الأهداف (غالبًا ما تكون خسارة) بالنسبة إلى مصدر ما (غالبًا متغيرات النموذج):
# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
6.0
يستخدم المثال أعلاه الحجميات ، لكن 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
).
[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([-1.6920902, -3.2363236], dtype=float32)>
التدرجات بالنسبة للنموذج
من الشائع تجميع tf.Variables
في وحدة tf.Module
أو إحدى فئاتها الفرعية (طبقات ، طبقة ، keras.Model
layers.Layer
من أجل نقاط التفتيش والتصدير .
في معظم الحالات ، ستحتاج إلى حساب التدرجات فيما يتعلق بمتغيرات النموذج القابلة للتدريب. نظرًا لأن جميع الفئات الفرعية لـ 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.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()) # [4.0, 108.0] (4 * x**3 at x = [1.0, 3.0])
print(tape.gradient(y, x).numpy()) # [2.0, 6.0] (2 * x at x = [1.0, 3.0])
[ 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.Tensor
.
أحد الأخطاء الشائعة هو استبدال متغير tf.Tensor
tf.Variable
بدلاً من استخدام 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. أخذ التدرجات من خلال عدد صحيح أو سلسلة
الأعداد الصحيحة والسلاسل غير قابلة للتفاضل. إذا كان مسار الحساب يستخدم أنواع البيانات هذه ، فلن يكون هناك تدرج.
لا يتوقع أحد أن تكون السلاسل قابلة للتفاضل ، ولكن من السهل إنشاء ثابت أو متغير 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.Operation
على أنها غير قابلة للتفاضل وستعود None
. آخرون ليس لديهم تدرج مسجل .
تعرض صفحة 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
) أو إعادة تنفيذ الوظيفة باستخدام عمليات أخرى.
أصفار بدلاً من لا شيء
في بعض الحالات ، قد يكون من الملائم الحصول على 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)