[ML] LSTM 모델에서 오버피팅 잡기
1.기존 LSTM 모델
일단은 라벨링 작업 모델이다. 실시간을 데이터가 들어와서 라벨링하는 모델을 만들기전,
정답지인 데이터, 그중 90%를 train.csv 나머지 10%를 test.py 로사용한다.
주된 코드는 다음과같다
import os
import json
import numpy as np
import tensorflow as tf
from keras.preprocessing.text import Tokenizer, tokenizer_from_json
from keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.layers import Embedding, Dense, LSTM
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.models import load_model
import pandas as pd
from konlpy.tag import Okt
def get_tokenizer(filename, tokens):
"""토큰화 도구(Tokenization)를 초기화하거나 파일에서 불러옵니다.
토큰화란 텍스트 데이터를 숫자 시퀀스로 변환하는 작업입니다.
tokenizer.json 파일에 저장된 기존 토크나이저를 재사용하거나, 새로 학습하여 저장합니다."""
if os.path.exists(filename):
with open(filename, "r", encoding="utf-8") as f:
data = json.load(f)
tokenizer = tokenizer_from_json(data)
else:
tokenizer = Tokenizer()
tokenizer.fit_on_texts(tokens)
tokenizer_json = tokenizer.to_json()
with open("tokenizer.json", "w", encoding="utf-8") as f:
f.write(json.dumps(tokenizer_json, ensure_ascii=False))
return tokenizer
def preprocess_1(data):
"""형태소 분석:
Okt 라이브러리를 사용하여 한국어 텍스트를 형태소 단위로 분리.
전처리:
특수문자 제거.
텍스트를 tokenized라는 새로운 열에 저장."""
okt = Okt()
data["product"] = data["product"].str.replace("[^A-Za-z0-9가-힣]", " ", regex=True)
data["tokenized"] = data["product"].apply(okt.morphs)
return data
def preprocess_2(data, tokenizer, max_len):
"""숫자 시퀀스로 변환
토크나이저를 사용해 형태소 데이터를 숫자 시퀀스로 변환.
길이가 부족한 시퀀스는 max_len 길이로 패딩.
"""
x = tokenizer.texts_to_sequences(data["tokenized"])
x = pad_sequences(x, maxlen=max_len, padding="post")
y = data["label"].tolist()
return x, y
def get_model(vocab_size, num_label):
"""LSTM model
Embedding Layer:
텍스트 데이터를 고차원 벡터로 매핑.
LSTM Layer:
시퀀스 데이터의 문맥 정보를 학습.
Dense Layer:
소프트맥스 활성화 함수를 사용해 각 클래스에 대한 확률 분포를 출력."""
model = Sequential()
model.add(Embedding(vocab_size, 64))
model.add(LSTM(256))
model.add(Dense(num_label, activation="softmax"))
print(model.summary())
return model
def main():
"""학습 및 저장"""
max_len = 20
num_label = 15
train_data = pd.read_csv("train.csv", header=0)
test_data = pd.read_csv("test.csv", header=0)
train_data = preprocess_1(train_data)
test_data = preprocess_1(test_data)
tokenizer = get_tokenizer("tokenizer.json", train_data["tokenized"])
vocab_size = len(tokenizer.word_index) + 1
train_x, train_y = preprocess_2(train_data, tokenizer, max_len=max_len)
text_x, test_y = preprocess_2(test_data, tokenizer, max_len=max_len)
model = get_model(vocab_size, num_label)
model.compile(
optimizer="rmsprop", loss="sparse_categorical_crossentropy", metrics=["acc"]
)
history = model.fit(np.array(train_x), np.array(train_y), epochs=5, batch_size=64)
tf.saved_model.save(model, "model-store/model")
main()
해당 train.py 실행해보면.
2.과적합(오버피팅) 발생
오버피팅 이 발생한다.
과적합(overfitting)
모델이 학습 데이터셋 안에서는 일정 수준 이상의 예측 정확도를 보이지만, 새로운 데이터에 적용하면 잘 맞지 않는 것을 의미
입력층, 은닉층, 출력층의 노드들이 많아지게 되면 학습 데이터의 패턴뿐만 아니라 노이즈까지 학습하게 된다고 한다.
이를 "학습 편향" 이라고함. (책에서 본 bias 인듯 => 1 or 0 출력 결정짓게하는 저항)
모델이 복잡할수록 학습 데이터에 완벽히 맞추는 것은 쉬워지지만, 새로운 데이터에서는 일반화되지 못할 가능성이 높아짐. (못된건만배우는 나와같다)
test.csv가 990개, train.csv가 8,898개라면 데이터 양이 적다고 할 수는 없다 (1차적으로 샘플사이즈를 확보했는지 확인)
적지않다면, 모델링을 손보면된다. 어떻게?
3.해결방안
크게 몇가지가 있다고 하는데, 우리는 이중 3가지를 적용해볼 예정이다.
이유는 지극히 개인적이다. 모델 단순화 는 간지가 안나서, 교차 검증.. 최후의 보루로 하고싶어서 뺀다.
그리하여,
- 오버피팅 감소:
- Dropout, L2 정규화, Early Stopping, 계층적 데이터 분리로 과적합 방지.
- 일반화 성능 향상:
- 더 적은 가중치와 적절한 뉴런 수로 모델이 새로운 데이터에 더 잘 대응.
- 학습 안정성 증가:
- Learning Rate Scheduler와 Early Stopping으로 과도한 학습 방지 및 안정적 학습.
- 신뢰성 향상:
- 계층적 데이터 분리로 학습과 검증 데이터 간 일관성을 유지.
진행한다.
4.적용
Dropout 사용 (얌생이 학습법 차단)
model.add(LSTM(128, dropout=0.3, recurrent_dropout=0.3))
- dropout=0.3: 뉴런의 30%를 학습 중 랜덤하게 비활성화하여 모델이 특정 뉴런에 과도하게 의존하지 않도록 만듦.
- recurrent_dropout=0.3: LSTM의 순환 연결에서도 일부 뉴런을 비활성화.
- 과적합(오버피팅) 방지: 특정 뉴런에 의존하는 과적합 위험을 줄임.
- 일반화 성능 향상: 다양한 데이터 패턴에 대응할 수 있도록 학습.
L2 정규화 추가 (과한학습시 취침등소등시키기 )
model.add(Dense(num_label, activation="softmax", kernel_regularizer=l2(0.01)))
- l2(0.01): 가중치 크기에 패널티를 부여하여 가중치가 과도하게 커지지 않도록 제한.
- 모델 복잡도를 줄임: 큰 가중치를 제한함으로써 모델의 복잡성을 낮추고, 과적합 가능성을 줄임.
- 더 나은 일반화: 검증 데이터와 새로운 데이터에서도 성능이 향상.
Early Stopping (참교육. 모난싹자르기)
early_stopping = EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True)
- monitor="val_loss": 검증 손실을 모니터링.
- patience=3: 3 에포크 동안 검증 손실이 개선되지 않으면 학습 중단.
- 과적합 방지: 검증 손실이 증가하기 시작하면 학습을 조기에 중단하여 학습 데이터에 과도하게 적합되지 않도록 함.
- 학습 시간 절약: 불필요한 에포크를 방지하여 자원을 절약.
Learning Rate Scheduler ( 기억망각곡선에 의해 잊어버린것 나머지학습, 기억력개선안되면 학습률 다운시키기)
lr_scheduler = ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=2, min_lr=0.0001)
- monitor="val_loss": 검증 손실을 기준으로 학습률 조정.
- factor=0.5: 학습률을 절반으로 감소.
- patience=2: 2 에포크 동안 검증 손실이 개선되지 않으면 학습률 감소.
- 학습 안정화: 초기 학습률이 너무 커서 최적값을 지나치는 문제를 방지.
- 최적화 성능 개선: 학습 후반부에 작은 학습률로 세부적인 조정을 가능하게 함.
모델 구조 간소화(공부방 청소시키기)
model.add(LSTM(128))
- 이전 코드의 LSTM(256) → LSTM(128)로 변경.
- 모델 복잡도 감소: 더 적은 뉴런을 사용하여 계산량 감소 및 과적합 가능성 감소.
- 데이터 크기에 적합한 모델: 데이터 크기(8,898개)에 비해 과도하게 복잡했던 모델을 간소화.
계층적 데이터 분리 (학습과 문제풀이의 밸런스를 맞추어 진행)
train_x, val_x, train_y, val_y = train_test_split(
train_x, train_y, test_size=0.2, stratify=train_y, random_state=42
)
- stratify=train_y: 학습 데이터와 검증 데이터의 클래스 비율을 동일하게 유지.
- 클래스 불균형 방지: 검증 데이터에서도 학습 데이터와 동일한 레이블 분포를 가지게 하여 평가의 신뢰성 향상.
- 안정적인 검증: 레이블 분포가 동일하므로 검증 데이터의 성능이 과소평가되지 않음.
Epochs와 학습률의 조화 (물리적인 공부시간 끌어올려)
- Epochs: 5 → 20으로 증가.
- Learning Rate Scheduler를 추가하여 더 많은 에포크 동안 안정적으로 학습.
- 충분한 학습: 더 많은 에포크로 학습이 충분히 진행됨.
- 학습률 조정: 학습 후반부에 더 세밀한 최적화가 가능.
5.결과
정말이지 위 방법대로 공부(학습)하면 점수가(신뢰도) 떨어질수가 없다
기계
1. 학습 성능
- 학습 손실 (loss):
- 에포크 1: 2.5471 → 에포크 20: 0.4096으로 꾸준히 감소.
- 학습 정확도 (acc):
- 에포크 1: 19.52% → 에포크 20: 90.81%로 큰 폭으로 개선.
2. 검증 성능
- 검증 손실 (val_loss):
- 에포크 1: 2.3813 → 에포크 20: 0.9603으로 지속적으로 감소.
- 검증 정확도 (val_acc):
- 에포크 1: 20.56% → 에포크 20: 75.73%로 꾸준히 증가.
3. Learning Rate Scheduler 동작
- 학습률 (lr):
- 학습률이 에포크 8부터 0.001 → 0.0005로 줄어듦.
- 이는 검증 손실의 개선 속도가 느려졌을 때 학습률을 줄이는 효과를 보여줌.
4. 오버피팅 여부
- 학습 정확도 (acc)와 검증 정확도 (val_acc)의 차이:
- 에포크 20 기준: 학습 정확도 90.81%, 검증 정확도 75.73%.
- 약 15% 차이가 있지만, 이는 과도한 오버피팅이 아니라 데이터의 복잡성과 모델의 적합성 때문일 가능성이 큼.
- 검증 손실 감소:
- 검증 손실이 마지막 에포크까지 감소하고 있어, 아직 오버피팅 징후가 나타나지 않음.