텐서플로
텐서플로를 사용한 사용자 정의 모델과 훈련
텐서플로 함수와 그래프
텐서플로 그래프 사용법을 알기 위해 예로 입력의 세제곱을 계산하는 함수를 만들어 살펴본다.
import tensorflow as tf
def cube(x):
return x **3
정수나 실수 같은 파이썬 상수나 텐서를 사용하여 이 함수를 호출할 수 있다.
cube(2)
cube(tf.constant(2.0))
tf.function()을 사용하여 이 파이썬 함수를 텐서플로 함수로 바꿀 수 있다.
tf_cube = tf.function(cube)
tf_cube
이 텐서플로 함수는 원래 파이썬 함수처럼 사용할 수 있고 동일한 결과를 (항상 텐서로) 반환한다.
tf_cube(2)
tf_cube(tf.constant(2.0))
내부적으로 tf.function()은 cube() 함수에서 수행되는 계산을 분석하고 동일한 작업을 수행하는 계산 그래프를 생성하는 데, 아주 간단하게 수행된다. 또다른 방법은 tf.function 데코레이터가 있으며 실제로 더 널리 사용된다.
@tf.function
def tf_cube(x):
return x ** 3
원본 파이썬 함수는 필요할 때 여전히 텐서플로 함수의 python_function 속성으로 참조할 수 있다.
tf_cube.python_function(2) # 8 출력
텐서플로는 사용하지 않는 노드를 제거하고 표현을 단순화 하는 등 계산 그래프를 최적화한다. 최적화된 그래프가 준비되면 텐서플로 함수는 적절한 순서에 맞춰 가능하면 병렬로 그래프 내의 연산을 효율적으로 실행한다. 그렇기 때문에 일반적으로 텐서플로 함수는 원본 파이썬 함수보다 훨씬 빠르게 실행되며, 특히 복잡한 연산을 수행할 때 더 두드러진다.
파이썬 함수를 빠르게 실행하려면 텐서플로 함수로 변환하는 게 좋다.
tf.function() 을 호출할 때 jit_compile=True 로 설정하면 텐서플로는 XLA(eccelerated linear algebra)를 사용하여 해당 그래프를 위한 전용 커널을 컴파일하며, 종종 여러 연산을 융합한다.
사용자 정의 손실 함수, 사용자 정의 지표, 사용자 정의 층 또는 다른 사용자 정의 함수를 작성하고 이를 케라스 모델에 사용할 때, 케라스는 자동으로 이 함수를 텐서플로 함수로 변환한다. 따라서 tf.function()을 사용할 필요가 없다.
기본적으로 텐서플로 함수는 호출에 사용되는 입력 크기와 데이터 타입에 맞춰 매번 새로운 그래프를 생성한다.
오토그래프와 트레이싱
[텐서플로가 그래프를 생성하는 방법]
- 오토그래프: 파이썬 함수의 소스 코드를 분석, for 문, while문, if 문, 제어문(, break, continue, return 등)을 모두 찾는다.
- 함수의 코드를 분석한 후 오토그래프는 이 함수의 모든 제어문을 텐서플로 연산으로 바꾸어 업그레이드된 버전을 만든다.
- 텐서플로가 이 업그레이드된 함수를 호출하며, 이 함수는 그래프 모드로 실행된다. (호출 시 매개변수 값을 전달하는 대신 심볼릭 텐서 전달)
- 심볼릭 텐서는 실제하는 값이 없고, 이름, 데이터 타입, 크기만 가진다.
- 그래프 모드: 각 텐서프롤 연산이 해당 연산을 나타내고 텐서를 출력하기 위해 그래프에 노드 추가
- 최종 그래프는 트레이싱 과정을 통해 생성된다.
텐서플로 함수 사용법
대부분의 경우 텐서플로 연산을 수행하는 파이썬 함수를 텐서플로 함수로 바꾸는 것은 @tf.function 데코레이터를 사용하면 케라스가 나머지를 알아서 처리하기 때문에 간단하다. 하지만 몇 가지 지켜야할 규칙이 있다.
[텐서플로 함수 사용 시 따라야 할 규칙]
1. 넘파이나 표준 라이브러리를 포함해서 다른 라이브러리를 호출하면 트레이싱 과정에서 실행되며, 이 호출은 그래프에 포함되지 않는다. 실제 텐서플로 그래프는 텐서플로 구성 요소(텐서, 연산, 변수, 데이터셋 등)만 포함될 수 있다.
- 트레이싱 과정에서 코드가 실행되는 것을 원하지 않는다면 np.sum() 대신에 tf.reduce_sum()을, sorted() 내장 함수 대신에 tf.sort()와 같이 사용하면 된다.
- np.random.rand()를 반환하는 텐서플로 함수 f(x)를 정의한다면, 이 함수가 트레이싱될 때 난수가 생성된다. f(tf.constatnt(2.))와 f(tf.constant(3.))은 같은 난수를 반환하지만 f(tf.constant([2., 3.]))은 다른 값을 반환한다. 따라서, np.random.rand()를 tf.random.uniform([])으로 바꾸면 이 연산이 그래프의 일부분이 되므로 호출할 때마다 난수가 생성된다.
- 텐서플로에서 지원하지 않는 코드가 어떤 것을 로깅하거나 파이썬 카운터를 업데이트 하는 등의 부수적인 작업을 하면 함수를 트레이싱할 때 호출되므로 텐서플로 함수를 호출할 때 이 코드가 실행되지 않는다.
- 임의의 코드를 tf.py_function()으로 감쌀 수 있다. 하지만 텐서플로가 이 코드에 대해서 최적화를 수행할 수 없어 성능이 저하되며, 파이썬 가능한 플랫폼에서만 이 그래프가 실행되므로 이식성이 낮아진다.
2. 다른 파이썬 함수나 텐서플로 함수를 호출할 수 있으나, 텐서플로가 계산 그래프에 있는 이러한 함수들의 연산을 감지하므로 동일한 규칙을 따른다. 이 함수들에는 @tf.function 데코레이터를 적용할 필요가 없다.
3. 함수에서 텐서플로 볏누 또는 데이터셋이나 큐와 같은 상태가 있는 다른 텐서플로 객체를 만든다면 처음 호출될 때만 수행되어야 하며, 그렇지 않으면 예외가 발생한다. 일반적으로 텐서플로 함수 밖에서 변수를 생성하는 것이 좋다. 변수에 새로운 값을 할당하려면 = 연산자 대신에 assign() 메서드를 사용하는 게 좋다.
4. 파이썬 함수의 소스 코드는 텐서플로에서 사용 가능해야 한다. 만약 소스 코드를 사용할 수 없다면 그래프 생성 과정이 실패하거나 일부 기능을 사용할 수 없게 된다.
- 예를 들어 소스 코드에 접근할 수 없는 파이썬 셸에서 함수를 정의
- 또는 컴파일된 *.pyc 파이썬 파일을 제품 환경에 배포
5. 텐서플로는 텐서나 tf.data.Dataset을 순회하는 for 문만 감지하기 때문에 for i in range(x) 대신 for i in tf.range(x)를 사용해야 한다. 그렇지 않으면 이 반복문이 그래프에 표현되지 못하고, 트레이싱 단계에서 실행된다.
6. 성능 면에서는 반복문보다 벡터화된 구현을 사용하는 것이 좋다.
텐서플로 가이드: 그래프 소개
출처: 텐서플로 공식 사이트
이 가이드에서는 TensorFlow를 사용하여 코드를 간단하게 변경하고 그래프를 가져오는 방법, 그래프를 저장하고 표시하는 방법을 배운다.
◆ 그래프란?
즉시 실행에는 몇 가지 고유한 장점이 있지만 그래프 실행은 Python 외부에서 이식성을 가능하게 하며 성능이 더 우수한 경향이 있다. 그래프 실행은 텐서 계산이 tf.Graph 또는 간단히 "그래프"라고도 하는 TensorFlow 그래프로 실행됨을 의미한다.
그래프는 계산의 단위를 나타내는 tf.Operation 객체와 연산 간에 흐르는 데이터의 단위를 나타내는 tf.Tensor 객체의 세트를 포함한다. 데이터 구조는 tf.Graph 컨텍스트에서 정의된다. 그래프는 데이터 구조이므로 원래 Python 코드 없이 모두 저장, 실행 및 복원할 수 있다.
◆ 그래프의 이점
그래프를 사용하면 유연성이 크게 향상된다. 모바일 애플리케이션, 임베디드 기기 및 백엔드 서버와 같은 Python 인터프리터가 없는 환경에서 TensorFlow 그래프를 사용할 수 있다. TensorFlow는 그래프를 Python에서 내보낼 때 저장된 모델의 형식으로 그래프를 사용한다.
그래프는 쉽게 최적화되어 컴파일러가 다음과 같은 변환을 수행할 수 있다.
- 계산에서 상수 노드를 접어 텐서의 값을 정적으로 추론한다("일정한 접기").
- 독립적인 계산의 하위 부분을 분리하여 스레드 또는 기기 간에 분할한다.
- 공통 하위 표현식을 제거하여 산술 연산을 단순화한다.
▶위와 같은 변환 및 기타 속도 향상을 수행하기 위한 전체 최적화 시스템으로 Grappler가 있다.
요약하면, 그래프는 TensorFlow가 빠르게, 병렬로, 그리고 효율적으로 여러 기기에서 실행할 때 아주 유용하다.
그러나 편의를 위해 Python에서 머신러닝 모델(또는 기타 계산)을 정의한 다음 필요할 때 자동으로 그래프를 구성하려고 한다.
◆ 설정하기
필요한 라이브러리를 가져온다.
import tensorflow as tf
import timeit
from datetime import datetime
◆ 그래프 이용하기
tf.function을 직접 호출 또는 데코레이터로 사용하여 TensorFlow에서 그래프를 만들고 실행한다. tf.function은 일반 함수를 입력으로 받아 Function을 반환한다. Function은 Python 함수로부터 TensorFlow 그래프를 빌드하는 Python callable이다. Python의 경우와 동일한 방식으로 Function를 사용한다.
※ python에서 callable 이란 호출가능한 클래스 인스턴스, 함수, 메서드 등 객체를 의미한다.
# 파이썬 함수 정의.
def a_regular_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# `a_function_that_uses_a_graph` 는 텐서플로 함수.
a_function_that_uses_a_graph = tf.function(a_regular_function)
# 몇 개의 텐서 생성.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)
orig_value = a_regular_function(x1, y1, b1).numpy()
# 텐서플로 함수를 파이썬 함수처럼 부름.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)
겉보기에 Function은 TensorFlow 연산을 사용하여 작성하는 일반 함수처럼 보이나, 그 안을 들여다 보면 매우 다르다. Function는 하나의 API 뒤에서 여러 tf.Graph를 캡슐화하며, 이것이 Function이 속도 및 배포 가능성과 같은 그래프 실행의 이점을 제공하는 방식이다.
tf.function은 함수 및 이 함수가 호출하는 다른 모든 함수에 적용된다.
def inner_function(x, y, b):
x = tf.matmul(x, y)
x = x + b
return x
# `outer_function`을 함수로 만들기 위해 데코레이터 사용.
@tf.function
def outer_function(x):
y = tf.constant([[2.0], [3.0]])
b = tf.constant(4.0)
return inner_function(x, y, b)
# 호출 가능 항목은 'inner_기능'과 'outer_기능'을 포함한 그래프를 생성.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
◆ Python 함수를 그래프로 변환하기
TensorFlow를 사용하여 작성하는 모든 함수에는 if-then 절, 루프, break, return, continue 등과 같은 내장된 TF 연산과 Python 논리가 혼합되어 있다. TensorFlow 연산은 tf.Graph에 의해 쉽게 캡처되지만 Python 관련 논리는 그래프의 일부가 되기 위해 추가 단계를 거쳐야 한다. tf.function은 AutoGraph(tf.autograph)라는 라이브러리를 사용하여 Python 코드를 그래프 생성 코드로 변환한다.
def simple_relu(x):
if tf.greater(x, 0):
return x
else:
return 0
# 'tf_simple_relu'는 'simple_relu'를 감싸는 텐서플로우 함수.
tf_simple_relu = tf.function(simple_relu)
print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
그래프를 직접 볼 필요는 없겠지만 결과를 검사하여 정확한 결과를 확인할 수 있다. 읽기가 쉽지 않으므로 너무 주의 깊게 볼 필요는 없다.
# AutoGraph의 그래프 생성 출력
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
with ag__.FunctionScope('simple_relu', '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 (do_return, retval_)
def set_state(vars_):
nonlocal do_return, retval_
do_return, retval_ = vars_
def if_body():
nonlocal do_return, retval_
try:
do_return = True
retval_ = ag__.ld(x)
except:
do_return = False
raise
def else_body():
nonlocal do_return, retval_
try:
do_return = True
retval_ = 0
except:
do_return = False
raise
ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
return fscope.ret(retval_, do_return)
# 그래프 자체
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
name: "x"
op: "Placeholder"
attr {
key: "_user_specified_name"
value {
s: "x"
}
}
attr {
key: "dtype"
value {
type: DT_INT32
}
}
attr {
key: "shape"
value {
shape {
}
}
}
}
node {
name: "Greater/y"
op: "Const"
attr {
key: "dtype"
value {
type: DT_INT32
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_INT32
tensor_shape {
}
int_val: 0
}
}
}
}
node {
name: "Greater"
op: "Greater"
input: "x"
input: "Greater/y"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
node {
name: "cond"
op: "StatelessIf"
input: "Greater"
input: "x"
attr {
key: "Tcond"
value {
type: DT_BOOL
}
}
attr {
key: "Tin"
value {
list {
type: DT_INT32
}
}
}
attr {
key: "Tout"
value {
list {
type: DT_BOOL
type: DT_INT32
}
}
}
attr {
key: "_lower_using_switch_merge"
value {
b: true
}
}
attr {
key: "_read_only_resource_inputs"
value {
list {
}
}
}
attr {
key: "else_branch"
value {
func {
name: "cond_false_47"
}
}
}
attr {
key: "output_shapes"
value {
list {
shape {
}
shape {
}
}
}
}
attr {
key: "then_branch"
value {
func {
name: "cond_true_46"
}
}
}
}
node {
name: "cond/Identity"
op: "Identity"
input: "cond"
attr {
key: "T"
value {
type: DT_BOOL
}
}
}
node {
name: "cond/Identity_1"
op: "Identity"
input: "cond:1"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
node {
name: "Identity"
op: "Identity"
input: "cond/Identity_1"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
library {
function {
signature {
name: "cond_false_47"
input_arg {
name: "cond_placeholder"
type: DT_INT32
}
output_arg {
name: "cond_identity"
type: DT_BOOL
}
output_arg {
name: "cond_identity_1"
type: DT_INT32
}
}
node_def {
name: "cond/Const"
op: "Const"
attr {
key: "dtype"
value {
type: DT_BOOL
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_BOOL
tensor_shape {
}
bool_val: true
}
}
}
}
node_def {
name: "cond/Const_1"
op: "Const"
attr {
key: "dtype"
value {
type: DT_BOOL
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_BOOL
tensor_shape {
}
bool_val: true
}
}
}
}
node_def {
name: "cond/Const_2"
op: "Const"
attr {
key: "dtype"
value {
type: DT_INT32
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_INT32
tensor_shape {
}
int_val: 0
}
}
}
}
node_def {
name: "cond/Const_3"
op: "Const"
attr {
key: "dtype"
value {
type: DT_BOOL
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_BOOL
tensor_shape {
}
bool_val: true
}
}
}
}
node_def {
name: "cond/Identity"
op: "Identity"
input: "cond/Const_3:output:0"
attr {
key: "T"
value {
type: DT_BOOL
}
}
}
node_def {
name: "cond/Const_4"
op: "Const"
attr {
key: "dtype"
value {
type: DT_INT32
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_INT32
tensor_shape {
}
int_val: 0
}
}
}
}
node_def {
name: "cond/Identity_1"
op: "Identity"
input: "cond/Const_4:output:0"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
ret {
key: "cond_identity"
value: "cond/Identity:output:0"
}
ret {
key: "cond_identity_1"
value: "cond/Identity_1:output:0"
}
attr {
key: "_construction_context"
value {
s: "kEagerRuntime"
}
}
arg_attr {
value {
attr {
key: "_output_shapes"
value {
list {
shape {
}
}
}
}
}
}
}
function {
signature {
name: "cond_true_46"
input_arg {
name: "cond_identity_1_x"
type: DT_INT32
}
output_arg {
name: "cond_identity"
type: DT_BOOL
}
output_arg {
name: "cond_identity_1"
type: DT_INT32
}
}
node_def {
name: "cond/Const"
op: "Const"
attr {
key: "dtype"
value {
type: DT_BOOL
}
}
attr {
key: "value"
value {
tensor {
dtype: DT_BOOL
tensor_shape {
}
bool_val: true
}
}
}
}
node_def {
name: "cond/Identity"
op: "Identity"
input: "cond/Const:output:0"
attr {
key: "T"
value {
type: DT_BOOL
}
}
}
node_def {
name: "cond/Identity_1"
op: "Identity"
input: "cond_identity_1_x"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
ret {
key: "cond_identity"
value: "cond/Identity:output:0"
}
ret {
key: "cond_identity_1"
value: "cond/Identity_1:output:0"
}
attr {
key: "_construction_context"
value {
s: "kEagerRuntime"
}
}
arg_attr {
value {
attr {
key: "_output_shapes"
value {
list {
shape {
}
}
}
}
attr {
key: "_user_specified_name"
value {
s: "x"
}
}
}
}
}
}
versions {
producer: 1994
min_consumer: 12
}
다음 내용
[출처]
핸즈 온 머신러닝
https://etloveguitar.tistory.com/142
텐서플로 가이드 https://www.tensorflow.org/guide/intro_to_graphs?hl=ko
'[파이썬 Projects] > <파이썬 딥러닝, 신경망>' 카테고리의 다른 글
[딥러닝] 심층 신경망 훈련 - 4 (1) | 2024.11.26 |
---|---|
[딥러닝] 텐서플로를 사용한 데이터 적재와 전처리 (0) | 2024.11.25 |
[딥러닝] 심층 신경망 훈련 - 3 (1) | 2024.11.25 |
[딥러닝] 텐서플로를 사용한 사용자 정의 모델과 훈련 (0) | 2024.11.24 |
[딥러닝] 심층 신경망 훈련 (1) | 2024.11.24 |