روز جامعه ML 9 نوامبر است! برای به روز رسانی از TensorFlow، JAX به ما بپیوندید، و بیشتر بیشتر بدانید

آشنایی با شیب و تمایز خودکار

مشاهده در TensorFlow.org در Google Colab اجرا کنید مشاهده منبع در GitHub دانلود دفترچه یادداشت

تمایز خودکار و گرادیان ها

تمایز خودکار برای اجرای ماشین الگوریتم های یادگیری مانند مفید است پس انتشار برای آموزش شبکه های عصبی.

در این راهنما، شما راه برای محاسبه شیب با TensorFlow، به ویژه در کشف اعدام مشتاق .

برپایی

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

شیب محاسبه

برای تمایز به طور خودکار، TensorFlow نیاز به یاد داشته باشید که چه عملیاتی به چه منظور در طول پاس رو به جلو است. سپس، در طول پاس رو به عقب، TensorFlow عبور این لیست از عملیات را در جهت معکوس به شیب محاسبه.

نوارهای گرادیان

TensorFlow فراهم می کند tf.GradientTape API برای تمایز به صورت خودکار. این است که، محاسبه گرادیان یک محاسبه با توجه به برخی از ورودی ها، معمولا tf.Variable است. 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 و یا یکی از زیر کلاس خود ( 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.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

نکاتی در مورد عملکرد

  • یک سربار کوچک وجود دارد که مربوط به انجام عملیات در داخل نوار گرادیان است. برای اجرای مشتاقانه ، این هزینه قابل توجهی نخواهد بود ، اما شما هنوز هم باید از نوار چسب در اطراف مناطقی که فقط مورد نیاز است استفاده کنید.

  • نوارهای گرادیان از حافظه برای ذخیره نتایج میانی ، از جمله ورودی و خروجی ، برای استفاده در هنگام عبور عقب استفاده می کنند.

    برای بهره وری، برخی از OP (مانند 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

این امر باعث می شود که گرادیان مجموع مجموع زیان ها یا گرادیان مجموع محاسبه ضرر به صورت عنصری محاسبه شود.

اگر شما نیاز به یک شیب جداگانه برای هر آیتم، برای اشاره Jacobians .

در برخی موارد می توانید از Jacobian صرف نظر کنید. برای محاسبه عنصری ، گرادیان مجموع مشتق هر عنصر را نسبت به عنصر ورودی آن می دهد ، زیرا هر عنصر مستقل است:

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.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 بازدید کنندگان stateful به هستند، و تمام شیب در tensors که از طریق آنها منتقل متوقف خواهد شد.

گرادیان ثبت نشده است

برخی 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)