לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

מבוא לשיפועים ובידול אוטומטי

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

בידול אוטומטי והדרגות

בידול אוטומטי שימושי להטמעת אלגוריתמים של למידת מכונה כמו למשל התאמת גב לאימון רשתות עצביות.

במדריך זה תחקרו דרכים לחישוב שיפועים עם TensorFlow, במיוחד בביצוע נלהב .

להכין

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

שיפועי מחשוב

כדי להבדיל באופן אוטומטי, TensorFlow צריך לזכור אילו פעולות מתרחשות באיזה סדר במהלך המעבר קדימה . ואז, במהלך המעבר לאחור , TensorFlow חוצה את רשימת הפעולות בסדר הפוך כדי לחשב שיפועים.

קלטות שיפוע

TensorFlow מספק את ה- API של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) כדי לחשב את השיפוע של יעד כלשהו (לרוב הפסד) ביחס למקור כלשהו (לרוב משתני המודל):

# 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([-5.887418, -1.753759], dtype=float32)>

שיפועים ביחס למודל

זה מקובל לאסוף tf.Variables ל- tf.Module או לאחד layers.Layer המשנה שלו ( layers.Layer keras.Model , keras.Model ) לצורך מחסום וייצוא .

ברוב המקרים, תרצה לחשב שיפועים ביחס למשתנים הניתנים להכשרה של המודל. מכיוון שכל קבוצות המשנה של tf.Module מצטברים את המשתנים שלהם במאפיין Module.trainable_variables , אתה יכול לחשב את 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.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_dx = 2 * y, where y = x ** 2
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')

png

בקרת זרימה

מכיוון שקלטת שיפוע מתעדת פעולות תוך כדי ביצוע, מטפלים באופן טבעי בזרימת בקרת פייתון (למשל if 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.Variable ב- tf.Tensor , במקום להשתמש ב- 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.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 ו- tf.queue הן בעלות מצב, והן יפסיקו את כל הדרגתיות על הטנזורים שעוברים דרכן.

לא נרשם שיפוע

חלק tf.Operation הים רשום כמי שאינו גזיר ויחזור None . לאחרים אין רישום שיפוע .

הדף tf.raw_ops מראה אילו אופים ברמה נמוכה רשומים שיפועים.

אם אתה מנסה לקחת מעבר צבע באמצעות float op שלא נרשם שיפוע, הקלטת תזרוק שגיאה במקום להחזיר בשקט 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)