ग्रेडिएंट और स्वचालित भेदभाव का परिचय

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 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 या उसके उपवर्गों ( 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']
प्लेसहोल्डर17

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
प्लेसहोल्डर22

इंटरमीडिएट परिणाम

आप 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.gradient पद्धति को कहा जाता है, GradientTape द्वारा रखे गए संसाधन जारी हो जाते हैं। एक ही गणना पर कई ग्रेडिएंट की गणना करने के लिए, एक ग्रेडिएंट टेप बनाएं जिसमें 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.]
प्लेसहोल्डर26 l10n-प्लेसहोल्डर
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
प्लेसहोल्डर33

इससे हानियों के संग्रह के योग का ग्रेडिएंट या तत्व-वार हानि गणना के योग का ग्रेडिएंट लेना आसान हो जाता है।

यदि आपको प्रत्येक आइटम के लिए एक अलग ग्रेडिएंट की आवश्यकता है, तो जैकोबियन देखें।

कुछ मामलों में आप जैकोबियन को छोड़ सकते हैं। तत्व-वार गणना के लिए, योग का ढाल प्रत्येक तत्व के व्युत्पन्न को उसके इनपुट-तत्व के संबंध में देता है, क्योंकि प्रत्येक तत्व स्वतंत्र है:

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)
34 एल10एन-प्लेसहोल्डर
plt.plot(x, y, label='y')
plt.plot(x, dy_dx, label='dy/dx')
plt.legend()
_ = plt.xlabel('x')

पीएनजी

बहाव को काबू करें

चूंकि एक ग्रेडिएंट टेप संचालन को रिकॉर्ड करता है क्योंकि उन्हें निष्पादित किया जाता है, पायथन नियंत्रण प्रवाह स्वाभाविक रूप से नियंत्रित किया जाता है (उदाहरण के लिए, 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.Tensor

tf.Tensor को अपडेट करने के लिए Variable.assign का उपयोग करने के बजाय अनजाने में एक tf.Variable को 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 स्थिर या चर बनाना आसान है।

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 iterators और 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 का उपयोग करके) या अन्य ऑप्स का उपयोग करके फ़ंक्शन को फिर से लागू करना होगा।

शून्य के बजाय शून्य

कुछ मामलों में असंबद्ध ग्रेडिएंट के लिए None के बजाय 0 प्राप्त करना सुविधाजनक होगा। आप तय कर सकते हैं कि 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)