ช่วยปกป้อง Great Barrier Reef กับ TensorFlow บน Kaggle เข้าร่วมท้าทาย

การแยกความแตกต่างอัตโนมัติขั้นสูง

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

รู้เบื้องต้นเกี่ยวกับการไล่ระดับสีและความแตกต่างโดยอัตโนมัติ คู่มือรวมถึงทุกอย่างที่จำเป็นในการไล่ระดับสีคำนวณใน TensorFlow คู่มือนี้จะมุ่งเน้นไปที่ลึกคุณสมบัติทั่วไปน้อยของ tf.GradientTape API

ติดตั้ง

import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt

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

การควบคุมการบันทึกการไล่ระดับสี

ใน คู่มือความแตกต่างโดยอัตโนมัติ คุณเห็นวิธีการควบคุมซึ่งตัวแปรและเทนเซอร์ได้รับการจับตามองจากเทปขณะที่การสร้างการคำนวณการไล่ระดับสี

เทปยังมีวิธีการจัดการกับการบันทึก

หยุดบันทึก

หากคุณต้องการหยุดการบันทึกการไล่ระดับสีคุณสามารถใช้ 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. ไม่มีการไล่ระดับสีที่กำหนดไว้สำหรับ op ใหม่ที่คุณกำลังเขียน
  2. การคำนวณเริ่มต้นเป็นตัวเลขที่ไม่เสถียร
  3. คุณต้องการแคชการคำนวณที่มีราคาแพงจากการส่งต่อ
  4. คุณต้องการที่จะปรับเปลี่ยนค่า (สำหรับตัวอย่างเช่นการใช้ tf.clip_by_value หรือ tf.math.round ) โดยไม่ต้องแก้ไขการไล่ระดับสี

สำหรับกรณีแรกที่จะเขียน op ใหม่ที่คุณสามารถใช้ tf.RegisterGradient การตั้งค่าของคุณเอง (โปรดดูเอกสาร API สำหรับรายละเอียด) (โปรดทราบว่ารีจิสตรีการไล่ระดับสีเป็นแบบโกลบอล ดังนั้นควรเปลี่ยนด้วยความระมัดระวัง)

สำหรับหลังสามกรณีคุณสามารถใช้ 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 เอกสารมัณฑนากร API สำหรับรายละเอียดเพิ่มเติม

การไล่ระดับสีแบบกำหนดเองใน SavedModel

การไล่ระดับสีที่กำหนดเองสามารถบันทึก 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 ผู้จัดการบริบทจะถูกบันทึกไว้สำหรับความแตกต่างโดยอัตโนมัติ หากคำนวณการไล่ระดับสีในบริบทนั้น การคำนวณการไล่ระดับสีก็จะถูกบันทึกเช่นกัน ด้วยเหตุนี้ API เดียวกันจึงทำงานสำหรับการไล่ระดับลำดับที่สูงกว่าเช่นกัน

ตัวอย่างเช่น:

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 เพียงคำนวณลาดเกลาที่ เพื่อสร้าง เมทริกซ์รัฐ ไปที่ ตัวอย่างเช่นรัฐ ภายใต้ ส่วนจาโคเบียน

"ที่ซ้อนกันเรียกร้องให้ 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])]

จาโคเบียน

ตัวอย่างก่อนหน้านี้ทั้งหมดใช้การไล่ระดับของเป้าหมายสเกลาร์เทียบกับเทนเซอร์ต้นทางบางตัว

เมทริกซ์จาโคเบียน แสดงให้เห็นถึงการไล่ระดับสีของเวกเตอร์มูลค่าฟังก์ชั่น แต่ละแถวมีการไล่ระดับสีของหนึ่งในองค์ประกอบของเวกเตอร์

tf.GradientTape.jacobian วิธีช่วยให้คุณได้อย่างมีประสิทธิภาพในการคำนวณเมทริกซ์จาโคเบียน

โปรดทราบว่า:

  • เช่นเดียวกับ gradient ที่: sources อาร์กิวเมนต์สามารถเป็นเมตริกซ์หรือภาชนะของเทนเซอร์
  • ซึ่งแตกต่างจาก 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)

เมื่อคุณใช้จาโคเบียนที่เกี่ยวกับสเกลาผลที่มีรูปร่างของเป้าหมายและให้การไล่ระดับสีขององค์ประกอบที่เกี่ยวกับแหล่งที่มาของแต่ละ:

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')

png

แหล่งเทนเซอร์

ไม่ว่าจะใส่เป็นสเกลาหรือเมตริกซ์, tf.GradientTape.jacobian ได้อย่างมีประสิทธิภาพคำนวณการไล่ระดับสีขององค์ประกอบของแหล่งที่มาแต่ละที่เกี่ยวกับองค์ประกอบของเป้าหมายแต่ละ (s)

ยกตัวอย่างเช่นการส่งออกของชั้นนี้มีรูปทรงของ (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

ตัวอย่าง: Hessian

ในขณะที่ 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)

png

ขั้นตอนการอัปเดตวิธีการของนิวตันแสดงไว้ด้านล่าง:

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 การใช้นี้เป็นรูปแบบที่ไม่น่ารำคาญจะต้องเรียงต่อกันอย่างระมัดระวังและการหั่นการผลิตรัฐเต็มรูปแบบทั่วหลายตัวแปร

Batch Jacobian

ในบางกรณี คุณต้องการนำจาโคเบียนของเป้าหมายแต่ละกลุ่มโดยเทียบกับกลุ่มแหล่งที่มา โดยที่จาโคเบียนสำหรับคู่เป้าหมาย-แหล่งที่มาแต่ละคู่เป็นอิสระจากกัน

ยกตัวอย่างเช่นการป้อนข้อมูลที่นี่ 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])

จาโคเบียนเต็มรูปแบบของ 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')

png

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')

png

เพื่อให้ได้ผลที่ต้องการคุณสามารถรวมมากกว่าที่ซ้ำกัน 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`")

png

ในกรณีนี้ batch_jacobian ยังคงทำงานและผลตอบแทนบางสิ่งบางอย่างที่มีรูปร่างที่คาดไว้ แต่เนื้อหามันมีความหมายไม่ชัดเจน:

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