Концептуальное понимание GradientTape.gradient - PullRequest
1 голос
/ 13 марта 2020

Фон

В Tensorflow 2 существует класс с именем GradientTape, который используется для записи операций над тензорами, результат которых затем можно дифференцировать и подавать в некоторый алгоритм минимизации. Например, из документации у нас есть этот пример:

x = tf.constant(3.0)
with tf.GradientTape() as g:
  g.watch(x)
  y = x * x
dy_dx = g.gradient(y, x) # Will compute to 6.0

строка документа для метода gradient подразумевает, что первый аргумент может быть не просто тензор, но список тензоров:

 def gradient(self,
               target,
               sources,
               output_gradients=None,
               unconnected_gradients=UnconnectedGradients.NONE):
    """Computes the gradient using operations recorded in context of this tape.

    Args:
      target: a list or nested structure of Tensors or Variables to be
        differentiated.
      sources: a list or nested structure of Tensors or Variables. `target`
        will be differentiated against elements in `sources`.
      output_gradients: a list of gradients, one for each element of
        target. Defaults to None.
      unconnected_gradients: a value which can either hold 'none' or 'zero' and
        alters the value which will be returned if the target and sources are
        unconnected. The possible values and effects are detailed in
        'UnconnectedGradients' and it defaults to 'none'.

    Returns:
      a list or nested structure of Tensors (or IndexedSlices, or None),
      one for each element in `sources`. Returned structure is the same as
      the structure of `sources`.

    Raises:
      RuntimeError: if called inside the context of the tape, or if called more
       than once on a non-persistent tape.
      ValueError: if the target is a variable or if unconnected gradients is
       called with an unknown value.
    """

В вышеприведенном примере легко увидеть, что y, target, является функцией, которую нужно дифференцировать, а x является функцией В зависимой переменной «градиент» берется относительно.

Из моего ограниченного опыта выясняется, что метод gradient возвращает список тензоров, по одному на каждый элемент sources, и каждый из них Градиенты - это тензор, который имеет ту же форму, что и соответствующий член sources.

Вопрос

Приведенное выше описание поведения gradients имеет смысл, если target содержит единственный 1x1 «тензор» должен быть дифференцирован, потому что математически вектор градиента должен быть того же размера, что и область функции.

Однако, если target является списком тензоров, вывод gradients будет иметь ту же форму. Почему это так? Если target рассматривается как список функций, разве вывод не должен напоминать что-то вроде якобиана? Как мне интерпретировать это поведение концептуально?

1 Ответ

1 голос
/ 18 марта 2020

Так определяется tf.GradientTape().gradient(). Он имеет ту же функциональность, что и tf.gradients(), за исключением того, что последний нельзя использовать в активном режиме. Из документов из tf.gradients():

Возвращается список тензоров длины len(xs), где каждый тензор является sum(dy/dx) for y in ys

где xs равны sources и ys равны target.

Пример 1 :

Итак, скажем target = [y1, y2] и sources = [x1, x2]. Результат будет:

[dy1/dx1 + dy2/dx1, dy1/dx2 + dy2/dx2]

Пример 2 :

Вычислить градиенты для потерь на выборку (тензор) против уменьшенных потерь (скаляр)

Let w, b be two variables. 
xentropy = [y1, y2] # tensor
reduced_xentropy = 0.5 * (y1 + y2) # scalar
grads = [dy1/dw + dy2/dw, dy1/db + dy2/db]
reduced_grads = [d(reduced_xentropy)/dw, d(reduced_xentropy)/db]
              = [d(0.5 * (y1 + y2))/dw, d(0.5 * (y1 + y2))/db] 
              == 0.5 * grads

Пример Tensorflow приведенного выше фрагмента:

import tensorflow as tf

print(tf.__version__) # 2.1.0

inputs = tf.convert_to_tensor([[0.1, 0], [0.5, 0.51]]) # two two-dimensional samples
w = tf.Variable(initial_value=inputs)
b = tf.Variable(tf.zeros((2,)))
labels = tf.convert_to_tensor([0, 1])

def forward(inputs, labels, var_list):
    w, b = var_list
    logits = tf.matmul(inputs, w) + b
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        labels=labels, logits=logits)
    return xentropy

# `xentropy` has two elements (gradients of tensor - gradient
# of each sample in a batch)
with tf.GradientTape() as g:
    xentropy = forward(inputs, labels, [w, b])
    reduced_xentropy = tf.reduce_mean(xentropy)
grads = g.gradient(xentropy, [w, b])
print(xentropy.numpy()) # [0.6881597  0.71584916]
print(grads[0].numpy()) # [[ 0.20586157 -0.20586154]
                        #  [ 0.2607238  -0.26072377]]

# `reduced_xentropy` is scalar (gradients of scalar)
with tf.GradientTape() as g:
    xentropy = forward(inputs, labels, [w, b])
    reduced_xentropy = tf.reduce_mean(xentropy)
grads_reduced = g.gradient(reduced_xentropy, [w, b])
print(reduced_xentropy.numpy()) # 0.70200443 <-- scalar
print(grads_reduced[0].numpy()) # [[ 0.10293078 -0.10293077]
                                #  [ 0.1303619  -0.13036188]]

Если вы вычисляете потери (xentropy) для каждого элемента в пакете, окончательные градиенты каждой переменной будут суммой всех градиентов для каждый образец в партии (что имеет смысл).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...