सहायता Kaggle पर TensorFlow साथ ग्रेट बैरियर रीफ की रक्षा चैलेंज में शामिल हों

उन्नत स्वचालित भेदभाव

TensorFlow.org पर देखें Google Colab में चलाएं GitHub पर स्रोत देखें नोटबुक डाउनलोड करें

परिचय ढ़ाल और स्वत: भेदभाव करने के लिए गाइड TensorFlow में calculate ढ़ाल के लिए आवश्यक सब कुछ शामिल है। इस गाइड गहरी, के कम आम सुविधाओं पर केंद्रित है tf.GradientTape एपीआई।

सेट अप

import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.figsize'] = (8, 6)

ग्रेडिएंट रिकॉर्डिंग को नियंत्रित करना

में स्वत: भेदभाव गाइड आप कैसे जो चर और tensors टेप द्वारा देखा जाता है ढाल गणना के निर्माण, जबकि नियंत्रित करने के लिए देखा था।

टेप में रिकॉर्डिंग में हेरफेर करने के तरीके भी हैं।

रिकॉर्डिंग बंद करें

आप ढ़ाल रिकॉर्डिंग बंद करना चाहते हैं, तो आप उपयोग कर सकते हैं tf.GradientTape.stop_recording अस्थायी रूप से रिकॉर्डिंग को निलंबित करने का।

यदि आप अपने मॉडल के बीच में एक जटिल ऑपरेशन को अलग नहीं करना चाहते हैं तो यह ओवरहेड को कम करने के लिए उपयोगी हो सकता है। इसमें मीट्रिक या मध्यवर्ती परिणाम की गणना करना शामिल हो सकता है:

x = tf.Variable(2.0)
y = tf.Variable(3.0)

with tf.GradientTape() as t:
  x_sq = x * x
  with t.stop_recording():
    y_sq = y * y
  z = x_sq + y_sq

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

स्क्रैच से रिकॉर्डिंग रीसेट/शुरू करें

आप पूरी तरह से शुरू करना चाहते हैं, तो का उपयोग tf.GradientTape.reset । सीधे शब्दों में आमतौर पर पढ़ने में आसान ढाल टेप ब्लॉक से बाहर निकलने और पुन: प्रारंभ कर रहा है, लेकिन आप उपयोग कर सकते हैं reset जब बाहर निकलने टेप ब्लॉक मुश्किल या असंभव है विधि।

x = tf.Variable(2.0)
y = tf.Variable(3.0)
reset = True

with tf.GradientTape() as t:
  y_sq = y * y
  if reset:
    # Throw out all the tape recorded so far.
    t.reset()
  z = x * x + y_sq

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

परिशुद्धता के साथ ढाल प्रवाह रोकें

ऊपर दी गई वैश्विक टेप नियंत्रण के विपरीत, tf.stop_gradient समारोह और अधिक सटीक है। इसका उपयोग ग्रेडियेंट को किसी विशेष पथ के साथ बहने से रोकने के लिए किया जा सकता है, बिना टेप तक पहुंच की आवश्यकता के:

x = tf.Variable(2.0)
y = tf.Variable(3.0)

with tf.GradientTape() as t:
  y_sq = y**2
  z = x**2 + tf.stop_gradient(y_sq)

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

कस्टम ग्रेडिएंट

कुछ मामलों में, आप यह नियंत्रित करना चाह सकते हैं कि डिफ़ॉल्ट का उपयोग करने के बजाय ग्रेडिएंट की गणना कैसे की जाती है। इन स्थितियों में शामिल हैं:

  1. आपके द्वारा लिखे जा रहे नए ऑप के लिए कोई परिभाषित ग्रेडिएंट नहीं है।
  2. डिफ़ॉल्ट गणना संख्यात्मक रूप से अस्थिर है।
  3. आप फॉरवर्ड पास से एक महंगी गणना को कैश करना चाहते हैं।
  4. आप एक मूल्य (उदाहरण के लिए का उपयोग कर संशोधित करना चाहते हैं tf.clip_by_value या tf.math.round ढाल को संशोधित करने के बिना)।

पहले मामले के लिए, एक नया सेशन आप उपयोग कर सकते हैं लिखने के लिए tf.RegisterGradient अपने खुद के स्थापित करने के लिए (विवरण के लिए एपीआई दस्तावेज़ देखें)। (ध्यान दें कि ग्रेडिएंट रजिस्ट्री वैश्विक है, इसलिए इसे सावधानी के साथ बदलें।)

बाद के तीन मामलों के लिए, आप उपयोग कर सकते tf.custom_gradient

यहाँ लागू होने वाला एक उदाहरण है tf.clip_by_norm मध्यवर्ती ढाल रहे हैं:

# Establish an identity operation, but clip during the gradient pass.
@tf.custom_gradient
def clip_gradients(y):
  def backward(dy):
    return tf.clip_by_norm(dy, 0.5)
  return y, backward

v = tf.Variable(2.0)
with tf.GradientTape() as t:
  output = clip_gradients(v * v)
print(t.gradient(output, v))  # calls "backward", which clips 4 to 2
tf.Tensor(2.0, shape=(), dtype=float32)

का संदर्भ लें tf.custom_gradient अधिक जानकारी के लिए डेकोरेटर एपीआई डॉक्स।

सहेजे गए मॉडल में कस्टम ग्रेडिएंट

कस्टम ढ़ाल विकल्प का उपयोग करके SavedModel को बचाया जा सकता tf.saved_model.SaveOptions(experimental_custom_gradients=True)

SavedModel में बचाया जा करने के लिए, ढाल समारोह (अधिक जानने के लिए बाहर की जाँच मिल होना चाहिए tf.function के साथ बेहतर प्रदर्शन गाइड)।

class MyModule(tf.Module):

  @tf.function(input_signature=[tf.TensorSpec(None)])
  def call_custom_grad(self, x):
    return clip_gradients(x)

model = MyModule()
tf.saved_model.save(
    model,
    'saved_model',
    options=tf.saved_model.SaveOptions(experimental_custom_gradients=True))

# The loaded gradients will be the same as the above example.
v = tf.Variable(2.0)
loaded = tf.saved_model.load('saved_model')
with tf.GradientTape() as t:
  output = loaded.call_custom_grad(v * v)
print(t.gradient(output, v))
INFO:tensorflow:Assets written to: saved_model/assets
tf.Tensor(2.0, shape=(), dtype=float32)

ऊपर के उदाहरण के बारे में एक नोट: आप के साथ ऊपर कोड की जगह का प्रयास करें tf.saved_model.SaveOptions(experimental_custom_gradients=False) , ढाल अभी भी लोड हो रहा है पर एक ही परिणाम का उत्पादन करेगा। कारण यह है कि ढाल रजिस्ट्री अभी भी समारोह में इस्तेमाल कस्टम ढाल होता है call_custom_op । हालांकि, अगर आप कस्टम ढ़ाल के बिना बचत, के तहत भरी हुई मॉडल चलाने के बाद क्रम को पुनः आरंभ tf.GradientTape त्रुटि फेंक देगा: LookupError: No gradient defined for operation 'IdentityN' (op type: IdentityN)

एकाधिक टेप

एकाधिक टेप निर्बाध रूप से इंटरैक्ट करते हैं।

उदाहरण के लिए, यहां प्रत्येक टेप टेंसर का एक अलग सेट देखता है:

x0 = tf.constant(0.0)
x1 = tf.constant(0.0)

with tf.GradientTape() as tape0, tf.GradientTape() as tape1:
  tape0.watch(x0)
  tape1.watch(x1)

  y0 = tf.math.sin(x0)
  y1 = tf.nn.sigmoid(x1)

  y = y0 + y1

  ys = tf.reduce_sum(y)
tape0.gradient(ys, x0).numpy()   # cos(x) => 1.0
1.0
tape1.gradient(ys, x1).numpy()   # sigmoid(x1)*(1-sigmoid(x1)) => 0.25
0.25

उच्च-क्रम के ग्रेडिएंट

के अंदर संचालन tf.GradientTape संदर्भ प्रबंधक स्वचालित भेदभाव के लिए दर्ज हैं। यदि उस संदर्भ में ग्रेडिएंट की गणना की जाती है, तो ग्रेडिएंट गणना भी दर्ज की जाती है। नतीजतन, ठीक वही एपीआई उच्च-क्रम वाले ग्रेडिएंट के लिए भी काम करता है।

उदाहरण के लिए:

x = tf.Variable(1.0)  # Create a Tensorflow variable initialized to 1.0

with tf.GradientTape() as t2:
  with tf.GradientTape() as t1:
    y = x * x * x

  # Compute the gradient inside the outer `t2` context manager
  # which means the gradient computation is differentiable as well.
  dy_dx = t1.gradient(y, x)
d2y_dx2 = t2.gradient(dy_dx, x)

print('dy_dx:', dy_dx.numpy())  # 3 * x**2 => 3.0
print('d2y_dx2:', d2y_dx2.numpy())  # 6 * x => 6.0
dy_dx: 3.0
d2y_dx2: 6.0

जबकि यह है कि आप एक अदिश समारोह का दूसरा व्युत्पन्न देता है, इस पैटर्न, एक हेस्सियन मैट्रिक्स का उत्पादन करने के बाद से सामान्य नहीं है tf.GradientTape.gradient केवल एक अदिश की ढाल गणना करता है। एक का निर्माण करने के हेस्सियन मैट्रिक्स , के लिए जाना हेस्सियन उदाहरण के तहत Jacobian अनुभाग

"नेस्ट करने के लिए कॉल tf.GradientTape.gradient " एक अच्छा पैटर्न जब आप एक ढाल से एक अदिश गणना कर रहे हैं, और फिर जिसके परिणामस्वरूप अदिश, एक दूसरे ढाल गणना के लिए एक स्रोत के रूप में कार्य करता है निम्न उदाहरण में है।

उदाहरण: इनपुट ग्रेडिएंट नियमितीकरण

कई मॉडल "प्रतिकूल उदाहरण" के लिए अतिसंवेदनशील होते हैं। तकनीकों का यह संग्रह मॉडल के आउटपुट को भ्रमित करने के लिए मॉडल के इनपुट को संशोधित करता है। के रूप में सरल कार्यान्वयन-ऐसे विरोधात्मक उदाहरण फास्ट ढाल विधि हमले हस्ताक्षर का उपयोग कर इनपुट के संबंध में उत्पादन की ढाल के साथ एक ही चरण -takes; "इनपुट ग्रेडिएंट"।

एक तकनीक विरोधात्मक उदाहरण के लिए मजबूती बढ़ाने के लिए इनपुट ढाल नियमितीकरण (फिनले और Oberman, 2019) है, जो प्रयास इनपुट ढाल की भयावहता को कम करने। यदि इनपुट ग्रेडिएंट छोटा है, तो आउटपुट में परिवर्तन भी छोटा होना चाहिए।

नीचे इनपुट ग्रेडिएंट नियमितीकरण का एक सरल कार्यान्वयन है। कार्यान्वयन है:

  1. एक आंतरिक टेप का उपयोग करके इनपुट के संबंध में आउटपुट के ग्रेडिएंट की गणना करें।
  2. उस इनपुट ग्रेडिएंट के परिमाण की गणना करें।
  3. मॉडल के संबंध में उस परिमाण के ढाल की गणना करें।
x = tf.random.normal([7, 5])

layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)
with tf.GradientTape() as t2:
  # The inner tape only takes the gradient with respect to the input,
  # not the variables.
  with tf.GradientTape(watch_accessed_variables=False) as t1:
    t1.watch(x)
    y = layer(x)
    out = tf.reduce_sum(layer(x)**2)
  # 1. Calculate the input gradient.
  g1 = t1.gradient(out, x)
  # 2. Calculate the magnitude of the input gradient.
  g1_mag = tf.norm(g1)

# 3. Calculate the gradient of the magnitude with respect to the model.
dg1_mag = t2.gradient(g1_mag, layer.trainable_variables)
[var.shape for var in dg1_mag]
[TensorShape([5, 10]), TensorShape([10])]

जैकोबियन

पिछले सभी उदाहरणों ने कुछ स्रोत टेंसर (ओं) के संबंध में एक स्केलर लक्ष्य के ग्रेडियेंट को लिया।

मैट्रिक्स Jacobian एक वेक्टर महत्वपूर्ण समारोह के ढ़ाल प्रतिनिधित्व करता है। प्रत्येक पंक्ति में वेक्टर के तत्वों में से एक का ग्रेडिएंट होता है।

tf.GradientTape.jacobian विधि आप कुशलतापूर्वक एक मैट्रिक्स Jacobian गणना करने के लिए अनुमति देता है।

ध्यान दें कि:

  • जैसा gradient : sources तर्क एक टेन्सर या tensors के एक कंटेनर हो सकता है।
  • विपरीत gradient : target टेन्सर एक भी टेन्सर होना चाहिए।

अदिश स्रोत

पहले उदाहरण के रूप में, यहां एक स्केलर-स्रोत के संबंध में वेक्टर-लक्ष्य का जैकोबियन है।

x = tf.linspace(-10.0, 10.0, 200+1)
delta = tf.Variable(0.0)

with tf.GradientTape() as tape:
  y = tf.nn.sigmoid(x+delta)

dy_dx = tape.jacobian(y, delta)

परिणाम जब आप एक अदिश के संबंध में Jacobian ले लक्ष्य के आकार है, और स्रोत के संबंध में प्रत्येक तत्व की ढाल देता है:

print(y.shape)
print(dy_dx.shape)
(201,)
(201,)
plt.plot(x.numpy(), y, label='y')
plt.plot(x.numpy(), dy_dx, label='dy/dx')
plt.legend()
_ = plt.xlabel('x')

पीएनजी

टेंसर स्रोत

चाहे इनपुट अदिश या टेन्सर है, tf.GradientTape.jacobian कुशलतापूर्वक लक्ष्य (लक्ष्यों) के प्रत्येक तत्व के संबंध में स्रोत के प्रत्येक तत्व की ढाल गणना करता है।

उदाहरण के लिए, इस परत के उत्पादन का एक आकार है (10, 7) :

x = tf.random.normal([7, 5])
layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)

with tf.GradientTape(persistent=True) as tape:
  y = layer(x)

y.shape
TensorShape([7, 10])

और परत के कर्नेल के आकार है (5, 10) :

layer.kernel.shape
TensorShape([5, 10])

कर्नेल के संबंध में आउटपुट के जैकोबियन का आकार उन दो आकृतियों को एक साथ समेटे हुए है:

j = tape.jacobian(y, layer.kernel)
j.shape
TensorShape([7, 10, 5, 10])

आप लक्ष्य के आयामों पर योग हैं, तो आप योग है कि द्वारा गणना हो गया होता की ढाल के साथ छोड़ दिया रहे tf.GradientTape.gradient :

g = tape.gradient(y, layer.kernel)
print('g.shape:', g.shape)

j_sum = tf.reduce_sum(j, axis=[0, 1])
delta = tf.reduce_max(abs(g - j_sum)).numpy()
assert delta < 1e-3
print('delta:', delta)
g.shape: (5, 10)
delta: 2.3841858e-07

उदाहरण: हेसियन

जबकि tf.GradientTape एक के निर्माण के लिए एक स्पष्ट विधि नहीं देता हेस्सियन मैट्रिक्स इसे का उपयोग एक निर्माण करने के लिए संभव है tf.GradientTape.jacobian विधि।

x = tf.random.normal([7, 5])
layer1 = tf.keras.layers.Dense(8, activation=tf.nn.relu)
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.relu)

with tf.GradientTape() as t2:
  with tf.GradientTape() as t1:
    x = layer1(x)
    x = layer2(x)
    loss = tf.reduce_mean(x**2)

  g = t1.gradient(loss, layer1.kernel)

h = t2.jacobian(g, layer1.kernel)
print(f'layer.kernel.shape: {layer1.kernel.shape}')
print(f'h.shape: {h.shape}')
layer.kernel.shape: (5, 8)
h.shape: (5, 8, 5, 8)

एक के लिए इस हेस्सियन उपयोग करने के लिए न्यूटन की विधि कदम है, आप पहली बार बाहर अपने कुल्हाड़ियों एक मैट्रिक्स में समतल है, और एक वेक्टर में ढाल बाहर समतल होगा:

n_params = tf.reduce_prod(layer1.kernel.shape)

g_vec = tf.reshape(g, [n_params, 1])
h_mat = tf.reshape(h, [n_params, n_params])

हेसियन मैट्रिक्स सममित होना चाहिए:

def imshow_zero_center(image, **kwargs):
  lim = tf.reduce_max(abs(image))
  plt.imshow(image, vmin=-lim, vmax=lim, cmap='seismic', **kwargs)
  plt.colorbar()
imshow_zero_center(h_mat)

पीएनजी

न्यूटन की विधि अद्यतन चरण नीचे दिखाया गया है:

eps = 1e-3
eye_eps = tf.eye(h_mat.shape[0])*eps
# X(k+1) = X(k) - (∇²f(X(k)))^-1 @ ∇f(X(k))
# h_mat = ∇²f(X(k))
# g_vec = ∇f(X(k))
update = tf.linalg.solve(h_mat + eye_eps, g_vec)

# Reshape the update and apply it to the variable.
_ = layer1.kernel.assign_sub(tf.reshape(update, layer1.kernel.shape))

हालांकि यह एक एकल के लिए अपेक्षाकृत आसान है tf.Variable , एक गैर तुच्छ मॉडल को यह लागू करने सावधान संयोजन और टुकड़ा करने की क्रिया की आवश्यकता होती है कई चर भर में एक पूर्ण हेस्सियन उत्पादन करने के लिए होगा।

बैच जैकोबियन

कुछ मामलों में, आप स्रोतों के ढेर के संबंध में लक्ष्य के प्रत्येक स्टैक का जैकोबियन लेना चाहते हैं, जहां प्रत्येक लक्ष्य-स्रोत जोड़ी के लिए जैकोबियन स्वतंत्र होते हैं।

उदाहरण के लिए, यहाँ इनपुट x आकार का है (batch, ins) और आउटपुट y आकार का है (batch, outs) :

x = tf.random.normal([7, 5])

layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)

with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:
  tape.watch(x)
  y = layer1(x)
  y = layer2(y)

y.shape
TensorShape([7, 6])

से भरा Jacobian y के संबंध में x के आकार (batch, ins, batch, outs) , भले ही आप केवल चाहते हैं (batch, ins, outs) :

j = tape.jacobian(y, x)
j.shape
TensorShape([7, 6, 7, 5])

ढेर में प्रत्येक आइटम की ढ़ाल स्वतंत्र हैं, तो हर (batch, batch) इस टेन्सर का टुकड़ा एक विकर्ण मैट्रिक्स है:

imshow_zero_center(j[:, 0, :, 0])
_ = plt.title('A (batch, batch) slice')

पीएनजी

def plot_as_patches(j):
  # Reorder axes so the diagonals will each form a contiguous patch.
  j = tf.transpose(j, [1, 0, 3, 2])
  # Pad in between each patch.
  lim = tf.reduce_max(abs(j))
  j = tf.pad(j, [[0, 0], [1, 1], [0, 0], [1, 1]],
             constant_values=-lim)
  # Reshape to form a single image.
  s = j.shape
  j = tf.reshape(j, [s[0]*s[1], s[2]*s[3]])
  imshow_zero_center(j, extent=[-0.5, s[2]-0.5, s[0]-0.5, -0.5])

plot_as_patches(j)
_ = plt.title('All (batch, batch) slices are diagonal')

पीएनजी

वांछित परिणाम पाने के लिए आपको डुप्लिकेट से अधिक जोड़ सकते हैं batch आयाम, वरना का उपयोग कर विकर्णों चयन tf.einsum :

j_sum = tf.reduce_sum(j, axis=2)
print(j_sum.shape)
j_select = tf.einsum('bxby->bxy', j)
print(j_select.shape)
(7, 6, 5)
(7, 6, 5)

पहली जगह में अतिरिक्त आयाम के बिना गणना करना अधिक कुशल होगा। tf.GradientTape.batch_jacobian विधि ठीक करता है:

jb = tape.batch_jacobian(y, x)
jb.shape
WARNING:tensorflow:5 out of the last 5 calls to <function pfor.<locals>.f at 0x7f7d601250e0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
TensorShape([7, 6, 5])
error = tf.reduce_max(abs(jb - j_sum))
assert error < 1e-3
print(error.numpy())
0.0
x = tf.random.normal([7, 5])

layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)
bn = tf.keras.layers.BatchNormalization()
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)

with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:
  tape.watch(x)
  y = layer1(x)
  y = bn(y, training=True)
  y = layer2(y)

j = tape.jacobian(y, x)
print(f'j.shape: {j.shape}')
WARNING:tensorflow:6 out of the last 6 calls to <function pfor.<locals>.f at 0x7f7cf062fa70> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
j.shape: (7, 6, 7, 5)
plot_as_patches(j)

_ = plt.title('These slices are not diagonal')
_ = plt.xlabel("Don't use `batch_jacobian`")

पीएनजी

इस मामले में, batch_jacobian अभी भी चलाता है और उम्मीद आकार के साथ रिटर्न कुछ है, लेकिन यह की सामग्री को एक अस्पष्ट अर्थ है:

jb = tape.batch_jacobian(y, x)
print(f'jb.shape: {jb.shape}')
jb.shape: (7, 6, 5)