본문 바로가기
Book Review/[케라스 창시자에게 배우는 딥러닝] 리뷰

다양하게 케라스 딥러닝 모델 구축하기

by 3n952 2023. 1. 26.

케라스(keras)를 활용해서 딥러닝 모델을 만드는 API는 세 가지가 있습니다.

 

1. Sequential 모델 

2. 함수형 API 

3. Model subclassing(서브클래싱)

 

이번 포스팅에서는 위 세가지 keras API에 대해 학습해보고 리뷰하겠습니다.


각 방법마다 쓰이는 특징이 다르며 모델 구축의 복잡한 정도도 다릅니다. 

 

-Sequential 모델: 초보자 혹은 간단한 모델을 구축하기 위해서는 sequential API + 내장 층을 이용하여 모델을 구축할 수 있습니다.

사용적인 측면에서 간편하다는 장점이 있습니다.

 

-함수형 API: 일반적인 문제를 다루거나 맞춤형 솔루션이 필요한 태스크, 엔지니어를 위해서는 함수형 API + 내장 층 or

함수형 API + 사용자 정의 층 + 사용자 정의 지표, 손실 등 을 이용하여 그래프와 같은 모델 구조를 구축합니다.

이 API는 사용성과 유연성 사이의 적절한 중간 지점이 되어 널리 사용됩니다.

 

- Model subclassing(서브클래싱): 이전 케라스 API를 다루는 포스팅에서 잠깐 본 것과 같이 모든 것을 밑바닥부터 만드는 것입니다. 

모델을 구성할 내용을 세부적으로 컨트롤하고 싶을 때 사용합니다. 따라서 유연성에서 최고의 성능을 보입니다. 하지만 이때 여러 가지 케라스 내장 기능을 사용하지 못하기 때문에 실수가 발생할 위험이 있습니다. 

 

그럼 하나하나씩 예를 들며 살펴보겠습니다.

 

1. Sequential 모델 

케라스 모델을 가장 간단한 방법입니다. 이름에서도  알 수 있듯이 sequential클래스를 사용하는 것입니다.

코드를 보면 더욱 쉽게 이해할 수 있습니다.

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(64, activation = 'relu'),
    layers.Dense(10, activation = 'softmax')
])
model = keras.Sequential()
model.add(layers.Dense(64, activation = 'relu'))
model.add(layers.Dense(10, activation = 'softmax'))

아래와 위의 예제 코드는 같은 모델을 구성하는 예제입니다.

다만 차이점은 아래의 예제에서는 add()메서드를 통해 층을 점진적으로 구성했다는 점입니다.

 

 

sequential클래스로 모델을 구성할 때 알아야 할 것은 입력 크기에 따른 가중치 할당입니다.

즉, 입력 크기가 들어와야 가중치를 만들 수 있는 것입니다. 

가중치 확인을 위해 build()메서드를 사용하여 입력 크기를 갖는 샘플을 입력해 보겠습니다.

코드는 다음과 같습니다.

model.build(input_shape = (None, 3))
model.weights

만들어진 모델을 호출하여 입력 크기에 맞는 가중치 값들이 랜덤으로 생성되어 있음을 알 수 있습니다.

 

build() 메서드를 사용하지 않고 바로 모델의 입력 크기를 미리 지정하는 방법도 있습니다.

이를 위해 Input클래스를 사용합니다.

 

코드는 다음과 같습니다.

model = keras.Sequential()
model.add(keras.Input(shape = (3,)))
model.add(keras.layers.Dense(64, activation = 'relu'))


#또는 
model = keras.Sequential()
model.add(keras.layers.Dense(64, activation = 'relu', input_shape = (3,)))

이렇게 가중치를 생성하면 sequential 모델이 점진적으로 층을 추가하고 만들 때 summary() 메서드를 사용하여

현재 모델의 구조를 확인할 수 있습니다.

가중치를 생성하지 않으면 (=build() 메서드, Input클래스 사용하지 않으면) 호출이 불가합니다.

코드는 다음과 같습니다.

model.summary()

summary()결과

첫 번째 dense층(밀집층)에서는 (64,) 크기의 값을 반환하며 이때 사용된 파라미터들은 총 256개(3 * 64 + 64)입니다.

저 파라미터 값들을 학습하면서 딥러닝의 효과를 볼 수 있는 것입니다.

 

 

2. 함수형 API 

앞서 살펴본 sequential모델은 간편하고 사용하기 쉽지만 적용할 수 있는 것이 극히 제한적입니다.

그 이유는 하나의 입력과 하나의 출력만을 가지며 순서대로 쌓은 모델만 표현할 수 있기 때문입니다.

실제로는 다중 입력에 대한 다중 출력이 필요한 이슈를 다루며, 비선형적인 구조를 가진 모델을 필요하기도 합니다.

이를 해결하기 위해 필요한 것은 함수형 API를 사용한 모델을 만드는 것입니다.

 

함수형 API 모델은 리스트와 같이 하나하나가 단순 연결된 구조가 아니라 여러 입력에 대한 여러 출력이

이어진 그래프, 트리 형식과 비슷합니다. 

 

간단한 예제를 통해 함수형 API모델에 대해 설명하겠습니다.

해당 예제는 고객 이슈 리뷰에 대한 우선순위를 정하고 적절한 부서로 전달하는 시스템입니다.

 

입력: 이슈 리뷰의 제목(텍스트), 텍스트 본문(텍스트), 태그(범주형 입력으로 원-핫인코딩 가정)

+입력받은 텍스트는 인코딩했다고 가정하겠습니다.

출력: 이슈 리뷰의 우선점수, 리뷰를 처리해야 할 부서

 

 

 

입력과 출력, 가정은 몰라도 되니 모델이 어떻게 구성되고 훈련되는지에 집중해 보겠습니다!

해당 예제를 다루는 함수형 API 모델을 만드는 코드는 다음과 같습니다.

from tensorflow import keras
from tensorflow.keras import layers 
import numpy as np

#입력에 대한 가정, 값
vocabulary_size = 10000
num_tags = 100
num_departments = 4

#모델의 입력 정의
title = keras.Input(shape=(vocabulary_size,), name="title")
text_body = keras.Input(shape=(vocabulary_size,), name="text_body")
tags = keras.Input(shape=(num_tags,), name="tags")

#입력특성을 하나의 텐서feature로 저장: concatenate()사용
features = layers.Concatenate()([title, text_body, tags])
#중간층을 적용함으로써 입력 특성을 더욱 풍부한 표현으로 재결합
features = layers.Dense(64, activation="relu")(features)

#모델의 출력을 정의
priority = layers.Dense(1, activation="sigmoid", name="priority")(features)
department = layers.Dense(num_departments, activation="softmax", name="department")(features)

#모델구성
model = keras.Model(inputs=[title, text_body, tags], outputs=[priority, department])

입력할 다중 입력이 층을 거쳐 다중 출력으로 나오도록 모델을 구성하였습니다.

모델이 구성되었으니 이제 주어진 데이터로 학습을 해보겠습니다.

데이터를 리스트로 받는 fit() 메서드를 사용하면 학습이 이뤄집니다.

위의 코드에서 만들어진 모델을 사용하기 때문에 이어서 코드를 작성해 주면 됩니다.

 

 

데이터는 임의로 만들어진 데이터이므로 신경 쓰지 않아도 괜찮습니다!

코드는 다음과 같습니다.

#더미 입력데이터(임의적이므로 신경x)
title_data = np.random.randint(0, 2, size=(num_samples, vocabulary_size))
text_body_data = np.random.randint(0, 2, size=(num_samples, vocabulary_size))
tags_data = np.random.randint(0, 2, size=(num_samples, num_tags))

#더미 타겟 데이터
priority_data = np.random.random(size=(num_samples, 1))
department_data = np.random.randint(0, 2, size=(num_samples, num_departments))

model.compile(optimizer="rmsprop",
              loss=["mean_squared_error", "categorical_crossentropy"],
              metrics=[["mean_absolute_error"], ["accuracy"]])
model.fit([title_data, text_body_data, tags_data],
          [priority_data, department_data],
          epochs=1)
model.evaluate([title_data, text_body_data, tags_data],
               [priority_data, department_data])
priority_preds, department_preds = model.predict([title_data, text_body_data, tags_data])

compile단계에서 우선순위를 알려주는데 판단하는 loss함수는 mean_squared_error이며 부서를 알려주는데 판단하는 loss함수는 categorical_crossentropy입니다. 입력 순서에 맞게 순서를 해당 내용을 할당해줘야 합니다.

metrics 파라미터도 마찬가지입니다.

 

 

함수형 모델은 그래프 데이터 구조이므로 이를 활용하는 것이 함수형 API 모델의 핵심입니다.

이전 그래프 노드를(층의 출력) 새 모델의 일부로 사용하는 것이 이에 해당됩니다.

이를 위해 모델 시각화특성 추출(feature extraction)의 기능을 사용할 수 있습니다.

 

 

 

모델의 연결구조를 summary()로 확인했지만 시각화하기 위해서는 plot_model() 메서드를 사용하면 됩니다.

앞서 만든 모델을 시각화하여 확인해 보도록 하겠습니다.

코드는 다음과 같습니다.

#모델 그리기
keras.utils.plot_model(model, "ticket_classifier.png", show_shapes = True)

keras.utils.plot_model(불러올 모델, "만들 시각화 파일 이름")이 기본입니다.

show_shapes = True로 지정해 주면 모델에 있는 각 층의 입출력 크기를 보여줍니다.

plot_model()실행화면

 

 

모델을 시각화했으면 다른 모델에서 중간 특성을 재사용하는 특성 추출을 수행할 수 있습니다.

즉, 중간 특성을 따로 사용하는 모델을 따로 만드는 것입니다.

개별 노드를 조사하고 재사용(층 호출) 하기 위해 모델의 layers 속성을 파악하는 것이 중요합니다.

#모델의 모든 층의 리스트. 차례대로 표현됨
model.layers

#모델의 특정 층에서의 input : 4번째 층의 입력 크기와 타입
model.layers[3].input

#모델의 특정 층에서의 output : 4번째 층의 입력 크기와 타입
model.layers[3].output

 

 

이를 통해 필요한 층을 추가하여 모델을 재구성할 수 있습니다.

코드는 다음과 같습니다.

features = model.layers[4].output
difficulty = layers.Dense(3, activation="softmax", name="difficulty")(features)

new_model = keras.Model(
    inputs=[title, text_body, tags],
    outputs=[priority, department, difficulty])

 

 

기존의 5번째 층의 output을 입력받는 층을 구성하는 새로운 모델을 만들었습니다.

새로운 모델의 그래프를 시각화해 보면 다음과 같습니다.

new_model의 그래프

 

 

 

2. 모델 서브클래싱

모델 서브클래싱은 저수준의 방법으로 밑바닥 부터 직접 만들어 모든 것을 상세히 컨트롤 하고 싶을 때 사용합니다.

 

model 클래스를 서브클래싱(상속)하는 방법은 다음과 같습니다.

 

- __init__()메서드에서 모델이 사용할 층 정의

- call()메서드에서 앞서 만든 층을 사용하여 모델의 정방향 패스 정의

- 서브클래스의 객체를 만들고 데이터와 함께 호출하여 가중치 만들기

 

 

이해를 더욱 쉽게 하기 위해서 이전 예제(고객 이슈 리뷰 관리 모델)를 서브클래싱 모델로 다시 만들어 보겠습니다.

먼저 코드를 먼저 보겠습니다.

from tensorflow import keras
from tensorflow.keras import layers

#서브클래싱
class customticket(keras.Model):
  def __init__(self, num_department):
    super().__init__() #부모 클래스 생성자 호출
    # 사용할 층 정의
    self.concat_layer = layers.Concatenate()
    self.mixing_layer = layers.Dense(64, activation = 'relu')
    self.priority_scorer = layers.Dense(1, activation = 'sigmoid')
    self.department_classifier = layers.Dense(num_department, activation = 'softmax')
  
  #정방향 패스 정의
  def call(self, inputs):
    title = inputs['title']
    text_body = inputs['text_body']
    tags = inputs['tags']
    features =  self.concat_layer([title, text_body, tags])
    fetures = self.mixing_layer(features)
    priority = self.priority_scorer(features)
    department = self.department_classifier(features)
    return priority, department

 

각각의 주석을 보면 이해하기 쉽습니다.

먼저 keras.Model의 부모 클래스를 받아 사용자 층을 정의합니다.

그 후 call()메서드로 각 층에서의 정방향 패스를 정의하고 출력을 만듭니다.

 

이렇게 모델을 정의하면 클래스의 객체를 만들 수 있습니다.

어떠한 입력 데이터로 처음 모델을 호출하면 가중치가 만들어 집니다.

모델을 호출하고 가중치가 만들어지면 keras.Model 클래스로 훈련, 평가를 할 수 있습니다.

바로 fit(), evaluate(), predict()메서드를 사용하는 것이죠!

 

 

앞선 코드처럼 모델 서브클래싱으로 모델을 만든 후 다음 코드는 위 설명에 대한 예시 코드입니다.

#데이터 임의로 생성(중요 x)
import numpy as np
num_samples = 1280
vocabulary_size = 10000
num_tags = 100


title_data = np.random.randint(0, 2, size=(num_samples, vocabulary_size))
text_body_data = np.random.randint(0, 2, size=(num_samples, vocabulary_size))
tags_data = np.random.randint(0, 2, size=(num_samples, num_tags))

priority_data = np.random.random(size = (num_samples, 1))
department_data = np.random.randint(0,2, size = (num_samples, 4))

#객체 만들고 데이터 입력후 호출 (중요 o)
model = customticket(num_department = 4)
priority , department = model({'title': title_data, 'text' : text_data, 'tags' : tags_data})

#모델 컴파일, 훈련
model = customticket(num_department = 4)
priority , department = model({'title': title_data, 'text_body' : text_body_data, 'tags' : tags_data})

model.compile(optimizer = 'rmsprop',
              loss = ['mean_squared_error', 'categorical_crossentropy'],
              metrics = [['mean_absolute_error'], ['accuracy']])

model.fit({'title':title_data,
           'text_body':text_body_data,
           'tags': tags_data},
          [priority_data, department_data],
          epochs = 1
          )

model.evaluate({'title':title_data,
           'text_body':text_body_data,
           'tags': tags_data},
           [priority_data, department_data]
           )

 

앞서 살펴본 세 가지 방식의 딥러닝 모델 만드는 것은 혼합하여 사용하는 것이 중요합니다.

각각의 방법을 상호 운영할 수 있는 것 입니다.

가령 함수형 모델에서 서브 클래싱 층을 사용하거나,

서브클래싱 층이 일부로 함수형 모델을 사용하거나 등등 입니다.

따라서 이를 적절하게 혼합하여 사용하는 것이 필수적입니다.

 

 

예시 코드는 다음과 같습니다.

-서브클래싱한 모델을 포함하는 함수형 모델

class Classifier(keras.Model):

    def __init__(self, num_classes=2):
        super().__init__()
        if num_classes == 2:
            num_units = 1
            activation = "sigmoid"
        else:
            num_units = num_classes
            activation = "softmax"
        self.dense = layers.Dense(num_units, activation=activation)

    def call(self, inputs):
        return self.dense(inputs)

inputs = keras.Input(shape=(3,))
features = layers.Dense(64, activation="relu")(inputs)
outputs = Classifier(num_classes=10)(features)
model = keras.Model(inputs=inputs, outputs=outputs)

 

 

-함수형 모델을 포함하는 서브클래싱 모델

inputs = keras.Input(shape=(64,))
outputs = layers.Dense(1, activation="sigmoid")(inputs)
binary_classifier = keras.Model(inputs=inputs, outputs=outputs)

class MyModel(keras.Model):

    def __init__(self, num_classes=2):
        super().__init__()
        self.dense = layers.Dense(64, activation="relu")
        self.classifier = binary_classifier

    def call(self, inputs):
        features = self.dense(inputs)
        return self.classifier(features)

model = MyModel()