[AI] 규칙 기반 챗봇 with Python

JM·2022년 11월 14일
0

참고자료
- https://datasciencedojo.com/blog/rule-based-chatbot-in-python/
- https://frhyme.github.io/python-lib/nltk-wordnet/
- https://regexr.com/

목차

  • 챗봇이란?
  • 챗봇 종류
  • Rule-based 챗봇을 만들기 위한 도구들
  • 챗봇 실행 과정
  • 파이썬을 이용한 챗봇 구현

챗봇이란?

출처 : https://namu.wiki/w/챗봇

메신저에서 유저와 소통하는 봇 을 말한다. 단순히 정해진 규칙에 맞춰서 메시지를 입력하면 발화를 출력하는 단순한 챗봇에서부터 상대방의 발화를 분석하여 인공지능에 가까운 발화를 내놓는 챗봇까지 다양한 챗봇들이 있다.

본래, 메시지를 규칙 기반으로 송출해주는 정도에 지나지 않았으나, 2016년에 있었던 구글 딥마인드와 알파고의 등장 이후 챗봇에도 AI 기술을 접목시키려는 시도가 활발하게 이루어지고 있다. 또한, 기업에서도 큰 관심을 보여 각종 은행, 보험회사 등의 상담 챗봇 등이 다수 등장하였다.

Siri나 빅스비같은 음성 인식 비서 서비스도 큰틀에서 따지자면 챗봇을 내장한 형태라 할 수 있다. 내부적으로 챗봇에 음성인식 기술을 접목시켜, 사용자가 말한 내용을 컴퓨터가 이해할 수 있도록 텍스트로 변환하고 이를 질의로 보내 답을 발화시키고 TTS 기술로 음성으로 바꾸는 것. 물론 순수한 챗봇은 음성인식 기술이 없이 사용자가 입력한 텍스트만을 다룬다.


챗봇 종류

Rule-based Chatbots

규칙 기반 챗봇은 미리 저장된 응답 데이터베이스를 사용한다. 사전에 정의된 규칙에 따라 사용자의 입력은 적절한 응답으로 매칭된다. 이 때문에, 규칙 기반 챗봇은 고유한 답변을 생성하지 못한다. 하지만, 응답 데이터베이스의 규모가 충분히 크고 규칙이 잘 정립되었을 경우, 충분히 생산성있고 유용하게 사용될 수 있다.

규칙 기반 챗봇의 간단한 형태는 사용자 입력과 응답에 대해 1대ㅐ1 테이블 구조를 갖고 있다. 이 봇은 매우 제한적으로 행동하며, 오직 사전에 정의된 응답만 답변할 수 있다는 한계점을 갖는다.

AI-based chatbots

최근 머신러닝이 발전하면서, 챗봇을 구현하는 새로운 접근법이 등장하였다. 인공지능을 활용하여 매우 정밀하고 직관적인 챗봇을 구현할 수 있게 되었다. 규칙 기반 챗봇과는 다르게, AI 기반 챗봇은 복잡한 머신러닝 모델을 기반으로 하며 스스로 학습하는 것이 가능하다.



Rule-based 챗봇을 만들기 위한 도구들

Natural Language Toolkit(NLTK)

NLTK는 인간의 언어 데이터를 쉽게 처리하도록 도와주는 파이썬 라이브러리이다. 즉, 자연어 처리를 도와주는 도구이다.

Regular Expression(RegEx) in Python

정규 표현식은 단어 or 문장 등에서 특정한 패턴을 검색하고 찾아주는데 도움을 주는 식이다. 해당 식은 문자들의 특정한 나열로 이뤄진다. 정규 표현식은 UNIX에서 텍스트를 검색하고 매칭하는데 사용된다. 파이썬에서 re 라이브러리를 사용한다.



챗봇 실행 과정

  1. 사용자 입력
  2. 키워드를 중심으로 입력된 데이터를 탐색
  3. 입력을 키워드를 기반으로 한 특정 의도 와 매칭
  4. 매칭된 의도 에 해당하는 응답을 선택
  5. 응답을 출력

과정 3 에서 키워드를 특정 의도 와 매칭하기 위해 NLTK 를 사용한다. NLTK 에서 제공하는 wordnet 을 활용하여 특정 의도(Intent) 에 해당하는 동의어들을 알아낸다. 사용자는 챗봇의 규칙을 알지 못하기 때문에, 임의의 단어들을 조합하여 입력을 수행할 것이다. 따라서 챗봇은 사용자의 입력들에서 키워드를 추출한 이후, 해당 키워드들이 어떤 특정 의도(Intent) 와 가장 연관있는 지 알아내야 한다. (이 부분이 챗봇의 성능을 좌우할 것 같다.)

챗봇 실행 과정 중, 가장 핵심적으로 개발해야 하는 부분은 과정 3 에서 사용될 추론 엔진 이다. 특정 의도(Intent) 와 매칭되는 응답을 미리 정의하는 것은 엑셀로 표현될 만큼 간단하다. 하지만, 추론 엔진 은 임의의 입력값이 어떤 의도 와 매칭되는 지 알고리즘을 개발해야 하기 때문에 심의를 기울여야 한다.



파이썬을 이용한 챗봇 구현

간단한 챗봇을 구현하기 위해 은행 서비스 를 가정하겠다. 은행 서비스에서 제공할 IntentResponse는 다음과 같다.

IntentResponse
GreetHi! How can I help you?
TimingsWe are open from 9AM to 5PM.

챗봇 구현 과정은 다음과 같다.

  1. Importing Dependencies
  2. Building the Keyword List
  3. Building a dictionary of Intents
  4. Defining a dictionary of responses
  5. Matching Intents and Generating Responses

Importing Dependencies

import re
# import nltk
# nltk.download()
from nltk.corpus import wordnet

wordnet은 단어들 간의 의미론적인 관계를 정의한 어휘 데이터베이스이다. 이후 wordnet 을 활용하여 Intent 로 정의해 놓은 키워드들의 동의어들을 추출한 이후, 해당 동의어들을 value로 하는 a dictionary of synonyms 를 생성할 것이다. 이를 통해 직접 사용자가 사용할 가능성이 있는 단어들을 사전에 정의할 필요없이, 동의어 확장이 가능하다.

conda 환경 사용 시 nltk의 특정 부분이 설치되지 않아 프로그램이 실행되지 않는 현상을 발견하였다. 이를 해결하고자, 소스코드 내에서 명시적으로 nltk를 다운로드하였다.


Building the Keyword List

# Building a list of keywords
list_words = ['hello','timings']
list_syn = {}
for word in list_words:
    sysnonyms = []
    for syn in wordnet.synsets(word):
        for lem in syn.lemmas():
            # Remove any special charaters from sysnonym strings
            lem_name = re.sub('[^a-zA-Z0-9 \n\.]',' ',lem.name())
            sysnonyms.append(lem_name)
    list_syn[word]=set(sysnonyms)

Keyword List 는 챗봇이 사용자의 입력을 받은 이후 탐색할 대상이다. 많은 Keyword 를 추가할 수록, 사용자의 입력에 대해 적절한 응답을 수행할 가능성이 많아지기 때문에, 결과적으로 챗봇이 성능이 올라갈 것이다. 앞에서 말했듯이, wordnet 을 사용하여 Intent 의 동의어들을 dictionary 구조에 저장할 것이다. 소스코드에서 list_syn 이 저장될 변수이다.

위 소스코드에서 각 함수들과 단어들이 무엇을 의미하는지 확인해보자.

  • synset : wordnet 을 탐색하기 위해 NLTK 에서 제공하는 인터페이스이다. synset 인스턴스는 동의의들을 그룹화한 것이다.
    Synset instance
    - name : 인스턴스 이름(hello.n.01)
    - meaning : 기준이 되는 동의어(an expression of greeting)
    - example : 예시(['every morning they exchanged polite hellos'])
  • wordnet.synsets(word) : list of synset 을 반환한다. 이 리스트는 비어있을 수 있으며, 여러 item들을 가지고 있을 수도 있다.
  • syn.lemmas() : synset에 포함되어 있는 단어 기본형 리스트(lemma는 단어의 기본형태 정도로 표현할 수 있을 것 같다)

list_syn에 저장된 데이터

hello
{'hello', 'howdy', 'hi', 'hullo', 'how do you do'}
timings
{'time', 'clock', 'timing'}


Building a dictionary of Intents

# Building dictionary of Intents & Keywords
keywords = {}
keywords_dict = {}

# Defining a new key in the keywords dictionary
keywords['greet'] = []
# Populating the values in the keywords dictionary with synonyms of keywords
# formatted with RegEx metacharacters
for synonym in list(list_syn['hello']):
    keywords['greet'].append('.*\\b' + synonym + '\\b.*')

# Defining a new key in the keywords dictionary
keywords['timings'] = []
for synonym in list(list_syn['timings']):
    keywords['timings'].append('.*\\b' + synonym + '\\b.*')

for intent, keys in keywords.items():
    # Joining the values in the keywords dictionary with the OR(|) operator updating
    # them in keywords_dict dictionary
    keywords_dict[intent]=re.compile('|'.join(keys))

앞서 지정한 list_syn(키워드의 동의어 집합 리스트)을 Intent 와 매칭 시키는 dictionary 를 생성해야 한다. list_syn 은 사용자가 입력할 가능성이 있는 키워드를 정의해놓은 데이터라면, keywords_dict 는 해당 키워드들이 실제 Intent 와 매칭될 수 있도록 정의하는 자료구조이다.

  • \b : Word boundary. Matches a word boundary position between a word character and non-word character or position (Start / end of string)https://regexr.com/
  • | : Alternation. Acts like a boolean OR. Matches the expression before or after the |

Defining a dictionary of responses

# Building a dictionary of responses
responses={
    'greet' : 'Hello! How can I help you?',
    'timings'  : 'We are open from 9AM to 5PM, Monday to Friday. We are closed on weekends and public holidays',
    'fallback' : 'I dont quite understand. Could you repeat that?'
}

Matching Intents and Generating Responses

print("Welcome to MyBank. How may I help you?")
# While loop to run the chatbot indefinetely
while True:
    # Takes the user input and converts all characters to lowercase
    user_input = input().lower()
    # Defining the Chatbot's exit condition
    if user_input == 'quit':
        print('Thank you for visiting.')
        break
    matched_intent = None
    for intent, pattern in keywords_dict.items():
        # Using the regular expression search function to look for keywords in user input
        if re.search(pattern, user_input):
            # if a keyword matches, select the corresponding intent from the keywords_dict dictionary
            matched_intent = intent
    # The fallback intent is selected by defualt
    key = 'fallback'
    if matched_intent in responses:
        # If a keyword matches, the fallback intent is replaced by
        # the matched intent as the key for the responses dictionary
        key = matched_intent
    # The chatbot prints the response that matches the seleted intent
    print(responses[key])



전체 소스코드

import re
import nltk
# nltk.download()
from nltk.corpus import wordnet

# Building a list of keywords
list_words = ['hello','timings']
list_syn = {}
for word in list_words:
    sysnonyms = []
    for syn in wordnet.synsets(word):
        for lem in syn.lemmas():
            # Remove any special charaters from sysnonym strings
            lem_name = re.sub('[^a-zA-Z0-9 \n\.]',' ',lem.name())
            sysnonyms.append(lem_name)
    list_syn[word]=set(sysnonyms)

# Building dictionary of Intents & Keywords
keywords = {}
keywords_dict = {}
# Defining a new key in the keywords dictionary
keywords['greet'] = []
# Populating the values in the keywords dictionary with synonyms of keywords
# formatted with RegEx metacharacters
for synonym in list(list_syn['hello']):
    keywords['greet'].append('.*\\b' + synonym + '\\b.*')

# Defining a new key in the keywords dictionary
keywords['timings'] = []
# Populating the values in the keywords dictionary with synonyms of keywords
# formatted with RegEx metacharacters
for synonym in list(list_syn['timings']):
    keywords['timings'].append('.*\\b' + synonym + '\\b.*')

for intent, keys in keywords.items():
    # Joining the values in the keywords dictionary with the OR(|) operator updating
    # them in keywords_dict dictionary
    keywords_dict[intent]=re.compile('|'.join(keys))

print(keywords)
print(keywords_dict)

# Building a dictionary of responses
responses={
    'greet' : 'Hello! How can I help you?',
    'timings'  : 'We are open from 9AM to 5PM, Monday to Friday. We are closed on weekends and public holidays',
    'fallback' : 'I dont quite understand. Could you repeat that?'
}

print("Welcome to MyBank. How may I help you?")
# While loop to run the chatbot indefinetely
while True:
    # Takes the user input and converts all characters to lowercase
    user_input = input().lower()
    # Defining the Chatbot's exit condition
    if user_input == 'quit':
        print('Thank you for visiting.')
        break
    matched_intent = None
    for intent, pattern in keywords_dict.items():
        # Using the regular expression search function to look for keywords in user input
        if re.search(pattern, user_input):
            # if a keyword matches, select the corresponding intent from the keywords_dict dictionary
            matched_intent = intent
    # The fallback intent is selected by defualt
    key = 'fallback'
    if matched_intent in responses:
        # If a keyword matches, the fallback intent is replaced by
        # the matched intent as the key for the responses dictionary
        key = matched_intent
    # The chatbot prints the response that matches the seleted intent
    print(responses[key])
profile
나는 사는데로 생각하지 않고, 생각하는데로 살겠다

0개의 댓글