코딩헤딩

Deep learning[딥러닝] RNN 응용 규칙기반 챗봇 본문

머신러닝 | 딥러닝

Deep learning[딥러닝] RNN 응용 규칙기반 챗봇

멈머이 2024. 1. 9. 21:56
728x90

* 라이브러리 정의

import tensorflow as tf
### 단어사전 만들기
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

 

* 규칙기반 데이터 정의하기(질문/답변)

questions = [
    "전기요금 어때?",
    "안녕하세요",
    "너 이름이 뭐니?",
      .
      . 
      .
      
      
answers = [
    "전기요금이 계속 인상되고 있어요!",
    "안녕하세요! 반가워요^^",
    "제 이름은 챗봇이에요.",
      .
      .
      .
len(questions), len(answers)

결과 : (49, 49)

 

* 단어사전 만들기

tokenizer = Tokenizer()
tokenizer

결과 : <keras_preprocessing.text.Tokenizer at 0x1d3fe1bab20>

- 텍스트 데이터를 토큰(단어)화 하여 단어 사전을 구축하기


* 질문과 답변을 하나의 데이터로 합쳐서 전체 텍스트 데이터로 사용 
  - 이를 기반으로 토큰나이저가 단어 사전을 구축하게 됨
  - 각 단어들은 순차적인 인덱스 번호를 부여받게 됨
  - 토큰나이저가 인식하는 문장 내에서 단어의 의미 : 띄어쓰기가 없는 연결되어 있으면 하나의 단어로 인지


* 문장 내에서 순차적 인덱스 부여하기

tokenizer.fit_on_texts(questions + answers)

- fit_in_texts(텍스트 문장) : 문장 내에서 단어들을 추출하여 순차적 인덱스 부여하기


* 단어별 부여된 인덱스 확인하기

print("단어 개수 : ", len(tokenizer.word_index))
tokenizer.word_index

결과 :

단어 개수 :  74
{'가장': 1,
 '좋아하는': 2,
 '무엇인가요': 3,
 '음식은': 4,

    .

    .

    .

 

* 신경망에서 사용할 단어의 크기 설정 : 하나 더 크게 설정

voca_size = len(tokenizer.word_index) + 1
voca_size

결과 : 75

 

* texts_to_sequences(문장) : 질문에 대한 텍스트 문장을 단어사전의 인덱스 번호로 변환하기

questions_sequences = tokenizer.texts_to_sequences(questions)
print(len(questions_sequences))
questions_sequences

결과 :

49
[[57, 15],
 [21],
 [58, 59, 60],
 [61, 62],

  .

  .

  .

 

* 질문 데이터 단어의 기이 통일시키기(정규화)

question_padded = pad_sequences(questions_sequences,padding="post")
print(len(questions_sequences))
question_padded

결과 :

array([[57, 15,  0,  0,  0,  0],
       [21,  0,  0,  0,  0,  0],
       [58, 59, 60,  0,  0,  0],

           .

           .

           .

  - 잘라낼 max길이 기준 : 문장들 중 최대  max단어길이 기준으로
     (maxien를 넣지 않으면, 문장들의 길이가 가장 긴 것 기준으로 한다. / 디폴트로, 알아서 가장 긴 개수를 찾아낸다.)
  - 채우기는 뒤쪽

* 답변에 대한 데이터로 각 단어사전의 인덱스 번호로 변환하기

answerss_sequences = tokenizer.texts_to_sequences(answers)
print(len(answerss_sequences))
answerss_sequences

결과 :

49

[[64, 65, 66, 13],
 [21, 67],
 [68, 69, 70],

     .

     .

     .

 

* 답변 데이터에 대한 길이 통일시키기

answers_padded = pad_sequences(answerss_sequences,padding="post")
print(len(answers_padded))
len(answers_padded[1])

결과 :

49 

7

 

* 모델 생성

model = tf.keras.Sequential()
model

결과 : <keras.engine.sequential.Sequential at 0x1d3fe1ba220>

 

* 계층 추가하기
  - 단어 임베딩(입력계층으로 사용) : 출력 64
  - simple RNN 사용 : 출력 138, 활성화 함수 relu

model.add(tf.keras.layers.Embedding(voca_size, 64, input_length=question_padded.shape[1]))
model.add(tf.keras.layers.SimpleRNN(128, activation="relu"))
### 입력을 복제하는 전처리 계층 : 텍스트에 대한 Enoder와 Decoder를 담당함
model.add(tf.keras.layers.RepeatVector(answers_padded.shape[1]))
model.add(tf.keras.layers.SimpleRNN(128, activation="relu", return_sequences=True))
model.add(tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(voca_size, activation="softmax")))

 각 타임별로 동일한 가중치를 적용하도록 처리하며,
  - 전체 시퀀스들의 이전/다음 인덱스 처리 값을 가지고 있다고 보면 됨
  - 예측에 주로 사용되는 계층
  - 최종 출력 계층을 감싸고 있음

 

model.summary()

 

 

 

 

 

 

 

 

 

 

 

 

 

* 모델 설정하기

model.compile(optimizer="adam",
              loss="sparse_categorical_crossentropy",
              metrics=["accuracy"])

* 모델 훈련하기

model.fit(question_padded, answers_padded, epochs=100, batch_size=64, verbose=1)

결과 ;

Epoch 1/100
1/1 [==============================] - 1s 1s/step - loss: 4.3152 - accuracy: 0.0058
Epoch 2/100
1/1 [==============================] - 0s 9ms/step - loss: 4.2899 - accuracy: 0.0087
Epoch 3/100
1/1 [==============================] - 0s 10ms/step - loss: 4.2638 - accuracy: 0.3936

                                                                   .

                                                                   .

                                                                   .

"""질문 / 답변 테스트"""
user_input = "너 이름이 뭐니?"

"""새로운 질문 텍스트를 단어사전의 인덱스 번호로 변환하기"""
input_seq = tokenizer.texts_to_sequences([user_input])
input_seq

"""단어의 길이을 훈련에서 사용한 길이로 통일 시키기(정규화)"""
padded_seq = pad_sequences(input_seq, padding="post",
                          maxlen=question_padded.shape[1])

padded_seq

"""예측하기 : 새로운 질문에 대한 답변 추룰하기"""
pred = model.predict(padded_seq)
len(pred[0][0])

"""답변으로 가장 확률이 높은 값 추출하기"""
pred_index = tf.argmax(pred, axis=-1).numpy()[0]
print(len(pred_index), pred_index)

"""
시퀀스에 해당하는 텍스트 추출하기
     - 인덱스에 해당하는 실제 텍스트 추출하기
"""
response = tokenizer.sequences_to_texts([pred_index])[0]
response

결과:

7 [68 69 70  0  0  0  0]
'제 이름은 챗봇이에요'

 

model, tokenizer

결과 :

(<keras.engine.sequential.Sequential at 0x1d3fe1ba220>,
 <keras_preprocessing.text.Tokenizer at 0x1d3fe1bab20>)


* 답변 함수 만들기

def get_Generate_Response(model, tokenizer, user_input):
    """새로운 질문 텍스트를 단어사전의 인덱스 번호로 변환하기"""
    input_seq = tokenizer.texts_to_sequences([user_input])
    input_seq
    
    """단어의 길이을 훈련에서 사용한 길이로 통일 시키기(정규화)"""
    padded_seq = pad_sequences(input_seq, padding="post",
                              maxlen=question_padded.shape[1])
    
    padded_seq
    
    """예측하기 : 새로운 질문에 대한 답변 추룰하기"""
    pred = model.predict(padded_seq)
    len(pred[0][0])
    
    """답변으로 가장 확률이 높은 값 추출하기"""
    pred_index = tf.argmax(pred, axis=-1).numpy()[0]
    # print(len(pred_index), pred_index)
    
    """
    시퀀스에 해당하는 텍스트 추출하기
         - 인덱스에 해당하는 실제 텍스트 추출하기
    """
    response = tokenizer.sequences_to_texts([pred_index])[0]
    return response

 

 

* 질문받고 응답하기 : 무한 반복

while True:
    """새로운 입력 받기"""
    user_input = input("질문입력 : ")

    """반복을 종료시키기"""
    if user_input == "종료":
        print("채팅을 종료 합니다-----------------")
        break

    """함수를 호출하여 질문에 대한 답변 받아오기"""
    response = get_Generate_Response(model, tokenizer, user_input)

    """답변 출력하기"""
    print("챗봇 : ", response)

728x90