머신러닝&딥러닝

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

s2h15 2024. 1. 8. 14:02
728x90

 

 

◇ 라이브러리 정의

# 라이브러리 정의
import tensorflow as tf
# 단어사전만들기
from tensorflow.keras.preprosing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

 

 

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

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

questions = [
    "전기요금 어때?",
    "안녕하세요",
    ...
]

answers = [
    "전기요금이 계속 인상되고 있어요!",
    "안녕하세요! 반가워요^^",
    ...
]
len(questions) , len(answers) 
(49, 49)

▶ 총 49개의 질문과 답변 데이터를 불러들임.

데이터가 많을 수록 정확도가 높아짐

 

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

 

① Tokenizer 생성

tokenizer = Tokenizer()

 

 

② 단어사전 구축하기

- 질문과 답변을 하나의 데이터로 합쳐서 전체 텍스트 데이터로 사용

- 이를 기반으로 토크나이저가 단어 사전을 구축하게됨

- 각 단어들은 순차적인 인덱스번호를 부여받게됨

- 토큰나이저가 인식하는 문장 내에서 단어의 의미 :

        연결되어 있으면 (띄어쓰기가 없음) 하나의 단어로 인지 

 

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

 

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

tokenizer.fit_on_texts(questions + answers)

 

 

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

#단어별 부여된 인덱스 확인하기
print("단어 개수 : ",len(tokenizer.word_index))
tokenizer.word_index
단어 개수 :  74

{'가장': 1,
 '좋아하는': 2,
...
 '파리에요': 74}

▶ 단어에 인덱스 번호가 1번부터 부여됨

 

③ 질문 데이터 단어사전의 인덱스로 변환하기

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

vocab_size = len(tokenizer.word_index) + 1
vocab_size
75

 

 

질문에 대한 텍스트 문장을 단어사전의 인덱스로 변환하기

 

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

questions_sequences = tokenizer.texts_to_sequences(questions)
print(questions_sequences)
questions_sequences
[[57, 15], [21], [58, 59, 60], [61, 62], [22, 23, 63], [24, 25], [26, 27, 15], [2, 4, 28], [29, 5, 6], [1, 2, 7, 3], [1, 8, 9, 10, 30], [11, 31, 32, 33], [2, 34, 35, 36], [1, 2, 12, 3], [1, 16, 17, 18, 19, 3] ....

 

 

질문 데이터 단어의 길이를 통일시키는 정규화하기

 

- 잘라낼 max 길이 기준 : 문장들 중 최대 max 단어 길이 기준으로

- maxlen을 넣지 않으면 문장들의 길이가 가장 긴것을 기준으로 한다.

   ( 디폴트로 알아서 가장 긴 개수를 찾아낸다 )

- 채우기는 뒤쪽

questions_padded = pad_sequences(questions_sequences,
                                    padding='post')
print(questions_padded)
questions_padded
[[57 15 0 0 0 0] [21 0 0 0 0 0] [58 59 60 0 0 0] [61 62 0 0 0 0] [22 23 63 0 0 0] [24 25 0 0 0 0] [26 27 15 0 0 0] [ 2 4 28 0 0 0] [29 5 6 0 0 0] [ 1 2 7 3 0 0] [ 1 8 9 10 30 0] ....

 

 

⑤ 답변 데이터 단어사전의 인덱스로 변환하기 & 단어 길이 통일시키기

# 답변데이터
answer_sequences = tokenizer.texts_to_sequences(answers)
answers_padded = pad_sequences(answer_sequences)
print(answers_padded)
[[64 65 66 13 0 0 0] [21 67 0 0 0 0 0] [68 69 70 0 0 0 0] [20 71 72 13 73 0 0] [22 23 74 0 0 0 0] [38 13 0 0 0 0 0] [39 40 0 0 0 0 0] [14 2 4 41 0 0 0] [ 5 42 43 6 0 0 0] ...

 

◇ 모델 처리

① 모델 생성 및 계층 추가

계층추가

- 단어 임베딩(입력계층으로 사용) : 출력 64

- simple RNN 사용 : 출력 128, 활성화함수 relu

- RepeatVector : 입력을 복제하는 전처리 계층

                            (텍스트에 대한 Encoder와 Decoder를 담당함)

- TimeDistributed : 각 타임별로 동일한 가중치를 적용하도록 처리

                               - 전체 시퀀스들의 이전/다음 인덱스 처리 값을 가지고 있다고 보면됨

                               - 예측에 주로 사용되는 계층

                               - 최종 출력 계층을 감싸고 있음

# 모델 생성하기
model = tf.keras.Sequential()
# 계층 추가하기
model.add(tf.keras.layers.Embedding(vocab_size, 64,
                                    input_length = questions_padded.shape[1]))
model.add(tf.keras.layers.SimpleRNN(128,activation='relu'))
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(vocab_size,
                                                                activation='softmax')))
model.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_1 (Embedding)     (None, 6, 64)             4800      
                                                                 
 simple_rnn_2 (SimpleRNN)    (None, 128)               24704     
                                                                 
 repeat_vector_1 (RepeatVect  (None, 7, 128)           0         
 or)                                                             
                                                                 
 simple_rnn_3 (SimpleRNN)    (None, 7, 128)            32896     
                                                                 
 time_distributed_1 (TimeDis  (None, 7, 75)            9675      
 tributed)                                                       
                                                                 
=================================================================
Total params: 72,075
Trainable params: 72,075
Non-trainable params: 0
_________________________________________________________________

 

 

② 모델 설정

 

- 숫자값, 다중분류이기 때문에 sparse_categorical_crossentropy 적용

 

# 모델 설정
model.compile(optimizer='adam',
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])

 

③ 모델 훈련하기

model.fit(questions_padded,answers_padded,epochs=90,
            batch_size=64,verbose = 1)
Epoch 90/90
1/1 [==============================] - 0s 5ms/step - loss: 0.1117 - accuracy: 0.9767

▶ epoch = 100으로 두었을때 훈련 정확도가 1 이 나와서 90으로 재설정하였다.

 

 

 

◇ 질문/답변 테스트

 

 새로운 질문 텍스트를 단어사전의 인덱스 번호로 변환하기

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

 

 

② 단어의 길이를 훈련에서 사용한 길이로 통일시키기

padded_seq = pad_sequences(input_seq,padding='post',
                            maxlen=questions_padded.shape[1])
print(padded_seq)
[[21 0 0 0 0 0]]

 

 

③ 예측하기 : 새로운 질문에 대한 답변 추출하기

pred = model.predict(padded_seq)

 

▶ len(pred[0]) = 7 , len(pred[0][0]) = 75

answers 문자열의 7개 단어 각각의 75개의 말뭉치에 대한 확률 나타남

 

④ 답변으로 가장 확률이 높은 값 추출하기

pred_index = tf.argmax(pred,axis=-1).numpy()[0]
len(pred_index), pred_index
(7, array([21, 67, 0, 0, 0, 0, 0], dtype=int64))

 

 

⑤ 시퀀스에 해당하는 텍스트 추출하기

 

- 인덱스에 해당하는 실제 텍스트 추출하기

response = tokenizer.sequences_to_texts([pred_index])[0]
response
'안녕하세요 반가워요'

 

질문/답변 테스트해보기(2)

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

 

 

◇ 질문/답변 무한반복 함수 생성 및 호출

 

○ 함수 생성

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

 

 

○ 함수 호출

while True:
    user_input = input()
    if user_input == "stop":
        print("---------채팅 종료---------")
        break
    
    response = get_Generate_Response(user_input)
    print(response)
안녕하세요 반가워요
---------채팅 종료---------
728x90