Введение

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

В мире машинного обучения, искусственного интеллекта и науки о данных TensorFlow стал мощной основой для построения и обучения моделей. Хотя TensorFlow можно использовать в интерактивном режиме, как и любую библиотеку Python, он также предлагает дополнительные инструменты для оптимизации производительности и экспорта моделей. Одним из таких инструментов является tf.function, который позволяет разработчикам отделять чистый код TensorFlow от кода Python, обеспечивая повышенную производительность и возможность экспорта.

Понимание графиков и tf.function

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

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

Оптимизация производительности с помощью tf.function

Чтобы проиллюстрировать концепцию построения графа и оптимизации производительности, рассмотрим следующий фрагмент кода:

@tf.function
def my_func(x):
  print('Tracing.\n')
  return tf.reduce_sum(x)

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

x = tf.constant([31, 1, 4])
my_func(x)

Вывод кода

Tracing.
<tf.Tensor: shape=(), dtype=int32, numpy=36>

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

Повторное использование и чувствительность подписи

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

Давайте изменим наш предыдущий пример и посмотрим на поведение tf.function при получении другого ввода:

x = tf.constant([8.0, 11.8, 14.3], dtype=tf.float32)
my_func(x)

Вывод кода

Tracing.
<tf.Tensor: shape=(), dtype=float32, numpy=34.1>

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

Управление переменными и функциями в TensorFlow

Модули и tf.Module

В TensorFlow tf.Module — это класс, который позволяет вам управлять объектами tf.Variable и соответствующими объектами tf.function. Это облегчает две важные функции:

  1. Сохранение и восстановление переменных. Вы можете сохранять и восстанавливать значения переменных с помощью tf.train.Checkpoint. Это особенно полезно во время обучения для эффективного сохранения и восстановления состояния модели.
  2. Импорт и экспорт. С помощью tf.saved_model вы можете импортировать и экспортировать значения tf.Variable объектов и связанных с ними tf.function графиков. Это позволяет запускать модель независимо от создавшей ее программы Python, повышая ее переносимость.

Пример, демонстрирующий использование tf.Module, выглядит следующим образом:

class MyModule(tf.Module):
  def __init__(self, value):
    self.weight = tf.Variable(value)
@tf.function
  def multiply(self, x):
    return x * self.weight

Здесь мы определяем подкласс tf.Module с именем MyModule, который включает переменную weight и tf.function с именем multiply. Функция multiply выполняет поэлементное умножение между входными данными x и переменной weight.

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

mod = MyModule(3)
mod.multiply(tf.constant([5, 6, 3]))

Вывод кода

<tf.Tensor: shape=(3,), dtype=int32, numpy=array([15, 18,  9], dtype=int32)>

В этом примере мы создаем экземпляр MyModule с начальным значением 3 и вызываем функцию multiply для тензора. Выходные данные представляют собой поэлементное умножение между тензором и переменной weight.

Сохранение и загрузка модулей

После того как вы определили и использовали объект tf.Module, вы можете сохранить его для будущего использования или развертывания. Чтобы сохранить модуль, вы можете использовать tf.saved_model.save() и указать желаемый путь сохранения:

save_path = './saved'
tf.saved_model.save(mod, save_path)

Результирующая SavedModel не зависит от кода, создавшего ее. Его можно загрузить из Python, других языковых привязок, TensorFlow Serving или даже преобразовать для работы с TensorFlow Lite или TensorFlow.js.

Чтобы загрузить и использовать сохраненный модуль, вы можете использовать tf.saved_model.load():

reloaded = tf.saved_model.load(save_path)
reloaded.multiply(tf.constant([5, 6, 3]))

Вывод кода

<tf.Tensor: shape=(3,), dtype=int32, numpy=array([15, 18,  9], dtype=int32)>

Слои и модели: построение и обучение сложных моделей

Созданный на основе tf.Module, TensorFlow предоставляет два класса более высокого уровня: tf.keras.layers.Layer и tf.keras.Model. Эти классы предлагают дополнительные функциональные возможности и удобные методы построения, обучения и сохранения сложных моделей.

tf.keras.layers.Layer служит базовым классом для реализации пользовательских слоев. Он предоставляет структуру для определения слоев с обучаемыми весами и может использоваться для создания пользовательских архитектур или расширения существующих.

Расширение tf.keras.layers.Layer, tf.keras.Model добавляет методы обучения, оценки и сохранения модели. Он обычно используется в качестве контейнера для нескольких слоев, что позволяет определять сложные архитектуры нейронных сетей и управлять ими.

Используя возможности tf.keras.layers.Layer и tf.keras.Model, вы можете создавать расширенные модели, обучать их на обширных наборах данных и сохранять их конфигурации и веса для будущего использования или развертывания.

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

Давайте построим модель: обучение базовой модели с нуля

Теперь мы объединим концепции тензоров, переменных, автоматического дифференцирования, графиков, tf.function, модулей, слоев и моделей, чтобы с нуля построить базовую модель машинного обучения с использованием TensorFlow. Эта часть предназначена для всех, кто хочет изучить и понять процесс построения и обучения модели.

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

Создание примера данных

Для начала давайте создадим несколько примеров данных. Мы создадим облако точек, которое примерно соответствует квадратичной кривой. Мы будем использовать библиотеку Matplotlib для визуализации данных.

import matplotlib
from matplotlib import pyplot as plt

matplotlib.rcParams['figure.figsize'] = [10, 6]
x = tf.linspace(-3, 3, 201)
x = tf.cast(x, tf.float32)
def f(x):
  y = x**2 + 2*x - 8
  return y
y = f(x) + tf.random.normal(shape=[201])
plt.plot(x.numpy(), y.numpy(), '.', label='Data')
plt.plot(x, f(x), label='Ground truth')
plt.legend();

Вывод кода

Создание квадратичной модели

Далее мы определим квадратичную модель со случайно инициализированными весами и смещением. Модель примет входные данные x и предскажет соответствующие выходные данные с помощью уравнения y = w_q * x^2 + w_l * x + b, где w_q представляет вес квадратичного члена, w_l представляет вес линейного члена, а b представляет член смещения.

class Model(tf.Module):
  def __init__(self):
    rand_init = tf.random.uniform(shape=[3], minval=0., maxval=5., seed=22)
    self.w_q = tf.Variable(rand_init[0])
    self.w_l = tf.Variable(rand_init[1])
    self.b = tf.Variable(rand_init[2])

@tf.function
  def __call__(self, x):
    return self.w_q * (x**2) + self.w_l * x + self.b
quad_model = Model()

Мы также определяем функцию plot_preds, которая помогает визуализировать предсказания модели.

def plot_preds(x, y, f, model, title):
  plt.figure()
  plt.plot(x, y, '.', label='Data')
  plt.plot(x, f(x), label='Ground truth')
  plt.plot(x, model(x), label='Predictions')
  plt.title(title)
  plt.legend()

Давайте построим начальные прогнозы модели:

plot_preds(x, y, f, quad_model, 'Initial Predictions')

Вывод кода

Определение функции потерь

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

def mse_loss(y_pred, y):
  return tf.reduce_mean(tf.square(y_pred - y))

Обучение модели

Теперь мы напишем базовый цикл обучения для обучения модели с нуля. Цикл будет использовать функцию потерь MSE и ее градиенты по отношению к входным данным для обновления параметров модели. Мы будем использовать мини-пакеты для обучения, что обеспечивает эффективность использования памяти и более быструю сходимость. tf.data.Dataset API используется для пакетной обработки и перетасовки данных.

batch_size = 32
dataset = tf.data.Dataset.from_tensor_slices((x, y))
dataset = dataset.shuffle(buffer_size=x.shape[0]).batch(batch_size)

epochs = 150
learning_rate = 0.01
losses = []
for epoch in range(epochs):
  for x_batch, y_batch in dataset:
    with tf.GradientTape() as tape:
      batch_loss = mse_loss(quad_model(x_batch), y_batch)
    grads = tape.gradient(batch_loss, quad_model.variables)
    for g,v in zip(grads, quad_model.variables):
        v.assign_sub(learning_rate*g)
  loss = mse_loss(quad_model(x), y)
  losses.append(loss)
  if epoch % 10 == 0:
    print(f'Mean squared error for step {epoch}: {loss.numpy():0.3f}')
plt.plot(range(epochs), losses)
plt.xlabel("Epoch")
plt.ylabel("Mean Squared Error (MSE)")
plt.title('MSE loss vs training iterations')

Вывод кода

Mean squared error for step 0: 35.056
Mean squared error for step 10: 11.213
Mean squared error for step 20: 4.120
Mean squared error for step 30: 1.982
Mean squared error for step 40: 1.340
Mean squared error for step 50: 1.165
Mean squared error for step 60: 1.118
Mean squared error for step 70: 1.127
Mean squared error for step 80: 1.087
Mean squared error for step 90: 1.087
Mean squared error for step 100: 1.098
Mean squared error for step 110: 1.094
Mean squared error for step 120: 1.086
Mean squared error for step 130: 1.089
Mean squared error for step 140: 1.088

Оценка обученной модели

Давайте посмотрим на производительность обученной модели:

plot_preds(x, y, f, quad_model, 'Predictions after Training')

Вывод кода

Использование tf.keras для обучения

Хотя реализация цикла обучения с нуля является образовательной, модуль tf.keras TensorFlow предоставляет удобные утилиты для моделей обучения. Мы можем использовать методы Model.compile и Model.fit, чтобы упростить процесс обучения.

Чтобы продемонстрировать это, мы создадим модель Sequential, используя tf.keras.Sequential. Мы будем использовать плотный слой (tf.keras.layers.Dense) для изучения линейных отношений и лямбда-слой (tf.keras.layers.Lambda) для преобразования входных данных для захвата квадратичных отношений.

new_model = tf.keras.Sequential([
    tf.keras.layers.Lambda(lambda x: tf.stack([x, x**2], axis=1)),
    tf.keras.layers.Dense(units=1, kernel_initializer=tf.random.normal)
])

new_model.compile(
    loss=tf.keras.losses.MSE,
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.01)
)
history = new_model.fit(x, y,
                        epochs=150,
                        batch_size=32,
                        verbose=0)
new_model.save('./my_new_model')

Давайте визуализируем ход обучения:

plt.plot(history.history['loss'])
plt.xlabel('Epoch')
plt.ylim([0, max(plt.ylim())])
plt.ylabel('Loss [Mean Squared Error]')
plt.title('Keras training progress')

Вывод кода

Наконец, мы можем оценить производительность модели Keras:

plot_preds(x, y, f, new_model, 'Predictions after Training')

Вывод кода

Заключение

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

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