[Computer Vision] 2(2). 텐서플로 2와 케라스 자세히 알아보기
텐서플로 2와 케라스 자세히 알아보기
이제 텐서플로 2의 주요 핵심 개념을 자세히 살펴보자.
핵심 개념
2019년 봄에 단순함과 사용 편의성에 초점을 맞춰 텐서플로의 새로운 버전이 출시됐다. 이 절에서는 텐서플로의 기본 개념을 소개하고 버전1에서 버전2로 어떻게 진화했는지 다룬다.
텐서 소개
텐서는 N차원의 배열이다. 텐서플로에서 Tensor 객체는 수치 값을 저장하기 위해 사용하며, 각 Tensor 객체는 다음 요소를 갖추고 있다.
- 타입: string, float32, float16, int8 등
- 형상: 데이터 차원.
- 순위: 차원 개수.
이미지와 같이 높이와 너비를 미리 알 수 없는 경우(부분적으로 형상을 알 수 없는 경우) 입력 형상을 (None, None, 3)과 같이 나타낸다.
텐서플로 그래프
연산이란 입력을 출력으로 변환하는 것이며, 텐서플로는 이 연산을 표현하기 위해 그래프를 사용한다. 이 그래프 개념은 텐서플로의 동작을 이해하는데 있어 매우 중요하다.
그래프를 활용하면 다음과 같은 장점이 있다.
- CPU에서 일부 연산을 실행하고 GPU에서 남은 연산을 실행한다.
- 분산 모델의 경우 그래프의 다양한 부분을 여러 다른 컴퓨터에서 실행한다.
- 불필요한 연산을 피하기 위해 그래프를 최적화해 계산 성능을 개선한다.
느긋한 실행과 조급한 실행 비교
- 텐서플로 1까지는 기본적으로 항상 느긋한 실행(lazy execution)을 사용했는데, 이는 프레임워크에 구체적으로 요청하기 전까지 연산이 실행되지 않기 때문이다.
- 반면 텐서플로 2는 조급한 실행(eager execution)을 지원한다.
import tensorflow as tf
a = tf.constant([1,2,3])
b = tf.constant([0,0,1])
c = tf.add(a,b)
print(c)
tf.Tensor([1 2 4], shape=(3,), dtype=int32)
만약 위의 코드를 텐서플로 1에서 실행했다면 결과는 다음과 같다.
Tensor(“Add:0”, shape=(3,), dtype=int32)
텐서플로 2에서 그래프 생성하기
먼저 그래프 생성과 최적화 과정을 보여줄 수 있는 간단한 예제를 살펴보자.
def compute(a, b, c):
d = a * b + c
e = a * b * c
return d, e
위 함수의 경우 d와 e를 계산할 때 동일한 연산인 a * b를 수행한다. 느긋한 실행에서는 그래프 최적화기가 a * b가 두번 실행되는 것을 피하기 위해 결과를 캐시에 저장하고 필요할 때 재사용한다. 또한 더 복잡한 연산의 경우 최적화기는 계산 속도를 높이기 위해 병렬 처리를 사용할 수 있다.
하지만 조급한 실행에서는 이러한 최적화 기법이 적용될 수 없다. 다행히도 텐서플로는 이를 해결할 수 있는 모듈을 포함하고 있는데, 바로 텐서플로 오토그래프이다.
텐서플로 오토그래프와 tf.function
텐서플로 오토그래프 모듈은 자동 최적화를 가능하게 해 조급한 실행 코드를 그래프로 변환하기 쉽게 만든다.
이는 단순히 함수의 맨 앞에 tf.function 데코레이터를 추가함으로써 수행된다.
@tf.function
def compute(a, b, c):
d = a * b + c
e = a * b * c
return d, e
✋ 파이썬 데코레이터는 함수를 감싸고 그 함수에 기능을 추가하거나 변경하는 것이 가능하게 해주는 개념이다.
일반적으로 오토 그래프는 다음의 경우에 사용한다.
- 모델을 다른 기기로 내보내야 할 때
- 성능이 무엇보다 중요하고 그래프 최적화를 통해 속도 개선이 필요할 때
그래프의 또 다른 장점으로 자동 미분을 들 수 있다. 조급한 실행모드에서 각 연산은 독립적이기 때문에 기본적으로 자동 미분이 가능하지 않지만, 텐서플로 2는 이를 가능하게 만드는 방법으로 그래디언트 테이프(gradient tape)를 제공한다.
그래디언트 테이프를 사용해 오차 역전파하기
여기서는 간단히 AxX = B 를 만족하는 X를 풀어야 한다고 가정하고, 그러기 위해 간단한 손실 abs(AxX - B)를 최소화할 것이다.
그래디언트 테이프를 사용하지 않는 경우 텐서플로는 연산을 저장하는 대신 작업 결과를 계산한다. 따라서 연산과 그 연산의 입력에 대한 정보가 없으면 자동으로 손실을 미분할 수 없다.
A, B = tf.constant(3.0), tf.constant(6.0)
X = tf.Variable(20.0) # 랜덤 값
loss = tf.math.abs(A*X-B)
print(loss)
tf.Tensor(54.0, shape=(), dtype=float32)
이제 tf.GradientTape를 사용해보자. tf.GradientTape의 컨텍스트에서 손실을 계산함으로써 텐서플로는 자동으로 모든 연산을 기록하고 그런 다음 역으로 이 모든 연산을 재생한다.
def train_step():
with tf.GradientTape() as tape:
loss = tf.math.abs(A*X-B)
dX = tape.gradient(loss, X)
print('X = {:.2f}, dX = {:2f}'.format(X.numpy(), dX))
X.assign(X - dX)
for i in range(7):
train_step()
X = 20.00, dX = 3.000000
X = 17.00, dX = 3.000000
X = 14.00, dX = 3.000000
X = 11.00, dX = 3.000000
X = 8.00, dX = 3.000000
X = 5.00, dX = 3.000000
X = 2.00, dX = 0.000000
위 코드는 단일 훈련 단계를 정의한다.
train_step이 호출될 때마다 손실이 그래디언트 테이프의 컨텍스트에서 제공된다. 그 다음 이 컨텍스트는 경사를 계산하기 위해 사용된다. 그러고 나서 X 변수가 업데이트 된다. 실제로 X가 공식의 해로 수렴하는 것을 볼 수 있다.
케라스 모델과 계층
앞에서 얻은 model 객체에는 여러 가지 유용한 메서드와 속성이 포함되어 있다.
- inputs와 outputs: 모델 입력과 출력에 접근
- layers: 모델 계층과 형상 목록
- summary(): 모델 아키텍쳐를 출력
- save(): 훈련에서 모델, 아키텍쳐의 현 상태를 저장한다. 추후에 훈련을 재개할 때 매우 유용하다. 모델은 tf.keras.models.load_model()을 사용해 인스턴스화 할 수 있다.
- save_weights(): 모델의 가중치만 저장한다.
순차형 API와 함수형 API
- 앞에서 사용한 순차형 API를 사용하는 대신 함수형 API를 사용할 수 있다.
model_input = tf.keras.layers.Input(shape=input_shape)
output = tf.keras.layers.Flatten()(model_input)
output = tf.keras.layers.Dense(128, activation='relu')(output)
output = tf.keras.layers.Dense(num_classes, activation='softmax')(output)
model = tf.keras.Model(model_input, output)
함수형 API는 순차형 API보다 훨씬 범용적으로 사용된다. 함수형 API는 모델을 분기(병렬 계층으로 구성)할 수 있고, 순차형 API는 선형 모델에만 사용 가능하다.
콜백
케라스 콜백은 케라스 모델의 기본 행위에 기능을 추가하기 위해 케라스 모델의 fit() 메서드에 전달할 수 있는 유틸리티 함수다.
- CSVLogger: 훈련 정보를 CSV 파일에 로그로 남긴다.
- EarlyStopping: 손실 혹은 메트릭이 더 이상 개선되지 않으면 훈련을 중지한다. 과적합을 피할 때 유용하다.
- LearningRateScheduler: 스케줄에 따라 세대마다 학습률을 변경한다.
- ReduceLROnPlateau: 손실이나 메트릭이 더이상 개선되지 않으면 학습률을 자동으로 감소시킨다.
tf.keras.callbacks.Callback의 서브 클래스를 생성함으로써 맞춤형 콜백을 생성할 수도 있다.
Leave a comment