🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Keras Functional API 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 2. · 12 Views

Keras Functional API 완벽 가이드

Keras Functional API를 활용하여 복잡한 딥러닝 모델을 설계하는 방법을 알아봅니다. Sequential 모델의 한계를 넘어 다중 입력, 다중 출력, 잔차 연결 등 실무에서 필요한 고급 아키텍처를 구현하는 기법을 익힐 수 있습니다.


목차

  1. Sequential_vs_Functional_API
  2. Input과_Layer_연결
  3. 다중_입력_출력_모델
  4. 잔차_연결_Skip_Connection
  5. 모델_시각화_plot_model
  6. 복잡한_아키텍처_설계

1. Sequential vs Functional API

김개발 씨는 딥러닝 입문 강의를 듣고 Sequential 모델로 이미지 분류기를 만들어봤습니다. 그런데 회사에서 받은 첫 프로젝트는 이미지와 텍스트를 동시에 입력받아야 하는 모델이었습니다.

"Sequential로는 안 되나요?" 선배에게 물었더니, "그건 Functional API를 써야 해"라는 답이 돌아왔습니다.

Sequential API는 레이어를 순서대로 쌓는 가장 간단한 방식입니다. 마치 블록을 일렬로 쌓는 것과 같습니다.

반면 Functional API는 레이어를 자유롭게 연결할 수 있어서, 복잡한 그래프 형태의 모델을 만들 수 있습니다. 실무에서 조금이라도 복잡한 모델을 다루려면 Functional API는 필수입니다.

다음 코드를 살펴봅시다.

# Sequential API - 단순한 일렬 구조
from tensorflow import keras
from tensorflow.keras import layers

# Sequential: 레이어를 순서대로 쌓습니다
sequential_model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=(784,)),
    layers.Dense(32, activation='relu'),
    layers.Dense(10, activation='softmax')
])

# Functional API - 자유로운 연결 구조
inputs = keras.Input(shape=(784,))
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dense(32, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs)

김개발 씨는 입사 2개월 차 주니어 개발자입니다. 대학교에서 머신러닝 수업을 들으면서 Keras Sequential 모델로 MNIST 손글씨 분류기를 만들어본 경험이 있습니다.

코드 몇 줄로 딥러닝 모델이 뚝딱 만들어지는 게 신기했습니다. 그런데 회사에서 받은 첫 미션은 조금 달랐습니다.

상품 이미지와 상품 설명 텍스트를 동시에 분석해서 카테고리를 분류하는 모델을 만들어야 했습니다. 김개발 씨는 고민에 빠졌습니다.

Sequential 모델은 입력이 하나뿐인데, 이건 입력이 두 개잖아요? 선배 개발자 박시니어 씨가 다가와 화면을 살펴봅니다.

"아, Sequential로는 이런 구조를 못 만들어요. Functional API를 써야 해요." 그렇다면 Sequential과 Functional의 차이는 정확히 무엇일까요?

쉽게 비유하자면, Sequential은 마치 직선 도로와 같습니다. 출발점에서 도착점까지 한 방향으로만 달릴 수 있습니다.

중간에 갈림길도 없고, 다른 도로와 합류하는 지점도 없습니다. 단순하고 명확하지만, 그만큼 할 수 있는 것이 제한적입니다.

반면 Functional API는 복잡한 도심의 도로망과 같습니다. 여러 출발점에서 시작해서 중간에 합류할 수도 있고, 한 도로에서 여러 갈래로 나뉠 수도 있습니다.

심지어 지름길을 만들어 특정 구간을 건너뛸 수도 있습니다. 자유도가 높은 만큼 다양한 경로를 설계할 수 있습니다.

Sequential이 처음 등장했을 때는 충분히 강력해 보였습니다. 대부분의 입문 예제가 Sequential로 작성되어 있고, 코드도 직관적입니다.

하지만 실무에서는 금방 한계에 부딪힙니다. 예를 들어 이미지와 텍스트를 함께 처리해야 한다면요?

중간 결과를 나중에 다시 사용해야 한다면요? 하나의 모델에서 여러 종류의 출력을 내야 한다면요?

이런 상황에서 Sequential은 속수무책입니다. 바로 이런 문제를 해결하기 위해 Functional API가 등장했습니다.

Functional API를 사용하면 레이어를 마치 함수처럼 호출할 수 있습니다. 이전 레이어의 출력을 다음 레이어의 입력으로 전달하는 방식입니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 keras.Input(shape=(784,))으로 입력 텐서를 정의합니다.

이것이 모델의 시작점입니다. 그 다음 layers.Dense(64, activation='relu')(inputs)에서 괄호가 두 번 나오는 것이 핵심입니다.

첫 번째 괄호는 레이어를 생성하고, 두 번째 괄호는 그 레이어에 입력을 전달합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 고객 이탈 예측 모델을 만든다고 가정해봅시다. 고객의 구매 이력 데이터와 고객 프로필 데이터를 각각 다른 방식으로 처리한 뒤, 중간에 합쳐서 최종 예측을 내야 합니다.

이런 구조는 Functional API 없이는 불가능합니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 간단한 모델에도 무조건 Functional API를 쓰려고 하는 것입니다. 단순한 일렬 구조라면 Sequential이 더 읽기 쉽고 유지보수하기 좋습니다.

적재적소에 맞는 도구를 선택하는 것이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, Functional API가 이래서 필요하군요!" 이제 두 개의 입력을 어떻게 연결하는지 배워볼 차례입니다.

실전 팁

💡 - 단순한 일렬 구조는 Sequential, 복잡한 구조는 Functional API를 선택하세요

  • Functional API의 핵심은 레이어를 함수처럼 호출하는 것입니다

2. Input과 Layer 연결

Functional API를 처음 접한 김개발 씨는 코드를 보다가 의아한 점을 발견했습니다. Dense(64)(inputs)처럼 괄호가 두 번 나오는 문법이 낯설었습니다.

"이게 대체 무슨 문법이에요?" 선배에게 물었더니, 이것이 바로 Functional API의 핵심이라고 합니다.

Functional API에서 Input은 모델의 진입점을 정의하고, 각 Layer는 함수처럼 호출됩니다. 레이어에 이전 출력을 전달하면 새로운 텐서가 반환되고, 이를 다음 레이어로 전달하는 방식으로 모델이 구성됩니다.

이런 함수 체이닝 방식이 Functional API의 이름이 된 이유입니다.

다음 코드를 살펴봅시다.

from tensorflow import keras
from tensorflow.keras import layers

# Step 1: 입력 정의 - 모델의 시작점
inputs = keras.Input(shape=(28, 28, 1), name='image_input')

# Step 2: 레이어를 함수처럼 호출하여 연결
x = layers.Conv2D(32, kernel_size=3, activation='relu')(inputs)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(64, kernel_size=3, activation='relu')(x)
x = layers.Flatten()(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)

# Step 3: 입력과 출력을 연결하여 모델 생성
model = keras.Model(inputs=inputs, outputs=outputs)
model.summary()

김개발 씨는 Functional API 코드를 처음 봤을 때 당황했습니다. 파이썬을 꽤 오래 다뤘다고 생각했는데, Dense(64)(inputs)처럼 괄호가 두 번 나오는 문법은 처음이었습니다.

클래스 인스턴스를 생성하고 바로 호출한다고요? 박시니어 씨가 웃으며 설명합니다.

"파이썬에서 클래스에 __call__ 메서드가 정의되어 있으면, 인스턴스를 함수처럼 호출할 수 있어요. Keras 레이어가 바로 그렇게 설계되어 있죠." 이해를 돕기 위해 비유를 들어보겠습니다.

Input은 마치 공장의 원자재 입고 창구와 같습니다. 모든 재료가 이곳을 통해 공장에 들어옵니다.

입고 창구에서는 재료의 규격을 정해놓습니다. 예를 들어 "28x28 크기의 흑백 이미지만 받습니다"라고 명시하는 것이죠.

레이어는 공장의 가공 기계와 같습니다. 원자재가 첫 번째 기계를 통과하면 1차 가공품이 나옵니다.

그 가공품이 두 번째 기계에 들어가면 2차 가공품이 나옵니다. 이렇게 여러 기계를 거치면서 최종 제품이 완성됩니다.

Functional API에서는 이 과정이 명시적으로 드러납니다. x = layers.Conv2D(32, 3)(inputs)라고 쓰면, 입력 텐서 inputs가 컨볼루션 레이어를 통과해서 새로운 텐서 x가 된다는 것을 한눈에 알 수 있습니다.

이 방식의 장점은 데이터의 흐름이 명확하게 보인다는 것입니다. Sequential에서는 레이어만 나열하면 끝이지만, Functional API에서는 어떤 텐서가 어디로 흘러가는지 코드에서 바로 확인할 수 있습니다.

위의 코드를 단계별로 살펴보겠습니다. 먼저 keras.Input(shape=(28, 28, 1))으로 입력 텐서를 정의합니다.

여기서 shape는 배치 크기를 제외한 입력 형태입니다. 28x28 픽셀의 흑백 이미지를 받겠다는 뜻입니다.

그 다음 layers.Conv2D(32, kernel_size=3, activation='relu')(inputs)에서 컨볼루션 레이어를 생성하고, 바로 inputs를 전달합니다. 반환값 x는 이 레이어를 통과한 결과 텐서입니다.

이어서 MaxPooling2D(pool_size=2)(x)로 풀링을 적용합니다. 이전 출력 x가 입력이 되는 것이죠.

마지막으로 keras.Model(inputs=inputs, outputs=outputs)로 모델을 생성합니다. 시작점과 끝점을 지정해주면 Keras가 알아서 그 사이의 모든 레이어를 연결해줍니다.

마치 지도에서 출발지와 도착지만 찍으면 내비게이션이 경로를 알아서 찾아주는 것과 같습니다. 실무에서 중요한 팁 하나를 알려드리겠습니다.

name 파라미터를 활용하세요. 위 코드에서 name='image_input'이라고 지정해두면, 나중에 모델을 디버깅하거나 저장할 때 훨씬 편리합니다.

레이어가 수십 개인 복잡한 모델에서 이름 없이 "dense_15", "conv2d_7" 같은 자동 생성 이름만 있으면 디버깅이 악몽이 됩니다. 주의할 점도 있습니다.

Input 텐서의 shape를 잘못 지정하면 나중에 오류가 발생합니다. 특히 이미지 데이터에서 채널 순서를 헷갈리기 쉽습니다.

Keras는 기본적으로 채널이 마지막에 오는 channels_last 형식을 사용합니다. (28, 28, 1)은 28x28 흑백 이미지를, (28, 28, 3)은 28x28 컬러 이미지를 의미합니다.

김개발 씨는 이제 Functional API의 기본 문법을 이해했습니다. "괄호 두 개가 이런 뜻이었군요!" 이제 진짜 Functional API의 강점을 발휘할 차례입니다.

바로 다중 입력과 다중 출력입니다.

실전 팁

💡 - 레이어에 name 파라미터를 지정하면 디버깅과 시각화가 쉬워집니다

  • Input의 shape는 배치 차원을 제외한 형태입니다

3. 다중 입력 출력 모델

김개발 씨가 맡은 프로젝트는 상품 이미지와 상품 설명을 동시에 분석해야 합니다. 이미지 따로, 텍스트 따로 분석하는 모델은 만들어봤는데, 둘을 합치는 건 어떻게 해야 할까요?

게다가 결과로 카테고리 분류와 가격대 예측을 동시에 해야 한다고 합니다.

다중 입력 모델은 여러 종류의 데이터를 각각 다른 경로로 처리한 뒤 합칩니다. 다중 출력 모델은 하나의 모델에서 여러 종류의 예측을 동시에 수행합니다.

Functional API의 진정한 강점은 이런 복잡한 구조를 자연스럽게 표현할 수 있다는 점입니다.

다음 코드를 살펴봅시다.

from tensorflow import keras
from tensorflow.keras import layers

# 다중 입력: 이미지와 텍스트
image_input = keras.Input(shape=(224, 224, 3), name='image')
text_input = keras.Input(shape=(100,), name='text')

# 이미지 처리 경로
x1 = layers.Conv2D(32, 3, activation='relu')(image_input)
x1 = layers.GlobalAveragePooling2D()(x1)
x1 = layers.Dense(64, activation='relu')(x1)

# 텍스트 처리 경로
x2 = layers.Embedding(10000, 64)(text_input)
x2 = layers.LSTM(64)(x2)

# 두 경로 합치기
combined = layers.concatenate([x1, x2])
combined = layers.Dense(64, activation='relu')(combined)

# 다중 출력: 카테고리 분류와 가격 예측
category_output = layers.Dense(5, activation='softmax', name='category')(combined)
price_output = layers.Dense(1, name='price')(combined)

model = keras.Model(
    inputs=[image_input, text_input],
    outputs=[category_output, price_output]
)

김개발 씨의 프로젝트는 이커머스 회사의 상품 분류 시스템입니다. 상품 이미지만으로 분류하면 정확도가 85%쯤 나옵니다.

상품 설명 텍스트만으로 분류하면 80%쯤 나옵니다. 하지만 둘을 합치면 95%까지 올라간다는 연구 결과가 있습니다.

멀티모달의 힘이죠. 문제는 이미지와 텍스트는 완전히 다른 종류의 데이터라는 것입니다.

이미지는 2D 픽셀 배열이고, 텍스트는 단어의 시퀀스입니다. 처리 방법도 완전히 다릅니다.

이미지는 CNN이 적합하고, 텍스트는 RNN이나 Transformer가 적합합니다. 쉽게 비유하자면, 다중 입력 모델은 마치 병원의 종합 건강검진과 같습니다.

혈액 검사, X-ray, 심전도 등 여러 검사를 각각 전문 장비로 진행합니다. 그리고 마지막에 의사가 모든 검사 결과를 종합해서 진단을 내립니다.

각 검사는 다른 관점에서 몸 상태를 파악하고, 종합하면 더 정확한 진단이 가능합니다. 다중 출력 모델은 하나의 진단에서 여러 결론을 내리는 것과 같습니다.

"당뇨병 위험이 있고, 고혈압 주의가 필요하며, 운동량을 늘려야 합니다." 이렇게 여러 종류의 출력을 한 번에 제공하는 것이죠. 위의 코드를 자세히 살펴보겠습니다.

먼저 두 개의 Input을 정의합니다. image_input은 224x224 컬러 이미지를, text_input은 길이 100의 정수 시퀀스를 받습니다.

각각 다른 형태의 데이터입니다. 이미지 경로에서는 Conv2D로 특징을 추출하고, GlobalAveragePooling2D로 2D 특징 맵을 1D 벡터로 압축합니다.

텍스트 경로에서는 Embedding으로 단어를 벡터로 변환하고, LSTM으로 시퀀스를 처리합니다. 핵심은 layers.concatenate([x1, x2])입니다.

두 경로의 출력을 하나로 합칩니다. 이미지에서 추출한 64차원 벡터와 텍스트에서 추출한 64차원 벡터가 합쳐져 128차원 벡터가 됩니다.

이제 이 합쳐진 정보로 최종 예측을 수행합니다. 출력도 두 개입니다.

category_output은 5개 카테고리 중 하나를 분류하는 softmax 출력입니다. price_output은 가격을 예측하는 회귀 출력입니다.

하나의 모델이 분류와 회귀를 동시에 수행하는 것입니다. 모델을 컴파일할 때는 각 출력에 대해 별도의 손실 함수를 지정할 수 있습니다.

분류에는 categorical_crossentropy, 회귀에는 mse를 사용하면 됩니다. 손실 가중치도 조절할 수 있어서, 어떤 출력에 더 집중할지 조절 가능합니다.

실무에서 다중 입력/출력 모델은 정말 자주 사용됩니다. 추천 시스템에서 사용자 정보와 상품 정보를 합치거나, 자율주행에서 카메라 영상과 라이다 데이터를 합치거나, 의료 진단에서 여러 종류의 검사 데이터를 합치는 경우가 대표적입니다.

주의할 점은 데이터 준비입니다. 학습시킬 때 입력 데이터를 딕셔너리나 리스트로 제공해야 합니다.

model.fit({'image': image_data, 'text': text_data}, {'category': category_labels, 'price': price_labels}) 형태로 사용합니다. Input과 Output에 지정한 name이 여기서 딕셔너리 키로 사용됩니다.

김개발 씨는 감탄했습니다. "Functional API가 없었다면 이걸 어떻게 구현했을까요?" 박시니어 씨가 답합니다.

"예전에는 여러 모델을 따로 학습시키고 결과를 수동으로 합쳤어요. 지금은 end-to-end로 한 번에 학습시킬 수 있죠."

실전 팁

💡 - Input과 Output에 의미 있는 name을 지정하면 데이터 전달이 직관적입니다

  • 다중 출력 시 각 출력의 손실 가중치를 조절하여 학습 균형을 맞추세요

4. 잔차 연결 Skip Connection

김개발 씨가 딥러닝 논문을 읽다가 ResNet이라는 것을 발견했습니다. 152개 층을 쌓았는데도 학습이 잘 된다고요?

예전에는 20층만 넘어가도 학습이 안 됐다는데, 비결이 뭘까요? 바로 잔차 연결, 영어로 Skip Connection이라고 합니다.

잔차 연결은 레이어의 입력을 출력에 더해주는 기법입니다. 레이어가 원본 정보를 변형하는 것이 아니라 원본에 무엇을 더할지를 학습합니다.

이 간단한 아이디어가 수백 개의 층을 쌓을 수 있게 만든 핵심입니다. Functional API에서는 이런 연결을 자연스럽게 표현할 수 있습니다.

다음 코드를 살펴봅시다.

from tensorflow import keras
from tensorflow.keras import layers

def residual_block(x, filters):
    # 원본 입력을 저장합니다
    shortcut = x

    # 메인 경로: 두 개의 컨볼루션
    x = layers.Conv2D(filters, 3, padding='same', activation='relu')(x)
    x = layers.BatchNormalization()(x)
    x = layers.Conv2D(filters, 3, padding='same')(x)
    x = layers.BatchNormalization()(x)

    # 잔차 연결: 원본을 더합니다
    x = layers.add([shortcut, x])
    x = layers.Activation('relu')(x)
    return x

# 모델 구성
inputs = keras.Input(shape=(32, 32, 64))
x = residual_block(inputs, 64)
x = residual_block(x, 64)
outputs = layers.GlobalAveragePooling2D()(x)
model = keras.Model(inputs, outputs)

2015년, 마이크로소프트 연구팀이 ImageNet 대회에서 우승했습니다. 그들의 모델 ResNet은 무려 152개 층을 가지고 있었습니다.

그 전까지 20~30층만 넘어가도 학습이 제대로 안 됐는데, 어떻게 가능했을까요? 비밀은 놀라울 정도로 간단했습니다.

레이어의 입력을 출력에 그냥 더해준 것입니다. 이것이 바로 잔차 연결입니다.

비유를 들어보겠습니다. 여러분이 사진을 보정한다고 생각해보세요.

일반적인 방식은 원본 사진을 완전히 새로운 사진으로 바꾸는 것입니다. 하지만 잔차 연결 방식은 다릅니다.

원본은 그대로 두고, "밝기를 +10, 채도를 +5" 같은 조정값만 학습하는 것입니다. 왜 이게 중요할까요?

깊은 네트워크에서 발생하는 문제는 그래디언트 소실입니다. 역전파 과정에서 그래디언트가 층을 거칠 때마다 점점 작아집니다.

100개 층을 거치면 그래디언트가 거의 0에 가까워져서 학습이 되지 않습니다. 잔차 연결은 이 문제를 해결합니다.

입력이 출력에 그대로 더해지기 때문에, 그래디언트가 잔차 연결을 통해 직접 전달됩니다. 마치 고속도로 위에 비상 차선이 있는 것과 같습니다.

정체가 심해도 비상 차선으로 빠져나갈 수 있습니다. 또 다른 장점도 있습니다.

레이어가 아무것도 하지 않는 것을 배우기 쉬워집니다. 잔차가 0이면 출력은 입력과 같습니다.

만약 특정 층이 필요 없다면, 그 층은 그냥 0을 출력하면 됩니다. 이것이 수백 개의 층을 쌓아도 성능이 떨어지지 않는 이유입니다.

위의 코드를 분석해보겠습니다. shortcut = x로 원본 입력을 저장합니다.

그리고 x는 컨볼루션 레이어들을 통과합니다. 마지막에 layers.add([shortcut, x])로 원본과 변환된 출력을 더합니다.

이것이 잔차 연결의 전부입니다. 중요한 점은 shortcutx의 형태가 같아야 더할 수 있다는 것입니다.

위 코드에서는 padding='same'을 사용하고 필터 수를 동일하게 유지해서 형태를 맞췄습니다. 만약 형태가 다르다면, shortcut에도 1x1 컨볼루션을 적용해서 형태를 맞춰야 합니다.

실무에서 잔차 연결은 거의 모든 최신 아키텍처에서 사용됩니다. ResNet뿐 아니라 Transformer, EfficientNet, ConvNeXT 등 대부분의 SOTA 모델이 잔차 연결을 포함합니다.

깊은 모델을 설계한다면 잔차 연결은 필수라고 생각하면 됩니다. 주의할 점은 더하기 전에 형태를 맞춰야 한다는 것입니다.

채널 수가 바뀌거나 해상도가 바뀌는 경우, shortcut 경로에도 변환을 적용해야 합니다. 이것을 projection shortcut이라고 합니다.

김개발 씨는 코드를 보며 감탄했습니다. "이렇게 간단한 아이디어가 딥러닝의 판도를 바꿨군요." 박시니어 씨가 고개를 끄덕입니다.

"단순한 아이디어가 가장 강력할 때가 많아요."

실전 팁

💡 - 잔차 연결 시 입력과 출력의 형태가 같아야 더할 수 있습니다

  • 채널 수가 변하면 1x1 컨볼루션으로 shortcut의 형태를 맞추세요

5. 모델 시각화 plot model

김개발 씨가 열심히 Functional API로 모델을 만들었습니다. 그런데 팀 미팅에서 모델 구조를 설명해야 합니다.

코드를 한 줄씩 읽어줄 순 없고, 다이어그램이 있으면 좋겠는데... Keras에는 모델을 그림으로 그려주는 기능이 있습니다.

plot_model 함수는 Keras 모델의 구조를 이미지 파일로 저장합니다. 레이어의 연결 관계, 입출력 형태, 레이어 이름 등을 한눈에 볼 수 있는 다이어그램을 생성합니다.

복잡한 모델일수록 시각화의 가치가 높아집니다. 코드 리뷰나 문서화에도 필수적인 도구입니다.

다음 코드를 살펴봅시다.

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.utils import plot_model

# 다중 입력 모델 예시
input_a = keras.Input(shape=(64,), name='feature_a')
input_b = keras.Input(shape=(32,), name='feature_b')

x1 = layers.Dense(32, activation='relu')(input_a)
x2 = layers.Dense(32, activation='relu')(input_b)
merged = layers.concatenate([x1, x2])
output = layers.Dense(1, activation='sigmoid', name='prediction')(merged)

model = keras.Model(inputs=[input_a, input_b], outputs=output)

# 모델 시각화
plot_model(model, to_file='model_architecture.png',
           show_shapes=True,       # 각 레이어의 형태 표시
           show_layer_names=True,  # 레이어 이름 표시
           rankdir='TB',           # 위에서 아래로 (TB) 또는 왼쪽에서 오른쪽 (LR)
           dpi=150)                # 해상도

# 간단한 텍스트 요약도 가능
model.summary()

팀 미팅 시간이 다가왔습니다. 김개발 씨는 이번 주에 만든 멀티모달 모델을 설명해야 합니다.

입력이 세 개고, 중간에 합쳐지는 부분이 두 군데고, 출력도 두 개입니다. 코드로는 50줄이 넘습니다.

이걸 어떻게 설명하죠? 박시니어 씨가 힌트를 줍니다.

"plot_model 써봤어요? 모델 구조를 그림으로 그려줘요." plot_model은 마치 건축 설계도와 같습니다.

건물을 설명할 때 벽돌 하나하나를 설명하지 않습니다. 설계도 한 장이면 전체 구조가 한눈에 들어옵니다.

모델도 마찬가지입니다. 코드 한 줄 한 줄보다 다이어그램 하나가 더 효과적일 때가 많습니다.

plot_model을 사용하려면 graphviz 라이브러리가 필요합니다. 시스템에 graphviz가 설치되어 있어야 하고, 파이썬 패키지 pydot 또는 pydotplus도 필요합니다.

설치가 안 되어 있으면 에러가 나니 미리 확인하세요. 위의 코드를 살펴보겠습니다.

plot_model(model, to_file='model_architecture.png')이 기본 사용법입니다. 모델과 저장할 파일 경로만 지정하면 됩니다.

하지만 몇 가지 옵션을 추가하면 훨씬 유용해집니다. show_shapes=True는 각 레이어의 입출력 형태를 표시합니다.

(None, 64) 같은 형태가 레이어 옆에 나타납니다. None은 배치 크기로, 학습할 때 결정됩니다.

이 정보가 있으면 데이터 흐름을 추적하기 쉽습니다. show_layer_names=True는 레이어 이름을 표시합니다.

Input에 지정한 'feature_a', 'feature_b' 같은 이름이 그대로 나타납니다. 이름을 잘 지어두면 다이어그램이 자명해집니다.

rankdir='TB'는 방향을 지정합니다. TB는 Top to Bottom으로 위에서 아래로 그립니다.

LR은 Left to Right로 왼쪽에서 오른쪽으로 그립니다. 모델 구조에 따라 더 보기 좋은 방향을 선택하면 됩니다.

model.summary()도 유용합니다. 터미널에 모델의 텍스트 요약을 출력합니다.

각 레이어의 이름, 출력 형태, 파라미터 수를 테이블로 보여줍니다. 전체 파라미터 수와 학습 가능한 파라미터 수도 알 수 있습니다.

실무에서 모델 시각화는 여러 상황에서 필요합니다. 코드 리뷰에서 "이 모델이 뭘 하는 건가요?"라는 질문에 답할 때, 기술 문서에 모델 아키텍처를 넣을 때, 논문이나 발표 자료를 준비할 때 모두 plot_model이 유용합니다.

주의할 점은 너무 복잡한 모델은 시각화해도 읽기 어렵다는 것입니다. 수백 개의 레이어가 있으면 그림이 너무 커지고 복잡해집니다.

이런 경우에는 서브모델로 나눠서 각각 시각화하거나, 중요한 부분만 따로 그리는 것이 좋습니다. 김개발 씨는 생성된 이미지를 보며 만족했습니다.

복잡해 보이던 모델이 그림 하나로 명확해졌습니다. 미팅에서 이 그림 한 장으로 5분 만에 설명을 끝낼 수 있었습니다.

실전 팁

💡 - graphviz와 pydot 설치가 필요합니다: pip install pydot graphviz

  • show_shapes=True로 각 레이어의 형태를 확인하면 디버깅에 유용합니다

6. 복잡한 아키텍처 설계

김개발 씨가 Functional API를 익히고 나니, 이제 세상의 모든 모델을 만들 수 있을 것 같습니다. Inception, U-Net, Transformer...

이런 복잡한 아키텍처도 직접 구현할 수 있을까요? 박시니어 씨가 말합니다.

"Functional API를 마스터했으면, 못 만들 모델이 없어요."

복잡한 아키텍처 설계의 핵심은 모듈화입니다. 반복되는 패턴을 함수로 추출하고, 이를 조합하여 전체 모델을 구성합니다.

Inception 모듈, 인코더-디코더 구조, 어텐션 메커니즘 등 현대 딥러닝의 핵심 요소들을 Functional API로 자유롭게 표현할 수 있습니다.

다음 코드를 살펴봅시다.

from tensorflow import keras
from tensorflow.keras import layers

def inception_module(x, filters):
    # 1x1 컨볼루션 경로
    path1 = layers.Conv2D(filters, 1, padding='same', activation='relu')(x)

    # 1x1 -> 3x3 경로
    path2 = layers.Conv2D(filters, 1, padding='same', activation='relu')(x)
    path2 = layers.Conv2D(filters, 3, padding='same', activation='relu')(path2)

    # 1x1 -> 5x5 경로
    path3 = layers.Conv2D(filters, 1, padding='same', activation='relu')(x)
    path3 = layers.Conv2D(filters, 5, padding='same', activation='relu')(path3)

    # MaxPool -> 1x1 경로
    path4 = layers.MaxPooling2D(3, strides=1, padding='same')(x)
    path4 = layers.Conv2D(filters, 1, padding='same', activation='relu')(path4)

    # 모든 경로 합치기
    return layers.concatenate([path1, path2, path3, path4], axis=-1)

# 모델 구성
inputs = keras.Input(shape=(224, 224, 3))
x = layers.Conv2D(64, 7, strides=2, padding='same', activation='relu')(inputs)
x = inception_module(x, 32)
x = inception_module(x, 64)
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense(1000, activation='softmax')(x)

model = keras.Model(inputs, outputs)

김개발 씨는 이제 Functional API의 기본기를 탄탄히 다졌습니다. 다중 입력, 다중 출력, 잔차 연결까지 구현할 수 있게 되었습니다.

이제 도전해볼 것은 실제 논문에 나오는 복잡한 아키텍처입니다. 박시니어 씨가 조언합니다.

"복잡한 모델을 만들 때 가장 중요한 건 모듈화예요. 큰 그림을 작은 조각으로 나누고, 각 조각을 함수로 만드는 거죠." 비유하자면, 레고 블록을 생각해보세요.

복잡한 레고 성이라도 결국은 작은 블록들의 조합입니다. 먼저 탑을 만들고, 벽을 만들고, 문을 만들어서 조립합니다.

딥러닝 모델도 마찬가지입니다. 작은 모듈을 만들고, 이들을 조합하여 전체 모델을 구성합니다.

위 코드의 Inception 모듈을 살펴보겠습니다. Inception은 구글이 2014년에 발표한 아키텍처입니다.

핵심 아이디어는 여러 크기의 필터를 병렬로 적용하는 것입니다. 1x1, 3x3, 5x5 컨볼루션과 풀링을 동시에 수행하고 결과를 합칩니다.

왜 이렇게 할까요? 이미지에서 중요한 특징이 어떤 크기일지 미리 알 수 없기 때문입니다.

작은 특징은 작은 필터가, 큰 특징은 큰 필터가 잘 잡아냅니다. 여러 크기를 동시에 적용하면 다양한 스케일의 특징을 모두 추출할 수 있습니다.

코드를 보면 네 개의 경로(path)가 있습니다. 각 경로는 다른 크기의 컨볼루션을 적용합니다.

마지막에 layers.concatenate로 네 경로의 출력을 합칩니다. 이렇게 만든 inception_module 함수를 여러 번 호출하면 Inception 네트워크가 완성됩니다.

이런 모듈화의 장점은 명확합니다. 첫째, 코드가 읽기 쉬워집니다.

x = inception_module(x, 64)라는 한 줄로 복잡한 연산이 추상화됩니다. 둘째, 재사용이 가능합니다.

같은 모듈을 여러 번 사용하거나 다른 프로젝트에서 가져올 수 있습니다. 셋째, 테스트가 쉬워집니다.

모듈별로 독립적으로 테스트할 수 있습니다. 실무에서는 어떤 패턴들이 자주 모듈화될까요?

인코더-디코더 구조가 대표적입니다. 인코더는 입력을 압축하고, 디코더는 압축된 표현을 복원합니다.

각각을 별도 함수로 만들면 됩니다. 어텐션 블록도 자주 모듈화됩니다.

Self-Attention, Cross-Attention 등을 함수로 만들어두면 Transformer 계열 모델을 쉽게 구성할 수 있습니다. 서브모델을 사용하는 방법도 있습니다.

keras.Model로 만든 모델 자체가 레이어처럼 동작합니다. 복잡한 부분을 서브모델로 만들고, 메인 모델에서 서브모델을 호출하는 구조도 가능합니다.

이렇게 하면 각 서브모델을 독립적으로 저장하고 불러올 수 있어서 더 유연합니다. 주의할 점은 과도한 추상화입니다.

한 번만 사용되는 코드를 굳이 함수로 만들 필요는 없습니다. 적절한 수준의 모듈화가 중요합니다.

또한 모듈 간의 인터페이스를 명확히 해야 합니다. 각 모듈이 어떤 형태의 텐서를 받고 반환하는지 문서화하면 협업이 쉬워집니다.

김개발 씨는 이제 자신감이 붙었습니다. "ResNet도, U-Net도, Transformer도 결국 작은 조각들의 조합이었군요." 박시니어 씨가 끄덕입니다.

"맞아요. Functional API를 익혔으면, 논문에 나오는 어떤 모델도 구현할 수 있어요.

이제 당신도 딥러닝 아키텍처 설계자예요."

실전 팁

💡 - 반복되는 패턴은 함수로 추출하여 재사용성을 높이세요

  • 서브모델을 활용하면 복잡한 구조도 깔끔하게 관리할 수 있습니다
  • 모듈의 입출력 형태를 명확히 문서화하면 협업이 쉬워집니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Keras#FunctionalAPI#DeepLearning#NeuralNetwork#ModelArchitecture#AI,DeepLearning

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.