1. "나 천재인가 봐"
처음 딥러닝 모델을 학습시켰을 때의 전율을 잊지 못합니다. 회사의 작은 프로젝트로 '사내 카페 샌드위치 재고 예측 AI'를 만들기로 했습니다. 데이터는 지난 1년치 판매량이었고, 저는 간단한 LSTM 모델을 돌렸습니다.
학습(Train) 정확도가 무려 99.9%가 나왔습니다.
"와, 나 AI에 재능 있나? 구글이 나 데려가야겠는데?"
자신만만하게 당장 다음 주 재고 발주에 이 모델을 도입하기로 했습니다. 모델은 "다음 주 월요일에 샌드위치가 500개 팔릴 것"이라고 예측했습니다. 평소 판매량의 2배였지만, 저는 AI를 믿고 500개를 발주했습니다.
결과는? 월요일 판매량 230개. 270개의 샌드위치가 고스란히 남았고, 저는 그날 밤새도록 남은 샌드위치를 처리해야 했습니다. (팀원들에게 무료로 나누어 주며 사과했죠.)
충격이었습니다. 학습 데이터에서는 기가 막히게 맞추던 녀석이, 실제로는 왜 이렇게 멍청할까요? 학습 데이터에 있는 특정 이벤트(예: 작년 월요일에만 있었던 사내 행사)까지 패턴으로 외워버린 탓이었습니다. 이게 바로 그 유명한 과적합(Overfitting)이었습니다.
2. 암기왕 vs 이해왕
과적합을 가장 쉽게 이해하는 비유는 "시험 공부"입니다. 이 비유를 곱씹어보면서 제가 뭘 잘못했는지 깨달았습니다.
과적합 모델 (암기왕)
- 학습 방법: 기출문제집(Train Data)을 달달 외웠습니다. 문제의 의도를 파악한 게 아니라, "1번 답은 3, 2번 답은 4..." 식으로 답만 외운 겁니다.
- 성적: 기출문제 시험을 보면 100점을 맞습니다. (Train Accuracy 100%)
- 실제: 하지만 문제가 조금만 바뀌거나(Test Data), 숫자만 바꿔도 틀립니다.
- 본질: 이해한 게 아니라 외운 겁니다. 제 모델은 "판매 추세"를 배운 게 아니라, "작년 7월 29일에는 500개가 팔렸다"는 사실 자체를 외워버린 것이나 다름없었죠.
일반화된 모델 (이해왕)
- 학습 방법: 문제 푸는 원리(Pattern)를 공부했습니다. "비가 오면 판매량이 10% 준다", "월급날에는 20% 는다" 같은 인과관계를 학습했습니다.
- 성적: 기출문제 점수는 90점으로 좀 낮을 수 있습니다. (암기왕보단 못하죠)
- 실제: 하지만 처음 보는 문제도 85점 이상 맞춥니다.
- 본질: 우리가 원하는 건 이런 모델입니다.
제 모델은 샌드위치 판매량의 '계절성'이나 '요일별 패턴'을 학습한 게 아니라, 학습 데이터에 있는 잡음(Noise)과 우연의 일치까지 법칙으로 착각하고 외워버린 겁니다.
3. 해결책 - 모델 괴롭히기
과적합을 막으려면 모델이 "쉽게 외우지 못하도록" 방해해야 합니다. 너무 평온한 환경에서 공부하면 잡생각(Noise)까지 기억하니까요. 역설적이지만, 공부 환경을 어렵게 만들어야 실력이 늡니다. 저는 샌드위치 사건 이후 세 가지 방법으로 모델을 괴롭히기 시작했습니다.
1. 데이터 증강 (Data Augmentation)
"문제를 비틀어서 내기"입니다. 이미지 처리라면 사진을 좌우로 뒤집고, 확대하고, 회전시켜서 학습시킵니다. 모델이 "아, 고양이는 뒤집혀 있어도 고양이구나"라고 깨닫게 만듭니다.
제 샌드위치 데이터의 경우, 노이즈를 섞었습니다. 과거 데이터에 랜덤하게 ±5% 정도의 변동을 주어 데이터를 뻥튀기했습니다. 이렇게 하면 모델은 "정확한 숫자"를 외우는 게 불가능해지고, 전체적인 "추세"에 집중하게 됩니다.
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.RandomRotation(0.1),
tf.keras.layers.RandomZoom(0.1),
])
2. 드롭아웃 (Dropout)
"공부할 때 한쪽 눈 가리기"입니다. 이 개념이 정말 천재적이라고 생각했습니다. 학습할 때 일부 뉴런을 랜덤하게 꺼버립니다(0으로 만듭니다).
만약 모델이 특정 뉴런(예: '작년 행사 여부')에만 과도하게 의존하고 있었다면, 그 뉴런이 꺼졌을 때 멘붕에 빠지겠죠? Dropout은 모델이 특정 특징(Feature) 하나에 몰빵하지 않고, 여러 특징을 골고루 활용하여 판단하도록 강제합니다. 마치 농구 팀에서 에이스 한 명에게만 패스하지 않고, 모든 선수가 골고루 득점하도록 훈련시키는 것과 같습니다.
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5), // 50%의 뉴런을 랜덤하게 끔
tf.keras.layers.Dense(10)
])
3. 조기 종료 (Early Stopping)
"밤새지 말고 적당히 자기"입니다. 우리는 보통 "공부는 많이 할수록 좋다(Epochs가 많을수록 좋다)"고 생각하지만, 딥러닝에서는 아닙니다. 너무 오래 학습하면 모델은 결국 데이터의 일반적인 패턴을 다 배우고 나서, 할 게 없으니 데이터의 노이즈까지 꾸역꾸역 외우기 시작합니다.
검증(Validation) 성능이 좋아지다가 정체되거나 떨어지기 시작하는 그 순간! 바로 그때가 학습을 멈춰야 할 타이밍입니다.
callback = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=3 // 3번 참았다가 개선 안 되면 종료
)
4. 규제 (Regularization) - 튀는 놈 잡기
L1(Lasso), L2(Ridge) 규제는 모델에게 "복잡해지면 벌점을 줄 거야"라고 경고하는 수학적 장치입니다. 저는 이 개념을 처음 접했을 때 "왜 모델이 똑똑해지는 걸 방해하지?"라고 생각했는데, 알고 보니 "똑똑한 체하면서 꼼수 부리는 걸" 막는 장치였습니다.
L1 규제 (Lasso)
- 특징: 가중치(Weight) 절대값의 합을 손실 함수(Loss Function)에 더합니다.
- 효과: 중요하지 않은 특징(Feature)의 가중치를 완전히 0으로 만들어 버립니다.
- 용도: 변수 선택(Feature Selection) 효과가 있습니다. "이 데이터 컬럼은 예측에 쓸모 없어!" 하고 가지치기를 해줍니다. 제 샌드위치 데이터에서 '그날 팀장님 기분' 같은 쓸데없는 변수를 제거하는 데 유용했겠죠.
L2 규제 (Ridge)
- 특징: 가중치 제곱의 합을 더합니다.
- 효과: 가중치가 0이 되진 않지만, 아주 작게 만듭니다.
- 용도: 특정 뉴런이 혼자서 너무 큰 값을 갖지 못하게 막아줍니다(튀는 놈 잡기). 일반적으로 L2를 더 많이 씁니다. 전체적으로 부드러운 결정 경계를 만들어줍니다.
# L2 Regularization 적용 예시
layer = tf.keras.layers.Dense(
units=64,
activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(0.01) # 0.01만큼 규제
)
5. 배치 정규화 (Batch Normalization)
규제는 아니지만, 간접적으로 과적합을 막아주는 강력한 기법입니다. 레이어마다 들어오는 데이터의 분포를 평균 0, 분산 1로 강제로 맞춰줍니다.
- 효과: 학습 속도가 비약적으로 빨라지고, 초기 가중치 설정(Initialization)에 덜 민감해집니다.
- 부수 효과: 자체적으로 약간의 노이즈를 추가하는 효과가 있어(각 배치마다 평균/분산이 다르므로), Dropout과 비슷한 규제 효과를 냅니다.
저는 샌드위치 예측 모델을 다시 짤 때, Dropout과 Batch Normalization을 적절히 섞어서 사용했습니다. 그랬더니 학습 정확도는 99%에서 92%로 떨어졌지만, 실제 예측 정확도는 50%에서 85%로 올랐습니다.
model.add(tf.keras.layers.Conv2D(32, 3))
model.add(tf.keras.layers.BatchNormalization()) # 여기!
model.add(tf.keras.layers.Activation('relu'))
6. 마무리 - 99%에 속지 말자
이제 저는 학습 정확도가 99%가 나오면 오히려 의심부터 합니다. "또 외웠구나? 어디서 사기를 쳐?"
중요한 건 검증(Validation) 정확도와 테스트(Test) 정확도입니다. 훈련 점수와 테스트 점수의 차이(Gap)가 작아야 좋은 모델입니다.
- 과소적합(Underfitting): 공부를 안 해서 둘 다 점수가 낮음. (모델이 너무 단순함)
- 과적합(Overfitting): 기출문제(Train)만 잘 풀고 실전(Test)은 망함. (모델이 너무 복잡함)
- 적절합(Generalization): 둘 다 준수하게 잘 나옴.
우리의 목표는 '하드디스크(암기)'를 만드는 게 아니라 '지능(일반화)'을 만드는 것임을 잊지 마세요. 샌드위치 사건은 뼈아팠지만, 덕분에 저는 데이터 사이언스의 가장 중요한 교훈을 얻었습니다. "데이터를 믿되, 모델의 암기력은 믿지 마라."
My AI Was a Fraud: 99% Accuracy, 0% Utility (Overfitting)
1. "Am I a Genius?"
I'll never forget the thrill of training my first deep learning model. I decided to build a 'Sandwich Inventory Prediction AI' for our office cafeteria as a small side project. The data was sales figures from the past year, and I ran a simple LSTM model.
The Training Accuracy hit a staggering 99.9%.
"Wow, do I have a hidden talent for AI? Google should be hiring me."
Confident, I reported to my mentor and suggested we use this model for next week's inventory orders immediately. The model predicted, "Next Monday, 500 sandwiches will be sold." It was double the usual sales, but trusting the AI, I ordered 500.
The result? Monday sales: 230 sandwiches. 270 sandwiches were left untouched, and I had to spend the entire night dealing with the leftovers (giving them out for free to friends while apologizing).
I was shocked. It predicted so perfectly on the training data, why was it so stupid in reality? It turned out the model had memorized even specific events in the training data (e.g., a local event nearby that only happened on a Monday last year) as a pattern. This was the famous Overfitting.
2. Memorization King vs. Understanding King
The best analogy for overfitting is "Exam Studying." Chewing on this analogy helped me realize what I did wrong.
Overfitting Model (Memorization King)
- Study Method: Memorized the Past Exam Book (Train Data). It didn't grasp the intent of the questions, but just memorized "Answer to Q1 is 3, Q2 is 4..."
- Grades: Gets 100 on the past exam. (Train Accuracy 100%)
- Reality: But if the question changes slightly (Test Data) or numbers change, it fails.
- Essence: It didn't learn, it memorized. My model didn't learn "sales trends," but effectively memorized the fact that "on July 29th last year, 500 were sold."
Generalized Model (Understanding King)
- Study Method: Studied the principles (Patterns) of solving problems. Learned causal relationships like "Sales drop 10% when it rains" or "Sales rise 20% on paydays."
- Grades: Might get 90 on past exams. (Not as high as the Memorization King)
- Reality: But gets 85+ on new exams too.
- Essence: This is the model we want.
My model didn't learn the 'seasonality' or 'daily patterns' of sandwich sales but mistook Noise and Coincidences in the training data for laws and memorized them.
3. Solution: Bully the Model
To prevent overfitting, you must hinder the model from "memorizing easily." If the study environment is too peaceful, it will even remember the stray thoughts (Noise). Paradoxically, making the study environment harsher improves real skills. After the sandwich incident, I started bullying my model in three ways.
1. Data Augmentation
"Twisting the questions." In image processing, you flip, zoom, and rotate photos during training. You force the model to realize, "Ah, a cat is still a cat even if upside down."
For my sandwich data, I added Noise. I artificially inflated the data by adding random fluctuations of ±5% to past data. This makes it impossible for the model to memorize "exact numbers" and forces it to focus on general "trends."
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip("horizontal"),
tf.keras.layers.layers.RandomRotation(0.1),
tf.keras.layers.RandomZoom(0.1),
])
2. Dropout
"Studying with one eye covered." I thought this concept was genius. You randomly turn off some neurons (set to 0) during training.
If the model was overly relying on a specific neuron (e.g., 'Last Year's Event Flag'), it would panic when that neuron is turned off. Dropout forces the model not to rely on a single feature but to use multiple features broadly for decision making. It's like training a basketball team so that they don't just pass to the ace player, but everyone scores.
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5), // Randomly drop 50% neurons
tf.keras.layers.Dense(10)
])
3. Early Stopping
"Don't pull all-nighters." We usually think "the more you study, the better (more Epochs)," but not in Deep Learning. If you train too long, the model learns all the general patterns and then, having nothing else to do, starts memorizing the noise in the data.
The moment Validation performance stops improving or starts to drop! That is the exact time to stop training.
callback = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=3 // Stop if no improvement for 3 epochs
)
4. Regularization: Penalizing Complexity
L1 (Lasso) and L2 (Ridge) regularization warn the model: "I'll penalize you if you get too complex." When I first encountered this, I thought, "Why hinder the model from getting smarter?" But it turns out it's a mechanism to prevent "acting smart with cheap tricks."
L1 Regularization (Lasso)
- Feature: Adds the absolute value of weights to the Loss Function.
- Effect: It forces unnecessary weights to become exactly zero.
- Usage: Great for Feature Selection. It automatically creates a "This data column is useless for prediction!" effect and prunes it. It would have been useful to remove useless variables like 'Manager's Mood' from my sandwich data.
L2 Regularization (Ridge)
- Feature: Adds the squared value of weights to the Loss Function.
- Effect: It makes weights very small, but rarely zero.
- Usage: Prevents any single neuron from dominating the decision (prevents "dictatorship" of a feature). Generally, L2 is used more often. It creates smoother decision boundaries.
# Applying L2 Regularization
layer = tf.keras.layers.Dense(
units=64,
activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(0.01) # Penalty factor
)
5. Batch Normalization
While not strictly a regularization technique, it has a surprisingly strong regularization side-effect. It forces the input distribution of each layer to have Mean 0 and Variance 1.
- Primary Goal: Accelerates training speed significantly and makes the model less sensitive to initialization.
- Side Effect: Since Mean/Variance is calculated per mini-batch, it adds slight noise to the training process. This noise acts like Dropout, preventing the model from overfitting to specific deterministic values.
When I rebuilt the sandwich prediction model, I mixed Dropout and Batch Normalization appropriately. The Training Accuracy dropped from 99% to 92%, but the actual Prediction Accuracy rose from 50% to 85%.
model.add(tf.keras.layers.Conv2D(32, 3))
model.add(tf.keras.layers.BatchNormalization()) # Magic happens here
model.add(tf.keras.layers.Activation('relu'))