정확도가 학습 초기부터 100%로 나올 때
데이터 분할, 모델 설정 또는 데이터셋 전처리 과정에서 생긴 잠재적인 문제일 가능성이 높다. 정확도가 100%가 나온 원인과 이를 해결할 수 있는 방법을 알아보자.
레이블 인코딩을 확인해보자. LabelEncoder가 label_pipeline 안에서 fit_transform을 호출하는데, 이 방식은 각 샘플을 처리할 때마다 전체 데이터에 대해 재적용될 수 있어, 모든 데이터가 같은 값으로 인코딩될 위험이 있다. 이를 방지하려면 한 번만 fit하여 레이블을 미리 인코딩하는 것이 좋다.
# 수정 전 코드
# 토크나이저 및 레이블 인코더 정의
tokenizer = get_tokenizer("basic_english")
label_encoder = LabelEncoder()
...
# 텍스트와 레이블 파이프라인 정의
text_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: label_encoder.fit_transform([x])[0]
# 수정 후 코드
# 토크나이저 및 레이블 인코더 정의
tokenizer = get_tokenizer("basic_english")
label_encoder = LabelEncoder()
label_encoder.fit(ratings) # 모든 레이블을 한 번만 학습
...
# 텍스트와 레이블 파이프라인 정의
text_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: label_encoder.transform([x])[0] # fit을 없앰.
데이터 불균형을 확인하자. 학습용 데이터와 테스트용 데이터가 불균형하게 분할되었거나, 하나의 클래스만 포함되었을 가능성도 있다. train_test_split 후 클래스 분포를 확인해보자.
print(f"Train labels distribution: {pd.Series(train_ratings).value_counts()}")
print(f"Test labels distribution: {pd.Series(test_ratings).value_counts()}")
데이터가 편향된 경우에 대해 더 알아보자. 예를 들어, 학습 데이터와 테스트 데이터 모두 1점 평점이 다른 평점보다 많다고 해보자. 이는 모델이 1점을 예측하는 데 유리하게 작용할 수 있으나, 2점, 3점, 4점, 5점을 잘 예측하지 못할 가능성이 높다. 이러한 데이터 불균형은 모델의 성능에 영향을 미칠 수 있다.
데이터의 불균형을 해결하기 위해 언더샘플링(많은 클래스에서 일부 데이터만 선택) 또는 오버샘플링(적은 클래스의 데이터를 늘리기) 방법을 사용할 수 있다. 다른 방법으로는 클래스 가중치(class weights)를 사용하여 손실 함수를 수정하여 각 레이블의 중요도를 다르게 설정할 수 있다.
학습 과정을 검토해보자. 만약 데이터셋에 문제가 없다면, 과적합 가능성이 있으므로 아래와 같은 설정 변경도 고려해보자.
- 학습률(Learning rate) : lr=0.01에서 너무 빠르게 수렴할 수 있으므로, 0.001 등으로 낮춰보자.
- 모델 복잡도 조정 : hidden_dim 또는 embed_dim을 낮추거나 드롭아웃(nn.Dropout)을 추가하여 일반화 성능을 높일 수 있다.
이러한 수정 후에도 과적합이 나타난다면, 데이터셋 자체의 크기를 증가시키거나 더 다양한 전처리 방식을 시도해보는 것도 좋은 방법이다.
과적합이 발생했다고 의심될 때
학습과 테스트 데이터 모두에서 정확도가 100%라면 과적합이 발생했을 가능성이 높다. 이는 모델이 훈련 데이터에 과도하게 맞춰져 새로운 데이터에 대해 일반화가 어려울 수 있다는 뜻이다. 과적합을 줄이기 위한 방법을 알아보자.
에포크 수를 줄여보자. 이를 통해 학습 횟수를 줄여서 과적합을 방지할 수 있다. 현재 10번 학습 중이라면, 이를 5~7번 정도로 줄여보자. 배치 크기 조절해보자. 일반적으로는 배치 크기를 키우는 것이 과적합 방지에 더 효과적이지만, 배치 크기를 조절하는 방식은 데이터 특성과 모델에 따라 다르게 적용될 수 있다. BATCH_SIZE 값을 더 작게 (예: 32~64) 줄이면 모델이 더 자주 가중치를 업데이트하고 일반화에 도움을 줄 수 있다. 모델 크기 줄여보자. embed_dim과 hidden_dim을 더 작은 값으로 설정하여 모델의 복잡도를 낮추어 보자. 예를 들어, embed_dim = 32, hidden_dim = 16 등으로 줄여보는 것다. 정규화 기법을 추가해보자. 드롭아웃(Dropout) 레이어를 추가하면 일부 노드를 무작위로 비활성화하여 과적합을 줄일 수 있다. 예를 들어, LSTM 다음에 nn.Dropout(0.5) 레이어를 추가하면 일반화에 도움이 된다.
여기서 배치 크기를 늘리는 것이 과적합 방지에 도움이 되는 경우가 많다. 배치 크기를 늘리면 한 번의 학습 단계마다 다양한 데이터 샘플을 학습하여 일반화에 유리해지고, 모델이 특정 샘플에 너무 집중하지 않도록 해준다. 그러나 일부 경우에는 배치 크기를 줄이는 것도 과적합 방지에 도움이 될 수 있다. 예를 들어, 작은 배치를 사용하면 노이즈가 더 많이 섞여서 학습이 안정적이지 않게 되며, 이로 인해 과적합이 다소 억제되기도 한다. 이는 모델이 특정 패턴에 너무 과도하게 맞춰지지 않도록 유도하는 역할을 하기도 한다.
도전 과제를 진행하면서 만난 다양한 오류들
- NameError: name 'dropout' is not defined
dropout 변수가 정의되지 않았기 때문에 발생한 것이다. LSTMModel 클래스의 __init__ 메서드에 드롭아웃 비율을 매개변수로 추가하여 이를 해결할 수 있다. 즉, LSTMModel의 __init__ 메서드에 dropout 매개변수를 추가했다. 또 기본 드롭아웃 비율을 0.5로 설정했는데, 이는 필요에 따라 다른 값으로 조정할 수 있다.
# 수정 전
# 모델 정의
class LSTMModel(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
super(LSTMModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, output_dim)
self.dropout = nn.Dropout(dropout) # 과적합 방지용 드롭아웃
def forward(self, text):
embedded = self.dropout(self.embedding(text)) # 드롭아웃 적용
output, (hidden, cell) = self.lstm(embedded) # (batch_size, seq_length, hidden_dim)
return self.fc(hidden[-1].squeeze(0)) # (batch_size, hidden_dim)
# 수정 후
# 모델 정의 (드롭아웃 매개변수 추가)
class LSTMModel(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim, dropout=0.5): # dropout 매개변수 추가
super(LSTMModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, output_dim)
self.dropout = nn.Dropout(dropout) # 과적합 방지용 드롭아웃
def forward(self, text):
embedded = self.dropout(self.embedding(text)) # 드롭아웃 적용
output, (hidden, cell) = self.lstm(embedded)
return self.fc(hidden[-1].squeeze(0))
- ValueError
text_pipeline(review) 호출 결과로 생성된 배열의 각 요소가 다른 길이를 가지기 때문이다. 이로 인해 NumPy는 불균형한 형태를 가진 배열을 생성할 수 없다. 해결 방법을 알아보자. 리뷰 텍스트를 수치화해보자. 각 리뷰를 수치화하여 모든 리뷰의 길이를 동일하게 만들기 위해 패딩을 적용한 후, 이를 사용해야 한다. SMOTE 전 오버샘플링할 데이터셋을 튜플 형태로 만들어보자. 오버샘플링을 위한 데이터를 준비하는 과정에서 각 리뷰에 대해 패딩을 적용해야 한다.
- IndexError: Dimension out of range
소프트맥스 함수에 입력된 텐서의 차원이 예상과 다를 때 발생한다. 즉, output 텐서가 1차원인 경우 소프트맥스를 적용할 수 없다. 이를 해결하기 위해 먼저 출력 텐서의 차원 확인해보자. 모델의 출력이 몇 차원인지 확인해야 한다. 출력이 1차원인 경우, F.softmax를 호출할 때 적용할 차원을 바꿔야 한다. 또 모델의 출력 형태를 확인해보자. 일반적으로 모델의 출력은 배치 크기와 클래스 수를 포함하는 2차원 텐서여야 한다. 즉, (batch_size, num_classes) 형식이어야 한다는 것이다. 이 형식이 아니면 모델의 아키텍처를 점검해야 한다.