쳇봇

딥러닝으로 자신만의 챗봇 만들기

coding art 2024. 6. 24. 08:01
728x90

chatbot macine learning sample code examples

 

이블로그는 다음의 영문 블로그 내용을 바탕으로 작성되었습니다.

 

How To Build Your Own Chatbot Using Deep Learning

https://towardsdatascience.com/how-to-build-your-own-chatbot-using-deep-learning-bb41f970e281

 

 

지능형 챗봇 솔루션을 구현하는 데 사용할 수 있는 강력한 봇 개발 프레임워크, 도구 및 플랫폼이 많이 있지만, 봇 개발 프레임워크나 다른 플랫폼을 사용하는 대신 딥 러닝을 사용하여 처음부터 간단하고 지능적인 챗봇을 개발하는 것은 어떨까? 이 튜토리얼에서는 Keras 딥 러닝을 사용하여 엔드투엔드 도메인별 지능형 챗봇 솔루션을 개발하는 방법을 배울 수 있다.

 

개념
코딩 작업 이전에 먼저 몇 가지 디자인 개념을 이해하자.

딥러닝 기반으로 모델을 개발할 예정이므로 이 모델을 학습시키기 위해서는 데이터가 필요할 것이다.

하지만 여기서는 단순한 챗봇을 만들 예정이므로 대규모 데이터 세트를 수집하거나 다운로드하지는 않을 것이다.

단지 모델을 학습시키기 위해 자체 데이터 세트를 만들면 되는 것이다.

우선 학습용 데이터 세트를 생성하려면 학습시키려는 의도(intent)가 무엇인지 이해해야 한다.

"의도"는 사용자가 챗봇과 상호 작용하려는 의도라든지 또는 챗봇이 특정 사용자로부터 받는 각각의 메시지 뒤에 숨은 의도를 의미합니다.

그러한 의도는 챗봇 솔루션을 개발 중인 도메인에 따라  다를 수 있다. 따라서 작업하려는 도메인과 관련하여 챗봇의 올바른 의도를 이해하는 것이 중요하다.

 

의도를 이해하는 것은 매우 중요한 포인트입니다. 질문에 답하고, 도메인 지식 기반을 검색하고, 다양한 작업을 수행하여 사용자와 대화를 계속하려면 챗봇이 실제로 사용자가 말하는 내용이나 의도하는 바를 이해해야 합니다.

이점이 바로 챗봇이 사용자의 의도를 식별하기 위해 사용자 메시지 이면의 의도를 이해해야 하는 이유입니다.

사용자가 원하는 것이 무엇인지 알고 정확한 응답을 제공하는 것처럼 느낄수 있도록 챗봇이 사용자의 의도를 이해하도록 하려면 어떻게 해야 할까?

여기서 전략은 미리 다양한 의도를 정의하고 해당 의도에 대한 학습 샘플을 준비한다.

해당 학습용 샘플 데이터를 모델을 학습시키기 위한 데이터(X)로 사용하고 의도를 모델 훈련 범주(Y)로 사용하여 챗봇 모델을 훈련시키도록 한다.

 

구현
챗봇 구현에 필요한 Python 패키지 및 라이브러리는 다음과 같습니다.

nltk==3.5 colorma==0.4.3numpy==1.18.5 scikit_learn==0.23.2 Flask==1.1.2

nltk(natural language toolkit)는 Porter stemming 알고리듬을 사용하여 단어별로 변환을 통해 토씨를 처리하여 단어의 원형(root form)을 찾아주는 라이브러리이다.

colorma 는 터미널에서 컬러 텍스트 출력을 지원한다.

scikit는 과학지원 라이브러리이다

.Flask는 간단한 framework를 지원하는 파이썬 라이브러리이다.

 

의도(intent) 정의
몇 가지만의 간단한 의도(intent)와 각 의도에 해당하는 메시지 묶음을 정의하고 각 의도 카테고리에 따라 어떤 응답이 해당하도록 설정해 놓을 것이다. 이러한 데이터 파일을  "intents.json"이라는 JSON 파일로 다음과 같이 생성한다.

간단한 챗봇이므로 매핑해보면 경우의 수가 그다지 많지는 않다. 그냥 재미로 해보자.

 

{"intents": [
  {"tag": "greeting",
  "patterns": ["Hi", "Hey", "Is anyone there?", "Hello", "Hay"],
  "responses": ["Hello", "Hi", "Hi there"]
  },
  {"tag": "goodbye",
  "patterns": ["Bye", "See you later", "Goodbye"],
  "responses": ["See you later", "Have a nice day", "Bye! Come back again"]
  },
  {"tag": "thanks",
  "patterns": ["Thanks", "Thank you", "That's helpful", "Thanks for the help"],
  "responses": ["Happy to help!", "Any time!", "My pleasure", "You're most welcome!"]
  },
  {"tag": "about",
  "patterns": ["Who are you?", "What are you?", "Who you are?" ],
  "responses": ["I.m Joana, your bot assistant", "I'm Joana, an Artificial Intelligent bot"]
  },
  {"tag": "name",
  "patterns": ["what is your name", "what should I call you", "whats your name?"],
  "responses": ["You can call me Joana.", "I'm Joana!", "Just call me as Joana"]
  },
  {"tag": "help",
  "patterns": ["Could you help me?", "give me a hand please", "Can you help?", "What can you do for me?", "I need a support", "I need a help", "support me please"],
  "responses": ["Tell me how can assist you", "Tell me your problem to assist you", "Yes Sure, How can I support you"]
  },
  {"tag": "createaccount",
  "patterns": ["I need to create a new account", "how to open a new account", "I want to create an account", "can you create an account for me", "how to open a new account"],
  "responses": ["You can just easily create a new account from our web site", "Just go to our web site and follow the guidelines to create a new account"]
  },
  {"tag": "complaint",
  "patterns": ["have a complaint", "I want to raise a complaint", "there is a complaint about a service"],
  "responses": ["Please provide us your complaint in order to assist you", "Please mention your complaint, we will reach you and sorry for any inconvenience caused"]
  }
  ]
  }

 

구글 NDRIVE 클라우드에 폴더 연결하기

from google.colab import drive
drive.mount('/content/drive')

 

데이터 준비

tensorflow 와 keras 를 비롯한 필요한 라이브러리들을 불러온다.

import json
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.preprocessing import LabelEncoder

 

json 데이터 파일을 로딩 및 필요한 데이터 추출

변수 "training_sentences" 는 모든 훈련 데이터(각 인텐트 카테고리의 샘플 메시지)를 포함하고 변수 "training_labels" 는 각 훈련 데이터에 해당하는 모든 대상 라벨을 포함합니다.

with open('/content/drive/MyDrive/Chatbot_Keras-main/intents.json') as file:
    data = json.load(file)
   
training_sentences = []
training_labels = []
labels = []
responses = []


for intent in data['intents']:
    for pattern in intent['patterns']:
        training_sentences.append(pattern)
        training_labels.append(intent['tag'])
    responses.append(intent['responses'])
   
    if intent['tag'] not in labels:
        labels.append(intent['tag'])
       
num_classes = len(labels)

 

그 다음 scikit-learn에서 제공하는 “LabelEncoder()” 함수를 사용하여 대상 레이블을 모델이 이해할 수 있는 형식으로 변환한다.

 

bl_encoder = LabelEncoder()
lbl_encoder.fit(training_labels)
training_labels = lbl_encoder.transform(training_labels)

 

다음으로 클라스 "Tokenizer" 를 사용하여 텍스트 데이터를 Corpus를 사용하여 벡터화하고 이를 통해 어휘 크기 (vocabulary size) 를 정의된 숫자까지 제한할 수 있게 된다. 텍스트 전처리 작업에 이 클래스를 사용하면 기본적으로 모든 구두점이 제거되어 텍스트가 공백으로 구분된 단어 시퀀스로 바뀌고 이러한 시퀀스는 토큰 목록으로 분할된다. 그런 다음 색인화되거나 벡터화된다. 추론 시 어휘 부족(토큰)을 처리하기 위해 'out of token' 값인 'oov_token'을 추가할 수도 있다.

vocab_size = 1000
embedding_dim = 16
max_len = 20
oov_token = "<OOV>"


tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_token)
tokenizer.fit_on_texts(training_sentences)
word_index = tokenizer.word_index
sequences = tokenizer.texts_to_sequences(training_sentences)
padded_sequences = pad_sequences(sequences, truncating='post', maxlen=max_len)

 

메소드 "pad_sequences" 는 모든 학습 텍스트 시퀀스를 동일한 크기로 만드는 데 사용된다.

모델 훈련
제안된 모델에 대한 신경망 아키텍처를 정의하고 이를 위해 Keras의 클라스 "Sequential" 딥러닝 모델을 사용한다.

 

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim, input_length=max_len))
model.add(GlobalAveragePooling1D())
model.add(Dense(16, activation='relu'))
model.add(Dense(16, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))

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

model.summary()

 

모델 아키텍쳐는 다음과 같이 주어진다.

 

 

모델을 500 에포크 학습시키도록 한다.

epochs = 500
history = model.fit(padded_sequences, np.array(training_labels), epochs=epochs)

 

학습 후에는 추론 시 사용할 수 있도록 학습에서 얻어진 가중치를 비롯 필요한 데이터 파일을 모두 저장하는 것이 좋다. 학습된 모델, 피팅된 토크나이저 객체 및 피팅된 라벨 인코더 객체를 pickle 명령을 사용하여 저장한다.

# to save the trained model
model.save("chat_model")

import pickle

# to save the fitted tokenizer
with open('/content/drive/MyDrive/Chatbot_Keras-main/tokenizer.pickle', 'wb') as handle:
    pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)
   
# to save the fitted label encoder
with open('/content/drive/MyDrive/Chatbot_Keras-main/label_encoder.pickle', 'wb') as ecn_file:
    pickle.dump(lbl_encoder, ecn_file, protocol=pickle.HIGHEST_PROTOCOL)

 

추론
좋아요!!!! 이제 모델이 어떻게 작동하는지 확인할 차례다. 😊

실제 사용자와 소통할 수 있는 채팅 기능을 구현할 예정이다. 새로운 사용자 메시지가 수신되면 챗봇은 새로운 텍스트 시퀀스와 학습 데이터 간의 유사성(similarity)을 계산한다. 각 카테고리별로 얻은 신뢰도 점수를 고려하여 사용자 메시지를 신뢰도 점수가 가장 높은 인텐트로 분류한다.

import json
import numpy as np
from tensorflow import keras
from sklearn.preprocessing import LabelEncoder

import colorama
colorama.init()
from colorama import Fore, Style, Back

import random
import pickle

with open("/content/drive/MyDrive/Chatbot_Keras-main/intents.json") as file:
    data = json.load(file)


def chat():
    # load trained model
    model = keras.models.load_model('chat_model')

    # load tokenizer object
    with open('/content/drive/MyDrive/Chatbot_Keras-main/tokenizer.pickle', 'rb') as handle:
        tokenizer = pickle.load(handle)

    # load label encoder object
    with open('/content/drive/MyDrive/Chatbot_Keras-main/label_encoder.pickle', 'rb') as enc:
        lbl_encoder = pickle.load(enc)

    # parameters
    max_len = 20
   
    while True:
        print(Fore.LIGHTBLUE_EX + "User: " + Style.RESET_ALL, end="")
        inp = input()
        if inp.lower() == "quit":
            break

        result = model.predict(keras.preprocessing.sequence.pad_sequences(tokenizer.texts_to_sequences([inp]),
                                             truncating='post', maxlen=max_len))
        tag = lbl_encoder.inverse_transform([np.argmax(result)])

        for i in data['intents']:
            if i['tag'] == tag:
                print(Fore.GREEN + "ChatBot:" + Style.RESET_ALL , np.random.choice(i['responses']))

        # print(Fore.GREEN + "ChatBot:" + Style.RESET_ALL,random.choice(responses))

print(Fore.YELLOW + "Start messaging with the bot (type quit to stop)!" + Style.RESET_ALL)
chat()

 

실행시켜 보자.

아래와 같은 콘솔(Console)형 대화창은 파이썬 명령인 input()을 사용하여 키보드로부터 문자열 입력을 받기위해 생성된다.

Start messaging with the bot (type quit to stop)!
User: Hi
1/1 [==============================] - 0s 70ms/step
ChatBot: Hello
User: Who are you
1/1 [==============================] - 0s 22ms/step
ChatBot: I.m Joana, your bot assistant
User: what is your name
1/1 [==============================] - 0s 22ms/step
ChatBot: I'm Joana!
User: I need a help
1/1 [==============================] - 0s 23ms/step
ChatBot: Tell me how can assist you
User: I need to create an account
1/1 [==============================] - 0s 25ms/step
ChatBot: You can just easily create a new account from our web site
User: an also I have a complaint
1/1 [==============================] - 0s 24ms/step
ChatBot: You can call me Joana.
User: thank you
1/1 [==============================] - 0s 22ms/step
ChatBot: My pleasure
User: 

 

알고리듬 별 것 없네요. 

키보드 입력 받아서 tag 데이터와 같은 놈 찾아서 해당하는 pattern 데이터 하나를 매치시키는 알고리듬이다.

그런데 pattern  데이터와 tag 데이터가 정확히 일치하는 것은 아니므로, 토큰화에 의해 벡터화된 pattern 데이터와 역시 벡터화 되어 있는 tag 데이터와의 내적 연산 즉 similarity 연산에 의해 수치값이 가장 높은 경우를 선택하여 처리한다.

실제 인공지능 챗봇과 비교해 보면 처리하는 범위랄까 또는 용량이랄까 그 차이가 아닌가 싶다.

챗봇 알고리듬 적용 사례로서 좀 더 본격적인 한국어 LLM 모델에서 챗봇 데이터 학습 사례를 찾아 볼까 한다.