Esta página foi traduzida pela API Cloud Translation.
Switch to English

Introdução aos gráficos e funções

Ver em TensorFlow.org Executar no Google Colab Ver fonte no GitHub Download do caderno

Introdução aos Gráficos e tf.function

Este guia vai abaixo da superfície do TensorFlow e Keras para ver como o TensorFlow funciona. Se você deseja iniciar imediatamente o Keras, consulte nossa coleção de guias do Keras .

Neste guia, você verá o núcleo de como o TensorFlow permite fazer alterações simples no seu código para obter gráficos, como eles são armazenados e representados e como você pode usá-los para acelerar e exportar seus modelos.

Esta é uma introdução resumida; para uma introdução completa a esses conceitos, consulte o guia tf.function .

O que são gráficos?

Nos três guias anteriores, você viu o TensorFlow executando ansiosamente . Isso significa que as operações do TensorFlow são executadas pelo Python, operação por operação e retornando resultados de volta ao Python. O Eager TensorFlow aproveita as GPUs, permitindo colocar variáveis, tensores e até operações em GPUs e TPUs. Também é fácil depurar.

Para alguns usuários, você pode nunca precisar ou querer sair do Python.

No entanto, a execução do TensorFlow op-a-op no Python impede uma série de acelerações disponíveis. Se você pode extrair cálculos de tensores do Python, pode transformá-los em um gráfico .

Gráficos são estruturas de dados que contêm um conjunto de objetos tf.Operation , que representam unidades de computação; e objetos tf.Tensor , que representam as unidades de dados que fluem entre operações. Eles são definidos em um contexto tf.Graph . Como esses gráficos são estruturas de dados, eles podem ser salvos, executados e restaurados sem o código Python original.

É assim que um gráfico simples de duas camadas se parece quando visualizado no TensorBoard.

um gráfico de fluxo tensor de duas camadas

Os benefícios dos gráficos

Com um gráfico, você tem muita flexibilidade. Você pode usar o gráfico TensorFlow em ambientes que não possuem um interpretador Python, como aplicativos móveis, dispositivos incorporados e servidores de back-end. O TensorFlow usa gráficos como o formato para modelos salvos quando os exporta do Python.

Os gráficos também são facilmente otimizados, permitindo que o compilador faça transformações como:

  • Inferir estaticamente o valor dos tensores dobrando nós constantes em seu cálculo ("dobragem constante") .
  • Separe sub-partes de uma computação que sejam independentes e divida-as entre threads ou dispositivos.
  • Simplifique as operações aritméticas, eliminando subexpressões comuns.

Existe todo um sistema de otimização, o Grappler , para realizar essa e outras acelerações.

Em resumo, os gráficos são extremamente úteis e permitem que o seu TensorFlow seja executado rapidamente , em paralelo e com eficiência em vários dispositivos .

No entanto, você ainda deseja definir nossos modelos de aprendizado de máquina (ou outros cálculos) no Python por conveniência e, em seguida, construir gráficos automaticamente quando precisar deles.

Traçando gráficos

A maneira como você cria um gráfico no TensorFlow é usar tf.function , como uma chamada direta ou como um decorador.

 import tensorflow as tf
import timeit
from datetime import datetime
 
 # Define a Python function
def function_to_get_faster(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

# Make some tensors
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
 
array([[12.]], dtype=float32)

tf.function -ized são chamadas de código Python que funcionam da mesma forma que seus equivalentes em Python. Eles têm uma classe específica ( python.eager.def_function.Function ), mas para você eles agem exatamente como a versão não rastreada.

tf.function rastreia recursivamente qualquer função Python que ele chama.

 def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes inner_function() as well as outer_function()
outer_function(tf.constant([[1.0, 2.0]])).numpy()
 
array([[12.]], dtype=float32)

Se você usou o TensorFlow 1.x, notará que em nenhum momento foi necessário definir um espaço Placeholder ou tf.Sesssion .

Controle de fluxo e efeitos colaterais

O controle de fluxo e os loops são convertidos em TensorFlow via tf.autograph por padrão. O Autograph usa uma combinação de métodos, incluindo padronização de construções de loop, desenrolamento e manipulação de AST .

 def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
 
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Você pode chamar diretamente a conversão de autógrafo para ver como o Python é convertido em operações do TensorFlow. Isso é, na maioria das vezes, ilegível, mas você pode ver a transformação.

 # Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
 
def tf__my_function(x):
    with ag__.FunctionScope('my_function', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (retval_, do_return)

        def set_state(vars_):
            nonlocal retval_, do_return
            (retval_, do_return) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) * ag__.ld(x))
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('retval_', 'do_return'), 2)
        return fscope.ret(retval_, do_return)


O autógrafo converte automaticamente cláusulas if-then , loops, break , return , continue e muito mais.

Na maioria das vezes, o Autograph funciona sem considerações especiais. No entanto, existem algumas ressalvas, e o guia tf.function pode ajudar aqui, bem como a referência completa do autógrafo

Vendo a velocidade

Apenas tf.function função de uso de tensor em tf.function não tf.function automaticamente seu código. Para pequenas funções chamadas algumas vezes em uma única máquina, a sobrecarga de chamar um gráfico ou fragmento de gráfico pode dominar o tempo de execução. Além disso, se a maior parte da computação já estava acontecendo em um acelerador, como pilhas de convoluções pesadas por GPU, a velocidade do gráfico não será grande.

Para cálculos complicados, os gráficos podem fornecer uma aceleração significativa. Isso ocorre porque os gráficos reduzem a comunicação do Python com o dispositivo e realizam algumas acelerações.

Esse código vezes algumas execuções em algumas pequenas camadas densas.

 # Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))

 
Eager time: 4.9249971129993355
Graph time: 2.026840765000088

Funções polimórficas

Ao rastrear uma função, você cria um objeto Function é polimórfico . Uma função polimórfica é uma chamada do Python que encapsula vários gráficos de funções concretas atrás de uma API.

Você pode usar esta Function em todos os diferentes tipos de dtypes e formas. Cada vez que você o chama com uma nova assinatura de argumento, a função original é rastreada novamente com os novos argumentos. A Function armazena o tf.Graph correspondente a esse rastreio em uma função concrete_function . Se a função já foi rastreada com esse tipo de argumento, você apenas obtém seu gráfico pré-rastreado.

Conceitualmente, então:

  • Um tf.Graph é a estrutura de dados bruta e portátil que descreve uma computação
  • Uma Function é um caching, rastreamento, despachante sobre ConcreteFunctions
  • Um ConcreteFunction é um invólucro compatível com o desejo em torno de um gráfico que permite executar o gráfico a partir do Python

Inspecionando funções polimórficas

Você pode inspecionar a_function , que é o resultado da chamada de tf.function na função Python my_function . Neste exemplo, chamar uma a_function com três tipos de argumentos resulta em três funções concretas diferentes.

 print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
 
<tensorflow.python.eager.def_function.Function object at 0x7f466417bf60>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

 # Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))

 
Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

 # Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
 
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

Neste exemplo, você está vendo muito longe na pilha. A menos que você esteja gerenciando especificamente o rastreio, normalmente não será necessário chamar funções concretas diretamente, como mostrado aqui.

Revertendo para execução ansiosa

Você pode se deparar com rastreamentos longos de pilha, especialmente aqueles que se referem a tf.Graph ou with tf.Graph().as_default() . Isso significa que você provavelmente está executando em um contexto gráfico. As principais funções do TensorFlow usam contextos gráficos, como model.fit() Keras.

Geralmente, é muito mais fácil depurar uma execução rápida. Os rastreamentos de pilha devem ser relativamente curtos e fáceis de compreender.

Nas situações em que o gráfico dificulta a depuração, você pode voltar a usar a execução rápida para depurar.

Aqui estão algumas maneiras de garantir que você esteja executando ansiosamente:

  • Chame modelos e camadas diretamente como callables

  • Ao usar o Keras compile / fit, em tempo de compilação, use model.compile(run_eagerly=True)

  • Defina o modo de execução global via tf.config.run_functions_eagerly(True)

Usando run_eagerly=True

 # Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
 
 # Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

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

Primeiro, compile o modelo sem ansiedade. Observe que o modelo não é rastreado; apesar do nome, a compile configura apenas funções de perda, otimização e outros parâmetros de treinamento.

 model.compile(run_eagerly=False, loss=loss_fn)
 

Agora, chame fit e veja se a função é rastreada (duas vezes) e, em seguida, o efeito ansioso nunca é executado novamente.

 model.fit(input_data, labels, epochs=3)
 
Epoch 1/3

Currently running eagerly 2020-08-04 01:22:21.848492

Currently running eagerly 2020-08-04 01:22:21.955102
2/2 [==============================] - 0s 1ms/step - loss: 1.4056
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0037
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0019

<tensorflow.python.keras.callbacks.History at 0x7f45f03c5630>

No entanto, se você executar uma única época com muita ansiedade, poderá ver o efeito colateral ansioso duas vezes.

 print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)

 
Running eagerly

Currently running eagerly 2020-08-04 01:22:22.152979
1/2 [==============>...............] - ETA: 0s - loss: 8.7806e-04
Currently running eagerly 2020-08-04 01:22:22.173295
2/2 [==============================] - 0s 5ms/step - loss: 4.6877e-04

<tensorflow.python.keras.callbacks.History at 0x7f45f0312940>

Usando run_functions_eagerly

Você também pode definir globalmente tudo para executar com entusiasmo. Observe que isso só funciona se você rastrear novamente; As funções rastreadas permanecerão rastreadas e executadas como um gráfico.

 # Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
 
Run all functions eagerly.

Currently running eagerly 2020-08-04 01:22:22.202726
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-08-04 01:22:22.206521

Currently running eagerly 2020-08-04 01:22:22.207818

 # Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

 
WARNING:tensorflow:From <ipython-input-17-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

Rastreamento e desempenho

O rastreamento custa algumas despesas gerais. Embora o rastreamento de pequenas funções seja rápido, modelos grandes podem demorar um pouco para serem rastreados. Geralmente, esse investimento é pago rapidamente com um aumento no desempenho, mas é importante estar ciente de que as primeiras épocas de qualquer treinamento em modelo grande podem ser mais lentas devido ao rastreamento.

Não importa o tamanho do seu modelo, você deseja evitar o rastreamento com frequência. Esta seção do guia tf.function discute como definir especificações de entrada e usar argumentos tensores para evitar retrocessos. Se você perceber que está obtendo desempenho incomumente ruim, é bom verificar se está refazendo acidentalmente.

Você pode adicionar um efeito colateral apenas ansioso (como imprimir um argumento Python) para poder ver quando a função está sendo rastreada. Aqui, você vê retrocessos extras, porque novos argumentos do Python sempre acionam o retrocesso.

 # Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  return x * x + tf.constant(2)

# This is traced the first time
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect
print(a_function_with_python_side_effect(tf.constant(3)))

# This retraces each time the Python argument chances
# as a Python argument could be an epoch count or other
# hyperparameter
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))

 
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Próximos passos

Você pode ler uma discussão mais aprofundada na página de referência da API tf.function e no guia .