Keras로 모델 만들기
이 챕터에서 배우는 것
Ch03에서 손으로 계산했던 뉴런과 층을, 이제 Keras 코드 몇 줄로 쌓습니다. 이 챕터는 모델을 만드는 것(구성)까지입니다. 학습에 필요한 손실 함수와 optimizer 설정은 Ch05에서 다룹니다.
시험 연관성: 실기의 딥러닝 문항은 거의 예외 없이 "은닉층 2개 이상의 모델을 만드시오"로 시작합니다. 이 챕터의 코드 골격이 그 문장의 정답입니다.
4.1 모델 만들기는 전체 4단계 중 첫 단계입니다
Keras로 딥러닝 모델을 다루는 일은 항상 같은 순서를 밟습니다. 구성 → 컴파일 → 학습 → 평가, 네 단계입니다.
이 순서는 Ch02에서 본 머신러닝의 3줄 패턴(생성 → fit → predict)과 대응됩니다. 다른 점은 sklearn이 한 줄로 끝내는 "모델 생성"이 Keras에서는 구성과 컴파일 두 단계로 나뉜다는 것입니다. 층을 어떻게 쌓을지(구성)와 그 층들을 어떤 기준으로 학습시킬지(컴파일)를 따로 정하기 때문입니다.
이 챕터가 책임지는 것은 첫 번째 칸, 구성입니다. 구성 단계에서 결정하는 것은 세 가지입니다.
- 입력을 몇 개 받을 것인가 (입력 shape)
- 은닉층을 몇 층, 몇 개 노드로 쌓을 것인가
- 출력을 몇 개, 어떤 활성화 함수(activation function)로 낼 것인가
⚠️ 시험 포인트
문제가 "모델을 만들고 학습시키시오"라고 한 문장으로 지시해도, 답안은 구성 → 컴파일 → 학습 세 덩어리를 모두 써야 합니다. 구성만 하고compile()을 빠뜨리면fit()에서 오류가 납니다.
4.2 Sequential — 코드 한 줄이 층 하나입니다
Sequential은 층을 위에서 아래로 한 줄로 쌓는 모델 컨테이너입니다. model.add()를 부른 순서가 그대로 데이터가 지나가는 순서가 됩니다.
Dense는 Ch03에서 본 완전연결층입니다. 앞 층의 모든 노드가 다음 층의 모든 노드에 연결됩니다. 인자는 두 개만 기억하면 됩니다.
| 인자 | 의미 | 실기에서 쓰는 값 |
|---|---|---|
units (첫 번째 위치 인자) |
이 층의 노드 개수 | 은닉층은 보통 32, 64, 128 |
activation |
노드 출력에 씌우는 활성화 함수 | 은닉층은 'relu' |
가장 기본이 되는 모델을 만들어 보겠습니다.
입력 8개를 받아 은닉층 2개를 거쳐 값 하나를 내는 모델을 구성하는 코드입니다.
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
tf.random.set_seed(42)
n_features = 8
model = Sequential()
model.add(Input(shape=(n_features,))) # 입력 정의
model.add(Dense(64, activation='relu')) # 은닉층 1
model.add(Dense(32, activation='relu')) # 은닉층 2
model.add(Dense(1, activation='sigmoid')) # 출력층
print(len(model.layers))
출력은 3입니다. Input은 실제 연산을 하는 층이 아니라 입력 형태 선언이므로 층 목록에 세지 않습니다. Dense 세 개만 층으로 잡힙니다.
리스트로 한 번에 넘기는 표기도 같은 결과를 냅니다. 취향에 따라 쓰되, 시험장에서는 한 줄씩 추가하는 add() 방식이 중간에 층을 넣고 빼기 편합니다.
model = Sequential([
Input(shape=(n_features,)),
Dense(64, activation='relu'),
Dense(32, activation='relu'),
Dense(1, activation='sigmoid'),
])
은닉층을 몇 개 쌓아야 할지 고민된다면, 실기 기준으로는 은닉층 2개, 노드 수는 앞 층이 크고 뒤 층이 작아지는 형태(64 → 32)를 기본값으로 삼으십시오. Ch03의 ch03_width-depth에서 봤듯 층과 노드를 무작정 늘리면 표현력은 커지지만 과적합 위험도 함께 커집니다.
⚠️ 시험 포인트
"은닉층 2개 이상"이라는 지시에서 은닉층은 입력층과 출력층을 뺀 중간층입니다.Dense(64)+Dense(1, 'sigmoid')는 은닉층이 1개뿐이라 조건을 채우지 못합니다. 출력층은 은닉층 개수에 포함되지 않습니다.
4.3 입력 shape — 샘플 수는 넣지 않습니다
Input(shape=(n,))의 n은 특성(컬럼)의 개수입니다. 데이터가 1,000행 8열이라면 shape=(8,)입니다. 행 수는 절대 들어가지 않습니다.
모델은 "샘플 하나가 어떻게 생겼는가"만 알면 되고, 샘플이 몇 개 들어올지는 학습할 때 알아서 처리하기 때문입니다.
전처리를 마친 데이터에서 입력 크기를 직접 뽑아내는 코드입니다.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print(X_train_scaled.shape) # (398, 30)
n_features = X_train_scaled.shape[1] # 30
print(n_features)
X_train_scaled.shape는 (398, 30), 즉 (샘플 수, 특성 수)입니다. 여기서 필요한 것은 뒤의 30뿐이므로 X_train_scaled.shape[1]로 꺼냅니다. 숫자를 직접 세어 적지 말고 항상 이 방식으로 뽑으십시오. 전처리 단계에서 원-핫 인코딩으로 컬럼이 늘어나거나 불필요한 컬럼을 지우면 특성 수가 바뀌는데, 손으로 적은 숫자는 그때 틀립니다.
shape=(30,)의 쉼표도 빠뜨리면 안 됩니다. 파이썬에서 (30)은 그냥 정수 30이고, 원소가 하나인 튜플은 (30,)으로 써야 합니다.
구버전 표기 참고. 오래된 코드에서는 첫 Dense에 입력 크기를 붙인 표기를 자주 봅니다.
# 구버전 표기 — 읽을 줄만 알면 됩니다. 새로 쓸 때는 Input을 쓰십시오.
model.add(Dense(64, activation='relu', input_shape=(n_features,)))
이 표기는 Keras 3에서 권장되지 않습니다. 이 교재는 Keras 2와 3 양쪽에서 동작하는 model.add(Input(shape=(n,)))를 표준으로 씁니다.
⚠️ 시험 포인트
Input(shape=(1000, 8))처럼 샘플 수를 넣는 것이 가장 흔한 실수입니다. shape에 들어가는 것은 샘플 한 개의 모양이며, 표 형태 데이터에서는 언제나(특성 수,)하나짜리 튜플입니다.
4.4 출력층 — 문제 유형이 units와 activation을 결정합니다
은닉층은 상황에 따라 조금 늘리거나 줄여도 점수에 큰 영향이 없습니다. 그러나 출력층은 문제 유형에 따라 정답이 하나로 정해집니다. 여기가 틀리면 모델이 아예 학습되지 않거나, 학습되더라도 결과를 해석할 수 없습니다.
| 문제 유형 | 출력층 코드 | 출력의 의미 |
|---|---|---|
| 이진분류 (0/1) | Dense(1, activation='sigmoid') |
1일 확률 하나 (0~1) |
| 다중분류 (K개 클래스) | Dense(K, activation='softmax') |
클래스별 확률 K개 (합 = 1) |
| 회귀 (연속값 예측) | Dense(1) — 활성화 지정 없음 |
예측한 숫자 그 자체 |
세 가지 모두 Ch03의 ch03_activation-shapes에서 본 활성화 함수의 출력 범위로 설명됩니다. sigmoid는 0~1 사이 하나를 내므로 "그럴 확률"에 맞고, softmax는 여러 값을 합이 1인 확률 분포로 만들어 주므로 "여러 클래스 중 하나"에 맞습니다. 회귀는 예측값이 음수일 수도, 수백만일 수도 있으므로 값을 좁히는 활성화 함수를 씌우면 안 됩니다.
세 유형의 출력층만 바꿔 보는 코드입니다. 앞의 은닉층 부분은 셋 다 동일합니다.
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
def build_hidden(n_features):
m = Sequential()
m.add(Input(shape=(n_features,)))
m.add(Dense(64, activation='relu'))
m.add(Dense(32, activation='relu'))
return m
# (1) 이진분류
binary_model = build_hidden(30)
binary_model.add(Dense(1, activation='sigmoid'))
# (2) 다중분류 — 클래스 개수를 데이터에서 뽑습니다
y_multi = np.array([0, 1, 2, 1, 0, 2, 2])
n_class = len(np.unique(y_multi)) # 3
multi_model = build_hidden(30)
multi_model.add(Dense(n_class, activation='softmax'))
# (3) 회귀 — activation을 쓰지 않습니다
reg_model = build_hidden(30)
reg_model.add(Dense(1))
print(n_class) # 3
다중분류의 K도 손으로 세지 말고 len(np.unique(y))로 뽑는 습관을 들이십시오. 클래스가 3개인데 Dense(2, activation='softmax')로 쓰면 학습 단계에서 라벨과 출력 개수가 맞지 않아 오류가 납니다.
📎 손실 함수는 Ch05에서
출력층 옆에는 항상 짝이 되는 손실 함수가 붙습니다. 다만 손실 함수 선택 규칙은 라벨이 정수인지 원-핫인지까지 따져야 해서 분기가 한 겹 더 깊습니다. 그 정본은 Ch05의ch05_loss-decision도해입니다. 이 챕터에서는 "출력층은 이렇게 생겼다"까지만 확실히 익히고, 짝짓기는 Ch05에서 한 번에 정리하십시오.⚠️ 시험 포인트
회귀 문제에Dense(1, activation='sigmoid')를 쓰는 실수가 자주 나옵니다. sigmoid는 출력을 0~1로 눌러 버리므로, 집값이나 잔여 수명 같은 값은 영원히 맞힐 수 없습니다. 회귀 출력층에는 활성화 함수를 쓰지 않습니다.
4.5 summary() — 만든 모델을 눈으로 확인합니다
model.summary()는 지금까지 쌓은 층을 표로 보여줍니다. 학습을 시작하기 전에 의도한 대로 만들어졌는지 확인하는 유일한 수단이므로, 구성 직후에 반드시 한 번 호출하십시오.
입력 4개 → 은닉 5개 → 출력 1개짜리 최소 모델로 표를 읽어 보겠습니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
demo = Sequential()
demo.add(Input(shape=(4,)))
demo.add(Dense(5, activation='relu'))
demo.add(Dense(1, activation='sigmoid'))
demo.summary()
출력의 요지는 다음과 같습니다(표 서식은 Keras 버전에 따라 조금 다르게 보일 수 있습니다).
Layer (type) Output Shape Param #
=====================================================
dense (Dense) (None, 5) 25
dense_1 (Dense) (None, 1) 6
=====================================================
Total params: 31
Trainable params: 31
Non-trainable params: 0
표에서 읽어야 할 것은 두 열입니다.
Output Shape의 None — 앞자리 None은 "샘플 수는 아직 정해지지 않았다"는 뜻입니다. 4.3에서 입력 shape에 행 수를 넣지 않은 결과가 여기 나타납니다. 오류가 아니므로 놀랄 필요가 없습니다. 뒷자리 숫자가 그 층의 units와 같은지만 확인하면 됩니다.
Param # — 그 층이 학습으로 조정하는 값(가중치와 편향)의 개수입니다. 계산식은 언제나 같습니다.
파라미터 수 = (앞 층 노드 수 × 이 층 노드 수) + 이 층 노드 수
앞부분은 가중치, 뒤에 더하는 것은 노드마다 하나씩 있는 편향입니다. 첫 층은 (4 × 5) + 5 = 25, 둘째 층은 (5 × 1) + 1 = 6이 되어 합이 31입니다. Ch03의 ch03_neuron에서 뉴런 하나가 w1*x1 + ... + b를 계산했던 그 w와 b의 총 개수입니다.
Total params가 0이거나 summary()가 "모델이 아직 build되지 않았다"는 메시지를 낸다면, Input을 빠뜨렸다는 신호입니다. 입력 크기를 모르면 Keras가 가중치 행렬 크기를 정할 수 없기 때문입니다.
4.6 Dropout과 BatchNormalization — 위치가 전부입니다
두 층은 예측값을 직접 만들지 않고 학습이 더 잘 되도록 돕는 보조 층입니다. 그래서 인자보다 어디에 넣느냐가 중요합니다.
| 층 | 하는 일 | 넣는 위치 |
|---|---|---|
Dropout(rate) |
학습 중 노드 일부를 임시로 꺼서 과적합을 줄임 | 은닉층 Dense 뒤 |
BatchNormalization() |
층을 지나는 값의 분포를 정돈해 학습을 안정시킴 | 은닉층 Dense 뒤, Dropout 앞 |
Dropout(0.3)의 0.3은 끄는 비율입니다. 남기는 비율이 아닙니다. 실기에서는 0.2~0.5 범위를 쓰고, 지시가 없으면 0.3 정도가 무난합니다. Dropout이 학습할 때와 예측할 때 어떻게 다르게 동작하는지는 Ch06의 ch06_dropout-mechanism에서 자세히 다룹니다.
보조 층을 넣은 표준 배치입니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, BatchNormalization
model = Sequential()
model.add(Input(shape=(30,)))
model.add(Dense(64, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.3))
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(1, activation='sigmoid'))
model.summary()
BatchNormalization은 summary()에서 특이한 모습을 보입니다. 파라미터가 노드 수의 4배(여기서는 64 × 4 = 256)로 잡히고, 그중 절반이 Non-trainable params로 분류됩니다. 학습으로 조정하는 값 두 종류와, 학습 중 관찰한 통계를 저장해 두는 값 두 종류가 함께 있기 때문입니다. 오류가 아니니 그대로 두면 됩니다.
⚠️ 시험 포인트
출력층 뒤에는 Dropout을 넣지 않습니다. 출력층 뒤에 Dropout을 두면 예측 확률 자체가 무작위로 꺼져 결과가 망가집니다. 마지막Dense는 언제나 모델의 맨 아래에 있어야 합니다.
시험장 표준 코드
전처리를 마친 데이터에서 모델 구성까지 이어지는, 그대로 옮겨 쓸 수 있는 완결 코드입니다. 이진분류 기준이며, 유형이 바뀌면 마지막 Dense 한 줄만 4.4의 표대로 교체하십시오.
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, BatchNormalization
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
np.random.seed(42)
tf.random.set_seed(42)
# 1) 데이터 준비 (Ch02 범위)
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
n_features = X_train_scaled.shape[1]
print('입력 특성 수:', n_features)
# 2) 모델 구성 (이 챕터)
model = Sequential()
model.add(Input(shape=(n_features,)))
model.add(Dense(64, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.3))
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(1, activation='sigmoid')) # ← 유형별 교체 지점
model.summary()
# 3) 구성한 모델 저장 (선택)
model.save('my_model.keras') # 구버전 표기: 'my_model.h5'
예상 출력 요지입니다.
입력 특성 수: 30
Layer (type) Output Shape Param #
===========================================================
dense (Dense) (None, 64) 1984
batch_normalization (BatchNor…) (None, 64) 256
dropout (Dropout) (None, 64) 0
dense_1 (Dense) (None, 32) 2080
dropout_1 (Dropout) (None, 32) 0
dense_2 (Dense) (None, 1) 33
===========================================================
Total params: 4,353
Trainable params: 4,225
Non-trainable params: 128
Dropout의 Param #가 0인 것은 정상입니다. 노드를 끄기만 할 뿐 학습하는 값이 없기 때문입니다.
여기까지가 이 챕터의 범위입니다. 이 모델은 아직 학습할 수 없습니다. 어떤 기준으로 오차를 재고 가중치를 갱신할지 정하지 않았기 때문입니다. 다음 챕터에서 compile()과 fit()을 붙여 이 모델을 실제로 학습시킵니다.
핵심 요약
| 항목 | 기억할 것 |
|---|---|
| 4단계 | 구성 → 컴파일(Ch05) → 학습(Ch05) → 평가(Ch07) |
| 입력 | model.add(Input(shape=(n_features,))), n_features = X_train_scaled.shape[1], 샘플 수는 넣지 않음 |
| 은닉층 | Dense(64, activation='relu') → Dense(32, activation='relu'), 은닉층 2개 이상 |
| 출력층 | 이진 Dense(1,'sigmoid') / 다중 Dense(K,'softmax') / 회귀 Dense(1) |
| 보조 층 | Dense 뒤에 BatchNormalization() → Dropout(0.3) 순서, 출력층 뒤에는 넣지 않음 |
| 확인 | model.summary()로 Output Shape와 Param #를 눈으로 검증 |
파라미터 수 계산식: (앞 층 노드 수 × 이 층 노드 수) + 이 층 노드 수
Ch04 확인 문제 — Keras로 모델 만들기
본 문제는 공개된 출제 기준에 따른 유형 연습 문제입니다. 기출 원문이 아닙니다.
이 챕터의 범위는 모델 구성까지입니다.
compile()·fit()은 Ch05, 평가는 Ch07에서 다룹니다.
따라서 모든 문항은Sequential/Input/Dense/Dropout/BatchNormalization/summary()만으로 풀립니다.총 6문항 (객관식 2 / 코드 빈칸 2 / 오류 찾기 2), 권장 소요 시간 20분.
공통 import는 다음과 같다고 가정합니다.
python import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Input, Dense, Dropout, BatchNormalization
문제
Q1. (객관식 · 기본) 입력 정의
전처리를 모두 마친 X_train의 shape가 (1200, 12)입니다.
이 데이터로 학습할 모델의 입력 정의로 옳은 것은?
model.add(Input(shape=(1200, 12)))model.add(Input(shape=(1200,)))model.add(Input(shape=(12,)))model.add(Input(shape=(12)))model.add(Input(shape=(12, 1200)))
Q2. (객관식 · 기본) Param # 읽기
다음과 같이 구성한 모델의 model.summary()가 출력하는 Total params는 얼마입니까?
model = Sequential()
model.add(Input(shape=(10,)))
model.add(Dense(16, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.summary()
- 27
- 208
- 312
- 339
- 360
Q3. (코드 빈칸 · 기본) 이진분류 모델 구성
전처리를 마친 X_tr의 shape가 (800, 15)이고, 타깃은 0 또는 1인 이진분류 문제입니다.
은닉층 2개(노드 64 → 32, 활성화 함수는 relu)를 두는 모델을 구성하십시오.
(가)에는 특성 수를 데이터에서 뽑아내는 코드를, (나)에는 입력 정의 한 줄을, (다)에는 출력층 한 줄을 채우십시오.
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
X_tr = np.zeros((800, 15)) # 전처리를 마친 학습 데이터라고 가정합니다
n_features = ______(가)______
model = Sequential()
model.add(______(나)______)
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(______(다)______)
model.summary()
추가로, 이 모델의 Total params를 계산식과 함께 쓰십시오.
Q4. (코드 빈칸 · 응용) 다중분류 모델 + 보조 층
y_train은 정수 라벨이고, X_tr의 특성 수는 20입니다.
첫 은닉층 뒤에 BatchNormalization → Dropout(0.3) 순서로 보조 층을 넣고, 두 번째 은닉층 뒤에도 Dropout(0.3)을 넣는 다중분류 모델을 구성하십시오.
(가)에는 클래스 개수를 데이터에서 뽑아내는 코드를, (나)·(다)에는 보조 층 두 줄을, (라)에는 출력층 한 줄을 채우십시오.
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, BatchNormalization
X_tr = np.zeros((10, 20))
y_train = np.array([0, 2, 1, 3, 2, 0, 1, 3, 3, 1])
n_features = X_tr.shape[1]
n_class = ______(가)______
model = Sequential()
model.add(Input(shape=(n_features,)))
model.add(Dense(64, activation='relu'))
model.add(______(나)______)
model.add(______(다)______)
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.3))
model.add(______(라)______)
model.summary()
추가로, summary()에서 BatchNormalization 층의 Param #가 몇으로 찍히는지와, 그중 일부가 Non-trainable params로 분류되는 이유를 한 줄로 쓰십시오.
Q5. (오류 찾기 · 기본) 입력 정의가 잘못된 모델
아래 코드는 오류 메시지 없이 실행되지만, summary()의 출력 shape가 의도와 다릅니다.
잘못된 줄 한 곳을 찾아 지적하고, 올바르게 고친 코드를 쓰십시오.
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
X_train = np.zeros((1000, 8)) # 샘플 1000개, 특성 8개
model = Sequential()
model.add(Input(shape=X_train.shape))
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
print(model.output_shape)
Q6. (오류 찾기 · 응용) 다중분류 모델의 두 가지 오류
y_train에는 0, 1, 2, 3 네 종류의 클래스가 들어 있습니다.
아래 모델 구성 코드에는 잘못된 곳이 두 군데 있습니다. 각각을 지적하고 수정한 전체 코드를 쓰십시오.
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout
y_train = np.array([0, 1, 2, 3, 1, 0, 3, 2])
model = Sequential()
model.add(Input(shape=(20,)))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(32, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.add(Dropout(0.3))
model.summary()
해답 및 해설 보기
Q1 해답
정답: 3번 model.add(Input(shape=(12,)))
정답 코드 (실증 예제)
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
X_train = np.zeros((1200, 12))
n_features = X_train.shape[1] # 12
model = Sequential()
model.add(Input(shape=(n_features,)))
model.add(Dense(32, activation='relu'))
print("X_train.shape :", X_train.shape)
print("n_features :", n_features)
print("출력 shape :", model.output_shape)
X_train.shape : (1200, 12)
n_features : 12
출력 shape : (None, 32)
왜 이렇게 푸는가
Input(shape=...)에 넣는 것은 샘플 한 개의 모양입니다. 표 형태 데이터에서 샘플 한 개는 컬럼 12개짜리 한 줄이므로 (12,)입니다. 샘플이 몇 개 들어올지는 학습할 때 정해지므로 모델은 알 필요가 없고, 그 자리는 summary()에서 None으로 표시됩니다.
숫자 12를 손으로 적지 말고 X_train.shape[1]로 뽑는 습관이 중요합니다. 원-핫 인코딩으로 컬럼이 늘거나 불필요한 컬럼을 지우면 특성 수가 바뀌는데, 손으로 적은 숫자는 그때 그대로 틀린 값이 됩니다.
자주 틀리는 지점
- 1번·2번처럼 샘플 수(1200)를 넣는 것 — 이 챕터에서 가장 흔한 실수입니다. shape에 행 수는 절대 들어가지 않습니다.
- 4번
shape=(12)— 괄호만 쳤을 뿐 파이썬에서는 그냥 정수12입니다. 원소가 하나인 튜플은 반드시 쉼표를 붙여(12,)로 씁니다. 실제로 실행하면ValueError: Cannot convert '12' to a shape.가 납니다. - 5번처럼 순서를 뒤집는 것도 같은 오해에서 나옵니다. 정답은 언제나
(특성 수,)하나짜리 튜플입니다.
Q2 해답
정답: 4번 (339)
정답 코드 (실증 예제)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential()
model.add(Input(shape=(10,)))
model.add(Dense(16, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(3, activation='softmax'))
model.summary()
hand = (10 * 16 + 16) + (16 * 8 + 8) + (8 * 3 + 3)
print("손 계산 :", (10 * 16 + 16), "+", (16 * 8 + 8), "+", (8 * 3 + 3), "=", hand)
print("실제 값 :", model.count_params())
Layer (type) Output Shape Param #
=====================================================
dense (Dense) (None, 16) 176
dense_1 (Dense) (None, 8) 136
dense_2 (Dense) (None, 3) 27
=====================================================
Total params: 339
손 계산 : 176 + 136 + 27 = 339
실제 값 : 339
왜 이렇게 푸는가
층마다 계산식은 언제나 동일합니다.
파라미터 수 = (앞 층 노드 수 × 이 층 노드 수) + 이 층 노드 수
앞부분이 가중치, 뒤에 더하는 것이 노드마다 하나씩 있는 편향입니다.
- 1층:
(10 × 16) + 16 = 176 - 2층:
(16 × 8) + 8 = 136 - 3층:
(8 × 3) + 3 = 27 - 합계: 339
여기서 "앞 층 노드 수"의 첫 값은 Input(shape=(10,))의 10입니다. Input은 계산을 하지 않으므로 summary() 표에 행으로 나타나지 않지만, 첫 Dense의 가중치 크기를 정하는 근거가 됩니다.
자주 틀리는 지점
- 편향을 빼고 세는 것 —
10×16 + 16×8 + 8×3 = 312(3번)가 되어 딱 편향 개수(16+8+3=27)만큼 모자랍니다. 오답 3번이 이 함정입니다. - 출력층(
Dense(3))을 파라미터 합계에서 빼먹는 것. 출력층도 가중치와 편향을 가진 학습 대상 층입니다. Input을 빠뜨린 채summary()를 부르면 Total params가 0으로 나오거나 "모델이 아직 build되지 않았다"는 메시지가 뜹니다. 입력 크기를 모르면 가중치 행렬 크기를 정할 수 없기 때문입니다.
Q3 해답
정답 코드
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
X_tr = np.zeros((800, 15))
n_features = X_tr.shape[1] # (가)
model = Sequential()
model.add(Input(shape=(n_features,))) # (나)
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid')) # (다)
model.summary()
print("층 개수(Dense만):", len(model.layers))
print("출력 shape:", model.output_shape)
Layer (type) Output Shape Param #
=====================================================
dense (Dense) (None, 64) 1,024
dense_1 (Dense) (None, 32) 2,080
dense_2 (Dense) (None, 1) 33
=====================================================
Total params: 3,137
층 개수(Dense만): 3
출력 shape: (None, 1)
Total params: (15 × 64 + 64) + (64 × 32 + 32) + (32 × 1 + 1) = 1024 + 2080 + 33 → 3,137
왜 이렇게 푸는가
- (가)
X_tr.shape[1]—shape는(샘플 수, 특성 수)이므로 필요한 것은 인덱스 1입니다. 숫자 15를 직접 적지 않는 이유는 Q1 해설과 같습니다. - (나)
Input(shape=(n_features,))— Keras 2와 3 양쪽에서 동작하는 표준 표기입니다. 구버전 표기Dense(64, activation='relu', input_shape=(n_features,))는 읽을 줄만 알면 되고, 새로 쓸 때는Input을 씁니다. - (다)
Dense(1, activation='sigmoid')— 타깃이 0 또는 1인 이진분류이므로 출력은 "1일 확률" 하나입니다. 노드 1개 +sigmoid가 짝입니다.
print(len(model.layers))가 3인 것도 확인 포인트입니다. Input은 입력 형태 선언일 뿐 연산 층이 아니어서 층 목록에 세지 않습니다.
자주 틀리는 지점
- 은닉층 개수 세기 — "은닉층 2개 이상"에서 은닉층은 입력층과 출력층을 뺀 중간층입니다.
Dense(64)하나만 두고 출력층을 붙이면 은닉층은 1개뿐이라 조건 미달입니다. X_tr.shape[0](=800)을 쓰는 실수. 인덱스 0은 샘플 수입니다.- 출력층에
activation='relu'를 그대로 두는 것. relu는 0 이상 아무 값이나 내므로 확률로 해석할 수 없습니다.
Q4 해답
정답 코드
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout, BatchNormalization
X_tr = np.zeros((10, 20))
y_train = np.array([0, 2, 1, 3, 2, 0, 1, 3, 3, 1])
n_features = X_tr.shape[1]
n_class = len(np.unique(y_train)) # (가) -> 4
model = Sequential()
model.add(Input(shape=(n_features,)))
model.add(Dense(64, activation='relu'))
model.add(BatchNormalization()) # (나)
model.add(Dropout(0.3)) # (다)
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(n_class, activation='softmax')) # (라)
model.summary()
print("클래스 개수:", n_class)
print("출력 shape:", model.output_shape)
Layer (type) Output Shape Param #
===========================================================
dense (Dense) (None, 64) 1,344
batch_normalization (None, 64) 256
dropout (Dropout) (None, 64) 0
dense_1 (Dense) (None, 32) 2,080
dropout_1 (Dropout) (None, 32) 0
dense_2 (Dense) (None, 4) 132
===========================================================
Total params: 3,812
Trainable params: 3,684
Non-trainable params: 128
클래스 개수: 4
출력 shape: (None, 4)
BatchNormalization의 Param #: 64 × 4 = 256이 찍히고, 그중 절반인 128이 Non-trainable입니다. 학습으로 조정하는 값 두 종류와, 학습 중 관찰한 통계를 저장해 두는 값 두 종류가 함께 들어 있기 때문입니다. 오류가 아니므로 그대로 두면 됩니다.
왜 이렇게 푸는가
- (가)
len(np.unique(y_train))— 클래스 개수는 눈으로 세지 말고 데이터에서 뽑습니다. 라벨에 0~3이 들어 있으므로 4가 나옵니다. 시험에서는 클래스가 5개, 7개인 데이터도 나오며, 손으로 센 숫자는 전처리 결과가 바뀌면 그대로 틀립니다. - (나)(다)
BatchNormalization()→Dropout(0.3)순서 — 둘 다 은닉층Dense뒤에 붙습니다. BatchNormalization이 층을 지나는 값의 분포를 먼저 정돈하고, 그다음 Dropout이 노드 일부를 끕니다. 순서를 바꿔도 실행은 되지만 교재 표준 배치는Dense → BatchNormalization → Dropout입니다. - (라)
Dense(n_class, activation='softmax')— 다중분류 출력층은 클래스 개수만큼 노드를 두고 softmax로 합이 1인 확률 분포를 만듭니다. 출력 shape가(None, 4)인지summary()로 반드시 확인하십시오.
Dropout(0.3)의 0.3은 끄는 비율이지 남기는 비율이 아닙니다. Dropout 층의 Param #가 0인 것도 정상입니다. 노드를 끄기만 할 뿐 학습하는 값이 없습니다.
자주 틀리는 지점
- 출력층 units와 클래스 수 불일치 —
Dense(3, 'softmax')처럼 클래스 수보다 적게 두면 구성 단계에서는 아무 말이 없다가 학습 단계에서 라벨과 출력 개수가 맞지 않아 오류가 납니다.len(np.unique(y))로 뽑으면 이 사고가 사라집니다. BatchNormalization을 괄호 없이model.add(BatchNormalization)으로 쓰는 것. 층은 인스턴스를 만들어 넣어야 하므로()가 필요합니다.- BatchNormalization의 Non-trainable params를 보고 "모델이 잘못 만들어졌다"고 판단해 층을 지우는 것. 정상 동작입니다.
Q5 해답
잘못된 줄: model.add(Input(shape=X_train.shape))
X_train.shape는 (1000, 8)이므로 샘플 수 1000이 입력 shape에 그대로 들어갔습니다.
정답 코드
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense
X_train = np.zeros((1000, 8))
# --- 잘못된 코드의 결과 확인 ---
bad = Sequential()
bad.add(Input(shape=X_train.shape)) # (1000, 8) 이 통째로 들어감
bad.add(Dense(32, activation='relu'))
bad.add(Dense(1, activation='sigmoid'))
print("[잘못] 출력 shape:", bad.output_shape)
# --- 수정 ---
fixed = Sequential()
fixed.add(Input(shape=(X_train.shape[1],))) # 특성 수만 튜플로
fixed.add(Dense(32, activation='relu'))
fixed.add(Dense(1, activation='sigmoid'))
print("[수정] 출력 shape:", fixed.output_shape)
fixed.summary()
[잘못] 출력 shape: (None, 1000, 1)
[수정] 출력 shape: (None, 1)
왜 이렇게 푸는가
이 오류가 위험한 이유는 실행이 성공한다는 데 있습니다. 오류 메시지가 없으니 그대로 다음 단계로 넘어가기 쉽습니다.
잘못된 코드의 출력 shape는 (None, 1000, 1)입니다. "샘플 하나가 1000줄짜리 표"라고 선언한 셈이므로, 모델은 샘플마다 예측을 1,000개씩 만들어 냅니다. 정답 y_train은 샘플당 값 하나이므로 학습 단계에서 shape 불일치 오류로 이어집니다.
수정은 X_train.shape[1]로 특성 수만 꺼내 원소 하나짜리 튜플로 넘기는 것입니다. 수정 후 출력 shape가 (None, 1), 즉 "샘플당 확률 1개"로 바뀝니다.
자주 틀리는 지점
shape=X_train.shape처럼 shape를 통째로 넘기는 것. 편해 보이지만 항상 샘플 수가 딸려 들어갑니다.- 오류가 학습 단계에서 터지기 때문에 원인을
fit()쪽에서 찾는 것. 구성 직후model.summary()를 한 번 호출해 출력 shape가(None, 1)인지 눈으로 확인하면 이 시간 낭비를 막을 수 있습니다. - 고칠 때
Input(shape=(8))으로 쉼표를 빠뜨리는 것.ValueError: Cannot convert '8' to a shape.가 납니다.
Q6 해답
잘못된 곳 2군데
model.add(Dense(2, activation='softmax'))—y_train의 클래스는 4개인데 출력층 노드가 2개입니다. 출력 shape가(None, 2)가 되어 클래스 4개를 표현할 수 없습니다.model.add(Dropout(0.3))이 출력층 뒤에 붙었습니다. 마지막Dense는 언제나 모델의 맨 아래에 있어야 합니다.
정답 코드
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Dropout
y_train = np.array([0, 1, 2, 3, 1, 0, 3, 2])
n_class = len(np.unique(y_train)) # 4
model = Sequential()
model.add(Input(shape=(20,)))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.3)) # 은닉층 뒤로 이동
model.add(Dense(n_class, activation='softmax')) # units를 클래스 수에 맞춤
model.summary()
print("출력 shape:", model.output_shape)
print("마지막 층 :", type(model.layers[-1]).__name__)
Layer (type) Output Shape Param #
=====================================================
dense (Dense) (None, 64) 1,344
dropout (Dropout) (None, 64) 0
dense_1 (Dense) (None, 32) 2,080
dropout_1 (Dropout) (None, 32) 0
dense_2 (Dense) (None, 4) 132
=====================================================
Total params: 3,556
출력 shape: (None, 4)
마지막 층 : Dense
수정 전 코드를 그대로 실행하면 출력 shape: (None, 2), 마지막 층: Dropout이 나옵니다. 이 두 줄만 확인해도 오류 두 개를 동시에 잡아낼 수 있습니다.
왜 이렇게 푸는가
- 출력층 units는 클래스 개수와 정확히 같아야 합니다. 다중분류 출력은 "각 클래스일 확률"의 목록이므로, 노드가 2개면 클래스 4개 중 2개분의 확률만 만들어집니다. 구성 단계에서는 오류가 나지 않고 학습을 시작해야 문제가 드러나므로,
n_class = len(np.unique(y_train))로 뽑아 쓰는 습관이 유일한 예방책입니다. - Dropout은 은닉층
Dense뒤에만 넣습니다. 출력층 뒤에 두면 최종 확률값 자체가 무작위로 0이 되어 예측 결과가 망가집니다. Dropout은 "학습이 잘 되도록 돕는 보조 층"이지 예측값을 만드는 층이 아니라는 점을 기억하십시오.
두 오류의 공통점은 summary() 한 번이면 잡힌다는 것입니다. 마지막 행이 Dense인지, Output Shape 뒷자리가 클래스 수와 같은지 — 구성 직후 이 두 가지만 확인하는 습관을 들이십시오.
자주 틀리는 지점
- 클래스 개수를 눈으로 세다 틀리는 것. 라벨이 1부터 시작하거나(1~4) 중간 번호가 비어 있는 데이터에서 특히 자주 발생합니다.
- 출력층 뒤
Dropout을 "마지막에 정규화를 한 번 더 하는 것"으로 잘못 이해하는 것. - 오류를 하나만 찾고 넘어가는 것. 실기에서는 한 셀 안에 실수가 겹치는 경우가 흔하므로, 구성 코드는 위에서 아래로 층 순서를 소리 내어 읽듯 검토하십시오.