Keras API может легко выполнять обратное распространение с помощью встроенных оптимизаторов и функций потерь. Однако бывают случаи, когда мы хотим специально манипулировать или применять градиент. Например, чтобы избежать взрывного градиента, мы можем захотеть обрезать градиент.

В общем, TensorFlow AutoDiff позволяет нам вычислять градиенты и управлять ими. В приведенном ниже примере мы вычисляем и строим производную сигмовидной функции. В глубоком обучении мы используем AutoDiff для выполнения настраиваемого обратного распространения ошибки.

GradientTape в записи прямого прохода

В TensorFlow (TF) tf.GradientTape записывает все операции прямого прохода в своем блоке «with» на «ленту». Эта запись будет использована для вычисления градиента обратного распространения в tape.gradient позже. В приведенном ниже коде записана операция loss = . Затем tape.gradient (loss, w) вычисляет градиент потерь относительно w.r.t. w (т.е. d L / d w).

Вот пример плотного слоя, в котором входными данными x является вектор, а не скаляр. Возвращенный градиент для w (dl_dw) - это тензор с формой (3, 2). Он имеет ту же форму, что и w, поскольку содержит по одному градиенту для каждого элемента в w.

Чтобы применить градиентный спуск к нейронной сети, мы меняем ее веса в соответствии со скоростью обучения и градиентами.

AutoDiff для модели нейронной сети

Давайте завершим наши демонстрации классификатором MNIST ниже. Модель Keras предоставляет свойство mnist_model.trainable_variables, поэтому нам не нужно самостоятельно отслеживать все обучаемые переменные.

tape.gradient возвращает список (grads) , содержащий тензор градиента для каждой обучаемой переменной. Затем мы можем применить градиентный спуск в соответствии с нашим выбором оптимизатора.

Для полноты, вот полный код.

Примечание: я использую скриншоты для кодов, потому что они лучше отформатированы. Для читателей, которым нужен исходный код, пожалуйста, обратитесь к руководству TensorFlow, откуда взято большинство кодов здесь. Из-за постоянных изменений в TF API сложно поддерживать актуальность кода в этой статье. Читатели всегда должны обращаться к новейшим документам.

Векторный вывод

Вычисленное значение y (например, функция потерь), показанное ранее, является скалярным значением. Но y может быть вектором. Например, y = x * [3., 4.], который является вектором. y состоит из двух компонентов и dy / dx = [3., 4.]. Но tape.gradient всегда возвращает градиенты такой же формы, как x. Действительно, tape.gradient возвращает сумму компонентов, которая здесь равна 7.

Постоянный

Ресурсы, удерживаемые на ленте, высвобождаются при вызове tape.gradient. Мы не можем вызывать одну и ту же ленту несколько раз для определения производной разных переменных. Чтобы разрешить несколько вызовов, убедитесь, что постоянное значение True для ленты. Используйте del tape, чтобы впоследствии освободить ресурсы, когда они не нужны.

Пользовательский градиент

Tensorflow обеспечивает автоматический расчет градиента. Тем не менее, некоторые вычисления градиента невозможно вычислить или могут быть численно нестабильными. В последнем случае промежуточный результат может стремиться к бесконечности, хотя в конечном итоге он может исчезнуть. Например, вычисленный ниже градиент возвращает NAN (не число) для x = 100, даже если фактический градиент равен 1,0.

Чтобы решить эту проблему, мы можем добавить аннотацию @ tf.custom_gradient и реализовать более стабильное и настраиваемое вычисление градиента. Этот новый метод возвращает как потерю, так и функцию настраиваемого градиента. Но этот аннотированный метод только вернет потерю вызывающей стороне и запишет пользовательскую функцию для вычисления градиентов в tape.gradient. Как показано ниже, градиент log (1 + eˣ) реализован как 1–1 / (1 + eˣ), что равно 1 для x = 100.

Отсечение градиента по Norm

Одно из основных применений явного управления градиентом - обрезать градиент по норме, чтобы избежать взрыва градиента. Вот пример кода, демонстрирующий идею.

Получение градиента None

В некоторых случаях возвращаемый градиент будет None. В этом разделе мы увидим, почему и как это исправить, если это неверно.

Необучаемые переменные, тензоры, константы

tf.GradientTape записывает операции в блоке with и отслеживает все обучаемые tf.Variable, с которыми эти операции связаны. Однако необучаемые переменные, тензоры и константы не отслеживаются автоматически. Это x1 (необучаемые tf.Variables), x2 (тензорные) и x3 ( tf.constant) ниже. Соответствующие им рассчитанные градиенты равны Нет.

смотреть

Если это нежелательно, мы можем исправить это, явно добавив их с помощью tape.watch. В приведенном ниже примере, посмотрев также и вход x, мы можем правильно вычислить его градиент.

Кстати, промежуточные результаты в блоке GradientTape (например, y ниже) будут записаны автоматически, даже если это тензор. Градиент dz / dy возвращает 18 ниже.

Нет

Как и ожидалось, tape.gradient возвращает None, если градиент не связан с не связанными переменными. В приведенном ниже коде z не связано с x, поэтому возвращаемый градиент - None.

Операции, выполняемые вне TensorFlow, например операции NumPy, не могут быть отслежены. Таким образом, градиент y, вычисленный с помощью Numpy ниже, не может быть вычислен. И операции связаны с целочисленными или строковыми типами данных, которые не различаются. Например, преобразование целых чисел в число с плавающей запятой не дифференцируемо, и соответствующий градиент равен None.

Присваивание x = x +1 превращает x в тензор. Поэтому, когда мы начинаем вторую эпоху и новый сеанс записи, x не будет касаться автоматически. Следовательно, для второй эпохи градиент относительно x - Нет.

Когда TF читает из объекта с состоянием, такого как tf.Variable, лента может видеть только текущее состояние, а не историю, которая к нему ведет. Операции, которые изменяют состояние переменной, блокируют распространение градиента. Следовательно, операция assign_add слева приведет к None для вычисления градиента. (Кажется, что присваивание записывает измененное состояние, но не операцию.) Чтобы исправить это, мы складываем переменные вместе и вместо этого присваиваем результат тензору. Это показано справа внизу.

Градиенты высшего порядка

tap.gradient можно рассматривать как другой оператор TensorFlow. Следовательно, мы можем использовать вложенный GradientTape для вычисления градиента более высокого порядка. Во внутреннем цикле «with» ниже мы вычисляем производную первого порядка, в то время как другой цикл отслеживает dy / dx и вычисляет его производную, то есть производную второго порядка от y wrt х.

Якобиан и гессиан

TensorFlow также предоставляет API для вычисления матрицы Якоби J.

Приведенный ниже код вычисляет якобиан относительно к скалярной переменной.

А этот вычисляет гессен.

Источники и ссылки

Руководство по TensorFlow: AutoDiff и Advanced AutoDiff