이 페이지는 Cloud Translation API를 통해 번역되었습니다.
Switch to English

그래프와 함수 소개

TensorFlow.org에서보기 Google Colab에서 실행 GitHub에서 소스보기 노트북 다운로드

그래프 및 tf.function 소개

이 안내서는 TensorFlow와 Keras의 표면 아래에서 TensorFlow의 작동 방식을 살펴 봅니다. 대신 Keras를 즉시 시작하려면 Keras 안내서 모음을 참조하십시오.

이 안내서에서는 TensorFlow를 사용하여 코드를 간단하게 변경하여 그래프를 가져 오는 방법과 그래프를 저장 및 표현하는 방법 및이를 사용하여 모델을 가속화하고 내보내는 방법의 핵심을 살펴 봅니다.

이것은 짧은 형식의 소개입니다. 이러한 개념에 대한 전체 소개 tf.function 안내서를 참조하십시오.

그래프 란 무엇입니까?

앞의 세 안내서에서 TensorFlow가 열심히 실행되는 것을 보았습니다. 이것은 TensorFlow 작업이 Python에 의해 실행되고 작업이 작업되고 결과가 다시 Python으로 반환됨을 의미합니다. Eager TensorFlow는 GPU를 활용하여 GPU와 TPU에 변수, 텐서 및 작업을 배치 할 수 있습니다. 디버깅도 쉽습니다.

일부 사용자의 경우 Python을 떠나지 않아도되고 싶을 수도 있습니다.

그러나 Python에서 TensorFlow를 op-by-op로 실행하면 여러 가지 가속 기능을 사용할 수 없습니다. 파이썬에서 텐서 계산을 추출 할 수 있다면 그래프 로 만들 수 있습니다.

그래프는 계산 단위를 나타내는 tf.Operation 객체 세트를 포함하는 데이터 구조입니다. 그리고 tf.Tensor 객체는 작업간에 흐르는 데이터 단위를 나타냅니다. 그것들은 tf.Graph 컨텍스트에서 정의됩니다. 이 그래프는 데이터 구조이므로 원래 Python 코드없이 모두 저장, 실행 및 복원 할 수 있습니다.

이것이 TensorBoard에서 시각화 될 때 간단한 2 계층 그래프 모양입니다.

2 층 텐서 플로우 그래프

그래프의 장점

그래프를 사용하면 유연성이 크게 향상됩니다. 모바일 애플리케이션, 임베디드 디바이스 및 백엔드 서버와 같은 Python 인터프리터가없는 환경에서 TensorFlow 그래프를 사용할 수 있습니다. TensorFlow는 그래프를 Python에서 내보낼 때 저장된 모델의 형식으로 그래프를 사용합니다.

그래프는 쉽게 최적화되어 컴파일러가 다음과 같은 변환을 수행 할 수 있습니다.

  • 계산에서 상수 노드를 접음으로써 텐서의 값을 정적으로 추론합니다 ( "일정한 접음") .
  • 독립적 인 계산의 하위 부분을 분리하여 스레드 또는 장치간에 분할합니다.
  • 공통 하위 표현식을 제거하여 산술 연산을 단순화합니다.

이 및 기타 속도 향상을 수행하기위한 전체 최적화 시스템 인 Grappler가 있습니다.

간단히 말해 그래프는 매우 유용하며 TensorFlow를 빠르게 실행 하고 병렬 로 실행 하며 여러 장치 에서 효율적 으로 실행할 수 있습니다 .

그러나 편의를 위해 Python에서 머신 러닝 모델 (또는 기타 계산)을 정의한 다음 필요할 때 자동으로 그래프를 구성하려고합니다.

추적 그래프

TensorFlow에서 그래프를 작성하는 방법은 tf.function 직접 호출 또는 데코레이터로 사용하는 것입니다.

 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 함수는 Python 함수와 동일하게 작동하는 Python 호출 가능 함수입니다. 그들은 특정 클래스 ( python.eager.def_function.Function )를 가지고 있지만 추적되지 않은 버전처럼 작동합니다.

tf.function 호출하는 모든 Python 함수를 재귀 적으로 추적합니다.

 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)

TensorFlow 1.x를 사용한 경우 Placeholder 또는 tf.Sesssion 을 정의 할 필요가 없었 음을 알 수 있습니다.

흐름 제어 및 부작용

흐름 제어 및 루프는 기본적으로 tf.autograph 를 통해 TensorFlow로 변환됩니다. 오토 그래프는 루프 구성 표준화, 언 롤링 및 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.]

오토 그래프 변환을 직접 호출하여 파이썬이 TensorFlow ops로 어떻게 변환되는지 확인할 수 있습니다. 이것은 대부분 읽을 수 없지만 변환을 볼 수 있습니다.

 # 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)


오토 그래프는 if-then 절, 루프, break , return , continue 등을 자동으로 변환 if-then .

대부분의 경우, 오토 그래프는 특별한 고려없이 작동합니다. 그러나 몇 가지주의 사항이 있으며 tf.function 안내서 가 여기에 도움이 될 수 있으며 완전한 사인 참조

속도 향상

tf.function 텐서 사용 함수를 tf.function 해도 코드 속도가 자동으로 빨라지지는 않습니다. 단일 시스템에서 몇 번 호출되는 작은 함수의 경우 그래프 또는 그래프 조각을 호출하는 오버 헤드가 런타임을 지배 할 수 있습니다. 또한 GPU가 많은 컨볼 루션 스택과 같은 가속기에서 대부분의 계산이 이미 수행 된 경우 그래프 속도가 크지 않습니다.

복잡한 계산의 경우 그래프는 상당한 속도 향상을 제공 할 수 있습니다. 그래프는 Python-to-device 통신을 줄이고 일부 속도 향상을 수행하기 때문입니다.

이 코드는 작은 조밀 한 레이어에서 몇 번 실행됩니다.

 # 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

다형성 함수

함수를 추적 할 때 Function 객체는 polymorphic 입니다. 다형성 함수는 하나의 API 뒤에 몇 가지 구체적인 함수 그래프를 캡슐화하는 Python 호출 가능 함수입니다.

Function 은 모든 종류의 dtypes 과 shape에 사용할 수 있습니다. 새로운 인수 시그니처를 사용하여 호출 할 때마다 원래 함수는 새로운 인수로 다시 추적됩니다. Function 다음 저장 tf.Graph A의 트레이스에 대응 concrete_function . 함수가 이미 그런 종류의 인수로 추적 되었다면, 당신은 미리 추적 된 그래프를 얻습니다.

개념적으로

  • tf.Graph 는 계산을 설명하는 원시 휴대용 데이터 구조입니다.
  • Function 는 ConcreteFunctions에 대한 캐싱, 추적, 디스패처입니다.
  • ConcreteFunction 은 파이썬에서 그래프를 실행할 수있는 그래프 주위의 열성적인 래퍼입니다.

다형성 함수 검사

파이썬 함수 my_function 에서 tf.function 을 호출 한 결과 인 a_function 을 검사 할 수 있습니다. 이 예에서 세 종류의 인수로 a_function 을 호출 a_function 세 가지 다른 구체적인 함수가 생성됩니다.

 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)

이 예제에서는 스택에 상당히 많이 접근하고 있습니다. 추적을 구체적으로 관리하지 않는 한 일반적으로 여기에 표시된대로 구체적인 기능을 직접 호출 할 필요는 없습니다.

열망하는 실행으로 되돌리기

긴 스택 추적, 특히 tf.Graph 또는 with tf.Graph().as_default() 를 참조하는 긴 추적 추적을 볼 수 있습니다. 이것은 그래프 컨텍스트에서 실행되고 있음을 의미합니다. TensorFlow의 핵심 함수는 model.fit()model.fit() 과 같은 그래프 컨텍스트를 사용합니다.

열망하는 실행을 디버깅하는 것이 훨씬 쉬운 경우가 많습니다. 스택 트레이스는 비교적 짧고 이해하기 쉬워야합니다.

그래프에서 디버깅이 까다로워지는 상황에서는 간절한 실행을 사용하여 디버깅 할 수 있습니다.

열심히 달리는 방법은 다음과 같습니다.

  • 모델과 레이어를 호출 가능 항목으로 직접 호출

  • model.compile(run_eagerly=True) 컴파일 / 피트를 사용할 때 컴파일 타임에 model.compile(run_eagerly=True)

  • tf.config.run_functions_eagerly(True) 를 통해 전역 실행 모드 설정

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)
 

먼저, 열심없이 모델을 컴파일하십시오. 모델은 추적되지 않습니다. 이름에도 불구하고 compile 은 손실 함수, 최적화 및 기타 교육 매개 변수 만 설정합니다.

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

이제 fit 호출하고 함수가 추적되고 (두 번) 열성 효과가 다시 실행되지 않는지 확인하십시오.

 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>

그러나 단 한 번의 에포크도 실행하면 열망하는 부작용을 두 번 볼 수 있습니다.

 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>

run_functions_eagerly 사용

모든 것을 열심히 실행하도록 전역 적으로 설정할 수도 있습니다. 다시 추적하는 경우에만 작동합니다. 추적 된 기능은 계속 추적되어 그래프로 실행됩니다.

 # 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.

추적 및 성능

추적에는 약간의 오버 헤드가 발생합니다. 작은 기능을 추적하는 것은 빠르지 만 큰 모델은 추적하는 데 시간이 많이 걸릴 수 있습니다. 이 투자는 일반적으로 성능 향상으로 신속하게 상환되지만 대형 모델 교육의 처음 몇 획기가 추적으로 인해 느려질 수 있다는 점에 유의해야합니다.

모델의 크기에 상관없이 자주 추적하지 않으려 고합니다. tf.function 안내서 의이 섹션에서는 입력 사양을 설정하고 텐서 인수를 사용 하여 리 레이싱을 피하는 방법에 대해 설명합니다. 비정상적으로 성능이 저하되는 것을 발견 한 경우 실수로 다시 추적하는지 확인하는 것이 좋습니다.

함수가 언제 추적되는지 알 수 있도록 열성적인 부작용 (예 : Python 인수 인쇄)을 추가 할 수 있습니다. 새로운 파이썬 인수가 항상 리트 레이싱을 트리거하기 때문에 추가 리트 레이싱이 표시됩니다.

 # 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)

다음 단계

tf.function API 참조 페이지 및 안내서 에서보다 자세한 내용을 읽을 수 있습니다.