Adventure Time - Finn 3
본문 바로가기
AI/ML

순환 신경망으로 IMDB 리뷰 분류하기 - Word embedding (단어 임베딩)

by hyun9_9 2026. 4. 13.

순환신경망에서 텍스트를 처리할때 자주 사용하는 방법! 단어 임베딩

각 단어를 고정된 크기의 실수 벡터로 바꾸어 줌

↑ 10개의 실수 벡터로 단어 'cat' 을  임베딩

 

이런 단어 입베딩으로 만들어진 벡터는 One-Hot encoding 된 벡터보다 

훨씬 의미있는 값으로 채워져 있기에 자연어 처리 에서 더 좋은 성능을 내는 경우가 많다

 

keras 에는 단어 임베딩 벡터를 만드는 Embedding layer 준비되어있음

 

Embedding

  • Embedding 레이어도 모델에 추가되어 학습되는 레이어 다 (즉 parameter 가 있다!)
    • input_dim x output_dim 개의 parameter 학습
      • 내부적으로 table 형태의 구조
  • 처음에는 모든 벡터가 랜덤으로 초기화 되지만 훈련을 통해 데이터에서 점점 좋은 단어 임베딩으로 학습되어진다

단어 임베딩의 장점은 걍 정수 데이터를 받는다

 즉, One-Hot 인코딩으로 변경된 train_oh 배열이 아니라 train_seq 를 바로 사용할 수있다

 따라서 메모리를 훨씬 효율적으로 사용할 수있다.

 

앞서 Ont-Hot Encoding은 샘플 차원 하나를 10000차원으로 늘렸기 때문에 (100, ) 의 샘플이  -> (100,10000) 으로 커짐

단어 임베딩도 (100, ) 크기의 샘플을 ,예를 들면 -> (100,20) 과 같이 2차원 배열로 늘린다

 하지만 ont-hot 인코딩과 달리 훨씬 작은 크기로도 단어를 잘 표현 할수있다.

 

 

데이터 준비

num_words = 10000  # 단어 사전의 크기
max_len = 100   # 패딩 길이
word_index = imdb.get_word_index() # 단어 사전

# 데이터셋 로딩
(train_input, train_target), (test_input, test_target) =\
  imdb.load_data(num_words=num_words)
  
# Train data 에서 Validation data 세트 분리하기
train_input, val_input, train_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2,
    random_state=42)
    
# 패딩
train_seq = pad_sequences(sequences=train_input, maxlen=max_len,)
val_seq = pad_sequences(sequences=val_input, maxlen=max_len)
test_seq = pad_sequences(sequences=test_input, maxlen=max_len)

set_seed(42)

# Embedding 레이어를  SimpleRNN 앞에 추가하여 model 생성
model2 = keras.Sequential(name = "Embedding_RNN")

model2.add(keras.layers.Input(shape=(max_len,)))

model2.add(keras.layers.Embedding(
    input_dim=num_words + 3, # 어휘 사전의 크기  + IMDB 데이터셋의 특수 토큰
    output_dim = 16, # 임베딩 벡터의 크기
))

model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation='sigmoid'))

"""
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ embedding (Embedding)           │ (None, 100, 16)        │       160,048 │
      입력: (100,) -> 출력 (100,16)
      parameter 개수 :
        input_dim * output_dim
        (num_words + 3) * 16 = 10003 * 16 = 160,048

├─────────────────────────────────┼────────────────────────┼───────────────┤
│ simple_rnn_1 (SimpleRNN)        │ (None, 8)              │           200 │
      입력 : (100,16) -> (8,)
      parameter 개수 :
        입력 : 16 * 8 = 128
        은닉 : 8 * 8 = 64
        bias: 8

├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 1)              │             9 │


└─────────────────────────────────┴────────────────────────┴───────────────┘
"""

모델 컴파일 및 학습 🕒

model2.compile(optimizer='adam',  # adam 옵티마이저
               loss='binary_crossentropy',
               metrics=['accuracy'])
               
# 모델 저장경로
model_path = os.path.join(base_path, 'best-embedding-model.keras')

checkpoint_cb = keras.callbacks.ModelCheckpoint(
      model_path,
      save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history2 = model2.fit(train_seq, train_target,   # train_seq! 정수 인코딩된 시퀀스 그대로 입력 가능!
                      epochs=100, batch_size=64,
                     validation_data=(val_seq, val_target),  # val_seq!
                     callbacks=[checkpoint_cb, early_stopping_cb])


# train loss, validation loss
plt.plot(history2.history['loss'])
plt.plot(history2.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

 

평가

검증점수, 테스트점수 확인

 

# best_epoch 확인
early_stopping_cb.best_epoch

# 검증세트 확인
loss, accuracy = model2.evaluate(val_seq, val_target, batch_size=64)
print(f'Val Loss: {loss}')
print(f'Val Accuracy: {accuracy}')

# 테스트 세트 점수 확인
loss, accuracy = model2.evaluate(test_seq, test_target, batch_size=64)
print(f'Test Loss: {loss}')
print(f'Test Accuracy: {accuracy}')

 

 

설정값 저장: JSON 활용

딥러닝 모델을 실서비스에 배포하거나 나중에 다시 사용할 때, 모델 (ex *.keras)만 저장해서는 안 됩니다.

학습할때 설정했던 정보 들도 저장하고, 예측, 평가시 불러와서 사용해야 한다. 가령.

  • 전처리 정보(스케일)
  • 어휘사전정보
  • 패딩정보

max_len, num_words 같은 단일 값들과 word_index 같은 딕셔너리는 JSON 형식으로 저장하는 것이 가장 가볍고 범용적입니다.

 

# 저장할 설정값들 모으기
model_config = {
  'max_len': max_len,
  'num_words': num_words,
  'word_index': word_index
}

config_path = os.path.join(base_path, 'best-embedding-config.json')

with open(config_path, 'w', encoding='utf-8') as f:
    json.dump(model_config, f, ensure_ascii=False, indent=4)

 

 

모델 & 설정 불러오기

# 모델 경로, 모델 불러오기
model_path = os.path.join(base_path, 'best-embedding-model.keras')
model = keras.models.load_model(model_path)

# 설정파일 경로, 불러오기
config_path = config_path = os.path.join(base_path, 'best-embedding-config.json')
with open(config_path, 'r', encoding='utf-8') as f:
    config = json.load(f)

# ✅설정 변수 세팅
max_len = config['max_len']
num_words = config['num_words']
word_index = config['word_index']

model.summary()

 

예측하기

# 예측함수 정의
def predict_review(text, model, word_index, max_len):
    # 1. 소문자 변환 (간단한 전처리)
    text = text.lower()

    # 2. 단어를 인덱스로 변환
    # IMDB 데이터셋은 0: 패딩, 1: 문장 시작, 2: 사전에 없는 단어(OOV)로 예약됨
    # 실제 단어 인덱스는 word_index[단어] + 3 임
    encoded = []
    for word in text.split():

      index = word_index.get(word, -3) + 3  # IMDB 사전에 없다면 0 처리. (OOV 처리해도 좋음.)

      if index >= num_words:
        index = 2      # 사전에 없는 단어이면 2 (OOV) 토큰 부여

      encoded.append(index)


    # 3. 문장 시작 표시(1) 추가 및 패딩 처리
    # (IMDB 데이터셋의 관례를 따름)
    encoded = [1] + encoded
    padded = pad_sequences([encoded], maxlen=max_len)

    # 4. 모델 예측
    score = model.predict(padded, verbose=0)[0].item()

    # 5. 결과 출력
    if score > 0.5:
        print(f"[{text[:30]}...] -> 😀긍정적 리뷰입니다. (확률: {score*100:.2f}%)")
    else:
        print(f"[{text[:30]}...] -> 💥부정적 리뷰입니다. (확률: {(1-score)*100:.2f}%)")

    return score
# --- 실행 예시 ---

# 😀긍정적 리뷰 샘플들
positive_samples = [
    "This movie was fantastic! I really enjoyed it.",
    "This movie was absolutely wonderful and the acting was great",
    'The best documentary I have watched in a very long time. This is definitely a must see for everyone. This family and their love and support for each other is truly amazing.',
    "The acting was superb and the story was compelling.",
    "A very entertaining film. It was emotional, funny, and inspiring all at the same time.",
]

# 💥부정적 리뷰 샘플들
negative_samples = [
    "Worst movie ever I hated every minute of it boring and bad",
    "This movie was terrible and boring",
    "This is a very one sided documentary about a woman who is sentenced to a 15 year mandatory prison sentence for dealing drugs. The documentary is made by her family and they want you to believe she doesn't deserve her sentence. However if you read the court reports you will see that she was a drug dealer, she lied to the police, and she was found guily after a trial. The tragic part of this documentary is that the woman left behind a husband and three little girls who will forever be damaged by not having their mother around. Yes you will feel sorry for the children because none of this was their fault. However their drug dealing mom got the sentence she deserved and now she has to spend the rest of her life making it up to these kids. The big lesson of this documentary is if you committ a crime in the US you will get locked up!",
    "Terrible film. Boring plot, bad acting, complete waste of time.",
    "It was okay, nothing special but not terrible either.",
]

for sample in positive_samples:
    predict_review(sample, model2, word_index, max_len)

print()

for sample in negative_samples:
    predict_review(sample, model2, word_index, max_len)