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

🛠 기타/Data & AI

Attention 기초 (리뷰 요약하기 예제)

inu 2020. 8. 18. 21:43
반응형

Attention

  • 기존 seq2seq(https://inuplace.tistory.com/580)에서%EC%97%90%EC%84%9C) 발생하던 정보 손실 문제를 대처하기 위한 기법이다.
  • 단순히 인코더에서 상태값을 넘겨받고 그를 기반으로 연산을 진행하는 것으로는 정보가 부족하다. (쩡확한 연산진행이 어렵다.)
  • 연산을 진행하면서 계속해서 인코더의 상태를 참조하면서 그를 활용한다.
  • 인코더의 각 시점의 은닉상태값과 디코더의 은닉상태값을 dot_procut(혹은 다른연산)하고, 그를 softmax함수에 통과시켜 확률값을 얻어낸다. 그리고 그렇게 나온 확률값을 다시 인코더의 각 시점의 은닉상태값들에 곱해 최종적인 'Attention Value'를 얻어낸다.
  • 그렇게 얻어진 Attention Value를 디코더의 은닉상태와 결합한다. (Concatenate)
  • 최종적으로 그렇게 결합된 벡터에 다시 가중치들을 곱해 최종적으로 softmax 함수로 들어가 다음값을 도출할 새로운 벡터를 연산한다.
  • 마지막으로 softmax함수를 통과해 다음값을 구하게되고 이러한 과정을 반복하는 것은 seq2seq와 동일하다.
  • Attention Value 도출을 위해 dot product을 사용하는 dot product attention 외에도 scaled dot, general, concat 등 다양한 방법들이 존재한다.

리뷰 요약 예제 : 데이터 전처리 및 준비

import numpy as np
import re
import pandas as pd
import csv
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

reviews = pd.read_csv("../data/Musical_instruments_reviews.csv")

reviews = reviews.loc[:, ['reviewText', 'summary']]
reviews = reviews.dropna(axis=0)

reviews['reviewText'] = reviews['reviewText'].str.lower()
reviews['reviewText'] = reviews['reviewText'].str.replace('[^\w]', ' ')

reviews['summary'] = reviews['summary'].str.lower()
reviews['summary'] = reviews['summary'].str.replace('[^\w]', ' ')
encoder_input, decoder_input, decoder_output = [], [], []

for stc in reviews['reviewText']:
    encoder_input.append(stc.split())

for stc in reviews['summary']:
    decoder_input.append(("<start> "+stc).split())

for stc in reviews['summary']:
    decoder_output.append((stc+" <end>").split())
  • 각 데이터를 encoder_input, decoder_input, decoder_output으로 나누어 넣는다.
  • decoder_input, decoder_output은 시작과 끝의 표기를 위해 <start>와 <end>를 더해 넣는다.
tokenizer_re = Tokenizer()
tokenizer_re.fit_on_texts(encoder_input)
encoder_input = tokenizer_re.texts_to_sequences(encoder_input)

tokenizer_su = Tokenizer()
tokenizer_su.fit_on_texts(decoder_input)
tokenizer_su.fit_on_texts(decoder_output)
decoder_input = tokenizer_su.texts_to_sequences(decoder_input)
decoder_output = tokenizer_su.texts_to_sequences(decoder_output)

encoder_input = pad_sequences(encoder_input, padding="post")
decoder_input = pad_sequences(decoder_input, padding="post")
decoder_output = pad_sequences(decoder_output, padding="post")
  • 인덱스화(문자열->숫자) 및 패딩(길이 맞추기)를 진행한다.
su_to_index = tokenizer_su.word_index
index_to_su = tokenizer_su.index_word
  • 추후 활용을 위해 decoder_input과 decoder_output이 fit되어 있는 tokenizer_su의 word_index와 index_word를 따로 저장해놓는다. (단어->인덱스, 인덱스->단어)
test_size = 2500
encoder_input_train = encoder_input[:-test_size]
decoder_input_train = decoder_input[:-test_size]
decoder_output_train = decoder_output[:-test_size]

encoder_input_test = encoder_input[-test_size:]
decoder_input_test = decoder_input[-test_size:]
decoder_output_test = decoder_output[-test_size:]
  • 학습 데이터와 테스트 데이터로 각 데이터들을 나눈다.

리뷰 요약 예제 : 모델 초기화 및 학습

from tensorflow.keras.layers import Input, LSTM, Embedding, Dense, Masking, Concatenate
from tensorflow.keras.models import Model

encoder_inputs = Input(shape=(2089,))
encoder_embed = Embedding(len(tokenizer_re.word_index)+1, 50)(encoder_inputs)
encoder_mask = Masking(mask_value=0)(encoder_embed)
encoder_outputs, h_state, c_state = LSTM(50, return_state=True, return_sequences=True)(encoder_mask)
  • reviewText의 최대길이(패딩길이)가 되는 2089를 인풋값으로 잡는다.
  • return sequences = True 를 통해서 어텐션을 구할 때 필요한 전체 시점의 히든 상태값이 리턴되도록 한다.
decoder_inputs = Input(shape=(27,))
decoder_embed = Embedding(len(tokenizer_su.word_index)+1, 50)(decoder_inputs)
decoder_mask = Masking(mask_value=0)(decoder_embed)
decoder_lstm = LSTM(50, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_mask, initial_state=[h_state, c_state])
  • decoder 모델의 구성은 기본 seq2seq와 큰 차이가 없다.
attn_layer = AttentionLayer()
attn_out, attn_states = attn_layer([encoder_outputs, decoder_outputs])
decoder_concat_input = Concatenate()([decoder_outputs, attn_out])

decoder_dense = Dense(len(tokenizer_su.word_index)+1, activation='softmax')
decoder_softmax_outputs = decoder_dense(decoder_concat_input)
  • 전체 시점의 은닉상태값이 표기된 encoder_outputs와 decoder_outputs를 활용해 aatn_out(각 시점의 어텐션 벨류)와 attn_states(가중치)를 얻어낸다.
  • 어텐션 벤류와 디코더의 히든상태를 결합(Concatenate)해 새로운 출력벡터를 얻어낸다.
  • 그리고 그를 softmax함수에 통과시켜 각 단어에 대한 확률치를 얻어낼 수 있도록 한다.
model = Model([encoder_inputs, decoder_inputs], decoder_softmax_outputs)
model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy', metrics=['acc'])
model.fit(x = [encoder_input_train, decoder_input_train], y = decoder_output_train, validation_data = ([encoder_input_test, decoder_input_test], decoder_output_test), batch_size = 128, epochs = 50)
  • 모델을 생성하고 학습시킨다.

리뷰 요약 예제 : 예측

encoder_model = Model(encoder_inputs, [encoder_outputs, h_state, c_state])
  • 어텐션 계산을 위해 출력으로 encoder_outputs까지 설정한다.
encoder_h_state = Input(shape=(50,))
encoder_c_state = Input(shape=(50,))

pd_decoder_outputs, pd_h_state, pd_c_state = decoder_lstm(decoder_mask, initial_state=[encoder_h_state, encoder_c_state])

# 어텐션 구현부
# 2089는 시점 (단어, 패딩) 의 수, 50은 히든 스테이트의 차원
pd_encoder_outputs = Input(shape=(2089, 50))
pd_attn_out, pd_attn_states = attn_layer([pd_encoder_outputs, pd_decoder_outputs])
pd_decoder_concat = Concatenate()([pd_decoder_outputs, pd_attn_out])

pd_decoder_softmax_outputs = decoder_dense(pd_decoder_concat)

decoder_model = Model([decoder_inputs, pd_encoder_outputs, encoder_h_state, encoder_c_state], [pd_decoder_softmax_outputs, pd_h_state, pd_c_state])
  • attn_layer에 인코더 전체시점의 아웃풋인 pd_encoder_outputs와 디코더 전체시점의 아웃풋인 pd_decoder_outputs를 넣어 계산해준다. (attn_out : Attention Value / attn_states : 가중치값)
  • 기존의 디코더 전체시점 아웃풋(pd_decoder_outputs)에 Attention Value(attn_out)를 concat하여 하나의 벡터로 만들어준다.
  • 그렇게 생성된 함수형 레이어를 하나의 모델로 만들어 예측에 사용한다.(어텐션은 디코더 모델 안에서 사용하는거기 때문에, 인풋으로 encoder outputs 까지 넣어준다.)
input_stc = input()
token_stc = input_stc.split()
encode_stc = tokenizer_re.texts_to_sequences([token_stc])
pad_stc = pad_sequences(encode_stc, maxlen=2089, padding="post")

en_out, en_hidden, en_cell = encoder_model.predict(pad_stc)

predicted_seq = np.zeros((1,1))
predicted_seq[0, 0] = su_to_index['<start>']

decoded_stc = []

while True:
    # 여기서 인풋으로 en_out 도 같이 넣어준다!
    output_words, h, c = decoder_model.predict([predicted_seq, en_out, en_hidden, en_cell])

    predicted_word = index_to_su[np.argmax(output_words[0,0])]

    if predicted_word == '<end>':
        break

    decoded_stc.append(predicted_word)

    predicted_seq = np.zeros((1,1))
    predicted_seq[0, 0] = np.argmax(output_words[0, 0])

    en_hidden = h
    en_cell = c

print(' '.join(decoded_stc))
  • 기본적인 처리는 seq2seq와 크게 다르지 않다.
  • 다만 모델에서 en_out(인코더의 아웃풋)도 필요하기 때문에 함께 넣어 활용한다.
반응형