날짜를 저장하십시오! Google I / O가 5 월 18 일부터 20 일까지 반환됩니다. 지금 등록

그래프 및 함수 소개

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

그래프 및 tf.function 소개

이 가이드는 TensorFlow 및 Keras의 내부를 살펴봄으로써 TensorFlow의 동작 방식을 알아봅니다. 대신 Keras를 바로 시작하려면 Keras 가이드 모음을 참조하세요.

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

참고: TensorFlow 1.x에만 익숙한 사용자를 위해 이 가이드는 매우 다른 그래프 뷰를 보여줍니다.

이 가이드는 짧은 형식의 소개입니다. 개념에 대한 전체 소개는 tf.function 가이드를 참조하세요.

그래프란 무엇인가요?

이전의 3가지 가이드에서 TensorFlow가 즉시(eagerly) 실행되는 것을 보았습니다. 이는 TensorFlow 연산이 Python에 의해 실행되고 연산별로 결과가 다시 Python으로 반환됨을 의미합니다. Eager TensorFlow는 GPU의 장점을 활용하여 GPU와 TPU에 변수, 텐서 및 연산을 배치할 수 있습니다. 디버깅도 쉽습니다.

일부 사용자의 경우 Python가 전혀 필요하지 않거나 Python을 아예 떠나고 싶을 수도 있습니다.

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

그래프는 계산의 단위를 나타내는 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화된 함수는 해당 Python과 같게 동작하는 Python callable입니다. Python callable은 특정 클래스(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로 변환됩니다. Autograph는 루프 구성의 표준화, 언롤링 및 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.]

Autograph 변환을 직접 호출하여 Python이 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)

Autograph는 자동으로 if-then 절, 루프, break, return, continue 등을 변환합니다.

대부분의 경우, Autograph는 특별한 고려 없이 동작합니다. 그러나 몇 가지 주의 사항이 있으며, tf.function 가이드전체 autograph 참조가 도움이 될 수 있습니다.

속도 향상

tf.function에서 텐서를 사용하는 함수를 래핑하는 것만으로는 코드의 속도를 자동으로 높일 수 없습니다. 단일 기기에서 몇 번 호출되는 작은 함수의 경우, 그래프나 그래프 조각 호출의 오버헤드가 런타임의 대부분을 차지할 수도 있습니다. 또한, GPU를 많이 사용하는 컨볼루션과 같이 대부분의 계산이 가속기에서 이미 발생한 경우, 그래프의 속도 향상은 크지 않을 것입니다.

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

다음 코드는 일부 작은 밀집 레이어에서 몇 번 실행됩니다.

# 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: 5.2257935800003
Graph time: 2.298326567999993

다형 함수

함수를 추적할 때 생성하는 Function 객체는 polymorphic입니다. 다형 함수는 하나의 API 뒤에서 몇 가지 concrete 함수 그래프를 캡슐화하는 Python callable입니다.

Function은 모든 종류의 dtypes과 형상에 사용할 수 있습니다. 새로운 인수 서명을 사용하여 호출할 때마다 원래 함수는 새로운 인수로 다시 추적됩니다. 그런 다음 Functionconcrete_function에서의 추적에 해당하는 tf.Graph를 저장합니다. 함수가 이미 해당 종류의 인수로 추적되었다면, 미리 추적된 그래프를 얻습니다.

개념적으로, 다음과 같습니다.

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

다형 함수 검사하기

Python 함수 my_function에서 tf.function 호출의 결과인 a_function을 검사할 수 있습니다. 다음 예제에서는 3가지 종류의 인수를 사용하여 a_function을 호출하면 3가지 concrete 함수가 생성됩니다.

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 0x7f477562c2e8>
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)

이 예제에서는 스택에 상당히 근접한 것을 볼 수 있습니다. 추적을 구체적으로 관리하지 않는 한, 일반적으로 여기에 표시된 concrete 함수를 직접 호출할 필요는 없습니다.

즉시 실행으로 되돌리기

tf.Graph를 참조하거나 tf.Graph().as_default()를 사용하여 참조하는 경우와 같이 긴 스택 추적을 만나게 될 수도 있습니다. 이는 그래프 컨텍스트에서 실행 중일 가능성이 있음을 의미합니다. TensorFlow의 핵심 함수는 Keras의 model.fit()과 같은 그래프 컨텍스트를 사용합니다.

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

그래프에서 디버깅이 까다로워지는 상황에서는 즉시 실행을 사용하여 되돌린 후 디버깅할 수 있습니다.

즉시 실행을 확인하는 방법은 다음과 같습니다.

  • 모델과 레이어를 callable로 직접 호출

  • 컴파일에서 Keras compile/fit 사용 시, 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)

먼저, eager 없이 모델을 컴파일하세요. 모델은 추적되지 않습니다. compile은 손실 함수, 최적화 및 기타 훈련 매개변수만 설정합니다.

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

이제 fit을 호출하고 함수가 추적되고(두 번) eager 효과가 다시 실행되지 않는지 확인하세요.

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

Currently running eagerly 2020-11-12 01:36:12.902783

Currently running eagerly 2020-11-12 01:36:13.019066
2/2 [==============================] - 0s 2ms/step - loss: 1.9175
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0088
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0049
<tensorflow.python.keras.callbacks.History at 0x7f46dc6f6b00>

그러나 eager에서 단 한 번의 epoch만 실행해도 eager 부작용을 두 번 볼 수 있습니다.

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-11-12 01:36:13.216839
1/2 [==============>...............] - ETA: 0s - loss: 0.0043
Currently running eagerly 2020-11-12 01:36:13.243194
2/2 [==============================] - 0s 7ms/step - loss: 0.0023
<tensorflow.python.keras.callbacks.History at 0x7f46dc7f3fd0>

run_functions_eagerly 사용하기

모든 것을 즉시 실행하도록 전역적으로 설정할 수도 있습니다. 재추적할 때만 동작합니다. 추적된 함수는 추적된 상태로 그래프로 실행됩니다.

# Now, globally set everything to run eagerly
tf.config.experimental_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)
WARNING:tensorflow:From <ipython-input-1-6bee22f96a72>: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.
Run all functions eagerly.

Currently running eagerly 2020-11-12 01:36:13.276261
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-11-12 01:36:13.281188

Currently running eagerly 2020-11-12 01:36:13.282615
# Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

추적 및 성능

추적에는 약간의 오버헤드가 발생합니다. 작은 함수를 추적하는 것은 빠르지만 큰 모델은 추적하는 데 시간이 오래 걸릴 수 있습니다. 이러한 노력은 일반적으로 성능 향상으로 신속하게 이어지지만, 대형 모델의 훈련에서 처음 몇 번의 epoch가 추적으로 인해 느려질 수 있다는 점에 유의해야 합니다.

모델 크기와 관계없이, 빈번한 추적은 피해야 합니다. tf.function 가이드의 이 섹션은 재추적을 피하기 위해 입력 명세를 설정하는 방법과 텐서 인수를 사용하는 방법에 관해 설명합니다. 비정상적으로 성능이 저하되고 있음을 발견하면, 실수로 재추적을 하고 있지 않은지 확인하는 것이 좋습니다.

함수가 언제 추적되는지 확인할 수 있도록 eager 부작용(예: Python 인수 인쇄)을 추가할 수 있습니다. 새로운 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.functionAPI 참조 페이지 및 가이드에서 자세한 내용을 읽을 수 있습니다.