TensorFlow.org에서 보기 |
Google Colab에서 실행 |
GitHub에서 소스 보기 |
노트북 다운로드 |
텐서플로 2에서는 즉시 실행(eager execution)이 기본적으로 활성화되어 있습니다. 직관적이고 유연한 사용자 인터페이스를 제공하지만 성능과 배포에 비용이 더 듭니다(하나의 연산을 실행할 때는 훨씬 간단하고 빠릅니다).
성능을 높이고 이식성이 좋은 모델을 만들려면 tf.function을 사용해 그래프로 변환하세요. 하지만 조심해야 할 점이 있습니다. tf.function은 무조건 속도를 높여주는 마법의 은총알이 아닙니다!
이 가이드는 tf.function의 이면에 있는 개념을 이해하고 효과적으로 사용할 수 있도록 돕습니다.
여기서 배울 주요 내용과 권고 사항은 다음과 같습니다:
- 즉시 실행 모드에서 디버깅한 다음
@tf.function으로 데코레이팅하세요. - 객체 변경(object mutation)이나 리스트 요소 추가 같은 Python의 부수 효과에 의존하지 마세요.
tf.function은 텐서플로 연산과 가장 잘 동작합니다: 넘파이와 파이썬 호출은 상수로 바뀝니다.
설정
# Update TensorFlow, as this notebook requires version 2.9 or later
!pip install -q -U tensorflow>=2.9.0
import tensorflow as tf
2022-12-14 22:12:06.346776: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory 2022-12-14 22:12:06.346872: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory 2022-12-14 22:12:06.346882: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
에러 출력을 위한 헬퍼 함수를 정의합니다:
import traceback
import contextlib
# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
try:
yield
except error_class as e:
print('Caught expected exception \n {}:'.format(error_class))
traceback.print_exc(limit=2)
except Exception as e:
raise e
else:
raise Exception('Expected {} to be raised but no error was raised!'.format(
error_class))
기초
사용법
정의하는 Function(예: @tf.function 데코레이터를 적용하는 예시)은 핵심 TensorFlow 연산과 매우 비슷합니다. 즉, 즉시 실행할 수 있으며 그래디언트 계산과 같은 작업이 가능합니다.
@tf.function # The decorator converts `add` into a `Function`.
def add(a, b):
return a + b
add(tf.ones([2, 2]), tf.ones([2, 2])) # [[2., 2.], [2., 2.]]
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
[2., 2.]], dtype=float32)>
v = tf.Variable(1.0)
with tf.GradientTape() as tape:
result = add(v, 1.0)
tape.gradient(result, v)
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>
다른 함수 내부에 사용할 수 있습니다.
@tf.function
def dense_layer(x, w, b):
return add(tf.matmul(x, w), b)
dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
[3., 3.],
[3., 3.]], dtype=float32)>
Function은 Eager 코드보다 빠릅니다. 특히 그래프에 작은 ops가 많을 때 그렇습니다. 하지만 (합성곱처럼) 계산량이 많은 ops 몇 개로 이루어진 그래프는 속도 향상이 크지 않습니다.
import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)
@tf.function
def conv_fn(image):
return conv_layer(image)
image = tf.zeros([1, 200, 200, 100])
# Warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")
Eager conv: 0.006060872000489326 Function conv: 0.006282959999225568 Note how there's not much difference in performance for convolutions
추적
이 섹션에서는 향후 변경될 수 있는 구현 세부 정보를 포함하여 내부에서 Function이 작동하는 방식을 노출합니다. 그러나 추적이 발생하는 이유와 시기를 이해하면 tf.function을 효과적으로 사용하기가 훨씬 쉽습니다!
"추적"이란 무엇입니까?
Function은 TensorFlow Graph에서 프로그램을 실행합니다. 그러나 tf.Graph는 사용자가 즉시 실행 TensorFlow 프로그램에서 작성하고자 하는 모든 요소를 나타낼 수는 없습니다. 예를 들어 Python은 다형성을 지원하지만 tf.Graph는 입력에 데이터 유형과 차원의 지정을 요구합니다. 또는 사용자가 명령줄 인수 읽기, 오류 발생 또는 더 복잡한 Python 객체 작업과 같은 부수적인 작업을 수행할 수도 있지만, 이 중 어떤 작업도 tf.Graph에서 실행할 수 없습니다.
Function은 코드를 두 단계로 분리하여 이러한 문제를 해소합니다.
"추적"이라고 하는 첫 번째 단계에서
Function은 새tf.Graph를 만듭니다. Python 코드는 정상적으로 실행되지만 모든 TensorFlow 연산(예: 두 개의 텐서 추가)이 지연되어, 결국 실행되지 않고tf.Graph에 의해 캡처됩니다.두 번째 단계에서는 첫 번째 단계에서 지연된 모든 부분을 포함하는
tf.Graph가 실행됩니다. 이 단계는 추적 단계보다 훨씬 빠릅니다.
입력에 따라 Function이 호출시 항상 첫 번째 단계를 실행하지는 않습니다. 이 결정이 내려지는 방식을 더 잘 이해하려면 아래의 "추적 규칙"을 참조합니다. 첫 번째 단계를 건너뛰고 두 번째 단계만 실행하면 TensorFlow가 높은 성능을 발휘합니다.
Function이 추적하기로 결정하면 추적 단계 바로 다음에 두 번째 단계가 이어지므로 Function 호출로tf.Graph가 만들어지는 동시에 실행됩니다. 나중에 get_concrete_function으로 추적 단계만 실행하는 방법을 볼 수 있습니다.
다른 유형의 인수를 Function으로 전달하면 두 단계가 모두 실행됩니다.
@tf.function
def double(a):
print("Tracing with", a)
return a + a
print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()
Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)
Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)
Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)
같은 인수 유형으로 Function을 반복해서 호출하는 경우, 생성되는 그래프가 동일하므로 TensorFlow는 추적 단계를 건너뛰고 이전에 추적한 그래프를 재사용합니다.
# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)
pretty_printed_concrete_signatures()를 사용하여 사용 가능한 모든 추적을 볼 수 있습니다.
print(double.pretty_printed_concrete_signatures())
double(a)
Args:
a: int32 Tensor, shape=()
Returns:
int32 Tensor, shape=()
double(a)
Args:
a: float32 Tensor, shape=()
Returns:
float32 Tensor, shape=()
double(a)
Args:
a: string Tensor, shape=()
Returns:
string Tensor, shape=()
지금까지 tf.function이 TensorFlow의 그래프 추적 로직을 통해 캐시된 동적 디스패치 레이어를 생성하는 과정을 확인했습니다. 다음은 용어에 대한 보충 설명입니다.
tf.Graph는 언어에 구애받지 않고 TensorFlow 계산을 이식 가능하게 원시 형태로 표현한 것입니다.ConcreteFunction은tf.Graph를 래핑합니다.Function은ConcreteFunction의 캐시를 관리하고 입력에 적합한 캐시를 선택합니다.tf.function은 Python 함수를 래핑하여Function객체를 반환합니다.- 추적(tracing)은
tf.Graph를 생성하고 추적(trace)이라고도 하는ConcreteFunction에서 이를 래핑합니다.
추적 규칙
호출하면 Function이 각 인수의 tf.types.experimental.TraceType을 사용하여 기존 ConcreteFunction에 호출 인수를 일치시킵니다. 일치하는 ConcreteFunction이 발견되면 호출이 전달됩니다. 일치하는 항목이 없으면 새 ConcreteFunction이 추적됩니다.
일치하는 항목이 여러 개 있는 경우 가장 구체적인 서명이 선택됩니다. 즉, C++ 또는 Java의 일반 함수 호출과 마찬가지로 매칭이 서브타이핑으로 수행됩니다. 예를 들어 TensorShape([1, 2])는 TensorShape([None, None])의 하위 유형이므로 TensorShape([1, 2])는 TensorShape([None, None])로 생성한 ConcreteFunction에 전달할 수 있지만 TensorShape([1, None])를 사용하는 ConcreteFunction가 존재하고 더 구체적일 경우 더 높은 우선순위를 갖습니다.
TraceType은 다음과 같이 입력 인수에서 결정됩니다.
Tensor의 경우 유형이Tensor의dtype및shape에 의해 매개변수화됩니다. 순위 형상은 순위가 지정되지 않은 형상의 하위 유형입니다. 고정 차원은 알 수 없는 차원의 하위 유형입니다.Variable의 경우 유형이Tensor와 유사하지만 제어 종속성을 올바르게 연결하는 데 필요한 변수의 고유 리소스 ID도 포함합니다.- Python 기본 값의 경우 유형은 값 자체에 해당합니다. 예를 들어
3값의TraceType은int가 아니라LiteralTraceType<3>입니다. list및tuple등과 같은 순서가 유지되는 Python 컨테이너의 경우 유형이 요소 유형에 따라 매개변수화됩니다. 예를 들어[1, 2]의 유형은ListTraceType<LiteralTraceType<1>, LiteralTraceType<2>>이고[2, 1]의 유형은 앞선 유형과는 달리ListTraceType<LiteralTraceType<2>, LiteralTraceType<1>>입니다.dict와 같은 Python 매핑의 경우 유형은 동일한 키에서 실제 값 대신의 값 유형으로의 매핑이기도 합니다. 예를 들어{1: 2, 3: 4}의 유형은MappingTraceType<<KeyValue<1, LiteralTraceType<2>>>, <KeyValue<3, LiteralTraceType<4>>>>입니다. 순서가 정해져 있는 컨테이너와 달리{1: 2, 3: 4}및{3: 4, 1: 2}는 동일한 유형을 갖습니다.__tf_tracing_type__메서드를 구현하는 Python 객체의 경우 해당 메소드가 반환하는 모든 항목이 유형으로 지정됩니다.- 다른 Python 개체의 경우 유형은 매칭을 위해 객체의 Python 동등성 및 해싱을 사용하는 제네릭
TraceType입니다(참고: 객체에 대한 weakref에 의존하므로 객체가 범위 내에 있거나 삭제되지 않은 경우에만 작동합니다).
참고: TraceType은 Function 입력 매개변수를 기반으로 하므로 전역 및 자유 변수에 대한 변경만으로는 새 추적이 생성되지 않습니다. Python 전역 및 자유 변수를 처리할 때 권장되는 방법은 이 섹션을 참고합니다.
재추적 제어
Function이 두 개 이상의 추적을 생성하는 경우 재추적을 수행하면 TensorFlow가 각 입력 세트에 대해 올바른 그래프를 생성하는 데 도움이 됩니다. 그러나 추적은 비용이 많이 드는 작업입니다! 호출할 때마다 Function이 새 그래프를 재추적하면 tf.function을 사용하지 않는 경우보다 코드가 더 느리게 실행됩니다.
추적 동작을 제어하기 위해 다음 방법을 사용할 수 있습니다.
고정된 input_signature를 tf.function에 전달하기
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
print("Tracing with", x)
return tf.where(x % 2 == 0, x // 2, 3 * x + 1)
print(next_collatz(tf.constant([1, 2])))
# You specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
next_collatz(tf.constant([[1, 2], [3, 4]]))
# You specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
next_collatz(tf.constant([1.0, 2.0]))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception
<class 'ValueError'>:
Caught expected exception
<class 'ValueError'>:
Traceback (most recent call last):
File "/tmpfs/tmp/ipykernel_184655/3551158538.py", line 8, in assert_raises
yield
File "/tmpfs/tmp/ipykernel_184655/1851403433.py", line 9, in <module>
next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
inputs: (
tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32))
input_signature: (
TensorSpec(shape=(None,), dtype=tf.int32, name=None)).
Traceback (most recent call last):
File "/tmpfs/tmp/ipykernel_184655/3551158538.py", line 8, in assert_raises
yield
File "/tmpfs/tmp/ipykernel_184655/1851403433.py", line 13, in <module>
next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
inputs: (
tf.Tensor([1. 2.], shape=(2,), dtype=float32))
input_signature: (
TensorSpec(shape=(None,), dtype=tf.int32, name=None)).
유연성을 위해 알 수 없는 차원 사용하기
TensorFlow는 형상에 따라 텐서를 일치시키므로 None 차원을 와일드카드로 사용하면 Function이 크기가 가변적인 입력에 대한 추적을 재사용할 수 있습니다. 길이가 다른 시퀀스 또는 각 배치에 대해 다른 크기의 이미지가 있는 경우에 크기가 가변적인 입력이 발생할 수 있습니다(Transformer 및 Deep Dream 튜토리얼의 예제 참조).
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
print('Tracing with', x)
return x
# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)
파이썬 리터럴 대신 텐서 전달하기
종종 Python 인수는 하이퍼파라미터와 그래프 구성을 제어하는 데 사용됩니다(예: num_layers=10, training=True 또는 nonlinearity='relu'). 따라서 Python 인수가 변경되면 그래프를 다시 추적해야 합니다.
그러나 그래프 구성을 제어하는 데 Python 인수를 사용하지 않을 수도 있습니다. 이러한 경우 Python 값이 변경되면 불필요한 재추적이 실행될 수 있습니다. 예를 들어, AutoGraph가 동적으로 펼쳐지는 훈련 루프를 생각해봅니다. 여러 추적에도 불구하고 생성된 그래프는 실제로 동일하므로 다시 추적할 필요가 없습니다.
def train_one_step():
pass
@tf.function
def train(num_steps):
print("Tracing with num_steps = ", num_steps)
tf.print("Executing with num_steps = ", num_steps)
for _ in tf.range(num_steps):
train_one_step()
print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)
print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
Retracing occurs for different Python arguments.
Tracing with num_steps = 10
Executing with num_steps = 10
Tracing with num_steps = 20
Executing with num_steps = 20
Traces are reused for Tensor arguments.
Tracing with num_steps = Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps = 10
Executing with num_steps = 20
강제로 다시 추적해야 하는 경우 새 Function을 만듭니다. 별도의 Function 객체는 추적을 공유하지 않을 것이 보장됩니다.
def f():
print('Tracing!')
tf.print('Executing')
tf.function(f)()
tf.function(f)()
Tracing! Executing Tracing! Executing
추적 프로토콜 사용하기
가능한 경우 대신 Python 유형을 tf.experimental.ExtensionType으로 변환하는 것이 좋습니다. 또한 ExtensionType의 TraceType은 이와 연결되어 있는 tf.TypeSpec입니다. 따라서 필요한 경우 기본 tf.TypeSpec을 재정의하여 ExtensionType의 Tracing Protocol을 제어할 수 있습니다. 자세한 내용은 확장 유형 가이드의 ExtensionType의 TypeSpec 사용자 정의하기섹션을 참고합니다.
그 외에는 특정 Python 유형과 관련하여 Function이 재추적해야 하는 시기를 직접 제어하기 위해 이에 대한 Tracing Protocol을 직접 구현할 수 있습니다.
@tf.function
def get_mixed_flavor(fruit_a, fruit_b):
return fruit_a.flavor + fruit_b.flavor
class Fruit:
flavor = tf.constant([0, 0])
class Apple(Fruit):
flavor = tf.constant([1, 2])
class Mango(Fruit):
flavor = tf.constant([3, 4])
# As described in the above rules, a generic TraceType for `Apple` and `Mango`
# is generated (and a corresponding ConcreteFunction is traced) but it fails to
# match the second function call since the first pair of Apple() and Mango()
# have gone out out of scope by then and deleted.
get_mixed_flavor(Apple(), Mango()) # Traces a new concrete function
get_mixed_flavor(Apple(), Mango()) # Traces a new concrete function again
# However, each subclass of the `Fruit` class has a fixed flavor, and you
# can reuse an existing traced concrete function if it was the same
# subclass. Avoiding such unnecessary tracing of concrete functions
# can have significant performance benefits.
class FruitTraceType(tf.types.experimental.TraceType):
def __init__(self, fruit_type):
self.fruit_type = fruit_type
def is_subtype_of(self, other):
return (type(other) is FruitTraceType and
self.fruit_type is other.fruit_type)
def most_specific_common_supertype(self, others):
return self if all(self == other for other in others) else None
def __eq__(self, other):
return type(other) is FruitTraceType and self.fruit_type == other.fruit_type
def __hash__(self):
return hash(self.fruit_type)
class FruitWithTraceType:
def __tf_tracing_type__(self, context):
return FruitTraceType(type(self))
class AppleWithTraceType(FruitWithTraceType):
flavor = tf.constant([1, 2])
class MangoWithTraceType(FruitWithTraceType):
flavor = tf.constant([3, 4])
# Now if you try calling it again:
get_mixed_flavor(AppleWithTraceType(), MangoWithTraceType()) # Traces a new concrete function
get_mixed_flavor(AppleWithTraceType(), MangoWithTraceType()) # Re-uses the traced concrete function
<tf.Tensor: shape=(2,), dtype=int32, numpy=array([4, 6], dtype=int32)>
구체적인 함수 얻기
get_concrete_function 메서드를 사용해 트레이싱된 특정 함수를 얻을 수 있습니다.
print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))
Obtaining concrete trace Executing traced function tf.Tensor(b'aa', shape=(), dtype=string) tf.Tensor(b'bb', shape=(), dtype=string)
# You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))
tf.Tensor(b'cc', shape=(), dtype=string)
ConcreteFunction를 인쇄하면 입력 인수(유형 포함)와 그 출력 유형의 요약이 표시됩니다.
print(double_strings)
ConcreteFunction double(a)
Args:
a: string Tensor, shape=()
Returns:
string Tensor, shape=()
구체적인 함수의 서명을 직접 검색할 수도 있습니다.
print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)
호환되지 않는 유형의 구체적인 추적을 사용하면 오류가 발생합니다.
with assert_raises(tf.errors.InvalidArgumentError):
double_strings(tf.constant(1))
Caught expected exception
<class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:
Traceback (most recent call last):
File "/tmpfs/tmp/ipykernel_184655/3551158538.py", line 8, in assert_raises
yield
File "/tmpfs/tmp/ipykernel_184655/3196284684.py", line 2, in <module>
double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_166 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_166]
구체적인 함수의 입력 서명에서 Python 인수가 특별하게 처리된다는 것을 알 수 있습니다. TensorFlow 2.3 이전에는 Python 인수가 구체적인 함수의 서명에서 제거되었습니다. TensorFlow 2.3부터 Python 인수는 서명에 남아 있지만 추적 중에 설정된 값을 사용하도록 제한됩니다.
@tf.function
def pow(a, b):
return a ** b
square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)
ConcreteFunction pow(a, b=2)
Args:
a: float32 Tensor, shape=<unknown>
Returns:
float32 Tensor, shape=<unknown>
assert square(tf.constant(10.0)) == 100
with assert_raises(TypeError):
square(tf.constant(10.0), b=3)
Caught expected exception
<class 'TypeError'>:
Traceback (most recent call last):
File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/monomorphic_function.py", line 1487, in _call_impl
return self._call_with_flat_signature(args, kwargs,
File "/tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/eager/polymorphic_function/monomorphic_function.py", line 1532, in _call_with_flat_signature
raise TypeError(f"{self._flat_signature_summary()} got unexpected "
TypeError: pow(a) got unexpected keyword arguments: b.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmpfs/tmp/ipykernel_184655/3551158538.py", line 8, in assert_raises
yield
File "/tmpfs/tmp/ipykernel_184655/2310937119.py", line 4, in <module>
square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3.
그래프 얻기
각 구체적인 함수는 tf.Graph를 감싸는 호출 가능한 래퍼입니다. tf.Graph 객체를 검색하는 것이 일반적으로 수행해야 하는 작업은 아니지만 구체적인 함수에서 쉽게 얻을 수 있습니다.
graph = double_strings.graph
for node in graph.as_graph_def().node:
print(f'{node.input} -> {node.name}')
[] -> a ['a', 'a'] -> add ['add'] -> Identity
디버깅
일반적으로 tf.function 내부에서 할 때보다 Eager 모드가 디버깅하기 쉽습니다. tf.function으로 데코레이팅하기 전에 Eager 모드에서 에러가 없는지 확인합니다. 디버깅 과정을 위해 tf.config.run_functions_eagerly(True)으로 전체 tf.function을 비활성화하고 나중에 다시 활성화할 수 있습니다.
다음은 tf.function 내에서만 나타나는 문제를 추적할 때 사용할 수 있는 몇 가지 팁입니다.
- Python
print함수는 추적(tracing)하는 동안에만 호출되므로 함수가 (재)추적될 때 추적하는데 도움이 됩니다. tf.print함수는 언제나 실행되므로 실행하는 동안 중간 값을 추적할 때 도움이 됩니다.tf.debugging.enable_check_numerics을 사용하면 쉽게 NaN과 Inf가 발생되는 곳을 추적할 수 있습니다.pdb(Python 디버거)는 추적 중에 어떤 일이 일어나는지 이해하는데 도움이 될 수 있습니다(주의:pdb는 사용자를 AutoGraph로 변환된 소스 코드로 이동시킵니다).
AutoGraph 변환
AutoGraph는 tf.function안에 기본으로 활성화되어 있는 라이브러리이며 Python의 Eager 코드를 그래프 호환 TensorFlow ops로 변환합니다. 여기에는 if, for, while과 같은 제어 흐름이 포함됩니다.
tf.cond와 tf.while_loop 같은 텐서플로 연산을 여전히 사용할 수 있지만 파이썬으로 제어 흐름을 작성하는 것이 만들기도 이해하기도 쉽습니다.
# A simple loop
@tf.function
def f(x):
while tf.reduce_sum(x) > 1:
tf.print(x)
x = tf.tanh(x)
return x
f(tf.random.uniform([5]))
[0.395097852 0.319370866 0.79926908 0.448182821 0.534545779]
[0.375746667 0.308937907 0.663628 0.420404136 0.48884818]
[0.359007984 0.299470544 0.580772758 0.397270799 0.453301787]
[0.344339937 0.290828 0.523226857 0.377611309 0.424609303]
[0.331346363 0.282896698 0.480186611 0.360631227 0.400806427]
[0.31973 0.275583893 0.446393 0.345769882 0.380638719]
[0.309262753 0.268812954 0.418929547 0.332618743 0.363262028]
[0.299766243 0.262519956 0.39602825 0.32087183 0.348084033]
[0.291098654 0.256651 0.376545459 0.310295016 0.334675252]
[0.283145696 0.251160443 0.35970363 0.300705433 0.322715402]
[0.275814 0.246009186 0.344952911 0.291958034 0.311960101]
[0.269026428 0.241163582 0.331891924 0.283935964 0.302219182]
[0.262718678 0.236594483 0.320219725 0.276543975 0.293342143]
[0.256836593 0.232276499 0.309705526 0.269703448 0.285208]
[0.25133431 0.228187427 0.30016917 0.263348848 0.277718306]
[0.246172518 0.224307656 0.291467398 0.257425129 0.270792]
[0.241317406 0.220619902 0.283484846 0.251885563 0.26436162]
[0.23673968 0.217108801 0.276127309 0.246690288 0.258370548]
[0.232413873 0.213760659 0.269317031 0.241804957 0.252770811]
[0.228317648 0.210563242 0.262989193 0.237199858 0.247521505]
[0.224431336 0.207505539 0.257089257 0.232849166 0.242587417]
[0.220737576 0.20457764 0.251571 0.228730202 0.237938166]
[0.217220932 0.201770619 0.246394828 0.224823058 0.2335473]
[0.213867664 0.199076355 0.241526753 0.221110165 0.229391694]
[0.210665509 0.196487486 0.236937299 0.217575923 0.225451022]
[0.20760341 0.193997309 0.232600808 0.214206412 0.221707344]
[0.204671398 0.191599682 0.228494823 0.210989177 0.218144745]
[0.201860532 0.189289033 0.22459957 0.207913101 0.214749053]
[0.199162707 0.187060192 0.2208976 0.204968125 0.211507618]
[0.196570486 0.18490845 0.217373401 0.202145159 0.208409086]
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.19407718, 0.18282945, 0.21401317, 0.19943602, 0.20544322],
dtype=float32)>
관심있다면 오토그래프가 생성한 코드를 확인해 볼 수 있습니다.
print(tf.autograph.to_code(f.python_function))
def tf__f(x):
with ag__.FunctionScope('f', '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 (x,)
def set_state(vars_):
nonlocal x
(x,) = vars_
def loop_body():
nonlocal x
ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)
def loop_test():
return ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1
ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
try:
do_return = True
retval_ = ag__.ld(x)
except:
do_return = False
raise
return fscope.ret(retval_, do_return)
조건문
AutoGraph는 if <condition> 문장을 이와 대등한 tf.cond 호출로 변경합니다. 이런 대체는 <condition>이 텐서일 때 수행됩니다. 그렇지 않다면 if 문장은 Python 조건문으로 실행됩니다.
추적하는 동안 Python 조건문을 실행하기 때문에 정확히 하나의 조건 분기만 그래프에 추가됩니다. Autograph가 없다면 이렇게 추적된 그래프는 데이터 종속 제어 흐름이 있는 경우 대체 분기를 사용할 수 없습니다.
tf.cond는 조건문의 두 분기를 모두 추적하고 그래프에 추가하여 실행 시 분기를 동적으로 선택합니다. 추적에는 의도하지 않은 부작용이 있을 수 있습니다. 자세한 내용은 AutoGraph 추적 효과를 확인하세요.
@tf.function
def fizzbuzz(n):
TensorFlow.org에서 보기
Google Colab에서 실행
GitHub에서 소스 보기
노트북 다운로드