[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🛠 기타/Data & AI

순환신경망 - LSTM 기초

inu 2020. 7. 29. 22:54
반응형

순환신경망 (Recurrent Neural Network, RNN)

  • RNN은 입력과 출력을 시퀀스 단위로 처리하는 모델이다.
  • 번역기를 생각해보면, 입력에 사용되는 데이터인 문장도 결국 단어의 시퀀스이고 출력도 단어의 시퀀스이다.
  • 이러한 시퀀스를 적절하게 처리하기 위해 고안된 모델들을 시퀀스 모델이라고 한다.
  • RNN은 그 중 기초가 되는 모델이라고 할 수 있다.
  • 기존에 알던 신경망처럼 단순히 은닉층에서 출력층 방향으로만 값이 전달되는 것이 아니라, '메모리 셀'이라는 특수한 은닉층이 존재하여 이전의 값을 기억하는 일종의 메모리 역할을 수행한다.
  • 이러한 메모리 셀은 다음시점의 자신에게 'hidden state'라고 불리는 특수한 값을 전달해 활용할 수 있도록 한다.
  • 참고 : https://wikidocs.net/22886

LSTM (Long Short-Term Memory, LSTM)

  • 바닐라 RNN (기초 RNN)의 한계 : RNN의 시점이 길어지면 길어질수록 기존에 가지고 있던 정보가 뒤로 충분히 전달되지 못하는 형상이 발생한다. 중요한 정보가 앞쪽에 존재할 수도 있는데 그러한 정보가 충분히 전달되지 못한다는 것은 성능에 치명적인 영향을 준다.
  • 따라서 이러한 '장기 의존성 문제'를 극복하기 위해 'hidden state'와 더불어 장기기억을 위한 정보인 'cell state'를 추가적으로 가진다. 이것이 LSTM의 핵심이다.
  • 시그모이드 함수와 하이퍼볼릭탄젠트 함수 등을 이용해 이러한 값들을 적절히 조절해가면서 정보를 다음 시점으로 전달해나간다.
  • 참고 : https://wikidocs.net/22888

사용예제

from tensorflow.keras.datasets import imdb

(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=5000)
  • 텐서플로우에 내장된 imdb 데이터를 불러온다.
  • 해당 데이터셋은 이미 인덱스화가 되어있어 따로 처리해줄 필요가 없다.
  • 최대 인덱스는 빈도순으로 5000까지만 설정하고, 나머지는 임의로 처리한다.
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 문장 길이를 맞춰준다
max_len = 500
X_train = pad_sequences(X_train, maxlen=max_len)
X_test = pad_sequences(X_test, maxlen=max_len)
print(X_train[1])
print(X_test[1])
  • 문장들의 길이를 일정하게 하기 위해 pad_sequences를 활용한다.
  • maxlen의 길이가 되도록 현재의 모든 문장 배열 앞에 0을 추가한다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Embedding

# 레이어들을 쌓을 모델을 생성
model = Sequential()
# 단어를 임베딩하는데, 5000개의 단어를 120차원으로 내보내겠다
model.add(Embedding(5000, 120))
# RNN - simpleRNN / LSTM
model.add(LSTM(120))
# 긍정/부정을 판단하니까 이진 분류 -> sigmoid 함수 사용
model.add(Dense(1, activation='sigmoid'))
  • Sequential로 레이어들을 쌓을 모델을 초기화한다.
  • Embedding: 인덱스로 표시되어있는 문장 배열을 모두 120차원의 벡터로 임베딩해주는 레이어이다. 현재 단어는 5000개까지 존재한다고도 알려준다. 5000개의 단어들이 120차원으로 표시된다.
  • LSTM : 그렇게 임베딩된 문장 배열들에서 이전 정보들을 기억하고 이들을 포함해 값을 추출해준다.
  • Dense : 일반적으로 이전 레이어 정보 전체를 받는 레이어이다. 노드는 1개이며 시그모이드함수로 이진분류를 진행한다.
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

# 혹시 5회 이상 검증데이터 loss가 증가하면, 과적합될 수 있으므로 학습을 조기종료!
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
# 훈련을 거듭하면서, 가장 검증데이터 정확도가 높았던 순간을 체크포인트로 저장
model_check = ModelCheckpoint('the_best.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
  • EarlyStopping, ModelCheckpoint 모두 최적 모델을 찾기위한 callback정보를 리턴한다.
  • EarlyStopping : 반복문을 돌면서 모델을 찾을 때, 초반에 최고 성능의 모델이 찾아져 더 좋은 성능의 모델이 발견되지 않는 경우가 있다. 이를 찾아서 알려준다.
  • ModelCheckpoint : 처음에 입력된 모델('the_best.h5')를 기준으로 하고, 정확도를 체크하며 해당 값이 최고일 때를 찾도록 한다. verbose는 출력문(진행상황)을 얼마나 출력할지 설정하는 옵션이고, save_best_only는 True로 하면 최고인 값만을 저장하게하는 옵션이다.
# 긍정/부정을 판단하니까 손실함수는 이진 교차 엔트로피, 최적화는 adam, 평가 기준은 acc (출력할때 뜬다)
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=1, batch_size=64, callbacks=[early_stop, model_check])
  • 손실함수는 'binary_crossentropy(교차엔트로피)', 최적화함수는 'adam', 평가 기준은 acc로 설정하고 컴파일한다.
  • callbacks으로 이전에 구한 early_stop과 model_check을 넣어주어 적절히 동작하도록 한다.
# 정확도 측정
print(model.evaluate(X_test, y_test))
  • 학습 데이터를 기반으로 완성된 모델을 평가한다.

사용 예제2

  • imdb.load_data() 함수를 사용하지 않고 직접 데이터를 불러와서 해결하는 과정을 살펴보자.
import pandas as pd
import numpy as np

from tensorflow.keras.preprocessing.text import Tokenizer

imdb_data = pd.read_csv("../data/IMDB Dataset.csv")
# pos, neg 값을 숫자로 변환
imdb_data['sentiment'] = imdb_data['sentiment'].replace('positive', 1)
imdb_data['sentiment'] = imdb_data['sentiment'].replace('negative', 0)
# 정규표현식을 써서, 단어가 아니면 공백으로
imdb_data['review'] = imdb_data['review'].str.replace("[^\w]|br", " ")
# 혹시 공백이 있으면 제거
imdb_data['review'] = imdb_data['review'].replace("", np.nan)
imdb_data['sentiment'] = imdb_data['sentiment'].replace("", np.nan)
# null array 없애는 함수
imdb_data = imdb_data.dropna(how='any')

print("# preprocessing done")
  • 데이터셋을 준비하는 과정이다.
  • 현재 데이터의 종속 데이터값은 'positive', 'negative'와 같이 문자열로 표기되어 있기 때문에 이를 숫자형인 1과 0으로 변경해준다.
  • 정규표현식을 사용해 단어가 아닌 부분은 모두 공백으로 변경해준다.
  • 데이터중 완전 공백인 부분이 있다면 이를 결측치로 처리한다.
  • 결측치를 제거하고 데이터 준비를 완료했다는 메세시를 출력한다.
from sklearn.model_selection import train_test_split

review_train, review_test, y_train, y_test = train_test_split(imdb_data['review'], imdb_data['sentiment'], test_size=0.25, shuffle=False, random_state=20)

print("# split done")
  • train_test_split함수를 이용해 해당 데이터를 학습데이터와 테스트데이터로 분류해준다.
  • 테스트 데이터의 사이즈는 0.25, 셔플은 하지않으며, 랜덤 시드값은 20이다.
  • split이 완료되었다는 메시지를 출력한다.
stopwords = ['a', 'an']

X_train = []
for stc in review_train:
    token = []
    words = stc.split()
    for word in words:
        if word not in stopwords:
            token.append(word)
    X_train.append(token)

X_test = []
for stc in review_test:
    token = []
    words = stc.split()
    for word in words:
        if word not in stopwords:
            token.append(word)
    X_test.append(token)

print("# tokenization done")
  • 각 독립 변수 데이터에서 stopword를 제거한다.
  • 임의로 a와 an만 설정해서 진행했다.
  • 작업이 완료되면 tokenization이 완료되었다는 메세지를 출력한다.
tokenizer = Tokenizer(5000)
# train 을 기준으로 단어마다의 인덱스를 부여
tokenizer.fit_on_texts(X_train)

# 인덱스로 변환
X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)

print("# int_encoding done")
  • 데이터를 인덱스화한다.
  • X_train만 fit하는 이유는 최초 기준을 X_train으로 삼기 위함, split 처리전 전체 데이터로 fit해도 괜찮다.
  • 유의미한 단어 5000개만 인덱스를 부여하도록 한다.
  • 학습 데이터와 테스트 데이터를 인덱스로 변경한다.
  • 인덱스 인코딩이 완료되었다는 메세지를 출력한다.
  • 이제 imdb.load_data()를 했을 때와 같은 데이터가 준비되었다. 이 후과정은 위와 같다.
반응형