Вы не использовали TensorFlow, пока не использовали это! Как использовать всю мощь TensorFlow для создания МОЩНЫХ алгоритмов!

Введение

Большинство людей, которые работали с TensorFlow, использовали API Keras. Keras API был приобретен Google и добавлен в TensorFlow для упрощения разработки нейронных сетей путем создания простого и систематического процесса обучения, тестирования и оценки нейронных сетей. Однако это верхушка айсберга! Оставайтесь с нами до самого конца, пока я открываю двери в новый, мощный мир пользовательских алгоритмов, градиентов и потерь.

Эта статья будет разделена на три отдельных раздела: Custom Training, Custom Loss и Custom Forward Passes!

Как мы сейчас используем TensorFlow?

В настоящее время TensorFlow используется следующим образом: загрузите данные, создайте последовательную сеть, выберите оптимизатор и функцию потерь и тренируйтесь! Как бы просто и удобно это ни звучало, он ограничен базовыми, «готовыми» сетями. Вот базовый пример из TensorFlow:

import tensorflow as tf
print("TensorFlow version:", tf.__version__)

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10)
])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

model.compile(optimizer='adam',
              loss=loss_fn,
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test,  y_test, verbose=2)

Как вы увидите, каждый компонент этой программы можно настроить, расширить и настроить!

Математика???

Еще одна вещь: нам нужно изучить некоторые основы математики TensorFlow! Чтобы понять, почему это так важно — возможно, самая важная функция TensorFlow — вы должны сначала понять основы многомерного исчисления.

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

Индивидуальное обучение

Несмотря на запутанный фасад, TensorFlow делает это относительно просто. Во время обучения выделяют три основных этапа:

  1. Проход вперед
  2. Рассчитать убыток
  3. Расчет/распространение градиентов.

Давайте создадим простой пример:

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)
            loss_value = loss_fn(y_batch_train, logits)

        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %s samples" % ((step + 1) * batch_size))

Во-первых, мы перебираем количество эпох, в течение которых должен работать алгоритм. Затем мы должны перебрать весь набор данных для одного шага обучения. Теперь к более важному шагу: GradientTape. Это способ для TensorFlow следить за изменениями переменных и использовать автоматическое дифференцирование (в обратном режиме) для поиска частных производных.

Градиентная лента

Эта функция отслеживает все логиты tf.Variable или TF на предмет изменений (с точки зрения математических операций, выполняемых над ними. Затем она создает график всех функций, выполняемых над ними как таковыми:

Затем он вычисляет частную производную по отношению к каждому предшествующему шагу. Наконец, мы умножаем части из узлов каждого пути и суммируем все произведения всех путей, ведущих к одному и тому же источнику. Например, давайте найдем ∂d/∂a.

Это гораздо более мощный и надежный подход, чем простое нахождение изменения d по отношению к a численно с использованием числового (дискретного) дифференцирования. Это связано с тем, что последний подход подвержен ошибкам деления на ноль.

Давайте воспользуемся простой практической демонстрацией! Попробуем численно и автоматически продифференцировать следующую функцию:

Символически дифференцировав (используя правило степени), мы получим следующее:

Чтобы проверить это, мы оценим градиент этой функции в точке (4, 156):

Теперь давайте напишем функцию GradientTape с TensorFlow:

import tensorflow as tf

def f_x(x):
 return 2 * (x ** 3) + 6 * x + 4

with tf.GradientTape() as tape:
  x = tf.constant(4.0)
  tape.watch(x)
  y = f_x(x)

print(tape.gradient(y, x))

====================================

>>> tf.Tensor(102.0, shape=(), dtype=float32)

Здесь важно учитывать, как мне пришлось добавить метод tape.watch(x). Это связано с тем, что GradientTape автоматически отслеживает только tf.Variable и логиты из TF NN и TF Losses. Вы должны преобразовать его в tf.constant и вручную просмотреть любые другие переменные.

Функция потери

Следующая впечатляющая функция, которую предоставляет TensorFlow, — это возможность создавать собственные функции потерь. Хотя это кажется очевидным, его нельзя недооценивать. Определяя, терпит ли нейронная сеть неудачу или преуспевает в выполнении своих задач, она служит учителем/наставником сети, влияя на градиенты и позволяя коррекции распространяться по сети. Это можно сделать достаточно просто:

@tf.function
def euclidean(y1, y2, T):
    y1 = tf.squeeze(tf.convert_to_tensor(y1, dtype='float32'))
    y2 = tf.squeeze(tf.convert_to_tensor(y2, dtype='float32'))
    dist = tf.sqrt(tf.reduce_sum(tf.square(tf.subtract(y1, y2)), axis=0))
    return tf.square(tf.subtract(dist, T))

Просто написать функцию и украсить ее функцией tf.function может быть достаточно, чтобы создать мощную функцию TF. Декоратор позволяет TensorFlow создавать графики (как показано в разделе GradientTape), необходимые для сохранения модели и повышения производительности.

Другой способ написать простую функцию потерь — использовать абстрактный класс tf.keras.losses.Loss. Хотя это удобный и более надежный способ создания функции потерь, он может иметь некоторые ограничения, налагаемые абстрактным классом для стандартизации.

class MeanSquaredError(Loss):

  def call(self, y_true, y_pred):
    return tf.reduce_mean(tf.math.square(y_pred - y_true), axis=-1)

Как написать пользовательскую функцию потерь?

Создание функции потерь — это скорее искусство, чем точная наука. Он включает в себя следующие предварительные шаги:

  1. Соберите все «метрические» требования, которым должна соответствовать ваша нейронная сеть.
  2. Сформулируйте способ оценки показателей численно.
  3. Используя прямые/обратные отношения, объедините эти показатели в единую математическую функцию потерь.

Одним из лучших примеров этой стратегии является функция контрастных потерь, используемая во многих современных сетях, таких как сиамская сеть.

D_w — Расстояние между точками

Y — истинное значение (0 = не связано; 1 = связано)

Эта функция потерь предназначена для вычисления расстояния между двумя тензорами для различения изображений. Он имеет два термина: один отвечает за вознаграждение алгоритма за расположение точек ближе друг к другу, а другой возвращает 0, если точки находятся дальше друг от друга. Это становится более очевидным, когда удаляются условия нормализации 1/2.

Оптимизация

Это последний основной способ настроить ваши приключения ИИ. Как объяснялось ранее, этап обратного распространения в разделе Математика??? был упрощен до линейной функции со скоростью обучения, масштабирующей градиент. Существуют намного более мощные методы для получения более эффективных алгоритмов с более быстрой сходимостью и более высокой точностью.

Адам

Почти все новички слышали об оптимизаторе Adam. В конце концов, это самый популярный оптимизатор для самых разных задач DNN. Это прекрасный пример оптимизатора на основе импульса с градиентами в квадрате. Во-первых, он должен возводить в квадрат (а затем извлекать квадратный корень) градиенты. Это уменьшает большую часть колебаний и удаляет весь смысл величины из градиентов, оставляя только величину шага. Затем он рассчитает скользящее среднее значение градиентов, чтобы предотвратить беспорядочные изменения параллельных градиентов и увеличить градиенты, если потери быстро сходятся.

Заключение

Как я (надеюсь) продемонстрировал в этой статье, обширная вселенная возможностей питает современные исследования и разработки в области глубокого обучения. Используете ли вы один или несколько методов, перечисленных в этой статье; используете ли вы TensorFlow, PyTorch, Julia и т. д.; существует множество переменных, предназначенных для настройки для ускорения разработки.