Giới thiệu về gradient và phân biệt tự động

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Phân biệt và Gradients tự động

Sự khác biệt tự động rất hữu ích cho việc triển khai các thuật toán học máy, chẳng hạn như backpropagation để đào tạo mạng nơ-ron.

Trong hướng dẫn này, bạn sẽ khám phá các cách tính toán độ dốc bằng TensorFlow, đặc biệt là trong quá trình thực thi nhanh chóng.

Thành lập

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

Tính toán gradient

Để tự động phân biệt, TensorFlow cần nhớ những thao tác nào xảy ra theo thứ tự trong quá trình chuyển tiếp . Sau đó, trong quá trình chuyển lùi , TensorFlow duyệt qua danh sách các thao tác này theo thứ tự ngược lại để tính toán độ dốc.

Băng Gradient

TensorFlow cung cấp API tf.GradientTape để phân biệt tự động; nghĩa là tính toán gradient của một phép tính đối với một số đầu vào, thường là tf.Variable s. TensorFlow "ghi lại" các hoạt động liên quan được thực thi bên trong ngữ cảnh của tf.GradientTape vào một "băng". Sau đó, TensorFlow sử dụng băng đó để tính toán độ dốc của một phép tính "được ghi lại" bằng cách sử dụng phân biệt chế độ đảo ngược .

Đây là một ví dụ đơn giản:

x = tf.Variable(3.0)

with tf.GradientTape() as tape:
  y = x**2

Khi bạn đã ghi lại một số hoạt động, hãy sử dụng GradientTape.gradient(target, sources) để tính toán gradient của một số mục tiêu (thường là mất mát) liên quan đến một số nguồn (thường là các biến của mô hình):

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
6.0

Ví dụ trên sử dụng vô hướng, nhưng tf.GradientTape hoạt động dễ dàng trên bất kỳ tensor nào:

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)

Để nhận được gradient loss liên quan đến cả hai biến, bạn có thể chuyển cả hai dưới dạng nguồn cho phương thức gradient . Băng rất linh hoạt về cách các nguồn được truyền và sẽ chấp nhận bất kỳ tổ hợp danh sách hoặc từ điển nào được lồng vào nhau và trả về gradient có cấu trúc theo cùng một cách (xem tf.nest ).

[dl_dw, dl_db] = tape.gradient(loss, [w, b])

Gradient đối với mỗi nguồn có hình dạng của nguồn:

print(w.shape)
print(dl_dw.shape)
(3, 2)
(3, 2)

Đây là phép tính gradient một lần nữa, lần này là chuyển từ điển các biến:

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

Gradients đối với một người mẫu

Thông thường, thu thập tf.Variables vào một tf.Module hoặc một trong các lớp con của nó ( layers.Layer , keras.Model ) để kiểm traxuất .

Trong hầu hết các trường hợp, bạn sẽ muốn tính toán độ dốc liên quan đến các biến có thể đào tạo của mô hình. Vì tất cả các lớp con của tf.Module tổng hợp các biến của chúng trong thuộc tính Module.trainable_variables , bạn có thể tính toán các độ dốc này trong một vài dòng mã:

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

Kiểm soát những gì băng xem

Hành vi mặc định là ghi lại tất cả các hoạt động sau khi truy cập một tf.Variable . Lý do cho điều này là:

  • Băng cần biết những phép toán nào cần ghi trong đường chuyền tiến để tính toán độ dốc trong đường chuyền ngược.
  • Băng giữ các tham chiếu đến đầu ra trung gian, vì vậy bạn không muốn ghi lại các thao tác không cần thiết.
  • Trường hợp sử dụng phổ biến nhất liên quan đến việc tính toán độ dốc của tổn thất đối với tất cả các biến có thể đào tạo của mô hình.

Ví dụ: phần sau không tính được gradient vì tf.Tensor không được "theo dõi" theo mặc định và tf.Variable không thể đào tạo:

# 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

Bạn có thể liệt kê các biến đang được xem bởi băng bằng cách sử dụng phương thức GradientTape.watched_variables :

[var.name for var in tape.watched_variables()]
['x0:0']

tf.GradientTape cung cấp các hook cho phép người dùng kiểm soát những gì được xem hoặc không được xem.

Để ghi lại các gradient liên quan đến tf.Tensor , bạn cần gọi 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

Ngược lại, để vô hiệu hóa hành vi mặc định của việc xem tất cả tf.Variables , hãy đặt watch_accessed_variables=False khi tạo băng gradient. Phép tính này sử dụng hai biến, nhưng chỉ kết nối gradient cho một trong các biến:

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 không được gọi trên x0 , không có gradient nào được tính liên quan đến nó:

# 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

Kết quả trung gian

Bạn cũng có thể yêu cầu độ dốc của đầu ra liên quan đến các giá trị trung gian được tính bên trong ngữ cảnh 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

Theo mặc định, các tài nguyên do GradientTape nắm giữ được giải phóng ngay sau khi phương thức