딥러닝 공부하기

[딥러닝] LSTM으로 초간단 감정분석 모델 만들어보기

차근차근 디지털 2023. 12. 20. 22:12

► LSTM( Long Short-Term Memory) 모델이란?

→ 주로 시퀀스 데이터를 처리하는 순환 신경망(RNN)의 한 종류입니다.

 기존의 RNN의 단점을 보완하여 긴 시퀀스 데이터에서 발생하는 그래디언트 소실(vanishing gradient) 문제를 해결하려는 목적으로 고안되었습니다.

 LSTM은 시퀀스 데이터에서 중요한 정보를 기억하고 중요하지 않은 정보는 잊어버리는 능력을 갖추고 있습니다. 이를 위해 셀 상태(cell state)와 숨겨진 상태(hidden state)라는 두 가지 상태를 사용합니다. 각각의 상태는 시간(timestep)에 따라 업데이트되며, 이를 통해 모델은 데이터의 장기 및 단기 패턴을 학습할 수 있습니다.

→ LSTM은 긴 시퀀스에서 장기 의존성을 학습하는 데 효과적이며, 자연어 처리(NLP), 음성 인식, 시계열 데이터 등 다양한 분야에서 사용되고 있습니다. 

 

► LSTM 탄생원인

→ RNN에서는 시퀀스의 길이가 길어질수록, 그래디언트가 시간에 걸쳐 지수적으로 감소하여 사라져버리는 문제가 있습니다. 이로 인해 모델은 장기적인 의존성을 학습하기 어려워집니다. 이러한 문제를 해결하기 위해 나온 구조 중 하나가 LSTM(Long Short-Term Memory)이며, LSTM은 vanishing gradient problem에 대한 효과적인 대안으로 사용됩니다.

 

► LSTM 작동방식

아래와 같은 방식으로 각 시간 단계에서 Cell State와 Hidden state를 업데이트하며, 장기 및 단기적인 패턴을 학습합니다. 각 게이트는 학습 가능한 가중치와 활성화 함수를 사용하여 어떤 정보를 전달하고 어떤 정보를 제거할지를 결정합니다. 이러한 메커니즘은 vanishing gradient problem를 해결하여 긴 시퀀스에서도 효과적인 학습을 가능하게 합니다.

 

- 셀 상태 (Cell State): 기억을 전달하는 역할을 하는 부분입니다. 정보를 저장하거나 삭제하는 것에 관여하며, 중요한 정보를 장기적으로 유지할 수 있습니다.
- 입력 게이트 (Input Gate): 현재의 입력과 이전 hidden state를 기반으로, 현재 시퀀스 데이터에서 어떤 정보를 기억할 것인지 결정합니다. 즉, 어떤 부분을 업데이트할지를 제어합니다.

- 망각 게이트 (Forget Gate): 이전의 셀 상태 중에서 어떤 정보를 잊을지를 결정합니다. 현재의 입력과 이전의 hidden state를 기반으로, 어떤 부분을 삭제할지를 결정합니다.
- 셀 상태 업데이트: Input GateForget Gate를 사용하여 현재의 셀 상태를 업데이트합니다. 새로운 정보를 추가하거나 이전 정보를 잊음으로써 셀 상태를 조절합니다.
- 출력 게이트 (Output Gate): 현재의 셀 상태에서 어떤 부분을 출력으로 내보낼지를 결정합니다. 현재의 입력과 이전의 hidden state를 기반으로, 어떤 부분을 출력으로 선택할지를 제어합니다.

 

► 감정분석의 특성은?

감정 분석은 문맥을 고려하는 것이 중요하기 때문에 기존의 RNN 모델보다 긴 시퀀스도 중요도에 따라 기억하고 함께 학습할 수 있는 LSTM모델이 더 알맞습니다. 또한 긍/부정으로 판단하는 이진 분류이고 긍정/부정/중립인 다중 분류로 나타내기도 합니다. 

 

※ 실습의 간편성과 이해도를 위해 4개의 문장으로 실습하였습니다. 학습 성능이 좋은게 이상합니다. 저는 단지 감정분석 모델 만드는 것을 쉽게 설명하기 위해 글을 썼지 모델 성능이 목적이 아닙니다. 언어 모델은 기본적으로 정말 정말 정말 대량의 데이터를 가지고 학습합니다. 언어 모델의 성능은 데이터양과 질, 모델의 종류, 하이퍼파라미터 등에 좌우됩니다. 

 

 

1. 관련 라이브러리 불러오기

import tensorflow as tf
from tensorflow.keras.layers import Dense, LSTM, Embedding, Dropout, Bidirectional
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np
import matplotlib.pyplot as plt

 

2. 학습할 말뭉치들과 긍부정 정답 데이터 가져오기

train_sentences = ['스토리 전개랑 배우들 연기가 미쳤어요!!', 
                   '이번 영화 너무 재밌었어요! 감동이 오랜만에 영화를 만들어서 걱정했는데 완전 몰입감 있고 대박이더라구요~',
                   '완전 핵 꿀잼! 무조건 영화관 가서 보세요!',
                   '완전 노잼인데? 뭐가 재밌다는거임? 좋은 배우들 다 끌어모아서 뭐한거임?'
                  ]
# 긍정 1, 부정 0
train_labels = np.array([1,1,1,0])

 

3. 토큰화하고 사전 만들기

vocab_size = 10000

tokenizer = Tokenizer(num_words = vocab_size, oov_token='<OOV>')
tokenizer.fit_on_texts(train_sentences)

# 사전 확인
tokenizer.index_word

 

4. 사전을 활용해서 문자를 숫자로 바꾸고, 패딩하기

sequences = tokenizer.texts_to_sequences(train_sentences)
padded = pad_sequences(sequences, maxlen=15, padding='post', truncating='post')
padded

# 패딩하기 전 문장의 길이가 얼마나 되는지 시각화해서 몇 개까지 자를건지 확인하기
# plt.hist([len(s) for s in sequences], bins=50);

 

5. 패딩 및 숫자로 잘 변했는지 확인하기

reverse_word_index = dict([(value, key) for (key, value) in tokenizer.word_index.items()])

def decode_review(sequence):
    return ' '.join([tokenizer.index_word.get(i, '<pad>') for i in sequence])

print(decode_review(padded[0]))
print()
print(train_sentences[0])

 

6. model 빌딩하고 컴파일하기

model = Sequential([
    Embedding(vocab_size+1, 64),
    Bidirectional(tf.keras.layers.LSTM(64)),
    Dense(64, activation='relu'),
    Dense(1, activation='sigmoid')
])

model.compile(loss='binary_crossentropy',optimizer='adam', metrics=['accuracy'])
model.summary()

 

7. 모델 학습하기

%%time
num_epochs = 10
history = model.fit(padded, train_labels, epochs=num_epochs, batch_size=128, verbose=1)

 

8. 데이터가 많았을 때, accuracy 와 loss 시각화

# %%time
# num_epochs = 30
# history = model.fit(train_padded, train_labels, epochs=num_epochs, batch_size=128,
#                 validation_data=(test_padded, test_labels), verbose=1)

# fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

# ax1.plot(history.history['accuracy'])
# ax1.plot(history.history['val_accuracy'])
# ax1.set_xlabel('Epochs')
# ax1.set_ylabel('accuracy')
# ax1.legend(['accuarcy', 'val_accuracy'])

# ax2.plot(history.history['loss'])
# ax2.plot(history.history['val_loss'])
# ax2.set_xlabel('Epochs')
# ax2.set_ylabel('loss')
# ax2.legend(['loss', 'val_loss'])
# plt.show()

 

9. 테스트 해보기

sample_text = ['최고최고!! 두번 보세요!']
sample_seq = tokenizer.texts_to_sequences(sample_text)
sample_padded = pad_sequences(sample_seq, maxlen=max_len, padding='post', truncating='post')

model.predict([sample_padded])

 

0.7로 긍정이라는 결론이 나온게 우연일까... 진짜일까... 보세요 때문인것 같은데...