프로젝트 기능 추가 과정 소개
이 문서에서는 프로그램의 기능 추가 과정에 대해 설명합니다.
프로젝트 기능 추가 과정
원 프로젝트의 이슈 목록을 보다 보면 사용자들이 프로젝트에 대해 기능 추가 요청을 하곤 합니다. 이러한 이슈 중 관심이 가고, 난이도가 낮은 것들을 골라 코드를 작성하여 프로젝트에 기능을 추가해 나갔습니다.
1. _generate_export_data 함수에서 중복 데이터 제거
시작
원본 repository에 대해 이슈를 탐색하던 중 이러한 이슈를 확인하게 되었습니다. 요약하자면, _generate_export_data
함수에서 중복된 데이터가 생긴다는 것이었습니다. 쉬운 내용으로 보여 한번 도전해 보았습니다.
해결 방안 찾기
ChatterBot은 학습된 데이터를 sqlite3 또는 mongodb에 저장하는데, 단순히 사용자의 실수로 중복된 데이터가 두 번 학습될 경우 데이터베이스에 두 번 저장되던 것이었습니다. 따라서 중복 학습을 막으면 해결될 일이었습니다. 그러나 전체 코드의 작동 방식을 완벽히 모르는 상태에서 학습 과정을 수정하는 것은 어떤 부작용을 가져올지 모르는 일이었습니다. 따라서 _generate_export_data
함수만 수정하기로 하였습니다. 중복 제거에는 다양한 방식이 있습니다. 처음에는 if data not in list:
등의 코드를 사용하였지만 추출 데이터가 클 경우 속도에 문제가 있을 것 같아 set()
자료구조를 사용하여 제거하기로 하였습니다.
문제점 발생
그러나 ChatterBot의 테스트 케이스를 통과하지 못했습니다. 로그를 확인해 보니 set()
자료구조는 수정될 수 있는 데이터를 허용하지 않기에 tuple
형태로 넣었지만, 원래는 list
형태였기에 테스트 케이스를 통과하지 못한 것입니다.
해결
list(map(list, result))
코드를 통해 결과값의 tuple
들을 모두 list
로 변경해 줌으로써 테스트 케이스를 통과하였습니다.
2. 텔레그램과 챗봇 형태로 연동
시작
처음은 이 이슈를 보고 시작하게 되었습니다. 텔레그램 봇의 경우 봇을 만드는 난이도가 상당히 낮은 축에 속하고, python-telegram-bot
이라는 파이썬 전용 라이브러리도 존재하기 때문에 개발하기 쉬울 것이라고 생각하게 되었습니다. 이미 chatterbot
은 django
와의 연동을 위해 chatterbot/ext
폴더에 관련 라이브러리들이 존재하고 있어서, 해당 위치에 코드를 작성하였습니다.
코드 구조 소개
봇 등록
먼저 Bot을 Telegram의 BotFather 봇을 통해 추가해야 합니다. /newbot
명령어를 이용하면 바로 생성할 수 있습니다. 여기서 BotFather 봇이 생성된 봇의 TOKEN을 만들어 주니 잘 보관해야 합니다.
conf.py
TOKEN과 봇의 이름이 저장되는 곳입니다. 직접 사용자가 지정할 수도 있지만, 그렇지 않을 경우 이 파일에 저장된 기본값을 사용하게 됩니다.
class TelegramBot
텔레그램 봇의 본체입니다. 토큰과 봇의 이름, 봇 데몬, 명령어 리스트들로 초기화되는 클래스입니다. 초기화 이후 start()
메소드를 이용하여 봇을 작동시킬수 있습니다.
class ChatBot
ChatterBot을 TelegramBot 형태에 맞게 초기화 시킨 것입니다. 로직 등을 이 단계에서 직접 설정할 수 있습니다.
class BotHandler
텔레그램 봇의 명령어를 직접 설정할 수 있습니다. 함수를 선언하고 handler_list
에 추가하면 됩니다. 물론 클래스 외부에서 선언하여 함수만 추가시키는 것도 가능합니다.
apps.py
TelegramBot과 ChatBot을 초기화시키고 실행시키는 파일입니다.
봇 작동
apps.py
를 통해 봇을 실행시키면, 다음과 같이 chatterbot에 의한 답변이 오는 것을 볼 수 있습니다.
3. 질답시 답변을 Wikipedia에서 가져오기
시작
처음은 이 이슈를 보고 시작하게 되었습니다. 버그를 고치면서 어느 정도 프로젝트의 작동방식에 익숙해지게 되었고, 또 Wikipedia는 개인적인 사정으로 다뤄 본적이 있기에 해볼만한 주제라고 생각하였습니다. 처음에는 response_selection
단계에서, 즉 생성된 답변을 선택하는 과정에서 답변을 만들어내려고 했고, 실제로 코드도 그렇게 작성하였는데 답을 만들어 내는 단계는 LogicAdapter
단계라는 것을 깨닫고 LogicAdapter
형태로 코드를 작성하였습니다.
class LogicAdapter
chatterbot에서 Logic을 작성하기 위해서는 LogicAdapter
클래스를 무조건 상속하여 만들어야 합니다. 따라서 저는 LogicAdapter
를 상속한 WikipediaResponseAdapter
클래스를 작성하였습니다.
class WikipediaResponseAdapter
코드 구조는 다음과 같습니다. 먼저 Adapter를 초기화시킬 때 lang_code
와 cut_threshold
인자를 받아오도록 했습니다. 각각 어느 언어의 위키피디아를 선택할 건지와 답변의 길이를 정하는 항목입니다. 이 클래스에서 Override할 함수는 단 두개입니다. 이 두개의 함수만 Override해도 Logic을 작성할 수 있습니다.
can_process(input_statement)
이 Logic으로 input_statement를 처리할 수 있는지 True
or False
를 반환하는 함수입니다. 이 함수에서 봐야할 것은 크게 두 가지입니다. 먼저 input_statement에 찾을 만한 단어가 있는지, 또는 단어가 있다 하더라도 Wikipedia에 해당 단어가 존재하는지의 여부입니다. 전자는 chatterbot에 포함되어 있는 tagger.get_text_index_string(statement.text)
함수를 통해 해결할 수 있었습니다. 이 함수를 사용하면 해당 문장의 문장성분을 가져올 수 있습니다. 또한 Wikipedia를 직접 Parsing하는 방법도 있지만, Wikipedia의 위키 엔진인 MediaWiki는 REST API를 제공하기 때문에 requests
모듈을 통해 해당 문서가 존재하는지 API를 사용하는 방법을 선택하였습니다. 이 두가지 정보를 사용하여, 주어진 문장에 단어가 있고 해당 단어가 Wikipedia 존재할 때에만 True
를 반환합니다. 마지막으로 받아온 문자열이 너무 길면 자릅니다.
process(input_text)
답변을 실제로 만들어내는 과정입니다. can_process
함수에서 얻어놓은 데이터를 미리 저장해 두는데, 이 데이터를 활용하여 답변을 만듭니다. 또한 can_process
의 반환값이 True
여서 process
함수가 호출된 경우 confidence
를 1
로 설정하여 무조건 이 답변이 선택되도록 합니다.
결과
다음과 같이 위키피디아에서 답변을 얻어오는 것을 볼 수 있습니다.
4. 신뢰도가 높은 답변 선택하기
시작
처음은 이 이슈를 보고 시작하게 되었습니다. 다른 높은 신뢰도를 가지고 있는 답변이 있음에도 불구하고 해당 답변을 chatterbot이 선택하지 않는다는 이슈였습니다. 다만 이것이 bug는 아닌 것으로 보이는 것이, 원래 chatterbot에는 response_selection_method 태그를 이용하여 답변을 선택할 수 있게 하는 기능이 있습니다. 기본값이 get_first_response로 예상 답변 목록의 첫번째를 반환시키는데, 이 이슈에서도 제일 첫번째의 답변을 선택한 것으로 보아 아무것도 설정하지 않아 기본값으로 설정된 것으로 보입니다. 그런데 response_selection.py에 해당 기능이 없었기에 직접 구현하게 되었습니다.
response_selection.py
response_selection_method에 적용할 수 있는 함수들이 모여 있는 파일입니다. 여기에다가 함수를 구현하게 되었습니다.
get_high_confidence_response
높은 신뢰도를 가지는 답변을 반환하도록 하는 메소드입니다. input_statement와 response_list, 그리고 storage를 인자로 받는데, response_list 외에는 사용하지 않지만 다른 코드와의 호환성을 위해 넣어두었습니다. 답변 선택 과정은 최대값을 구하는 것과 비슷합니다. 첫번째 답변을 구해와서, 해당 답변보다 신뢰도가 높은 답변이 response_list 리스트 안에 있을 경우 교체합니다. 만약 그렇게 구한 답변의 신뢰도가 0일 경우, get_frequent_response 함수를 호출하여 중복된 답변이 있다면 신뢰도가 가장 높을 것으로 생각되므로 해당 답변을 선택하도록 합니다.