반응형
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]', ' ')
- https://www.kaggle.com/eswarchandt/amazon-music-reviews?select=Musical_instruments_reviews.csv
- reviewText를 활용해 summary를 도출할 것이다.
- 필요한 부분만 가져와서 결측치제거, 소문자화, 통상문자가 아닌 문자제거 등의 작업을 수행한다.
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(인코더의 아웃풋)도 필요하기 때문에 함께 넣어 활용한다.
반응형
'🛠 기타 > Data & AI' 카테고리의 다른 글
[scikit-learn 라이브러리] AdaBoostClassifier (Adaptive Boosting) (0) | 2020.08.20 |
---|---|
[scikit-learn 라이브러리] GradientBoosting (0) | 2020.08.19 |
Seq2Seq 기초 (챗봇 데이터 활용 예제) (2) | 2020.08.14 |
함수형 케라스와 모델 합성(앙상블) (0) | 2020.08.14 |
뉴스 헤드라인 기반 카테고리 분류하기 (0) | 2020.08.14 |