In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
TensorFlow eager execution —— это императивная программная среда, которая
вычисляет операции немедленно, без построения графов: операции возвращают
конкретные значения вместо построения вычислительного графа для последующего запуска. Это
облегчает начало работы с TensorFlow и отладкой моделей, а также
шаблонный код. Чтобы следовать этому руководству, выполните приведенныее ниже примеры кода
в интерактивном интерпретаторе python
.
Eager execution —— это гибкая платформа машинного обучения для исследований и экспериментов, обеспечивающая:
Eager execution поддерживает большинство операций TensorFlow и акселерацию GPU.
Замечание: Некоторые модели могут испытывать повышенную нагрузку при включенном eager execution. Мы продолжаем работать над улучшением производительности, но пожалуйста сообщите об ошибке если вы обнаружите проблему и поделитесь своим бенчмарком.
In [ ]:
from __future__ import absolute_import, division, print_function, unicode_literals
import os
try:
# %tensorflow_version существует только в Colab.
%tensorflow_version 2.x #gpu
except Exception:
pass
import tensorflow as tf
import cProfile
В Tensorflow 2.0, eager execution включено по умолчанию.
In [ ]:
tf.executing_eagerly()
Сейчас вы можете запускать операции TensorFlow и получать результаты немедленно:
In [ ]:
x = [[2.]]
m = tf.matmul(x, x)
print("hello, {}".format(m))
Включение eager execution меняет поведение операций TensorFlow—сейчас они
немедленно выполняются и возвращают свои значения в Python. Объекты tf.Tensor
ссылаются на конкретные значения вместо символьных дескрипторов на узлы в вычислительном
графе. Так как нет вычислительного графа, который нужно построить и выполнить позже
в сессии, легко можно проверить результаты используя print()
или отладчик. Оценка,
печать, и проверка значений тензора не нарушают последовательность вычислений
градиентов.
Eager execution прекрасно работает NumPy. Операции NumPy
принимают аргументы tf.Tensor
. Операция TensorFlow
tf.math
конвертирует
объекты Python и массивы NumPy в объекты tf.Tensor
. Метод
tf.Tensor.numpy
возвращает значение объекта в виде NumPy ndarray
.
In [ ]:
a = tf.constant([[1, 2],
[3, 4]])
print(a)
In [ ]:
# Поддержка вещания
b = tf.add(a, 1)
print(b)
In [ ]:
# Поддерживается перегрузка операторов
print(a * b)
In [ ]:
# Используем значения NumPy
import numpy as np
c = np.multiply(a, b)
print(c)
In [ ]:
# Получи значение numpy из тензора:
print(a.numpy())
# => [[1 2]
# [3 4]]
Основым преимуществом eager execution является то, что все функциональныее возможности основного языка доступны во время выполнения модели. Поэтому, например, легко написать fizzbuzz:
In [ ]:
def fizzbuzz(max_num):
counter = tf.constant(0)
max_num = tf.convert_to_tensor(max_num)
for num in range(1, max_num.numpy()+1):
num = tf.constant(num)
if int(num % 3) == 0 and int(num % 5) == 0:
print('FizzBuzz')
elif int(num % 3) == 0:
print('Fizz')
elif int(num % 5) == 0:
print('Buzz')
else:
print(num.numpy())
counter += 1
In [ ]:
fizzbuzz(15)
Здесь есть условия зависящие от значения тензора, эти значения выводятся во время выполнения.
Автоматическое дифференцирование
полезно для реализации алгоритмов машинного обучения, таких как
обратное распространение для обучения
нейронных сетей. Во время eager execution, используйте tf.GradientTape
чтобы отслеживать
операции для последующего вычисления градиента.
Вы можете использовать tf.GradientTape
для обучения и/или вычисления градиентов в eager. Это особенно полезно для сложных тренировочных циклов.
Поскольку во время каждого вызова могут выполняться разные операции, все
операции прямого прохода записываются на "ленту". Чтобы вычислить градиент, проиграйте
ленту назад, а затем сбросьте. A Конкретный tf.GradientTape
может вычислить
только один градиент; последующие вызовы выдадут runtime error.
In [ ]:
w = tf.Variable([[1.0]])
with tf.GradientTape() as tape:
loss = w * w
grad = tape.gradient(loss, w)
print(grad) # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)
In [ ]:
# Получим и отформатируем данные mnist
(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()
dataset = tf.data.Dataset.from_tensor_slices(
(tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),
tf.cast(mnist_labels,tf.int64)))
dataset = dataset.shuffle(1000).batch(32)
In [ ]:
# Построим модель
mnist_model = tf.keras.Sequential([
tf.keras.layers.Conv2D(16,[3,3], activation='relu',
input_shape=(None, None, 1)),
tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(10)
])
Даже без обучения вызовем модель и проверим выходные данные в eager execution:
In [ ]:
for images,labels in dataset.take(1):
print("Logits: ", mnist_model(images[0:1]).numpy())
Хотя у моделей keras есть встроенный цикл обучения (использование метода fit
), иногда вам нужна большая кастомизация. Вот пример цикла обучения реализованного с eager:
In [ ]:
optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
loss_history = []
Замечание: Используйте функцию assert в tf.debugging
чтобы проверить выполнение условия. Это работает в eager и graph execution.
In [ ]:
def train_step(images, labels):
with tf.GradientTape() as tape:
logits = mnist_model(images, training=True)
# Добавим assert-ы для проверки размеров выходных данных.
tf.debugging.assert_equal(logits.shape, (32, 10))
loss_value = loss_object(labels, logits)
loss_history.append(loss_value.numpy().mean())
grads = tape.gradient(loss_value, mnist_model.trainable_variables)
optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables))
In [ ]:
def train(epochs):
for epoch in range(epochs):
for (batch, (images, labels)) in enumerate(dataset):
train_step(images, labels)
print ('Epoch {} finished'.format(epoch))
In [ ]:
train(epochs = 3)
In [ ]:
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.xlabel('Batch #')
plt.ylabel('Loss [entropy]')
Объекты tf.Variable
хранят изменяемые значения типа tf.Tensor
, доступные во время
обучения, чтобы упростить автомматическое дифференцирование.
Наборы переменных могут быть инкапсулированы в слои или модели вместе с методами которые работают на них. См. Кастомные слои и модели Keras для подробностей. Основная разница между слоями и моделями это то, что модели добавляют методы такие, как Model.fit
, Model.evaluate
и Model.save
.
Например приведенный выше пример автоматического дифференцирования может быть переписан так:
In [ ]:
class Linear(tf.keras.Model):
def __init__(self):
super(Linear, self).__init__()
self.W = tf.Variable(5., name='weight')
self.B = tf.Variable(10., name='bias')
def call(self, inputs):
return inputs * self.W + self.B
In [ ]:
# Игрушечный датасет точек вокруг 3 * x + 2
NUM_EXAMPLES = 2000
training_inputs = tf.random.normal([NUM_EXAMPLES])
noise = tf.random.normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise
# The loss function to be optimized
def loss(model, inputs, targets):
error = model(inputs) - targets
return tf.reduce_mean(tf.square(error))
def grad(model, inputs, targets):
with tf.GradientTape() as tape:
loss_value = loss(model, inputs, targets)
return tape.gradient(loss_value, [model.W, model.B])
Далее:
In [ ]:
model = Linear()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
steps = 300
for i in range(steps):
grads = grad(model, training_inputs, training_outputs)
optimizer.apply_gradients(zip(grads, [model.W, model.B]))
if i % 20 == 0:
print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))
In [ ]:
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
In [ ]:
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
Примечание: Переменные хранятся до тех пор, пока не будет удалена последняя ссылка на объект python, с которой удалится и переменная.
tf.keras.Model
включает в себя удобный метод save_weights
позволяющий вам легко создавать чекпоинт:
In [ ]:
model.save_weights('weights')
status = model.load_weights('weights')
Используя tf.train.Checkpoint
вы можете получить полный контроль над процессом.
Этот раздел является сокращенной версией руководства чекпоинтов обучения.
In [ ]:
x = tf.Variable(10.)
checkpoint = tf.train.Checkpoint(x=x)
In [ ]:
x.assign(2.) # Присвоим новое значение переменной и сохраним.
checkpoint_path = './ckpt/'
checkpoint.save('./ckpt/')
In [ ]:
x.assign(11.) # Изменим переменную после сохранения.
# Восстановим значения из чекпоинта
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))
print(x) # => 2.0
Чтобы сохранять и загружать модели tf.train.Checkpoint
хранит внутреннее состояние объектов,
не требуя скрытых переменных. Чтобы записать состояние модели model
,
optimizer
и глобальный шаг передайте их в tf.train.Checkpoint
:
In [ ]:
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(10)
])
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
checkpoint_dir = 'path/to/model_dir'
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
model=model)
root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))
Замечание: Во многих обучающих циклах переменные создаются после вызова tf.train.Checkpoint.restore
. Эти переменные будут восстановлены сразу же после создания и проверки того, что контрольная точка была загружена полностью. Подробнее см. руководство по чекпоинтам обучения.
In [ ]:
m = tf.keras.metrics.Mean("loss")
m(0)
m(5)
m.result() # => 2.5
m([8, 9])
m.result() # => 5.5
TensorBoard это инструмент визуализации для понимания, отладки и оптимизации процесса обучения модели. Он использует события summary которые записываются во время работы программы.
Вы можете использовать tf.summary
чтобы писать сводку переменной в eager execution.
Например, чтобы записать сводные данные loss
каждые 100 шагов обучения:
In [ ]:
logdir = "./tb/"
writer = tf.summary.create_file_writer(logdir)
steps = 1000
with writer.as_default(): # или вызовите writer.set_as_default() перед циклом.
for i in range(steps):
step = i + 1
# Посчитайте потери с вашей реальной функцией обучения.
loss = 1 - 0.001 * step
if step % 100 == 0:
tf.summary.scalar('loss', loss, step=step)
In [ ]:
!ls tb/
tf.GradientTape
может быть также использован в динамических моделях. Это пример для
backtracking line search
несмотря на сложный порядок выполнения, алгоритм выглядит как обычный код NumPy, за исключением того что, там есть алгоритмы и
дифференцирование:
In [ ]:
def line_search_step(fn, init_x, rate=1.0):
with tf.GradientTape() as tape:
# Переменные автоматически отслеживаются.
# Но чтобы посчитать градиент от тензора, вам надо его `посмотреть (watch)`.
tape.watch(init_x)
value = fn(init_x)
grad = tape.gradient(value, init_x)
grad_norm = tf.reduce_sum(grad * grad)
init_value = value
while value > init_value - rate * grad_norm:
x = init_x - rate * grad
value = fn(x)
rate /= 2.0
return x, value
In [ ]:
@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
y = tf.identity(x)
def grad_fn(dresult):
return [tf.clip_by_norm(dresult, norm), None]
return y, grad_fn
Кастомные градиенты обычно используются для обеспечения численно стабильного градиента для последовательности операций:
In [ ]:
def log1pexp(x):
return tf.math.log(1 + tf.exp(x))
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
In [ ]:
# Вычисление градиента хорошо работает при x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
In [ ]:
# Однако, x = 100 терпит неудачу из-за числовой нестабильности.
grad_log1pexp(tf.constant(100.)).numpy()
Здесь функция log1pexp
может быть аналитически упрощена с помощью кастомного
градиента. Нижеприведенная реализация переиспользует значение для tf.exp(x)
которое
вычисляется во время прямого прохода, делая ее эффективнее за счет исключения
избыточных вычислений:
In [ ]:
@tf.custom_gradient
def log1pexp(x):
e = tf.exp(x)
def grad(dy):
return dy * (1 - 1 / (1 + e))
return tf.math.log(1 + e), grad
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
In [ ]:
# Как и ранее вычисление градиента работает хорошо при x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
In [ ]:
# И вычисление градиента также работает хорошо при x = 100.
grad_log1pexp(tf.constant(100.)).numpy()
In [ ]:
import time
def measure(x, steps):
# TensorFlow инициализирует GPU при первом использовании, исключим из времени.
tf.matmul(x, x)
start = time.time()
for i in range(steps):
x = tf.matmul(x, x)
# tf.matmul может возвращаться до завершения умножения матрицы
# (например, может возвращаться после включения операции в поток CUDA).
# Вызов x.numpy() ниже гарантирует, что все операции в очереди
# были завершены (и также скопирует результат в память хоста,
# поэтому мы включаем немного больше, чем просто время
# операции matmul).
_ = x.numpy()
end = time.time()
return end - start
shape = (1000, 1000)
steps = 200
print("Время на умножение {} матрицы на себя {} раз:".format(shape, steps))
# Выполнение на CPU:
with tf.device("/cpu:0"):
print("CPU: {} secs".format(measure(tf.random.normal(shape), steps)))
# Выполнение на GPU, если возможно:
if tf.config.experimental.list_physical_devices("GPU"):
with tf.device("/gpu:0"):
print("GPU: {} secs".format(measure(tf.random.normal(shape), steps)))
else:
print("GPU: не найдено")
Объект tf.Tensor
может быть скопирован на другое устройство для выполнения
его операции:
In [ ]:
if tf.config.experimental.list_physical_devices("GPU"):
x = tf.random.normal([10, 10])
x_gpu0 = x.gpu()
x_cpu = x.cpu()
_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU
_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0
Для сложных вычислительных моделей, таких как
ResNet50
обучение на GPU, производительность eager execution сравнима с выполнением tf.function
.
Но разрыв становится больше для моделей с меньшим числом вычислений и необходимо проделать работу
по оптимизации кода для моделей с большим количеством маленьких операций.
Хоть eager execution делает разработку и отладку более интерактивной,
выполнение графа в стиле TensorFlow 1.x имеет преимущества при распределенном обучении, оптимизации
производительности и запуске в продакшн. Чтобы преодолеть этот пробел, TensorFlow 2.0 вводит function
посредством API tf.function
. Для дополнительной информации, см. руководство tf.function.