본문 바로가기

인공지능/딥러닝

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

~ 목차 ~

라이브러리 정의 🐸

 

import tensorflow as tf

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

 

 

 

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

 

questions = [
    "전기요금 어때?",
    "안녕하세요",
    "너 이름이 뭐니?",
    "어떻게 지내세요?",
    "프랑스의 수도는 어디인가요?",
    "뭐 해?",
    "오늘 날씨 어때?",
    "좋아하는 음식은 뭐에요?",
    "무슨 일을 좋아해요?",
    "가장 좋아하는 색깔은 무엇인가요?",
    "가장 기억에 남는 여행은 어디에요?",
    "주말에는 뭐하고 시간을 보내나요?",
    "좋아하는 음악 장르가 있나요?",
    "가장 좋아하는 동물은 무엇인가요?",
    "가장 감명 깊게 본 영화는 무엇인가요?",
    "뭐 해?",
    "오늘 날씨 어때?",
    "좋아하는 음식은 뭐에요?",
    "무슨 일을 좋아해요?",
    "가장 좋아하는 색깔은 무엇인가요?",
    "가장 기억에 남는 여행은 어디에요?",
    "주말에는 뭐하고 시간을 보내나요?",
    "좋아하는 음악 장르가 있나요?",
    "가장 좋아하는 동물은 무엇인가요?",
    "전기요금 어때?",
    "안녕하세요",
    "너 이름이 뭐니?",
    "어떻게 지내세요?",
    "프랑스의 수도는 어디인가요?",
    "뭐 해?",
    "오늘 날씨 어때?",
    "좋아하는 음식은 뭐에요?",
    "무슨 일을 좋아해요?",
    "가장 좋아하는 색깔은 무엇인가요?",
    "가장 기억에 남는 여행은 어디에요?",
    "주말에는 뭐하고 시간을 보내나요?",
    "좋아하는 음악 장르가 있나요?",
    "가장 좋아하는 동물은 무엇인가요?",
    "가장 감명 깊게 본 영화는 무엇인가요?",
    "뭐 해?",
    "오늘 날씨 어때?",
    "좋아하는 음식은 뭐에요?",
    "무슨 일을 좋아해요?",
    "가장 좋아하는 색깔은 무엇인가요?",
    "가장 기억에 남는 여행은 어디에요?",
    "주말에는 뭐하고 시간을 보내나요?",
    "좋아하는 음악 장르가 있나요?",
    "가장 좋아하는 동물은 무엇인가요?",
    "가장 감명 깊게 본 영화는 '인셉션'이에요."
]

answers = [
    "전기요금이 계속 인상되고 있어요!",
    "안녕하세요! 반가워요^^",
    "제 이름은 챗봇이에요.",
    "저는 잘 지내고 있어요, 감사합니다!",
    "프랑스의 수도는 파리에요.",
    "일하고 있어요.",
    "오늘은 맑아요.",
    "제가 좋아하는 음식은 피자에요.",
    "일을 하는 것을 좋아해요.",
    "제가 가장 좋아하는 색깔은 파란색이에요.",
    "가장 기억에 남는 여행은 파리에 다녀온 것이에요.",
    "주말에는 쉬면서 책을 읽거나 친구들과 만나요.",
    "저는 다양한 음악을 즐겨듣습니다.",
    "가장 좋아하는 동물은 강아지에요.",
    "가장 감명 깊게 본 영화는 '인셉션'이에요.",
    "일하고 있어요.",
    "오늘은 맑아요.",
    "제가 좋아하는 음식은 피자에요.",
    "일을 하는 것을 좋아해요.",
    "제가 가장 좋아하는 색깔은 파란색이에요.",
    "가장 기억에 남는 여행은 파리에 다녀온 것이에요.",
    "주말에는 쉬면서 책을 읽거나 친구들과 만나요.",
    "저는 다양한 음악을 즐겨듣습니다.",
    "가장 좋아하는 동물은 강아지에요.",
    "전기요금이 계속 인상되고 있어요!",
    "안녕하세요! 반가워요^^",
    "제 이름은 챗봇이에요.",
    "저는 잘 지내고 있어요, 감사합니다!",
    "프랑스의 수도는 파리에요.",
    "일하고 있어요.",
    "오늘은 맑아요.",
    "제가 좋아하는 음식은 피자에요.",
    "일을 하는 것을 좋아해요.",
    "제가 가장 좋아하는 색깔은 파란색이에요.",
    "가장 기억에 남는 여행은 파리에 다녀온 것이에요.",
    "주말에는 쉬면서 책을 읽거나 친구들과 만나요.",
    "저는 다양한 음악을 즐겨듣습니다.",
    "가장 좋아하는 동물은 강아지에요.",
    "가장 감명 깊게 본 영화는 '인셉션'이에요.",
    "일하고 있어요.",
    "오늘은 맑아요.",
    "제가 좋아하는 음식은 피자에요.",
    "일을 하는 것을 좋아해요.",
    "제가 가장 좋아하는 색깔은 파란색이에요.",
    "가장 기억에 남는 여행은 파리에 다녀온 것이에요.",
    "주말에는 쉬면서 책을 읽거나 친구들과 만나요.",
    "저는 다양한 음악을 즐겨듣습니다.",
    "가장 좋아하는 동물은 강아지에요.",
    "가장 감명 깊게 본 영화는 '인셉션'이에요."
]

 

 

 

단어사전 만들기🐸

 

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

 

 

tokenizer = Tokenizer()
tokenizer

 

len(questions), len(answers)

(49, 49)

 

 

 

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

 

 

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

 

tokenizer = Tokenizer()
tokenizer

 

 

 

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

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

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

 

tokenizer.fit_on_texts(questions + answers)

 

 

 

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

word_index : 단어별 부여된 인덱스 확인(많이 나오는 빈도 순으로 부여됨)

 

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

 

단어 갯수 :  74
{'가장': 1,
 '좋아하는': 2,
 '무엇인가요': 3,
 '음식은': 4,
 '일을': 5,
 '좋아해요': 6,
 '색깔은': 7,
 '기억에': 8,
 '남는': 9,
 '여행은': 10,
 '주말에는': 11,
 '동물은': 12,
 '있어요': 13,
 '제가': 14,
 '어때': 15,
 '감명': 16,
 '깊게': 17,
 '본': 18,
 '영화는': 19,
 '저는': 20,
 '안녕하세요': 21,
 '프랑스의': 22,
 '수도는': 23,
 '뭐': 24,
 '해': 25,
 '오늘': 26,
 '날씨': 27,
 '뭐에요': 28,
 '무슨': 29,
 '어디에요': 30,
 '뭐하고': 31,
 '시간을': 32,
 '보내나요': 33,
 '음악': 34,
 '장르가': 35,
 '있나요': 36,
 "'인셉션'이에요": 37,
 '일하고': 38,
 '오늘은': 39,
 '맑아요': 40,
 '피자에요': 41,
 '하는': 42,
 '것을': 43,
 '파란색이에요': 44,
 '파리에': 45,
 '다녀온': 46,
 '것이에요': 47,
 '쉬면서': 48,
 '책을': 49,
 '읽거나': 50,
 '친구들과': 51,
 '만나요': 52,
 '다양한': 53,
 '음악을': 54,
 '즐겨듣습니다': 55,
 '강아지에요': 56,
 '전기요금': 57,
 '너': 58,
 '이름이': 59,
 '뭐니': 60,
 '어떻게': 61,
 '지내세요': 62,
 '어디인가요': 63,
 '전기요금이': 64,
 '계속': 65,
 '인상되고': 66,
 '반가워요': 67,
 '제': 68,
 '이름은': 69,
 '챗봇이에요': 70,
 '잘': 71,
 '지내고': 72,
 '감사합니다': 73,
 '파리에요': 74}

 

 

 

 

신경망에서 사용할 단어의 크기 설정

 : 하나 더 크게 설정

why? range범위 설정할때 +1 해서 적는 것과 동일한 이유

 

vocab_size = len(tokenizer.word_index) + 1
vocab_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],
 [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],
 [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],
 [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],
 [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, 37]]

 

 

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

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

 

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

 

49

 

[[64, 65, 66, 13],
 [21, 67],
 [68, 69, 70],
 [20, 71, 72, 13, 73],
 [22, 23, 74],
 [38, 13],
 [39, 40],
 [14, 2, 4, 41],
 [5, 42, 43, 6],
 [14, 1, 2, 7, 44],
 [1, 8, 9, 10, 45, 46, 47],
 [11, 48, 49, 50, 51, 52],
 [20, 53, 54, 55],
 [1, 2, 12, 56],
 [1, 16, 17, 18, 19, 37],
 [38, 13],
 [39, 40],
 [14, 2, 4, 41],
 [5, 42, 43, 6],
 [14, 1, 2, 7, 44],
 [1, 8, 9, 10, 45, 46, 47],
 [11, 48, 49, 50, 51, 52],
 [20, 53, 54, 55],
 [1, 2, 12, 56],
 [64, 65, 66, 13],
 [21, 67],
 [68, 69, 70],
 [20, 71, 72, 13, 73],
 [22, 23, 74],
 [38, 13],
 [39, 40],
 [14, 2, 4, 41],
 [5, 42, 43, 6],
 [14, 1, 2, 7, 44],
 [1, 8, 9, 10, 45, 46, 47],
 [11, 48, 49, 50, 51, 52],
 [20, 53, 54, 55],
 [1, 2, 12, 56],
 [1, 16, 17, 18, 19, 37],
 [38, 13],
 [39, 40],
 [14, 2, 4, 41],
 [5, 42, 43, 6],
 [14, 1, 2, 7, 44],
 [1, 8, 9, 10, 45, 46, 47],
 [11, 48, 49, 50, 51, 52],
 [20, 53, 54, 55],
 [1, 2, 12, 56],
 [1, 16, 17, 18, 19, 37]]

 

 

 

정규화 (단어길이 통일) 🐸

 

 

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

- 잘라낼 max 길이 기준 : 문장들 중 최대 max 단어길이 기준으로 설정해줌.
  (maxlen을 넣지 않으면, 문장들의 길이가 가장 긴 것을 기준으로 합니다. 
   디폴트값으로 알아서 가장 긴 갯수를 찾아냅니다.)
- 채우기는 뒤쪽

 

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

 

49
 
array([[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],
       [11, 31, 32, 33,  0,  0],
       [ 2, 34, 35, 36,  0,  0],
       [ 1,  2, 12,  3,  0,  0],
       [ 1, 16, 17, 18, 19,  3],
       [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],
       [11, 31, 32, 33,  0,  0],
       [ 2, 34, 35, 36,  0,  0],
       [ 1,  2, 12,  3,  0,  0],
       [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],
       [11, 31, 32, 33,  0,  0],
       [ 2, 34, 35, 36,  0,  0],
       [ 1,  2, 12,  3,  0,  0],
       [ 1, 16, 17, 18, 19,  3],
       [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],
       [11, 31, 32, 33,  0,  0],
       [ 2, 34, 35, 36,  0,  0],
       [ 1,  2, 12,  3,  0,  0],
       [ 1, 16, 17, 18, 19, 37]])
 

 

 

답변 데이터 단어의 길이 통일 시키기(정규화)

- 잘라낼 max 길이 기준 : 문장들 중 최대 max 단어길이 기준으로 설정해줌.
  (maxlen을 넣지 않으면, 문장들의 길이가 가장 긴 것을 기준으로 합니다. 
   디폴트값으로 알아서 가장 긴 갯수를 찾아냅니다.)
- 채우기는 뒤쪽

 

answers_padded = pad_sequences(answers_sequences, padding="post")
print(len(answers_padded))
answers_padded

 

49
array([[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],
       [14,  1,  2,  7, 44,  0,  0],
       [ 1,  8,  9, 10, 45, 46, 47],
       [11, 48, 49, 50, 51, 52,  0],
       [20, 53, 54, 55,  0,  0,  0],
       [ 1,  2, 12, 56,  0,  0,  0],
       [ 1, 16, 17, 18, 19, 37,  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],
       [14,  1,  2,  7, 44,  0,  0],
       [ 1,  8,  9, 10, 45, 46, 47],
       [11, 48, 49, 50, 51, 52,  0],
       [20, 53, 54, 55,  0,  0,  0],
       [ 1,  2, 12, 56,  0,  0,  0],
       [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],
       [14,  1,  2,  7, 44,  0,  0],
       [ 1,  8,  9, 10, 45, 46, 47],
       [11, 48, 49, 50, 51, 52,  0],
       [20, 53, 54, 55,  0,  0,  0],
       [ 1,  2, 12, 56,  0,  0,  0],
       [ 1, 16, 17, 18, 19, 37,  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],
       [14,  1,  2,  7, 44,  0,  0],
       [ 1,  8,  9, 10, 45, 46, 47],
       [11, 48, 49, 50, 51, 52,  0],
       [20, 53, 54, 55,  0,  0,  0],
       [ 1,  2, 12, 56,  0,  0,  0],
       [ 1, 16, 17, 18, 19, 37,  0]])

 

 

 

모델 처리 🐸

 

"""모델 생성하기"""

model = tf.keras.Sequential()
model

 

 

"""차원 확인하기"""

questions_padded.shape[1], answers_padded.shape[1]

(6, 7)

 

 

answers_padded

 

 

 

 

계층 추가하기 -  RepeatVector ,   TimeDistributed 🐸

 

 

계층 생성하기

- 단어임베딩(입력계층) : 출력 64
- simple RNN 사용 : 출력 128, 활성화합수 렐루

- RepeatVector() : 입력을 복제하는 전처리 계층,
                             텍스트에 대한 Encoder(암호화하는것)와 Decoder(암호를 푸는것)를 담당함

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

 

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.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

 

 

모델 훈련하기 🐸

 

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

 

 

 

질문/답변 테스트 🐸

 

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

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

"""단어의 길이를 훈련에서 사용한 길이로 통일시키기(정규화)"""
padded_seq = pad_sequences(input_seq, padding="post", maxlen=questions_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]
'제 이름은 챗봇이에요'

 

 

 

 

답변 함수 만들기 🐸

 

def get_Generate_Response(modeltokenizeruser_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("사용자 : ")

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

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

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

 

 

챗봇

 

 

728x90