Основы TensorFlow

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

Установка TensorFlow

TensorFlow можно установить с помощью стандартного менеджера пакетов pip. Библиотека, на момент написания статьи, работает начиная с версии python 3.7 и выше. Для установки нужно ввести следующие команды:

1pip install --upgrade pip 2pip install tensorflow

Тензоры (Tensors)

TensorFlow выполняет операции над многомерными массивами или тензорами, которые представлены экземплярами класса tf.Tensor. Вот как выглядит пример создания трех размерного массива.

1import tensorflow as tf 2 3t = tf.constant([ 4 [ 5 [1], 6 [2], 7 [3], 8 ], 9 [ 10 [4], 11 [1], 12 [6], 13 ], 14 [ 15 [7], 16 [8], 17 [9], 18 ], 19]) 20 21print(t)

Вывод у данного кода будет следующий:

1tf.Tensor( 2[[[1] 3 [2] 4 [3]] 5 6 [[4] 7 [1] 8 [6]] 9 10 [[7] 11 [8] 12 [9]]], shape=(3, 3, 1), dtype=int32)

Наиболее важными атрибутами tf.Tensor являются shape и dtype:

  • Tensor.shape показывает размерность массива.
  • Tensor.dtype говорит о типе элементов в тензоре.

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

1print(t.shape) 2print(t.dtype)

Вывод в консоль:

1(3, 3, 1) 2<dtype: 'int32'>

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

1t + t

Вывод в консоль:

1<tf.Tensor: shape=(3, 3, 1), dtype=int32, numpy= 2array([[[ 2], 3 [ 4], 4 [ 6]], 5 [[ 8], 6 [ 2], 7 [12]], 8 [[14], 9 [16], 10 [18]]], dtype=int32)>

Пример:

110 * t

Вывод в консоль:

1<tf.Tensor: shape=(3, 3, 1), dtype=int32, numpy= 2array([[[10], 3 [20], 4 [30]], 5 6 [[40], 7 [10], 8 [60]], 9 10 [[70], 11 [80], 12 [90]]], dtype=int32)>

Пример:

1tf.reduce_sum(t)

Вывод в консоль:

1<tf.Tensor: shape=(), dtype=int32, numpy=41>

Пример:

1tf.concat([t, t, t], axis=0)

Вывод в консоль:

1<tf.Tensor: shape=(6, 3, 1), dtype=int32, numpy= 2array([[[1], 3 [2], 4 [3]], 5 [[4], 6 [1], 7 [6]], 8 [[7], 9 [8], 10 [9]], 11 [[1], 12 [2], 13 [3]], 14 [[4], 15 [1], 16 [6]], 17 [[7], 18 [8], 19 [9]]], dtype=int32)>

Переменные (Variables)

Обычно объекты tf.Tensor являются иммутабельными, т.е. не изменяемыми. Для хранения весов модели или других мутабельных состояний в TensorFlow обычно используется tf.Variable. Создавать и изменять переменные можно следующими способами:

1var = tf.Variable([0.0, 0.0, 0.0]) 2print(var) 3var.assign([1, 2, 3]) 4print(var) 5var.assign_add([1, 1, 1]) 6print(var)

Вывод в консоль:

1<tf.Variable 'Variable:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)> 2<tf.Variable 'Variable:0' shape=(3,) dtype=float32, numpy=array([1., 2., 3.], dtype=float32)> 3<tf.Variable 'Variable:0' shape=(3,) dtype=float32, numpy=array([2., 3., 4.], dtype=float32)>

Графы и tf.function

TensorFlow предоставляет инструменты для:

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

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

1@tf.function 2def reduce(t): 3 print('Execute reduce function...') 4 return tf.reduce_sum(t)

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

1t1 = tf.constant([1, 2, 3]) 2reduce(t1)

Вывод в консоль:

1Execute reduce function... 2<tf.Tensor: shape=(), dtype=int32, numpy=6>

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

1t2 = tf.constant([3, 3, 3]) 2reduce(t2)

Вывод в консоль:

1<tf.Tensor: shape=(), dtype=int32, numpy=9>

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

1t3 = tf.constant([3., 3., 3.]) 2reduce(t3)

Вывод в консоль:

1Execute reduce function... 2<tf.Tensor: shape=(), dtype=float32, numpy=9.0>

Оптимизированные графы приносят следующую выгоду:

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

Модули, слои и модели

tf.Module это класс для управления экземплярами tf.Variable и объектами tf.function. Класс tf.Module необходим для поддержания двух важных функций:

  • позволяет сохранять и восстанавливать значения переменных с помощью tf.train.Checkpoint. Это полезно в процессе тренировки модели, позволяет быстро сохранить и восстановить ее состояние.
  • позволяет импортировать и экспортировать значения tf.Variable и вычислительные графы tf.function с помощью tf.saved_model. Это позволяет запускать модель независимо от python программы, с помощью которой она была создана.

Рассмотрим простейший пример создания модуля, с помощью tf.Module:

1class OperationModule(tf.Module): 2 def __init__(self, weight: float): 3 self.weight = tf.Variable(weight) 4 5 @tf.function 6 def multiply(self, multiplier: tf.constant) -> float: 7 return self.weight * multiplier 8 9op_module = OperationModule(5) 10op_module.multiply(tf.constant([1, 2, 3]))

Вывод в консоль:

1<tf.Tensor: shape=(3,), dtype=int32, numpy=array([ 5, 10, 15], dtype=int32)>

Сохраним модуль для его последующего переиспользования:

1save_path = './saved' 2tf.saved_model.save(op_module, save_path)

Теперь сохраненная модель не зависит от кода, в котором она создавалась. Попробуем восстановить ее в другую переменную и вызвать функцию multiply:

1reloaded = tf.saved_model.load(save_path) 2reloaded.multiply(tf.constant([5, 15, 20]))

Вывод в консоль:

1<tf.Tensor: shape=(3,), dtype=int32, numpy=array([ 25, 75, 100], dtype=int32)>

tf.keras.layers.Layer и tf.keras.Model – классы, построенные на базе tf.Module, но предоставляющие дополнительную функциональность и удобство при построении, тренировке и сохранении моделей.

Создание модели

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

1from matplotlib import pyplot as plt 2 3x = tf.linspace(-2, 2, 201) # Создаются равномерно расположенные значения в интервале 4x = tf.cast(x, tf.float32) # Приведение тензора к новому типу 5 6def f(x): 7 y = x**2 + 2*x - 5 8 return y 9 10y = f(x) + tf.random.normal(shape=[201]) # Применение функции f к тензору с добавлением случайных значений из нормального распределения 11plt.plot(x.numpy(), y.numpy(), '.', label='Данные') 12plt.plot(x, f(x), label='Функция f') 13plt.legend()

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

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

1class Model(tf.Module): 2 def __init__(self): 3 # Генерация трех случайных весов в интервале от 0 до 5 4 rand_init = tf.random.uniform(shape=[3], minval=0., maxval=5., seed=22) 5 # Инициализация весов модели случайными 6 self.w_q = tf.Variable(rand_init[0]) 7 self.w_l = tf.Variable(rand_init[1]) 8 self.b = tf.Variable(rand_init[2]) 9 10 @tf.function 11 def __call__(self, x): 12 # Квадратична функция модели: quadratic_weight * x^2 + linear_weight * x + bias 13 return self.w_q * (x**2) + self.w_l * x + self.b 14 15quad_model = Model()

Добавляем функцию, которая поможет отобразить предсказанные значения модели вместе с исходными и отображаем их:

1def plot_preds(x, y, f, model, title): 2 plt.figure() 3 plt.plot(x, y, '.', label='Данные') 4 plt.plot(x, f(x), label='Функция f') 5 plt.plot(x, model(x), label='Прогноз модели') 6 plt.title(title) 7 plt.legend() 8 9plot_preds(x, y, f, quad_model, 'До тренировки')

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

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

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

1batch_size = 32 # Задаем размер батча 2dataset = tf.data.Dataset.from_tensor_slices((x, y)) # Создаем датасет из тензоров x и y 3dataset = dataset.shuffle(buffer_size=x.shape[0]).batch(batch_size) # Перемешиваем записи в датасете 4 5# Задаем тренировочные параметры 6epochs = 100 7learning_rate = 0.01 8losses = [] 9 10for epoch in range(epochs): 11 for x_batch, y_batch in dataset: 12 with tf.GradientTape() as tape: 13 batch_loss = mse_loss(quad_model(x_batch), y_batch) 14 # Обновляем веса модели в соответствии с вычислениями градиента 15 grads = tape.gradient(batch_loss, quad_model.variables) 16 for g,v in zip(grads, quad_model.variables): 17 v.assign_sub(learning_rate*g) 18 # Сохраняем значения потерь модели на каждой эпохе 19 loss = mse_loss(quad_model(x), y) 20 losses.append(loss) 21 if epoch % 10 == 0: 22 print(f'Среднеквадратичная ошибка для шага {epoch}: {loss.numpy():0.3f}') 23 24# Plot model results 25print("\n") 26plt.plot(range(epochs), losses) 27plt.xlabel("Эпоха") 28plt.ylabel("Среднеквадратичная ошибка (MSE)") 29plt.title('MSE по эпохам')

В результате получим следующий график:

Оценим прогноз модели после обучения:

1plot_preds(x, y, f, quad_model, 'После обучения')

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

Начнем с создания последовательной модели keras с использованием tf.keras.Sequential. Один из простейших слоев в keras – это dense слой. Он способен обучаться многомерным линейным связям вида Y = W*X + b. Слой lambda необходим для выполнения трансформации данных на вход dense слою.

1new_model = tf.keras.Sequential([ 2 tf.keras.layers.Lambda(lambda x: tf.stack([x, x**2], axis=1)), 3 tf.keras.layers.Dense(units=1, kernel_initializer=tf.random.normal)]) 4 5new_model.compile( 6 loss=tf.keras.losses.MSE, 7 optimizer=tf.keras.optimizers.SGD(learning_rate=0.01)) 8 9history = new_model.fit(x, y, 10 epochs=100, 11 batch_size=32, 12 verbose=0) 13 14new_model.save('./new_model.keras') 15 16plt.plot(history.history['loss']) 17plt.xlabel('Эпоха') 18plt.ylim([0, max(plt.ylim())]) 19plt.ylabel('Потери [MSE]') 20plt.title('Keras тренировочный прогресс') 21 22plot_preds(x, y, f, new_model, 'После тренировки: Keras')

После выполнения этого кода будут отображены два графика:

На этом завершаем базовое рассмотрение основных элементов библиотеки TensorFlow. Jupyter ноутбук доступен в нашем репозитории по ссылке.

Тэги:
tensorflow
Дата публикации:
23.04.2024

avatar
master
Admin

Похожие статьи