Tensorflow로 모델을 만드는 3가지 방법

박재한·2022년 8월 12일
2

Deep Learning

목록 보기
20/22

참고 자료는 다음과 같다.

1. 3 Ways to create a keras model with Tessorflow 2.0

Keras 및 TensorFlow 2.0은 자체 신경망 아키텍처를 구현하는 세 가지 방법을 제공한다.

  • Sequential API
  • Functional API
  • Subclassing API
    Imgur

배경
Tensorflow는 처음부터 딥러닝 플랫폼으로 개발된 것이 아니었다. 처음에는 매트랩(Matlab)처럼 효율적인 수학용 라이브러리로 개발이 되었다. 그러나 이를 딥러닝에 활용하기 시작하면서 반복해서 저수순 API로 모델을 설계하는 것이 비효율적이어서 결국은 Google의 engineer는 딥러닝 개발용 고수준 API인 keras를 frontend로, tensorflow를 backend 하는 독립적인 고급 딥러닝 라이브러리로 개발했다.
정리하면 다음과 같다.
tensorflow : Google에서 개발된 머신러닝 및 딥러닝용 라이브러리. 세부적인 구현까지 가능하나 사용이 어렵다.
keras : 머신러닝 및 딥러닝용 라이브러리이지만 보다 직관적이고 쉬운 API를 제공한다. keras는 backend로 Tensorflow, Theano, CNTKL등 다양한 backend를 지원한다. 사용하기에 상대적으로 쉬운 반면에 복잡한 모델을 설계하기에 구현 범위에 한계가 있다.
이 keras에서 머신러닝 및 딥러닝용 모델을 만드는 3가지 방법을 제안하고 있다.

2. 3가지 방법 각각에 대해 알아보자.

2.1 Sequential API

Sequential API는 단일 입력, 출력 및 계층 분기를 사용하여 간단한 모델을 구축하는 가장 좋은 방법이다. 이것은 빨리 배우고 싶은 초보자에게 가장 좋은 선택이다.
Sequential Model은 모델을 이루는 요소들(layer)을 순서대로 일자로 배열한 것이다. Sequential API를 사용하면 각 레이어에 정확히 하나의 입력 텐서와 하나의 출력 텐서가 있는 일반 레이어 스택만을 만들 수 있는데 이를 간단하게 구성할 수 있게 해 준다.
Imgur
이름에서 알 수 있듯이 이 방법을 사용하면 순차적으로 연결된 단일 레이어 스택으로 구성된 신경망을 구축할 수 있다.
Imgur
다음의 예제 코드를 보면 더 잘 이해할 수 있을 것이다.

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# Define Sequential model with 3 layers
model = keras.Sequential(
    [
        layers.Dense(2, activation="relu", name="layer1"),
        layers.Dense(3, activation="relu", name="layer2"),
        layers.Dense(4, name="layer3"),
    ]
)
# Call model on a test input
x = tf.ones((3, 3))
y = model(x)

위의 코드에서 3개의 Dense 레이어를 서로 겹쳐 단일 경로를 생성했었다. 이것은 다음과 같이 작성하는 것과 같다.

# Create 3 layers
layer1 = layers.Dense(2, activation="relu", name="layer1")
layer2 = layers.Dense(3, activation="relu", name="layer2")
layer3 = layers.Dense(4, name="layer3")

# Call layers on a test input
x = tf.ones((3, 3))
y = layer3(layer2(layer1(x)))

여기서 y는 입력 x가 단일 고정 시퀀스로 한 계층에서 다른 계층으로 이동한 후 생성된다.

다음은 Sequential이 사용하기에 적합하지 않은 경우이다.
Sequential API 단점

  1. Multiple Inputs and Multiple Outputs: 네트워크 아키텍처가 둘 이상의 입력 계층에서 입력을 필요로 하는 작업을 위해 설계되었거나 실행을 위해 다른 입력이 병렬로 사용된다고 가정할 때 Sequential layer를 사용하는 것은 최선의 접근법이 아니다. 네트워크에 여러개의 출력이 있는경우도 마찬가지이다.

  2. 모든 개별 레이어에 다중 입력 또는 다중 출력이 있음 : 이것은 위의 점과 유사하지만 정확히 동일하지는 않다. 신경망 뒤에는 다양한 요소가 관련되어 있으며 아키텍처가 'T' 조인트와 같이 외부에서 오는 특정 작업을 수행하기 위해 하나 이상의 은닉 레이어가 필요하다고 가정해 본다면 Sequential API는 이를 구현할 수 없고 레이어에서 divergence나 junctions이 없는 단일 경로에 대해서만 고려되어야 한다.

  3. Layer sharing을 할 수 없다: 레이어 공유는 하나의 layer를 입력만 다르게 해서 다양한 출력을 내는 것인데 이는 한번에 multiple input, multiple output이 가능해야 하므로 한번에 하나의 layer에서 하나의 입출력만 가능한 Sequential API 모델에서는 사용할 수 없다.

  4. 비선형 토폴로지를 원하는 경우 : residual connections 또는 multi-branch model과 같은 것을 생성하는 경우, 이 또한 multiple input, multiple output이 가능해야 하므로 한번에 하나의 layer에서 하나의 입출력만 가능한 Sequential API 모델에서는 사용할 수 없다.

Sequential API 모델이 실제로 사용되어지고 있는 주요한 모델은 다음과 같다.

  • LeNet
  • AlexNet
  • VGGNet

2.2 Functional API

Functional API는 keras 모델을 구축하는 가장 보편적인 방법이다. Sequential API가 할 수 있는 모든 작업을 수행할 수 있으며 또한 여러 입력, 여러 출력, 분기 및 레이어를 공유할 수 있다. 간단하고 사용하기 쉬운 방법이며 여전히 우수한 사용자 정의 유연성을 허용한다.
Keras functional API는 Sequential Model API보다 더 유연한 모델을 생성하는 좋은 방법이다. Functional API는 비선형 토폴로지, 공유 레이어, 여러 입력 또는 출력을 포함하는 모델을 쉽게 처리할 수 있다. 딥러닝 모델이 일반적으로 계층의 방향성 순환 그래프라는 개념에 가장 잘 맞는 모델이다. 따라서 Functional API는 각 layer별로 그래프를 작성하는 방법이라고 볼 수도 있다.

이제 functional API 방식으로 은닉(hidden) 레이어의 입력과 출력을 모두 함께 쌓는 대신 레이어를 함수로 사용할 수 있다. 따라서 함수의 매개변수는 입력이고 함수의 리턴값은 출력이 된다.
Imgur
Sequential model은 매우 일반적이지만 때로는 더 복잡한 토폴로지 또는 다중 입력 또는 출력을 사용하여 신경망을 구축하는 것이 유용하거나 필요하기도 하다. 이를 위해 Keras는 Functional API를 제공한다.
정리하면 다음의 성격을 가지는 모델일 설계할 때 Function API가 필요하다.

  • 아키텍처 내부의 레이어를 쉽게 공유
  • 더 복잡한 모델 생성
  • 방향성 비순환 그래프 설계
  • 다중 입력 및 다중 출력
  • 방향성 비순환 그래프(DAG, directed acyclic graphs)를 설계한다.

물론 Function API로 Sequential API 모델도 당연히 설계가 가능하다.
Functional API를 사용하여 Functional API의 특성(예: 레이어 분기)을 활용한 대표적인 모델로는 다음과 같은 것들이 있다.

  • ResNet
  • GoogLeNet/Inception
  • Xception
  • SqueezeNet

이해를 돕기위해 다음의 예제 코드를 보도록 하자!

inputs = keras.Input(shape=(784,))
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

처음 Input layer에서 매개변수로 지정된 모양(shape)은 784차원 벡터로 설정된 입력 데이터의 모양(shape)이다. 각 샘플의 모양만 지정되므로 배치 크기는 항상 생략된다.

Dense layer 3개를 함수 호출을 통해 계층이 cascade식으로 차례로 입력과 출력이 전달이 되는 것을 확인한다.

마지막으로 우리의 신경망을 정의할 Model 객체를 만들기 위한 argument로 입력 레이어와 출력 레이어를 정의한다.

Example with branch
와이드 및 딥신경망은 비순차 아키텍처(non-sequential architecture)의 예 중 하나이다. 이 아키텍처에서는 깊은 경로를 사용하여 깊은 패턴을 학습하고 동시에 짧은 경로도 사용하는 간단한 패턴도 학습할 수 있지만 한번은 짧은 경로를 통해 그리고 다른 한번은 깊은 경로를 통해 중첩될 수 있는 두 가지 출력값을 입력값으로 받아들여 처리하고 싶다고 하자.
아래 그림을 보면 이해하기가 쉬울 것이다.
Imgur

예제 코드는 다음과 같다.

input_A = keras.layers.Input(shape=[5], name="wide_input")
input_B = keras.layers.Input(shape=[6], name="deep_input")
hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(30, activation="relu")(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
output = keras.layers.Dense(1, name="output")(concat)
model = keras.Model(inputs=[input_A, input_B], outputs=[output])

2.3 Subclassing API

Model subclassing는 모델, 계층 및 교육 프로세스를 완벽하게 제어해야 하는 고급 개발자를 위해 설계되었다. 모델을 정의하는 사용자 정의 클래스를 생성해야 하며 실험적인 요구 사항이 있는 연구원이라면 필요한 모든 유연성을 제공하기 때문에 model subclassing이 최선의 선택일 수 있다.
이 접근 방식을 사용하려면 객체 지향 프로그래밍(클래스 및 객체)의 개념을 이해해야한다.
Imgur

상속의 개념을 사용하고 tensorflow.keras.Model 클래스에서 사용할 수 있는 기능과 메서드를 사용한다. 코드로 넘어가기 전에 한 가지 더, __init__() method에서 SUPER() FUNCTION을 잊지 마라. 이는 부모 클래스에 대한 액세스를 지시하고 부모 클래스(tensorflow.keras.Model)의 생성자 함수를 호출한다.

class MyModel(tf.keras.Model):

    def __init__(self, num_classes=2):
        super(MyModel, self).__init__()
        self.dense1 = tf.keras.layers.Dense(64, activation="relu")
        self.dense2 = tf.keras.layers.Dense(64, activation="relu")
        self.classifier = tf.keras.layers.Dense(num_classes)

    def call(self, inputs):
        x = self.dense1(inputs)
        x = self.dense2(x)
        return self.classifier(x)


model = MyModel()
dataset = ...
model.fit(dataset, epochs=10)
model.save(filepath)

init: 이 클래스에서 객체를 정의하자마자 실행되는 생성자이다.

call(): 이 함수는 입력에서 출력으로의 변환을 위해 클래스에서 방금 생성한 객체를 호출할 때 사용된다. call() 함수의 입력(call함수의 argument)이 모델의 입력이 되고 call()함수의 리턴값이 모델의 출력이 된다.

API를 서브클래싱(Subclassing)하는 것은 신경망을 구축하는 가장 유연한 방법이지만 불행히도 비용이 든다. 모델 서브클래싱은 다른 어떤 방법보다 활용하기가 훨씬 더 어렵다.(다른 두 모델에 비해 이해하기가 다소 어렵다.) 디버깅하기가 훨씬 더 어렵고 Keras에서 저장할 수 없으므로 추가 유연성이 실제로 필요하지 않는 한 Sequential API 또는 Functional API가 필요할 수도 있다.
(keras에서 저장할 수 없다고 했는데 그것은 모델을 저장할 때 기존 HDF5형식으로 저장할 수 없다는 말이고, tensorflow 2.0에서 지원하는 TF 형식으로 저장하면 저장이 가능한다.)

사용의 불편한 점이 많음에도 불구하고 실제로 사용하는 이유는 몇몇 특수한 모델은 Sequential 및 Functional API를 사용하여 구현하는 것이 매우 어렵거나 아예 불가능할 때가 있다.

3. MNIST CNN Classifier model에서의 비교

MNIST CNN Classifier의 구조는 다음과 같다.
Imgur

3.1 Sequential API

방법1

model = Sequential([
    Conv2D(filters=32, kernel_size=3, activation=tf.nn.relu, padding='SAME',input_shape=(28, 28, 1)),
    MaxPool2D(padding='SAME'),
    Conv2D(filters=64, kernel_size=3, activation=tf.nn.relu, padding='SAME'),
    MaxPool2D(padding='SAME'),
    Flatten(),
    Dense(128, activation='relu'),
    Dropout(0.2),
    Dense(10, activation='softmax')
])

방법2

def create_model():
    model = Sequential()

    model.add(Conv2D(filters=32, kernel_size=3, activation=tf.nn.relu, padding='SAME', 
                                  input_shape=(28, 28, 1)))
    model.add(MaxPool2D(padding='SAME'))

    model.add(Conv2D(filters=64, kernel_size=3, activation=tf.nn.relu, padding='SAME'))
    model.add(MaxPool2D(padding='SAME'))

    model.add(Flatten())
    model.add(Dense(128, activation=tf.nn.relu))
    model.add(Dropout(0.4))
    model.add(Dense(10, activation=tf.nn.softmax))

    return model

model = create_model()

3.2 Functional API

많이 쓰이면서 다루기 쉬운 방법입니다. 하지면 최근(22년 기준)의 경향을 봤을때는 subclassing이 지배적입니다. Pytorch 와 동시에 사용하는 연구자 께서는 반드시 subclassing에 익숙해 지시기 바랍니다.

def create_model():
    inputs = Input(shape=(28, 28, 1))
    conv1 = Conv2D(filters=64, kernel_size=[3, 3], padding='SAME', activation=tf.nn.relu)(inputs)
    pool1 = MaxPool2D(padding='SAME')(conv1)
    conv2 = Conv2D(filters=128, kernel_size=[3, 3], padding='SAME', activation=tf.nn.relu)(pool1)
    pool2 = MaxPool2D(padding='SAME')(conv2)
    pool3_flat = Flatten()(pool2)
    dense4 = Dense(units=128, activation=tf.nn.relu)(pool3_flat)
    drop4 = Dropout(rate=0.4)(dense4)
    logits = Dense(10, activation='softmax')(drop4)

    model = Model(inputs=inputs, outputs=logits)

    return model

model = create_model()

3.3 Subclasing API

가장 구현하기에 어렵고 헷갈리는 모델구성 방식입니다. 하지만 이는 Pytorch와 동시에 구현하는데 최적의 방법입니다. 연구자 께서는 필히 이 방식에 적응하시기 바랍니다.

class MNISTModel(tf.keras.Model):
    def __init__(self):
        super(MNISTModel, self).__init__()
        self.conv1 = Conv2D(filters=64, kernel_size=[3, 3], padding='SAME', activation=tf.nn.relu)
        self.pool1 = MaxPool2D(padding='SAME')
        self.conv2 = Conv2D(filters=128, kernel_size=[3, 3], padding='SAME', activation=tf.nn.relu)
        self.pool2 = MaxPool2D(padding='SAME')
        self.pool3_flat = Flatten()
        self.dense4 = Dense(units=128, activation=tf.nn.relu)
        self.drop4 = Dropout(rate=0.4)
        self.dense5 = Dense(10, activation='softmax')
    def call(self, inputs, training=False):
        net = self.conv1(inputs)
        net = self.pool1(net)
        net = self.conv2(net)
        net = self.pool2(net)
        net = self.pool3_flat(net)
        net = self.dense4(net)
        net = self.drop4(net)
        net = self.dense5(net)
        return net

model = MNISTModel()
profile
바쁘게 부지런하게 논리적으로 살자!!!

0개의 댓글