본문 바로가기
HRDI_AI/[인공지능] RNN을 이용한 자연어처리 모델 구현

2. 자연어처리 인공신경망 - 3(Seq2Seq)

by Toddler_AD 2025. 12. 5.

2-3. Seq2Seq

  • Seq2Seq
    • PBMT 기술과 GNMT 기술로 나뉨
    • PBMT(Phrase-based Machine Translation)
      • 문장을 구절 단위로 나누고 번역
    • GNMT(Google Neural Machine Translation)
      • 인코더 입력, 디코더 입력, 그리고 디코더 출력으로 나눠진다.

  • Seq2Seq
    • RNN을 연결하여 하나의 시퀀스 데이터를 다른 시퀀스 데이터로 변환하는 모델
      • 시퀀스(Sequence)는 연관된 일련의 데이터를 의미
      • 자연어 문장이 시퀀스에 해당
      • 입력을 처리하기 위한 인코더(Encoder)와 출력을 위한 디코더(Decoder)가 연결되는 구조

  • GNMT
    • 인코더는 차례로 입력된 문장들의 모든 단어를 압축하여 컨텍스트 벡터를 생성
    • 컨텍스트 벡터는 차례로 입력된 문장의 모든 단어의 정보를 압축한 벡터이며 잠재 벡터(Latent Vector) 라고도 한다.
    • 디코더는 입력된 컨텍스트 벡터를 이용하여 출력 시퀀스를 생성하고 출력

  • 실습 - Seq2Seq를 이용한 영-한 번역기

# 모델 구축
# 입력값 토큰화, 임베딩
# 훈련을 정의하고 학습
# 평가
# 예측하기 위한 데이터를 토큰화, 임베딩후 predict
# 라벨 출력

from tensorflow.keras import Model, layers

# 입력 단어는 4글자 영단어, 전체문자수는 171개(SEP 포함)
enc_input = layers.Input(shape=(4, 171))
_, state_h, state_c = layers.LSTM(128, return_state=True)(enc_input)

dec_input = layers.Input(shape=(3, 171)) # 한글 단어 2자와 start 토큰 1개
lstm_out = layers.LSTM(128, return_sequences=True)(dec_input, initial_state=[state_h, state_c])

dec_output = layers.Dense(171, activation='softmax')(lstm_out)

model = Model(inputs=[enc_input, dec_input], outputs=dec_output)
model.summary()
Model: "functional_2"
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ Layer (type)        ┃ Output Shape      ┃    Param # ┃ Connected to      ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ input_layer_4       │ (None, 4, 171)    │          0 │ -                 │
│ (InputLayer)        │                   │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ input_layer_5       │ (None, 3, 171)    │          0 │ -                 │
│ (InputLayer)        │                   │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ lstm_4 (LSTM)       │ [(None, 128),     │    153,600 │ input_layer_4[0]… │
│                     │ (None, 128),      │            │                   │
│                     │ (None, 128)]      │            │                   │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ lstm_5 (LSTM)       │ (None, 3, 128)    │    153,600 │ input_layer_5[0]… │
│                     │                   │            │ lstm_4[0][1],     │
│                     │                   │            │ lstm_4[0][2]      │
├─────────────────────┼───────────────────┼────────────┼───────────────────┤
│ dense_2 (Dense)     │ (None, 3, 171)    │     22,059 │ lstm_5[0][0]      │
└─────────────────────┴───────────────────┴────────────┴───────────────────┘
 Total params: 329,259 (1.26 MB)
 Trainable params: 329,259 (1.26 MB)
 Non-trainable params: 0 (0.00 B)
model.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam') # metrics가 없으면 훈련시 loss만 출력됨
# 문자배열 생성
import pandas as pd
import numpy as np
# <START>, <END>, <PAD>
arr1 = [c for c in 'SEPabcdefghijklmnopqrstuvwxyz']
arr2 = pd.read_csv('./data/korean.csv', header=None)
# print(arr2[0].values.tolist())
num_to_char = arr1 + arr2[0].values.tolist()
print(len(num_to_char))
char_to_num = {char:i for i, char in enumerate(num_to_char)}
# print(char_to_num)
171
# 학습용 단어셋 불러오기
raw = pd.read_csv('./data/translate.csv', header=None)
eng_kor = raw.values.tolist()
print(len(eng_kor)) # 학습할 전체 단어 수 110개
110
# 단어를 숫자 배열로 변환
temp_eng = 'love'
temp_eng_n = [char_to_num[c] for c in temp_eng]
print(temp_eng_n)
np.eye(171)[temp_eng_n].shape
[14, 17, 24, 7]
(4, 171)
# 단어를 원-핫 인코딩된 배열로 변환
def encode(eng_kor):
    enc_in = []
    dec_in = []
    rnn_out = [] # decoder output
    for seq in eng_kor:
        eng = [char_to_num[c] for c in seq[0]]
        enc_in.append(np.eye(171)[eng])

        kor = [char_to_num[c] for c in ('S'+seq[1])]
        dec_in.append(np.eye(171)[kor])

        target = [char_to_num[c] for c in (seq[1] + 'E')]
        rnn_out.append(target)

    enc_in = np.array(enc_in)
    dec_in = np.array(dec_in)
    rnn_out = np.array(rnn_out)
    rnn_out = np.expand_dims(rnn_out, axis=2)
    return enc_in, dec_in, rnn_out
sample = [['word', '단어']]
encode(sample)[2]
array([[[ 61],
        [114],
        [  1]]])
X_enc, X_dec, y_rnn = encode(eng_kor)
%%time
model.fit([X_enc, X_dec], y_rnn, epochs=500)
Epoch 1/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 1s 8ms/step - loss: 5.1372
Epoch 2/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - loss: 5.0993 
Epoch 3/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - loss: 5.0503 
Epoch 4/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 4.9682 
Epoch 5/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 4.8043 
Epoch 6/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 4.4456 
Epoch 7/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.7817 
Epoch 8/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.5358 
Epoch 9/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.6085 
Epoch 10/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.4271 
Epoch 11/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.3862 
Epoch 12/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 3.3940 
Epoch 13/500
...
Epoch 500/500
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - loss: 0.0043 
CPU times: total: 48 s
Wall time: 22 s
enc_in, dec_in, _ = encode([['life', 'PP']])
# print(enc_in)
pred = model.predict([enc_in, dec_in])
print(pred.shape)
word = np.argmax(pred[0], axis=-1)
# print(pred[0][0])
print(np.argmax(pred[0][0]))
print(word)
num_to_char[word[0]], num_to_char[word[1]]
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 124ms/step
(1, 3, 171)
99
[99 80  1]
('생', '명')
from numpy.random import randint
pick = randint(0, len(eng_kor), 5)
choose = [ [eng_kor[i][0], 'PP'] for i in pick]
print(choose)
[['east', 'PP'], ['roof', 'PP'], ['read', 'PP'], ['fact', 'PP'], ['them', 'PP']]
enc_in, dec_in, _ = encode(choose)
pred = model.predict([enc_in, dec_in])
print(pred.shape)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 151ms/step
(5, 3, 171)
for i in range(len(choose)):
    eng = choose[i][0]
    word = np.argmax(pred[i], axis=-1)
    kor = ''
    for j in range(2):
        kor = kor + num_to_char[word[j]]
    print(eng, kor)
east 동쪽
roof 지붕
read 읽다
fact 사실
them 그들