본문 바로가기
AI 입문/Deep Learning

7장 - 케라스 완전 정복

by jhwannabe 2023. 1. 15.

7.1 다양한 워크플로

케라스 API 설계는 복잡성의 단계적 공개(progressive disclosure) 원칙을 따른다.

ㄴ 시작은 쉽게 하고, 필요할 때 단계마다 점진적으로 학습하여 아주 복잡한 경우를 처리할 수 있다. 

 

케라스는 매우 간단한 것부터 매우 유연한 것까지 다양한 워크플로를 제공한다. 이를 통해 다양한 요구 사항을 충족시킬 수 있다. 이런 모든 워크플로는 동일하게 Layer Model 같은 API를 기반으로 하기 때문에 한 워크플로의 구성 요소를 다른 워크플로에서 사용할 수 있다. 즉, 워크플로 간에 서로 호출할 수 있다.


7.2 케라스 모델을 만드는 여러 방법

케라스에서 모델을 만드는 API 세 가지

  • Sequential 모델 : 가장 시작하기 쉬운 API. 기본적으로 하나의 파이썬 리스트다. 따라서 단순히 층을 쌓을 수만 있다.
  • 함수형 API(Functional API) : 그래프 같은 모델 구조를 주로 다룸. 사용성과 유연성 사이의 적절한 중간 지점에 해당된다. 따라서 가장 널리 사용되는 모델 구축 API다.
  • Model 서브클래싱(subclassing) : 모든 것을 밑바닥부터 직접 만들 수 있는 저수준 방법. 모든 상세한 내용을 완전히 제어하고 싶은 경우에 적합하다. 하지만 여러 가지 케라스 내장 기능을 사용하지 못하기 때문에 실수가 발생할 위험이 많다.

7.2.1 Sequential 모델

케라스 모델을 만드는 가장 간단한 방법

동일한 모델을 add() 메서드를 통해 점진적으로 만들 수도 있음. (파이썬리스트의 append() 메서드와 비슷)

앞의 Sequential 모델은 어떤 가중치도 가지고 있지 않다. (코드 7-3)

가중치를 생성하려면 어떤 데이터로 호출하거나 입력 크기를 지정하여 build() 메서드를 호출해야 한다. (코드 7-4)

summary() 메서드를 사용하여 모델 구조를 출력할 수 있다.

케라스에서는 모델과 층을 포함해서 모든 것에 이름을 지정할 수 있다.

Sequential 모델을 점진적으로 만들 때 층을 추가하고 난 후 summary() 메서드를 호출하여 현재 모델을 확인할 수 있으면 유용하다. 하지만 모델의 build() 메서드를 호출하기 전까지는 summary() 메서드를 호출할 수 없다! Sequential 모델의 가중치를 바로 생성하는 방법이 있다. 모델의 입력 크기를 미리 지정하면 된다. 이를 위해 Input 클래스를 사용한다.

 

7.2.2 함수형 API

Sequential 모델은 사용하기 쉽지만 적용할 수 있는 곳이 극히 제한적이다. 하나의 입력과 하나의 출력을 가지며 순서대로 층을 쌓은 모델만 표현할 수 있다. 실제로 다중 입력(예를 들어 이미지와 이미지의 메타데이터), 다중 출력(예를 들어 데이터에 대해 여러 가지 항목을 예측하는 모델) 또는 비선형적인 구조를 가진 모델을 자주 만날 수 있다.

이런 경우에는 함수형 API를 사용한 모델을 만든다. 실전에서 이런 케라스 모델을 가장 흔하게 만날 수 있다. 

1. Input 클래스 객체를 정의하는 것으로 시작.

2. inputs 객체는 모델이 처리할 데이터의 크기와 dtype에 대한 정보를 가짐.

     ㄴ 이런 객체를 심볼릭 텐서(symbolic tensor)라고 부름. 실제 데이터를 가지고 있지 않지만 사용할 때 모델이 보게 될 데이터 텐서의 사양이 인코딩되어 있음. 즉, 미래의 데이터 텐서를 나타냄.

3. 그다음 층을 만들고 이 입력으로 호출.

4. 모든 케라스 층은 실제 데이터 텐서나 심볼릭 텐서로 호출할 수 있음. 후자의 경우 크기와 dtype 정보가 업데이트된 새로운 심볼릭 텐서를 반환.

5. 최종 출력을 얻은 후 입력과 출력을 Model 클래스에 전달하여 모델 객체 생성.

이 모델의 summary() 메서드 호출 결과

 

다중 입력, 다중 출력 모델

대부분 딥러닝 모델은 리스트와 같은 형태가 아니라 그래프를 닮음.

예를 들어 입력이 여러 개이거나 출력이 여러 개 → 이런 종류의 모델에서 함수형 API가 진짜 빛을 발함.

 

고객 이슈 티켓에 우선순위를 지정하고 적절한 부서로 전달하는 시스템을 만든다고 하면, 이 모델은 3개의 입력을 사용한다.

  • 이슈 티켓의 제목(텍스트 입력)
  • 이슈 티켓의 텍스트 본문(텍스트 입력)
  • 사용자가 추가한 태그(범주형 입력으로 여기에서는 원-핫 인코딩되었다고 가정)

텍스트 입력을 크기가 vocabulary_size인 0과 1로 이루어진 배열로 인코딩할 수 있음(텍스트 인코딩 기법은 11장 참고)

이 모델은 출력도 2개.

  • 이슈 티켓의 우선순위 점수로 0과 1 사이의 스칼라(시그모이드 출력)
  • 이슈 티켓을 처리해야 할 부서(전체 부서 집합에 대한 소프트맥스 출력)

함수형 API를 사용하여 이런 모델을 몇 줄의 코드로 만들 수 있다.

 

다중 입력, 다중 출력 모델 훈련하기

Sequential 모델을 훈련하는 것과 거의 같은 방법으로 이 모델을 훈련할 수 있다. 입력과 출력 데이터의 리스트로 fit() 메서드를 호출하면 된다. 데이터의 리스트는 Model 클래스에 전달한 순서와 같아야 한다.

입력 순서에 신경 쓰고 싶지 않다면 Input 객체와 출력 층에 부여한 이름을 활용해서 데이터를 딕셔너리로 전달할 수 있음.

함수형 API의 장점: 층 연결 구조 활용하기

함수형 모델 : 명시적인 그래프 데이터 구조

ㄴ 층이 어떻게 연결되어 있는지 조사하고 이전 그래프 노드(node)(층의 출력)를 새 모델의 일부로 재사용할 수 있음.

ㄴ 대부분의 연구자들이 심층 신경망에 대해 생각할 때 사용하는 ‘멘탈 모델(mental model)’인 층 그래프(graph of layers)에도 잘 맞음.

ㄴ 이를 통해 모델 시각화와 특성 추출이라는 두 가지 중요한 기능이 가능.

 

방금 정의한 모델의 연결 구조(모델의 토폴로지(topology)) 시각화

plot_model() 함수를 사용하여 함수형 모델을 그래프로 그릴 수 있음.

▲ plot_model()로 생성한 이슈 티켓 분류 모델 그
▲ 크기 정보가 추가된 모델 그림

텐서 크기에 None은 배치 크기를 나타낸다. 즉, 이 모델은 어떤 크기의 배치에서도 사용 가능하다.

 

층 연결 구조를 참조하여 그래프에 있는 개별 노드를 조사하고 재사용(층 호출)할 수 있다. model.layers 속성은 모델에 있는 모든 층의 리스트를 가지고 있다. 각 층에 대해 layer.input layer.output을 출력해 볼 수 있다.

이를 통해 특성 추출(feature extraction)을 수행하여 다른 모델에서 중간 특성을 재사용하는 모델을 만들 수 있다.

▲ 새로운 모델의 그래프

 

7.2.3 Model 서브클래싱

모델 구축 패턴 중 가장 고급 방법

3장에서 Layer 클래스를 상속하여 사용자 정의 층을 만드는 방법을 배웠다. Model 클래스를 상속하는 것도 매우 비슷하다.

  • __init__() 메서드에서 모델이 사용할 층을 정의.
  • call() 메서드에서 앞서 만든 층을 사용하여 모델의 정방향 패스를 정의.
  • 서브클래스의 객체를 만들고 데이터와 함께 호출하여 가중치를 만듦.

모델을 정의하고 나면 이 클래스의 객체를 만들 수 있다. Layer 클래스와 마찬가지로 어떤 데이터로 처음 호출할 때 가중치를 만다.

Layer 클래스 상속과 Model 클래스 상속의 차이점은?

ㄴ ‘층’은 모델을 만드는 데 사용하는 구성 요소

ㄴ ‘모델’은 실제로 훈련하고 추론에 사용하는 최상위 객체

ㄴ 간단히 말해서 Model 클래스는 fit(), evaluate(), predict() 메서드 가짐. (Layer 클래스에는 없음)

ㄴ (또 다른 차이점은 모델을 디스크에 파일로 저장할 수 있다. 이에 대해서는 잠시 후에 다룬다.)

 

Sequential이나 함수형 모델과 마찬가지로 Model을 상속하여 만든 모델을 컴파일하고 훈련할 수 있다.

Model 서브클래싱 워크플로는 모델을 만드는 가장 유연한 방법이다.

층의 유향 비순환 그래프(directed acyclic graph)로 표현할 수 없는 모델을 만들 수 있다.

ㄴ 예를 들어 call() 메서드가 for 루프 안에서 층을 사용하거나 재귀적으로 호출하는 모델이다.

 

주의: 서브클래싱된 모델이 지원하지 않는 것

서브클래싱 모델에서는 모델 로직을 많이 책임져야 하며 잠재적인 오류 가능성이 훨씬 커서 결과적으로 더 많은 디버깅 작업을 해야 함.

함수형과 서브클래싱 모델은 태생적으로 크게 다르다. 함수형 모델은 명시적인 데이터 구조인 층의 그래프이므로 출력하고 조사하고 수정할 수 있다. 서브클래싱 모델은 한 덩어리의 바이트코드(bytecode)이다. 원시 코드가 담긴 call() 메서드를 가진 파이썬 클래스이다. 이것이 (원하는 어떤 기능도 코드로 작성할 수 있어) 서브클래싱 워크플로의 유연성의 원천이지만 새로운 제약 사항이 발생한다.

 

 

7.2.4 여러 방식을 혼합하여 사용하기

중요한 것은 Sequential 모델, 함수형 API, Model 서브클래싱 패턴 중 하나를 선택한다고 다른 패턴의 사용을 제한하지 않는다는 점이다.

케라스 API로 만든 모든 모델은 어떤 것이든지 부드럽게 서로 상호 운영할 수 있다. 각각의 방법은 모두 동일한 워크플로 스펙트럼의 일부분이다.

 

7.2.5 작업에 적합한 도구 사용하기

각각의 워크플로를 언제 사용해야 하나요? 각 방법은 장단점이 있으므로 현재 작업에 가장 잘 맞는 것을 선택하면 됨.

 

일반적으로 함수형 API가 쉬운 사용성과 유연성 사이에 적절한 절충점이다.

ㄴ 층 연결 구조를 활용하여 모델 출력이나 특성 추출과 같은 용도에 잘 맞다. 함수형 API를 사용할 수 있다면, 즉 모델을 층의 유향 비순환 그래프로 표현할 수 있다면 Model 서브클래싱보다 이 방식을 사용할 것을 권장한다.

 

이 책의 모든 예제는 함수형 API를 사용 → 왜냐하면 이제부터 다룰 모든 모델을 층의 그래프로 표현할 수 있기 때문.

하지만 서브클래싱 층을 자주 사용함. → 일반적으로 서브클래싱 층을 포함한 함수형 모델을 사용하면 함수형 API의 장점을 유지하면서 높은 개발 유연성을 제공할 수 있음.


7.3 내장된 훈련 루프와 평가 루프 사용하기

간단한 워크플로를 커스터마이징할 수 있는 방법

  1. 사용자 정의 측정 지표를 전달한다.
  2. fit() 메서드에 콜백(callback)을 전달하여 훈련하는 동안 특정 시점에 수행될 행동을 예약한다.

 

7.3.1 사용자 정의 지표 만들기

지표(metric):  모델의 성능을 측정하는 열쇠 (훈련 데이터 성능과 테스트 데이터 성능 사이의 차이를 측정하는 것이 중요)

분류와 회귀에 일반적으로 사용되는 지표는 keras.metrics 모듈에 이미 포함되어 있지만 일반적이지 않은 작업에는 사용자 정의 지표를 만들 수 있어야 한다.

 

케라스 지표는 keras.metrics.Metric 클래스를 상속한 클래스이다.

ㄴ 층과 마찬가지로 지표는 텐서플로 변수에 내부 상태 저장.

ㄴ 층과 다른 점은 이 변수가 역전파로 업데이트되지 않는다는 것.

       ㄴ 따라서 상태 업데이트 로직을 update_state() 메서드 안에 직접 작성해야 함.

result() 메서드로 현재 지표 값을 반환한다.

또한, 객체를 다시 생성하지 않고 상태를 초기화하는 방법도 제공해야 하는데, 지표 객체 하나를 서로 다른 훈련 반복에 사용하거나 훈련과 평가에 모두 사용할 수 있도록 하는 것이다. reset_state() 메서드에서 이를 수행한다.

사용자 정의 지표는 내장 지표와 동일한 방식으로 사용할 수 있다. 

fit() 메서드가 출력하는 진행 표시줄에 RMSE 값이 표시되는 것을 볼 수 있다.

 

7.3.2 콜백 사용하기

콜백을 사용하는 몇 가지 사례

  • 모델 체크포인트(checkpoint) 저장 : 훈련하는 동안 어떤 지점에서 모델의 현재 가중치를 저장한다.
  • 조기 종료(early stopping) : 검증 손실이 더 이상 향상되지 않을 때 훈련을 중지한다(물론 훈련하는 동안 얻은 가장 좋은 모델을 저장한다).
  • 훈련하는 동안 하이퍼파라미터 값을 동적으로 조정한다 : 옵티마이저의 학습률 같은 경우
  • 훈련과 검증 지표를 로그에 기록하거나 모델이 학습한 표현이 업데이트될 때마다 시각화한다 : 앞서 보았던 fit() 메서드의 진행 표시줄이 하나의 콜백이다.

ModelCheckpoint와 EarlyStopping 콜백

모델을 훈련할 때는 미리 예상할 수 없는 것이 많습니다.

특히 최적의 검증 손실을 얻기 위해 얼마나 많은 에포크가 필요한지 알지 못합니다.

 

EarlyStopping 콜백 : 정해진 에포크 동안 모니터링 지표가 향상되지 않을 때 훈련을 중지한다. (일반적으로 ModelCheckpoint 콜백과 함께 사용)

 

7.3.3 사용자 정의 콜백 만들기

내장 콜백에서 제공하지 않는 특정 행동이 훈련 도중 필요하면 자신만의 콜백을 만들 수 있다. 콜백은 keras.callbacks.Callback 클래스를 상속받아 구현한다.

이 메서드들은 모두 logs 매개변수와 함께 호출된다.

▲ 손실 그래프를 저장하는 사용자 정의 콜백의 출력 결과

 

7.3.4 텐서보드를 사용한 모니터링과 시각화

텐서보드(https://www.tensorflow.org/tensorboard): 로컬에서 실행할 수 있는 브라우저 기반 애플리케이션

ㄴ 훈련하는 동안 모델 안에서 일어나는 모든 것을 모니터링하기 위한 가장 좋은 방법

 

텐서보드를 사용하여 다음과 같은 일을 수행할 수 있다.

  • 훈련하는 동안 측정 지표를 시각적으로 모니터링합니다.
  • 모델 구조를 시각화합니다.
  • 활성화 출력과 그레이디언트의 히스토그램을 그립니다.
  • 임베딩을 3D로 표현합니다.


7.4 사용자 정의 훈련, 평가 루프 만들기

내장 fit() 워크플로는 지도 학습(supervised learning)에만 초점이 맞추어져 있다. 내장 fit() 메서드로 충분하지 않은 상황이면 자신만의 훈련 로직을 직접 작성해야 다.

 

7.4.1 훈련 vs 추론

지금까지 본 저수준 훈련 루프 예제에서는 predictions = model(inputs)를 통해 단계 1(정방향 계산)을 수행하고 gradients = tape.gradient(loss, model.weights)를 통해 단계 2(그레이디언트 테이프로 계산한 그레이디언트를 추출)를 수행했다. 일반적으로 두 가지 중요한 세부 사항을 고려해야 한다.

Dropout 층과 같은 일부 케라스 층은 훈련(training)과 (예측을 만들기 위해 모델을 사용하는) 추론(inference)에서 동작이 다르다. 이런 층은 call() 메서드에 training 불리언(boolean) 매개변수를 제공한다. dropout(inputs, training=True)와 같이 호출하면 이전 층의 활성화 출력 값을 일부 랜덤하게 제외한다. 하지만 dropout(inputs, training=False)와 같이 호출하면 아무런 일을 수행하지 않는다. 함수형 모델과 Sequential 모델도 call() 메서드에서 training 매개변수를 제공한다. 정방향 패스에서 케라스 모델을 호출할 때는 training=True로 지정해야 한다. 따라서 정방향 패스는 predictions = model(inputs, training=True)가 다.

 

또한, 모델 가중치 그레이디언트를 추출할 때 tape.gradients(loss, model.weights)가 아니라 tape.gradients(loss, model.trainable_weights)를 사용해야 한다. 사실 층과 모델에는 두 종류의 가중치가 있다.

  • 훈련 가능한 가중치: Dense 층의 커널과 편향처럼 모델의 손실을 최소화하기 위해 역전파로 업데이트된다.
  • 훈련되지 않는 가중치: 해당 층의 정방향 패스 동안 업데이트된다.

훈련되지 않는 가중치를 가진 층은 9장에서 소개할 Batch Normalization뿐.

BatchNormalization 층은 처리하는 데이터의 평균과 표준 편차에 대한 정보를 추적하여 (6장에서 배운 개념인) 특성 정규화(feature normalization)를 실시간으로 근사하기 위해 훈련되지 않는 가중치가 필요하다.

 

이 두 가지를 고려하면 지도 학습을 위한 훈련 스텝은 다음과 같이 작성할 수 있다.

 

7.4.2 측정 지표의 저수준 사용법

저수준 훈련 루프에서 케라스 지표를 사용하게 될 것이다.

 

7.4.3 완전한 훈련과 평가 루프

정방향 패스, 역방향 패스, 지표 추적을 fit()과 유사한 훈련 스텝 함수로 연결한다. 이 함수는 데이터와 타깃의 배치를 받고 fit() 진행 표시줄이 출력하는 로그를 반환한다.

매 에포크 시작과 평가 전에 지표의 상태를 재설정해야 한다. 다음은 이를 위한 유틸리티 함수이다.

 

7.4.4 tf.function으로 성능 높이기

기본적으로 동일한 로직을 구현했지만 아마 직접 작성한 루프가 케라스에 내장된 fit() evaluate() 메서드보다 훨씬 느리게 실행된다는 것을 눈치챘을지 모르겠다. 기본적으로 텐서플로 코드는 넘파이나 일반적인 파이썬 코드와 비슷하게 즉시(eagerly) 라인 단위로 실행되기 때문이다. 즉시 실행(eager execution)은 코드 디버깅을 쉽게 만들어 준다. 하지만 성능 측면에서는 최적이 아니다.

텐서플로 코드는 계산 그래프(computation graph)로 컴파일하는 것이 더 성능이 좋다. 여기에서는 라인 단위로 해석되는 코드에서는 할 수 없는 전역적인 최적화가 가능하다. 이렇게 만드는 문법은 매우 간단하다. 다음과 같이 실행하기 전에 컴파일하고 싶은 함수에 @tf.function 데코레이터(decorator)를 추가하면 된다.

 

7.4.5 fit() 메서드를 사용자 정의 루프로 활용하기

사용자 정의 모델의 객체를 만들고 컴파일하고(손실은 모델 밖에서 이미 정의했기 때문에 옵티마이저만 전달합니다), 보통 때처럼 fit() 메서드로 훈련할 수 있다.

몇 가지 주의할 점이 있다.

  • 이 패턴 때문에 함수형 API로 모델을 만드는 데 문제가 되지 않는다. Sequential 모델, 함수형 모델, 서브클래싱 모델을 만드는지에 상관없이 이 방식을 사용할 수 있다.
  • 프레임워크가 알아서 처리하기 때문에 train_step 메서드를 오버라이딩할 때 @tf.function 데코레이터를 사용할 필요가 없다.

이제 compile() 메서드를 통해 지표와 손실을 설정하면 어떨까? compile() 메서드를 호출한 후 다음을 참조할 수 있다.

  • self.compiled_loss: compile() 메서드에 전달한 손실 함수
  • self.compiled_metrics: compile() 메서드에 전달된 지표 목록이 포함되어 있는 객체입니다. self.compiled_metrics.update_state()를 호출하여 모든 지표를 동시에 업데이트할 수 있습니다.
  • self.metrics: compile() 메서드에 전달한 실제 지표의 목록. 앞서 loss_tracking_metric으로 수동으로 했던 것과 비슷하게 손실을 추적하는 지표도 포함합니다.


7.5 요약

  • 케라스는 복잡성의 단계적 공개 원칙을 기반으로 다양한 워크플로를 제공합니다. 워크플로는 부드럽게 서로 상호 운영이 가능합니다.
  • Sequential 클래스, 함수형 API를 사용하거나 Model 클래스를 상속하여 모델을 만들 수 있습니다. 대부분의 경우 함수형 API를 사용할 것입니다.
  • 모델을 훈련하고 평가하는 가장 간단한 방법은 기본으로 제공되는 fit()과 evaluate() 메서드를 사용하는 것입니다.
  • 케라스 콜백은 fit() 메서드가 실행되는 동안 모델을 모니터링하고 모델의 상태에 따라 자동으로 행동을 수행할 수 있는 간단한 방법입니다.
  • train_step() 메서드를 오버라이딩하여 fit() 메서드의 동작을 완전히 제어할 수도 있습니다.
  • fit() 메서드를 넘어서 밑바닥부터 자신만의 훈련 루프를 작성할 수도 있습니다. 완전히 새로운 훈련 알고리즘을 구현하려는 연구자에게 유용한 기능입니다.
728x90
반응형