Rasa 개요

챗봇을 구축하기 위해 사용할 수 있는 라이브러리로는 Rasa가 있고, 한글 버전으로는 Kochat이 있다. 웹 인터페이스 기반으로 코드를 사용하지 않고 쉽게 챗봇을 만들 수 있는 DialogFlow라는 클라우드 서비스가 있지만 간단한 챗봇을 만들 때만 쓸 수 있고, 디테일한 구현이 필요할 땐 Rasa와 같은 오픈소스 라이브러리를 써야한다. Rasa는 크게 Rasa NLU와 Rasa Core로 나뉜다. rasa nlu는 인텐트(intent) 분류와 엔티티(entity) 추출에 사용하는 라이브러리이다. 이를 통해 챗봇 커스터마이징이 가능하고 상상한 거의 모든 종류의 챗봇을 만들 수 있다고 한다. rasa core는 인텐트 추가와 같은 스케일업을 도와주는 라이브러리다. 이 rasa core를 통해 챗봇 응답과 챗봇 동작을 명시 가능하다. 이 명시되는 정보를 액션(Action)이라 하는데, 다르게 말하면 dialouge state에 응답하기 위해 취해야할 행동을 뜻한다. 더하여 rasa core는 과거 히스토리(대화내역)을 기반으로 다음에 이루어져야 할 액션을 예측하는 확률모델을 생성하는 역할을 한다. 

 

Rasa NLU

rasa nlu를 사용하면 학습과 추론을 매우 손쉽게 할 수 있다. 아래 예시 코드와 같이 라이브러리를 제외하면 단 9줄 가량의 코드로도 동작한다. 

from rasa_nlu.training_data import load_data
from rasa_nlu.model import Trainer
from rasa_nlu import config
from rasa_nlu.model import Interpreter

def train_horoscopebot(data_json, config_file, model_dir):
    training_data = load_data(data_json)
    trainer = Trainer(config.load(config_file))
    trainer.train(training_data)
    model_directory = trainer.persist(model_dir, fixed_model_name='horoscopebot')

def predict_intent(text):
    interpreter = Interpreter.load('./models/nlu/default/horoscopebot')
    print(interpreter.parse(text))

 

위 예시 코드는 별자리를 알려주는 태스크를 수행하는 챗봇을 구현하기 위해 사용된 것으로 사용자 입력이 들어오면 아래와 같이 추론 가능하다.

위 결과를 보면 네 개의 사전 정의한 인텐트(greeting, get_horoscope, dob_intent, subscription)중 예측한 인텐트에 대한 신뢰점수가 confidence로 계산되는 것을 확인할 수 있다. 사용자는 별자리를 알고 싶다 말했고, 학습을 진행했던 결과 사용자의 발화(utterance)는 별자리를 묻는 get_horoscope일 확률이 96.12%라는 것을 보여준다. 위와 같이 rasa nlu를 사용해 챗봇 시스템을 학습하고 추론하기 위해서는 간단한 아래 과정을 거친다.

 

1. 챗봇 구현 범위 설정

2. 데이터셋 생성 (data.json)

3. 모델 학습

4. 추론

 

네 가지지만 사실상 챗봇 구현 범위 설정과 데이터셋 생성이 대부분이다. 챗봇 구현 범위 설정이란 모종의 태스크를 수행하는 챗봇을 만든다 가정할 때 사용자가 어떤 종류의 의도를 가질 수 있을지 정하는 것이다. 가령 음식점에서 사용할 수 있는 챗봇을 만든다면 손님은, 주문하거나 결제하거나 화장실 위치를 묻거나 인사하거나 기타 의도를 가진 말을 할 것이다. 이러한 사용자 발화의 범위 설정이 챗봇 시스템 구현의 첫 단계이다. 이후엔 데이터셋 생성이다. rasa nlu를 통해 학습하기 위해서는 json 형태로 데이터를 만들어야 한다. 별자리를 알려주는 태스크를 학습하기 위해 생성한 데이터셋의 예시는 아래와 같다.

 

기본적으로 예상되는 사용자의 발화를 text에 입력하고 그에 따른 라벨이 되는 인텐트의 종류를 입력해준다. 빈 칸의 엔티티는 현재 별자리 운세를 알려주는 태스크에서는 사용되지 않는다. 하지만 간단한 설명을 하자면, 엔티티는 범주를 의미하는 것으로 가령 음식 주문 할 때 A라는 음식 2개를 가져달라고 요청할 수 있다고 가정하면 "음식 종류"라는 엔티티는 "A"라는 값을 가질 것이고 "음식 수량"이란 엔티티는 '2'라는 값을 가질 것이다. 이처럼 하나의 인텐트에는 여러 엔티티를 가질 수 있는 것이 특징이다. 

 

위와 같이 사용자 발화 별 인텐트와 엔티티를 지정하기 위해서 직접 json 포맷으로 작성해줄 수 있지만 쉽게 작성할 수 있는 웹 인터페이스가 있으면 좋을 것이다. rasa에서는 rasa-nlu-trainer라는 웹 인터페이스를 통해 데이터셋을 만드는 작업을 쉽게 할 수 있도록 한다. 일종의 annotation 도구인 것이다. rasa-nlu-trainer는 node.js 기반으로 동작하기 때문에 별도로 node.js를 설치해주어야 하고 sudo npm i -g rasa-nlu-trainer라는 명령어를 통해 설치할 수 있다. 여기서 i는 install이고 g는 global을 뜻한다. 이를 실행하게 되면 다음과 같이 웹으로 쉽게 데이터셋을 만들 수 있도록하는 인터페이스를 제공한다. 

 

 

데이터셋을 직접 만드는데 가장 시간이 많이 걸릴 것이다. 가급적이면 공개되어 있는 것을 가져올 수 있겠지만 내가 원하는 특정 태스크를 수행하기 위해서는 대부분 커스터마이징이 필요할 것이고 이에 따른 데이터는 직접 구축하는 경우가 많을 것이다. 

 

Rasa Core

rasa nlu를 통해 예측된 intent에 따라 챗봇의 응답과 동작이 이루어질 것이다. 이 때 사용하는 것이 rasa core이다. rasa core를 통해 챗봇 응답과 챗봇 동작을 명시할 수 있다. 챗봇에서 동작을 'Action'이라 한다. rasa core를 사용하면 과거의 대화내역을 기반으로 챗봇이 해야할 다음 Action을 예측하는 모델을 생성할 수 있다. 이를 위해 가장 먼저 도메인 파일이라 불리는 것을 만들어 주어야 한다. 도메인 파일이란 쉽게 말해 어떤 주제 속에서 대화가 이뤄지는지 정의하는 파일이다. 음식점에서 사용할 챗봇이면 도메인이 음식점이 되는 것과 같다. 이 도메인 파일은 크게 5가지 내용을 포함 해야한다. 1. 인텐트 2. 엔티티 3. 슬롯 4. 템플릿 5. 액션이다. 도메인 파일은 yml 확장자를 가지며 아래와 같은 형태로 정의할 수 있다.

 

 

1. 인텐트는 언급했듯 발화의 의도를 말한다.

2. 엔티티는 발화 속 캐치해야 할 핵심 키워드를 뜻하고

3. 슬롯은 키워드가 채워질 공간을 말한다. 별자리 별 운세를 알려주는 태스크에서는 월/일을 알아야 하므로 MM, DD같은 슬롯이 정의될 수 있다. 

4. 템플릿은 말 그대로 인텐트에 대한 기본 응답을 뜻한다. 여러 개의 기본 응답을 만들어 랜덤으로 사용한다면 다양하기에 사용자 경험에 있어 친근감을 줄 수 있는 요소가 될 것이다.

5. 액션은 사용자 발화에 따라 어떤 행동을 취할 수 있을지를 정의하는 것이다. 

 

이렇게 도메인 파일 내에 5개의 요소를 넣어 yml 파일로 정의했으면 다음으론 거의 끝으로 스토리 파일을 만들어 주어야 한다.

스토리 파일이란 대화가 어떤 시나리오로 흐를 수 있는지 정의한 파일이다. 파일은 마크다운 확장자로 만들어준다. 예시는 아래와 같다.

 

간단하다. 첫 번째 스토리는 챗봇이 사용자에게 인사하고 별자리가 무엇이냐고 물어본다. 이후 Capricorn(마갈궁자리)라는 응답을 받아 slot에 채워주고 현재 별자리 운세를 가져와서 보내주고 이 서비스를 구독하겠는지 묻고 끝나는 상황이다. 두 번째 스토리는 첫 번째와 달리 챗봇이 인사한 다음 사용자가 바로 별자리를 알려주고 cancer(게자리)라는 정보를 slot에 채우고 현재 별자리 운세를 가져와 사용자에게 전달하고 이 서비스를 구독하겠는지 묻고 사용자가 구독한다는(True) 발화를 캐치한 다음 구독자 목록에 추가하는 프로세스로 이뤄지는 것을 알 수 있다. 

 

사용자는 미리 정의된 템플릿에 따라 액션을 취할 수 있지만 이는 정적이다. 예컨데 운세를 가져오거나 구독자 추가와 같은 동적으로 이뤄저야 하는 것은 별도의 API를 사용해 외부 데이터베이스와 연결될 필요가 있다. 이 때는 Custom 액션이라 하여 파이썬 스크립트를 별도로 만들어 주어야 한다. 간단한 코드 예시는 다음과 같다.

 

class GetTodaysHoroscope(Action):
    def name(self):
        return "get_todays_horoscope"

    def run(self, dispatcher, tracker, domain):
        user_horoscope_sign = tracker.get_slot('horoscope_sign')
        base_url = http://horoscope-api.herokuapp.com/horoscope/{day}/{sign}
        url = base_url.format(**{'day':"today", 'sign':user_horoscope_sign})
        # http://horoscope-api.herokuapp.com/horoscope/today/capricorn
        res = requests.get(url)
        todays_horoscope = res.json()['horoscope']
        response = "Your today's horoscope:\n{}".format(todays_horoscope)

        dispatcher.utter_message(response)
        return [SlotSet("horoscope_sign", user_horoscope_sign)]

 

오늘 별자리 별 운세를 가져오기 위해 GetTodaysHoroscope라는 클래스를 만들어주고 내부에 run이라는 비즈니스 로직을 작성해줄 수 있다. run의 매개변수로 크게 dispatcher, tracker, domain이 들어가는 것을 알 수 있다. 먼저 tracker는 현재 상태 추적기로 슬롯에 접근해 값을 가져올 수 있고 이외에도 최근 사용자의 발화 정보 내역들에 접근할 수 있다. dispatcher는 별자리 정보를 기반으로 운세를 알려주는 API 호출 결과를 사용자에게 전달하는 역할을 한다. domain은 글자 그대로 이 챗봇의 도메인을 의미한다. 마지막으로 보면 SlotSet이 있는데 이는 추후 사용자 발화 정보를 히스토리로 활용하기 위해 슬롯이라는 공간에 저장하는 것이다. 

 

마지막으로 학습은 아래와 같이 간단한 로직으로 이루어진다.

""" Training a chatbot agent """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from rasa_core import utils
from rasa_core.agent import Agent
from rasa_core.policies.keras_policy import KerasPolicy
from rasa_core.policies.memoization import MemoizationPolicy
from rasa_core.policies.sklearn_policy import SklearnPolicy
import warnings
import ruamel
warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

if __name__ == "__main__":
    utils.configure_colored_logging(loglevel="DEBUG")
    training_data_file = "./data/stories.md"
    model_path = "./models/dialogue"
    agent = Agent("horoscope_domain.yml", policies=[MemoizationPolicy(), KerasPolicy(), SklearnPolicy()])
    training_data = agent.load_data(training_data_file)
    agent.train(
        training_data,
        augmentation_factor=50,
        epochs=500,
        batch_size=10,
        validation_split=0.2
    )
    agent.persist(model_path)

 

main을 살펴보면 1. 로깅 설정 2.스토리 파일 경로 설정 3. 모델 저장 경로 설정 4. 다음 액션을 가져오는 에이전트 세팅 5~6. 학습, 마지막으로 에이전트 오브젝트(모델)를 지정된 경로에 저장하여 재사용하는 로직으로 구성된다. 이렇게 해주면 에이전트 모델 학습이 이루어진다. rasa nlu를 통해 추론한 사용자 발화 의도를 기반으로 rasa core를 통해 학습한 에이전트로 다음 챗봇 Action을 수행할 수 있다. 

 

Reference

[1] 파이썬으로 챗봇 만들기 

+ Recent posts