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