1. 데이터 준비 without Attention
학습 데이터로 프랑스어-영어 데이터를 가져옵니다.(* 데이터 출처: http://www.manythings.org/anki/)
Tab-delimited Bilingual Sentence Pairs from the Tatoeba Project (Good for Anki and Similar Flashcard Applications)
Introducing Anki If you don't already use Anki, vist the website at http://ankisrs.net/ to download this free application for Macintosh, Windows or Linux. About These Files Any flashcard program that can import tab-delimited text files, such as Anki (free)
www.manythings.org
import os
import re
import shutil
import zipfile
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
def download_zip(url, output_path):
response = requests.get(url, headers=headers, stream=True)
if response.status_code == 200:
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"ZIP file downloaded to {output_path}")
else:
print(f"Failed to download. HTTP Response Code: {response.status_code}")
url = "http://www.manythings.org/anki/fra-eng.zip"
output_path = "fra-eng.zip"
download_zip(url, output_path)
path = os.getcwd()
zipfilename = os.path.join(path, output_path)
with zipfile.ZipFile(zipfilename, 'r') as zip_ref:
zip_ref.extractall(path)
필요없는 칼럼을 제거하고 원하는 데이터만 출력해봅니다.
import pandas as pd
lines = pd.read_csv('fra.txt', names ] ['src', 'tar', 'lic'], sep='\t')
def lines['lic'] # 필요없는 칼럼 삭제
len(lines)
# output:
# 232736
lines.head()
결과
데이터의 양이 너무 많기 때문에(* 23만개 가량...) 6만개로 잘라서 사용합니다.
또한 target데이터에는 <sos>와 <eos>를 의미하는 '\t'와 '\n'을 넣어줍니다.
lines = lines.loc[:, 'src':'tar']
lines = lines[:60000]
lines.tar = lines.tar.map(lambda x: '\t' + x + '\n')
lines[:10]
결과
저희는 글자단위로 예측할 모델을 만들기때문에 전체 단어의 개수를 파악해줍니다.
src와 tar의 글자의 종류를 파악합니다.
그 후, index별로 mapping을 시켜준 후 dictionary로 저장합니다.
src_vocab = set()
for line in lines.src:
for char in line:
src_vocab.add(char)
tar_vocab = set()
for line in lines.tar:
for char in line:
tar_vocab.add(char)
src_vocab = sorted(list(src_vocab))
tar_vocab = sorted(list(tar_vocab))
src_vocab_size = len(src_vocab) + 1 # padding 값까지 추가
tar_vocab_size = len(tar_vocab) + 1 # padding 값까지 추가
src_to_idx = dict([(word, i+1) for i, word in enumerate(src_vocab)])
tar_to_idx = dict([(word, i+1) for i, word in enumerate(tar_vocab)])
idx_to_src = dict([(i+1, word) for i, word in enumerate(src_vocab)])
idx_to_tar = dict([(i+1, word) for i, word in enumerate(src_vocab)])
print(src_to_idx)
print(tar_to_idx)
# output:
# {' ': 1, '!': 2, '"': 3, '$': 4, '%': 5, '&': 6, "'": 7, ',': 8, ...
# {'\t': 1, '\n': 2, ' ': 3, '!': 4, '"': 5, '$': 6, '%': 7, '&': 8, ...
모델에 들어갈 input을 idx로 바꿔줍니다.
target데이터들 또한 idx로 바꿔줍니다.
encoder_input = []
for line in lines.src:
encoder_input.append([src_to_idx[w] for w in line])
decoder_input = []
for line in lines.tar:
encoder_input.append([tar_to_idx[w] for w in line])
decoder_target = []
for line in lines.tar:
encoder_input.append([src_to_idx[w] for w in line if w != '\t']) # <sos>토큰은 target은 담으면 안된다.
padding을 진행합니다.
!pip install keras
from keras.preprocessing.sequence import pad_sequences
max_src_len = max([len(line) for line in lines.src])
max_tar_len = max([len(line) for line in lines.tar])
print(max_src_len)
print(max_tar_len)
# output:
# 22
# 76
encoder_input = pad_sequences(encoder_input, maxlen=max_src_len, padding='post')
decoder_input = pad_sequences(decoder_input, maxlen=max_tar_len, padding='post')
decoder_target = pad_sequences(decoder_target, maxlen=max_tar_len, padding='post')
(* 이후 to_categorical을 통해 ont-hot encoding을 진행했지만 메모리 초과!!)
(* 차라리 Embedding Layer를 추가로 모델에 넣어줘서 메모리를 save한다. 하지만 loss = 'sparse categorical crossentropy'로 진행해야한다.)
https://bigdaheta.tistory.com/65
다중 분류 손실함수(categorical_crossentropy 와sparse_categorical_crossentropy 차이)
손실 함수(loss function)는 해당 데이터를 가지고 어떤 형태의 예측을 할 것인지에 따라 선택하면 되는데, 그중, 다중 클래스 분류를 위해 사용되는 손실 함수에 대해 정리해보고자 한다. 일반적으
bigdaheta.tistory.com
2. seq2seq without attention 모델
지난 모델들과 달리 Sequential을 사용하지 않고 Model을 사용합니다.
이는 여러개의 inputs들과 outputs들을 만들 수 있는 함수형 모델생성 방식입니다.
07-09 케라스의 함수형 API(Keras Functional API)
앞서 구현한 선형, 로지스틱, 소프트맥스 회귀 모델들과 케라스 훑어보기 실습에서 배운 케라스의 모델 설계 방식은 Sequential API을 사용한 것입니다. 그런데 Sequen…
wikidocs.net
embedding 차원은 64, LSTM의 hidden_units도 64로 초기화해줍니다.
embedding_dim = 64
hidden_units = 64
one-hot encoding을 하지 않으니 Embedding Layer를 추가해서 encoder를 만들어줍니다.
from keras.layers import Embedding, Input, LSTM
# 인코더
encoder_inputs = Input(shape=(None,))
# 64차원으로 임베딩
enc_emb = Embedding(src_vocab_size, embedding_dim)(encoder_inputs)
encoder_lstm = LSTM(hidden_units, return_state=True) # decoder에 넘겨야하니까
encoder_outputs, state_h, state_c = encoder_lstm(enc_emb)
encoder_states = [state_h, state_c]
decoder도 만듭니다.
from keras.layers import Dense
decoder_inputs = Input(shape=(None,))
# encoder의 state와 차원이 같아야한다. 따라서 hidden_units차원으로 맞춰준다.
dec_emb = Embedding(tar_vocab_size, hidden_units)(decoder_inputs)
decoder_lstm = LSTM(hidden_units, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(dec_emb, initial_state=encoder_states)
# 마지막 출력 단에서는 tar_vocab_size로 차원을 바꿔서 실제 단어들로 변환하기 위해 softmax로 활성화한다.
decoder_softmax_layer = Dense(tar_vocab_size, activation='softmax')
decoder_outputs = decoder_softmax_layer(decoder_outputs)
이를 바탕으로 seq2seq모델을 만들어줍니다.
이때 GPU를 사용해서 속도를 빠르게 합니다.
from keras.models import Model
with tf.device('/device:GPU:0'):
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy', metrics=['acc'])
history = model.fit(x=[encoder_input, decoder_input], y=decoder_target,
batch_size=128,
epochs=25,
validation_split=0.2)
학습을 시킨 후 결과를 시각화합니다.
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
hist_dict = history.history
print(hist_dict.keys())
plt.plot(hist_dict['loss'], 'b-', label='Train Loss')
plt.plot(hist_dict['val_loss'], 'r:', label='Validation Loss')
plt.legend()
plt.grid()
plt.figure()
plt.plot(hist_dict['acc'], 'b-', label='Train Accuracy')
plt.plot(hist_dict['val_acc'], 'r:', label='Validation Accuracy')
plt.legend()
plt.grid()
plt.show()
결과
3. 예측
해당 모델을 이용해서 예측을 수행합니다.
with tf.device('/device:GPU:0'):
# 사용하는 모델의 input과 output에 기존 학습했던 모델의 input과 output을 사용해서
# 위에서 학습한 모든 가중치들을 그대로 사용한다.
encoder_model = Model(encoder_inputs, encoder_states)
decoder_state_input_h = Input(shape=(hidden_units,))
decoder_state_input_c = Input(shape=(hidden_units,))
decoder_state_inputs = [decoder_state_input_h, decoder_state_input_c]
# 디코더의 embedding을 그대로 사용해줍니다.
decoder_outputs2, state_h2, state_c2 = decoder_lstm(dec_emb, initial_state=decoder_state_inputs)
decoder_states2 = [state_h2, state_c2]
decoder_outputs2 = decoder_softmax_layer(decoder_outputs2)
decoder_model = Model([decoder_inputs] + decoder_state_inputs, [decoder_outputs2] + decoder_states2)
예측 함수를 만들어줍니다.
import numpy as np
def decode_sequence(input_seq):
# 디코더의 input으로 들어가는 state들은 인코더의 output으로 만들어진다.
# 주어진 input_sequence를 인코더에 넣어 state를 만들어준다.
states_value = encoder_model.predict(input_seq)
# 가장 처음 decoder에 입력으로 들어가는 문자는 <sos>토큰, 즉 '\t'이다.
# 이를 위해 2차원 np.array에 '\t'에 해당하는 index값을 넣어 입력으로 넣어준다.
target_seq = np.zeros((1,1))
target_seq[0,0] = tar_to_idx['\t']
# stop이 True가 될때까지 반복문을 돈다.
stop = False
# 예측 문장
decoded_sent = ''
# 구현의 단순성을 위해 배치 사이즈를 1로 합니다.
while not stop:
# <sos>토큰과 state를 입력으로 받아 output_tokens와 states들을 만들어준다.
output_tokens, h, c = decoder_model.predict([target_seq] + states_value)
# output_token을 문자로 바꿔준다.
sampled_token_idx = np.argmax(output_tokens[0,-1,:])
sampled_char = idx_to_tar[sampled_token_idx]
# 만들어진 문자를 예측 문장에 추가
decoded_sent += sampled_char
# 만약 <eos>토큰이 나오거나 최대 문장 길이를 초과하면 종료한다.
if sampled_char == '\n' or len(decoded_sent) > max_tar_len:
stop = True
# 다음 input으로 들어가는 문자는 이번에 예측한 문자가 된다.
target_seq = np.zeros((1,1))
target_seq[0,0] = sampled_token_idx
# 다음 input으로 들어가는 state값은 이번에 나온 state값이 된다.
states_value = [h,c]
return decoded_sent
추가적으로 함수 몇 개를 만들어주면 예측이 완성됩니다.
# 원문의 정수 시퀀스를 텍스트 시퀀스로 변환
def seq_to_src(input_seq):
sentence = ''
for encoded_word in input_seq:
if(encoded_word != 0):
sentence = sentence + idx_to_src[encoded_word]
return sentence
# 번역문의 정수 시퀀스를 텍스트 시퀀스로 변환
def seq_to_tar(input_seq):
sentence = ''
for encoded_word in input_seq:
if(encoded_word != 0 and encoded_word != tar_to_idx['\t'] and encoded_word != tar_to_idx['\n']):
sentence = sentence + idx_to_tar[encoded_word]
return sentence
for seq_index in [300, 5000, 10000, 30000, 40000]:
input_seq = encoder_input[seq_index: seq_index + 1]
decoded_sentence = decode_sequence(input_seq)
print("입력문장 :",seq_to_src(encoder_input[seq_index]))
print("정답문장 :",seq_to_tar(decoder_input[seq_index]))
print("번역문장 :",decoded_sentence[1:-5])
print("-"*50)
# output:
'''
입력문장 : Go home.
정답문장 : Rentre à la maison.
번역문장 : Elle est le m
--------------------------------------------------
입력문장 : Grab my hand.
정답문장 : Saisissez-moi la main !
번역문장 : Avez le mon arr
--------------------------------------------------
입력문장 : We're so busy.
정답문장 : On est tellement occupés !
번역문장 : Nous ne sommes pas de la mais
--------------------------------------------------
입력문장 : Tom felt confused.
정답문장 : Tom se sentait confus.
번역문장 : Tom a l'air un chan
--------------------------------------------------
입력문장 : Give me back my pen.
정답문장 : Rends-moi mon stylo.
번역문장 : Arrête le mon ave
--------------------------------------------------
'''
4. Seq2seq with Attention(Loung)
루옹 어텐션을 적용시킨 모델을 만들어 학습시킵니다.
from keras.layers import Attention
with tf.device('/device:GPU:0'):
encoder_inputs = Input(shape=(None,))
decoder_inputs = Input(shape=(None,))
# Embedding 레이어에 mask_zero=True 추가
enc_emb = Embedding(src_vocab_size, embedding_dim)(encoder_inputs)
dec_emb = Embedding(tar_vocab_size, hidden_units)(decoder_inputs)
# LSTM 및 Attention 연결
encoder_lstm = LSTM(hidden_units, return_state=True, return_sequences=True)
encoder_outputs, state_h, state_c = encoder_lstm(enc_emb)
encoder_states = [state_h, state_c]
decoder_lstm = LSTM(hidden_units, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(dec_emb, initial_state=encoder_states)
# Attention 적용
attention_layer = Attention(score_mode='dot')
attention_score = attention_layer([decoder_outputs, encoder_outputs])
concat = Concatenate()([attention_score, decoder_outputs])
# 디코더의 소프트맥스 레이어 추가
decoder_softmax_layer = Dense(tar_vocab_size, activation='softmax')
decoder_outputs = decoder_softmax_layer(concat)
# 모델 컴파일 및 훈련
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy', metrics=['acc'])
history = model.fit(x=[encoder_input, decoder_input], y=decoder_target,
batch_size=128, epochs=25, validation_split=0.2)
'''
Epoch 25/25
375/375 ━━━━━━━━━━━━━━━━━━━━ 79s 209ms/step - acc: 0.8907 - loss: 0.3657 - val_acc: 0.8590 - val_loss: 0.4705
'''
정확도는 기존(* without attention) 88%에서 89%로,
평가 정확도는 기존 85.1%에서 85.9%로 소폭 상승했습니다.
5. 예측
예측을 위한 모델을 만들고, 함수도 재정의합니다.
with tf.device('/device:GPU:0'):
# Inference 모델 생성
encoder_model = Model(encoder_inputs, [encoder_outputs, encoder_states])
decoder_state_input_h = Input(shape=(hidden_units,))
decoder_state_input_c = Input(shape=(hidden_units,))
decoder_state_inputs = [decoder_state_input_h, decoder_state_input_c]
encoder_outputs_input = Input(shape=(None, hidden_units))
# Inference 시 사용되는 디코더 모델
decoder_outputs2, state_h2, state_c2 = decoder_lstm(dec_emb, initial_state=decoder_state_inputs)
attention_score = attention_layer([decoder_outputs2, encoder_outputs_input])
concat = Concatenate()([attention_score, decoder_outputs2])
decoder_outputs2 = decoder_softmax_layer(concat)
decoder_model = Model([decoder_inputs, encoder_outputs_input] + decoder_state_inputs,
[decoder_outputs2] + [state_h2, state_c2])
def decode_sequence(input_seq):
# 기존과 달리 attention이 출력으로 추가됨
attention, states_value = encoder_model.predict(input_seq)
target_seq = np.zeros((1,1))
target_seq[0,0] = tar_to_idx['\t']
stop = False
decoded_sent = ''
while not stop:
# 기존과 달리 attention이 입력으로 추가됨
output_tokens, h, c = decoder_model.predict([target_seq, attention] + states_value)
sampled_token_idx = np.argmax(output_tokens[0,-1,:])
sampled_char = idx_to_tar[sampled_token_idx]
decoded_sent += sampled_char
if sampled_char == '\n' or len(decoded_sent) > max_tar_len:
stop = True
target_seq = np.zeros((1,1))
target_seq[0,0] = sampled_token_idx
states_value = [h,c]
return decoded_sent
예측을 진행합니다.
for seq_index in [300, 5000, 10000, 30000, 40000]:
input_seq = encoder_input[seq_index: seq_index + 1]
decoded_sentence = decode_sequence(input_seq)
print("입력문장 :",seq_to_src(encoder_input[seq_index]))
print("정답문장 :",seq_to_tar(decoder_input[seq_index]))
print("번역문장 :",decoded_sentence[1:-5])
print("-"*50)
# outputs:
'''
입력문장 : Go home.
정답문장 : Rentre à la maison.
번역문장 : Allez le mo
--------------------------------------------------
입력문장 : Grab my hand.
정답문장 : Saisissez-moi la main !
번역문장 : Pourrez-mo
--------------------------------------------------
입력문장 : We're so busy.
정답문장 : On est tellement occupés !
번역문장 : Nous sommes sont en train de te m
--------------------------------------------------
입력문장 : Tom felt confused.
정답문장 : Tom se sentait confus.
번역문장 : Tom est ma cont
--------------------------------------------------
입력문장 : Give me back my pen.
정답문장 : Rends-moi mon stylo.
번역문장 : Donne le mange
--------------------------------------------------
'''
배움
데이터 전처리 단계에서 원-핫 인코딩을 하지 않아도 sparse categorical crossentropy를 사용하면 된다.
colab에서 GPU사용
'[Deep daiv.] > [Deep daiv.] NLP' 카테고리의 다른 글
[Deep daiv.] NLP, WIL - 9. ELMo and GPT (2) (0) | 2024.11.13 |
---|---|
[Deep daiv.] NLP, WIL - 9. ELMo and GPT (1) (1) | 2024.09.09 |
[Deep daiv.] WIL, NLP - 8. 순환 신경망 실습 (6) | 2024.09.02 |
[Deep daiv.] WIL, NLP - 6. 단어 임베딩 (1) | 2024.08.29 |
[Deep daiv.] WIL, NLP - 7. Seq2seq with Attention (0) | 2024.08.29 |