[챗봇] Python으로 Rule-Based(규칙 기반) 챗봇 만들기

    챗봇을 만드는 방법은 상당히 많이 있으나 가장 고전적이면서 가장 많이 활용되고, 기업들이 가장 많이 활용하고 있는 Rule-Based 챗봇을 만들어보도록 한다. 사실 룰기반 챗봇은 다른 말로 표현하는 것이 워낙 많기도 하다. 기업에서 상용적으로 사용하는 Rule-Based는 대표적으로 Intent(의도)와 Entity(개체)를 관리해서 하는 경우가 많고 이들도 큰 틀로 Rule-Based라 할 수 있다. 

     

    파이썬으로 룰기반의 간단한 챗봇만들기

     

    Rule-Based 설계하기

    Rule-Based가 뭔지 모르는 분들에게 간단하게 설명을 한다면 규칙을 직접 설계해서 어떤 말이 만들어진 규칙에 해당한다면 Answer를 하는 것이다. 여기서 설명할 Rule-Based는 너무나 방대한 챗봇을 만드는 것이 아니라 간단하게 만들어볼 것이며 Entity와 Intent를 분리하지 않는다.

     

    테스트 데이터

    Rule 데이터 Excel 화면

     

    request는 우선 올 대화를 예상해서 만들어 본 것이다. 사실 프로그램에서 request는 사용되지 않지만 rule을 설계하기 위해서 참고하는 레퍼런스라고 이해하면 된다. response는 챗봇이 실질적으로 응답을 하는 내용이다. 

     

    이 프로그램에서 사용되는 라이브러리는 pandas만 사용될 예정이며, 원래 자연어처리에 관련이 있는 챗봇은 konlpy와 같은 한국어 형태소 분석기를 사용해서 더욱 정확한 챗봇을 만들어야 하지만 konlpy없이 만들어본다. 판다스(pandas)가 없다면 pip install pandas로 설치를 진행한다

     

    (saibog) C:\project\discord>pip install pandas
    Collecting pandas
      Downloading pandas-1.2.0-cp38-cp38-win_amd64.whl (9.3 MB)
         |████████████████████████████████| 9.3 MB 3.3 MB/s
    Requirement already satisfied: pytz>=2017.3 in c:\anaconda3\envs\saibog\lib\site-packages (from pandas) (2020.5)
    Requirement already satisfied: numpy>=1.16.5 in c:\anaconda3\envs\saibog\lib\site-packages (from pandas) (1.18.5)
    Collecting python-dateutil>=2.7.3
      Using cached python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
    Requirement already satisfied: six>=1.5 in c:\anaconda3\envs\saibog\lib\site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)
    Installing collected packages: python-dateutil, pandas
    Successfully installed pandas-1.2.0 python-dateutil-2.8.1
    

     

    import pandas as pd
    
    chatbot_data = pd.read_excel("c:/project/steel/chatbot_data.xlsx")
    print(chatbot_data)
            request       rule                 response
    0       너의 이름은?       너|이름             저는 자비스라고 합니다
    1     네 이름을 말해줘    네|이름|말해             저는 자비스라고 합니다
    2     네 이름이 뭐니?     네|이름|뭐             저는 자비스라고 합니다
    3       놀러가고 싶다       놀러|싶           가끔씩 휴식하는 것도 좋죠
    4   느그 아부지 뭐하시노  느그|아부지|뭐하             우리 아부지 건달입니다
    5    말귀좀 알아듣는다?   말귀|알아듣는다       다행이네요. 열심히 배우고 있어요
    6           맛저해        맛저해                   맛저하세요~
    7           맛점해        맛점해                   맛점하세요~
    8       메리크리스마스      메리|크리               메리~ 크리스마스~
    9     면접에서 떨어졌어      면접|떨어         다음엔 꼭 붙을 수 있을거에요
    10  무슨 말인지 모르겠어    무슨|말|모르          죄송해요 학습이 덜 됐나봐요
    11          뭐해?         뭐해                   그냥 있어요
    12  아 월요일이 다가온다     월요일|다가               월요병이 심한가봐요
    13           안녕         안녕                    안녕하세요
    14      영화 추천해줘      영화|추천  아이언맨 시리즈와 어벤져스 시리즈를 보세요

    판다스에서 제공하는 read_excel 함수를 실행시켜 chatbot_data 변수에 저장한다.

     

     

    rule 저장

    # rule의 데이터를 split하여 list형태로 변환 후, index값과 함께 dictionary 형태로 저장
    chat_dic = {}
    row = 0
    for rule in chatbot_data['rule']:
        chat_dic[row] = rule.split('|')
        row += 1
    
    print(chat_dic)

     

    챗봇의 rule을 split한 후, 순서값과 함께 딕셔너리 형태로 저장한다

     

    {0: ['너', '이름'], 1: ['네', '이름', '말해'], 2: ['네', '이름', '뭐'], 3: ['놀러', '싶'], 4: ['느그', '아부지', '뭐하'], 5: ['말귀', '알아듣는다'], 6: ['맛저해'], 7: ['맛점해'], 8: ['메리', '크리'], 9: ['면접', '떨어'], 10: ['무슨', '말', '모르'], 11: ['뭐해'], 12: ['월요일', '다가'], 13: ['안녕'], 14: ['영화', '추천']}

     

    챗봇 함수

    def chat(request):
        for k, v in chat_dic.items():
            chat_flag = False
            for word in v:
                if word in request:
                    chat_flag = True
                else:
                    chat_flag = False
                    break
            if chat_flag:
                return chatbot_data['response'][k]
        return '무슨 말인지 모르겠어요'

    chat이라는 함수는 인자값이 request라는 문자열이며, chat_dic에 있는 딕셔너리 데이터를 loop 하여, 룰에 포함되어 있는지 비교를 한다. 룰을 비교할 때 해당 단어가 포함되어 있는지를 체크하는데 사실 여기서 단어가 포함되어 있는지만을 체크하는 방식은 옳지 않다. 이는 하단에 설명하도록 한다.

     

    채팅

    while True:
        req = input('대화를 입력해보세요. ')
        if req == 'exit':
            break
        else:
            print('jarvis : ', chat(req))

    이제 만들어진 챗봇 함수를 호출하여 rule이 모두 포함되어 있으면 response를 리턴하며 프로그램을 종료하고 싶으면 exit를 입력하면 된다.

     

    대화를 입력해보세요. 느그 아부지 뭐하시노?
    jarvis :  우리 아부지 건달입니다
    대화를 입력해보세요. 너는 뭐야?
    jarvis :  무슨 말인지 모르겠어요
    대화를 입력해보세요. 네 이름은 뭐야?
    jarvis :  저는 자비스라고 합니다
    대화를 입력해보세요. 뭐해
    jarvis :  그냥 있어요
    대화를 입력해보세요. 월요일이 다가오네
    jarvis :  월요병이 심한가봐요
    대화를 입력해보세요.

    결과를 보면 매우 잘 나온 것처럼 보일 수 있다. 사실 이렇게 만들어도 왠만한 사이트에는 크게 무리가 없이 작동이 잘된다. 하지만 Rule이라는 것은 순서가 매우 중요하다. 단어의 순서가 뒤죽박죽이어도 여기서는 포함만 되어 있으면 응답을 하기 때문에 아래와 같은 대화에도 응답을 하게 된다.

     

    대화를 입력해보세요. 다가온다 화요일이, 월요일이 어제던가?
    jarvis :  월요병이 심한가봐요

    Rule은 월요일 이후 다가오다가 와야 하는데 순서를 무시했기 때문에 이처럼 전혀 다른 대화를 응답해버린다. 이제 함수를 살짝 손을 대보도록 한다.

     

     

    챗봇 함수 수정

    def chat(request):
        for k, v in chat_dic.items():
            index = -1
            for word in v:
                try:
                    if index == -1:
                        index = request.index(word)
                    else:
                        # 이전 index 값은 현재 index값보다 이전이어야 한다.
                        if index < request.index(word, index):
                            index = request.index(word, index)
                        else:   # index 값이 이상할 경우 과감하게 break를 한다
                            index = -1
                            break
                except ValueError:
                    index = -1
                    break
            if index > -1:
                return chatbot_data['response'][k]
        return '무슨 말인지 모르겠어요'

    본 예제에서는 index 함수로 문자열의 위치값을 가져왔는데 사실 find함수를 사용하는 것이 더욱 적합한 예시였지만 index로 진행한 것을 참고하길 바란다(find를 쓰면 결과가 없을 경우 excep가 발생하지 않고 -1값이 리턴된다) 본 예시는 어디까지나 index를 사용하였기 때문에 ValueErrror except를 방지하기 위해서 try ~ except로 한번 감쌌다.

     

    두번째 나온 단어가 여러군데 등장하여, 에러가 날 수 있는 것도 생각해봐야 한다. 예를 들어, Rule에서 "월요일|다가"의 경우 "다가오고 있구나, 월요일이 또 다가왔어~" 라는 문장의 경우 결과를 뱉어내지 못할 수 있다. 단순히 다음 단어의 index 값이 이전단어보다 작을 경우만 체크해서 발생하는 것인데 이럴 경우를 대비하여 첫번째 rule이 등장했을 때의 index값 이후부터 찾는 로직을 적용하면 된다. 위 소스에서 request.index(word, index)라고 되어 있는 부분이 그 부분이다.

     

    이제 모두 수정하였으니 이상이 있는 부분도 모두 정상적으로 될지 테스트 해보도록 한다.

     

    대화를 입력해보세요. 느그 아부지 뭐하시노?
    jarvis :  우리 아부지 건달입니다
    대화를 입력해보세요. 아부지 느그 뭐하시노??
    jarvis :  무슨 말인지 모르겠어요
    대화를 입력해보세요. 느그 아부지 가만 있어보자...아 뭐하시노?
    jarvis :  우리 아부지 건달입니다
    대화를 입력해보세요. 메리 크리스마스다
    jarvis :  메리~ 크리스마스~
    대화를 입력해보세요. 맛점해
    jarvis :  맛점하세요~
    대화를 입력해보세요. 얌마 맛점해
    jarvis :  맛점하세요~
    대화를 입력해보세요. 다가온다 화요일이, 월요일이 어제던가?
    jarvis :  무슨 말인지 모르겠어요
    대화를 입력해보세요. 다가오고 있구나, 월요일이 또 다가왔어~
    jarvis :  월요병이 심한가봐요
    대화를 입력해보세요.exit
    
    Process finished with exit code 0
    

    나오지 말아야 될 부분은 나오지 않으며, 기존에 나오지 않았던 부분이 정상적으로 나오는 것을 확인할 수 있다.

     

    전체코드

    import pandas as pd
    
    chatbot_data = pd.read_excel("c:/project/steel/chatbot_data.xlsx")
    
    # rule의 데이터를 split하여 list형태로 변환 후, index값과 함께 dictionary 형태로 저장
    chat_dic = {}
    row = 0
    for rule in chatbot_data['rule']:
        chat_dic[row] = rule.split('|')
        row += 1
    
    
    def chat(request):
        for k, v in chat_dic.items():
            index = -1
            for word in v:
                try:
                    if index == -1:
                        index = request.index(word)
                    else:
                        # 이전 index 값은 현재 index값보다 이전이어야 한다.
                        if index < request.index(word, index):
                            index = request.index(word, index)
                        else:   # index 값이 이상할 경우 과감하게 break를 한다
                            index = -1
                            break
                except ValueError:
                    index = -1
                    break
            if index > -1:
                return chatbot_data['response'][k]
        return '무슨 말인지 모르겠어요'
    
    
    while True:
        req = input('대화를 입력해보세요.')
        if req == 'exit':
            break
        else:
            print('jarvis : ', chat(req))
    

     

    위 코드에 사용한 chatbot_data

    chatbot_data.xlsx
    0.01MB

    참고로 위 코드는 단순히 for문으로 loop를 돌아서 체크를 하게 되는데 Rule이 엄청나게 많을 경우 속도는 그만큼 느려지는 현상이 발생한다. 그래서 많은 룰을 처리하기 위해서는 형태소 분석과 같은 방식이 필요하고 이를 검색엔진에서 날려 후보군을 가져오는 방식등을 채용해서 룰을 비교하는 로직등을 이용한다.

     

    댓글

    Designed by JB FACTORY