AlgoPoolJa 2022. 3. 10. 23:50

최근 의도 분류기(Intent Classification) 를 실험해보고 이용해 볼 일이 생겨 의도 분류기로 사용할 수 있는 구글의 Dialogflow 를 어떻게 이용할 수 있는지 적어보려고 합니다.

Dialogflow 는 2016년에 기존 챗봇 빌더 시장에서 가장 유명한 플랫폼인 api.ai 를 구글이 인수하여 기존의 우수한 대화식 인터페이스는 살리고 구글의 강력한 머신러닝을 추가하여 만든 챗봇 빌더 플랫폼 입니다.

Dialogflow 는 영어뿐아니라 한국어, 중국어, 우크라이나어 등 다양한 언어를 지원 가능하며 더 많은 언어도 추가적으로 업데이트할 예정이라고 합니다.(하지만 영어를 가장 잘 지원하긴 합니다.)

어떤 경우에 사용할 수 있을까?

단순한 작업의 경우 코드를 직접 구성하지 않아도 되기 때문에 인공지능을 사용해보지 않은 사용자가 편하게 사용할 수 있습니다. 그렇지만 인공지능을 다뤄본 개발자의 경우 앞의 내용을 보고서 BERT 나 ROBERTA 같은 Encoder 모델을 사용하면 되는데 왜 굳이 Dialogflow 를 사용할까? 와 같은 의문점이 생길 수 있습니다. 하지만 위와 같은 딥러닝 모델 즉, Encoder 모델은 충분한 양(최소 만 단위의 데이터가 있어야 어느정도의 성능을 기대해 볼 수 있습니다)의 데이터가 있어야 합니다. 특정 의도 분류의 경우 충분히 많은 데이터를 모으기 어렵기 때문에 위와 같은 경우 구글의 Dialogflow 와 같은 서비스를 이용해 볼 수 있습니다.

Dialogflow 사전 지식

Dialogflow 사용에 앞서 도움이 될만한 기본 개념들을 몇개 설명드리고자 합니다. Dialogflow 는 챗봇 빌더 이기 때문에

  • 화자의 의도인 Intent
  • 화자의 발화에서 정보를 얻을 수 있는 속성인 Entity
  • 대화의 문맥인 Context

위 3가지를 이해하는 것이 중요하고 이를 잘 지원하도록 설계되어 있습니다.

해당 그림을 보면 날씨가 어떻게 되죠?, 지금 날씨는 어떤가요?, 씨애틀의 기온기온은 내일 어떠나요? 와 같은 질문을 통해 Dialogflow Agent 는 해당 발화들을 듣고 의도(Intent)를 Forecast Intent 로 분류 하였고 화자의 질문에 대하여 추가적인 정보인 시간과 위치(Entity) 정보도 뽑아낸것을 알 수 있습니다. 마지막으로 현재의 대화가 날씨와 관련된 문맥이므로 이후 대화에 날씨와 관련된 문맥을 참고하여 말을 해야하는것을 알 수 있습니다.(즉, 사용자가 날씨를 물어볼 지역인 Seattle 을 챗봇에 대한 대답으로 했는데 챗봇이 이에대한 응답으로Oh I love Seattle. Seattle is the city..... 와 같은 문맥에 맞지 않는 답을 하지 않도록 도와주는 것이 Context 입니다.)

파란색으로 되어있는 부분은 의도를 분류 할때 중요한 영향을 미친 요소이고 노란색을 시간($time) , 빨간색은 위치($location) 즉, Entity 를 의미합니다.

Dialogflow 시작하기

처음으로 시작하게 되면 구글 아이디로 로그인하라는 표시가 나옵니다.

그렇게 로그인을 하고 나면 아래와 같이 약관을 체크후 바로 서비스를 사용 할 수 있습니다.

첫번째로 저희가 해야 될 일은 Agent 를 만드는 일입니다. googleDialogflow 를 사용하기 위한 모든 출발점은 Agent 로 시작합니다.

  • Agent name : 사용할 Agent 의 이름
  • DEFAULT LANGUAGE : Chatbot builder 에 사용할 기본 언어를 추가할 수 있습니다. 다른 언어도 추가로 넣어줄 수 있습니다.
  • DEFAULT TIME ZONE : Chatbot builder 에 사용할 시간대를 입력합니다
  • GOOGLE PROJECT : 특정 GCP 프로젝트에 Dialogflow 를 넣고 싶으면 넣을 수 있습니다. 따로 값을 지정해주시 않으면 자동적으로 신규 GCP 가 생성됩니다.
  • AGENT TYPE : Mega Agent 로 설정할 수 있습니다. Mega Agent 는 여러 하위 에이전트를 결합하여 메가 에이전트라는 단일 에이전트로 만들 수 있습니다. 설정하지 않으면 일반적인 하위 에이전트로 생성됩니다.

Agent 를 생성하고 난뒤 위의 그림처럼 Intent, Entity, Context 등을 사용할 수 있습니다. 차례대로 어떻게 사용할 수 있는지 알아보겠습니다.

Intent

Intent 는 앞서 설명했던 의도를 분류할 때 사용합니다. 위의 그림에서 CREATE INTENT를 클릭하게 되면 분류하고 하는 의도를 생성해낼 수있습니다.

  • Intent name: 분류하고자 하는 Intent 의 이름을 입력합니다
  • Training phrases : 해당 Intent 로 분류될 사용자의 발화 예시를 입력합니다. 입력된 예시는 의도를 분류할 때 학습 용도로 사용됩니다.
  • Action: 사용자의 발화가 해당 Intent 에 분류되었을 경우 API 응답을 할 때나 fulfillment webhook 을 걸어줄 때 Action 필드를 사용하여 유용하게 사용할 수 있습니다.
  • Parameters : Traning phrase 에 넣어준 학습 문구에 추가적인 정보를 사용할 때 사용합니다.
  • Responseㄴs : 해당Intent` 에 분류되었을 때 대답할 문장을 설정합니다.

이해를 위해서 추가적인 설명을 해보겠습니다.

저는 사용자가 날씨를 물어보는 의도를 잡아내고 싶어 Forecast 라는 이름으로 Intent 를 생성하였습니다. 해당 의도를 잡아내기 위해 학습에 사용할 문구로 아래 문장들을 Tranining phrases에 추가해주었습니다.

  • Can you tell me weather in Seoul Tomorrow and 2 days after?
  • Was the weather hot in Tokyo yesterday afternoon?
  • How's the weather in Seattle tomorrow?
  • What's the weather in Seoul?

사용자가 날씨를 물어볼 때 필수적으로 알아야 하는 정보는 위치시간 입니다. 따라서 Action and parameters 에 위치 즉, 특정 도시를 나타내는 @sys.geo-city 와 시간을 @sys.date-time 을 넣어주었습니다. 표에 있는 필드들의 설명은 아래와 같습니다.

  • REQUIRED : 해당 parameter 정보가 없을 경우 올바른 Intent 의 응답을 하기 어려운 경우 체크해줍니다.
  • PARAMETER NAME : 해당 Parameter 의 이름을 적습니다.
  • ENTITY : 해당 Parameter 와 일치하는 ENTITY를 입력합니다. ENTITY Dialogflow 가 미리 정의해 놓은 Entity 를 사용할 수도 있고 사용자가 새롭게 정의할 수 있습니다.
    • @sys.geo-city 와 @sys.date-timeDialogflow 가 정의해 놓은 값을 사용한 것입니다.
  • VALUE : 해당 Parameter 의 변수명 입니다.
  • IS LIST : 한 개 사용자의 발화에 대해서 해당 Parameter 가 2개 이상 나올 수 있으면 IS LIST 로 체크합니다.
    • 예를들어 How's the weather in Seoul tomorrow and the day after tomorrow? 이라는 문장이 있다면 여기서 날짜에 해당 되는 Parametertomorrowthe day after tomorrow 입니다. 이 경우 date-timeIS LIST 로 체크됩니다.
  • PROMPTS : 해당 ParameterREQUIRED 일때 유저의 발화로 해당 Parameter 를 얻어내지 못했을 때 유저에게 물어볼 질문입니다.
    • 예를들어 What's the weather in seoul? 을 사용자가 물어보았을 때 필수 Parameterdate-time 이 없으므로 Should I tell you about yesterday's weather?PROMPTS 로 설정할 수 있습니다.
  • Default value : 만약 값이 주어지지 않았을 경우 해당 Parameter 의 초기 값을 설정해줄 수 있습니다.
    • Default value 는 아래의 사진 처럼 메뉴버튼을 눌러야 확인할 수 있습니다.

모두 완료가 되었으면 save 버튼을 누른후 저장해줍니다. 한번 Dialogflow 가 의도를 잘 분류하는지 확인해볼까요? 의도 분류는 간단하게 웹을 통해서 할 수도 있고 CURL 을 통해서도 확인할 수 있습니다.

{
  "responseId": "c7f4d998-f587-44c9-a745-6358c18c1bd6-53cb9be6",
  "queryResult": {
    "queryText": "How's the weather in Los Angeles at tomorrow?",
    "action": "ASK_FORCAST",
    "parameters": {
      "geo-city": "Los Angeles",
      "date-time": [
        "2022-03-08T12:00:00+09:00"
      ]
    },
    "allRequiredParamsPresent": true,
    "fulfillmentText": "Well, I don't know........",
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            "Well, I don't know........"
          ]
        }
      }
    ],
    "intent": {
      "name": "projects/test-fyyj/agent/intents/026147e7-3897-49ce-8a03-3fef1e9e4be5",
      "displayName": "Forecast"
    },
    "intentDetectionConfidence": 1,
    "languageCode": "en",
    "sentimentAnalysisResult": {
      "queryTextSentiment": {}
    }
  }
}

위의 예시는 간단하게 웹을 통해서 했고 그 아래의 예시는 CURL의 결과문 입니다. 예문으로는 How's the weather in Los Angeles at tomorrow? 를 넣었습니다. 그 결과 IntentForecast 를 잡은걸 볼 수 있습니다. 또한 action 필드도 ASK_FORCAST 로 나온것을 확인할 수 있습니다. 이를 나중에 코드레벨로 사용할 땐 아래와 같이 사용할 수 있습니다.

DialogFlowIntentResult result = restTemplate.getForObject("url...");
if (result.queryResult.action == "ASK_FORCAST") {
  // 일기예보 비즈니스 로직 수행
}

Entity

이전 글에서 Entity 를 일부 사용했지만(ex. @sys.date-time, @sys.geo-city) 이 예시들은 제가 별도로 추가해준 Entity 가 아닌 Dialogflow 에서 기본적으로 설정되어 있는 Entity 입니다. Entity 는 언어마다 다르게 설정 되어있기 때문에 해당 링크 를 참조 하시면 언어별 기본으로 사용할 수 있는 Entity를 알려줍니다. 이렇게 기본으로 사용할 수 있는 EntitySystem entity 라고 합니다 Dialogflow 에선 이미 많은 Entity 를 제공해주고 있지만 앱을 개발하다보면 추가적으로 커스터마이징을 해야할 Entity 를 추가해줘야하는 경우도 있습니다. 이를 Custom Entity 라고 합니다.

System Entity : 날짜, 시간, 이메일 등

Custom Entity : 야채, 고기, 사람 나이 등

Entity 와 관련된 작업을 하고 싶다면 Entities 에 들어가면 됩니다.

Entities 에 처음 들어가게 되면 아직 아무런 Entity를 만들지 않았기 때문에 Create the first one. 이라는 문구가 보입니다. 해당 문구를 클릭하거나 CREATE ENTITY를 눌러 새로윤 Entity를 만들어보겠습니다. 우리는 여기서 weather 이라는 Entity 를 정의해보도록 하겠습니다.

Entity 를 정의해줄 때 Dialogflow 에서 제공해주는 4가지 기능을 사용할 수 있습니다.

  • Define Synonyms
    • 동의어를 넣어주어 Entity 를 잡아낼 수 있습니다. 기본적으로 넣어준 단어뿐 아니라 동의어를 추가적으로 넣어 이를 잡아낼 수 있습니다. (Ex. Raining, raining, rainy)
  • Regexp entity
    • 정규표현식을 이용하여 정의하는 방법으로 여기서 사용할 수 있는 정규표현식은 이곳 을 참조하면 됩니다.
  • Allow automated expansion
    • Entity에 대하여 자동적으로 확장해 줄 수 있습니다. 예를 들어 Shopping list 라는 Entity 에 대하여 bread, butter, milk, apple, ice cream 과 같은 단어들을 Entity 의 예시로 넣어주고 사용자가 I need to buy some carrots 이라고 하면 carrotsEntity 에 대한 예시로 넣어져 있지 않지만 자동적으로 추가됩니다.
    • 하지만 2개 이상의 Entity 에 대해서 이 기능을 모두 활성화 시키면 두개의 Entity 에 모두 해당되어 충돌이 일어나기도 합니다.
    • 이 기능을 올바르게 동작시키려면 충분한 예시를 넣어주는게 중요합니다.
  • Fuzzy matching
    • 사용자가 스펠링을 틀리거나 단어의 일부분만 입력으로 넣었을 때 이를 내부적으로 교정하여 Entity 에 매칭시켜줍니다.

저는 오타 교정과 동의어 기능은 사용하고 싶어 각각 Fuzzy matching 기능과 Define Synonyms 기능을 체크하였습니다. 동의어로는 RainingRainy, raining 을 넣어주었습니다. 이렇게 Entity 를 정의하고 나면 Intent 의 학습 문구에 이를 사용할 수 있습니다.

학습에 사용할 학습문구로 Is it raining now? 라는 문구를 넣어주었고 이에 대응하는 EntityrainingWeather 에, nowdate-time 에 넣어주었습니다. 이렇게 하면 추가적으로 Weather이란 Entity 를 정의해서 추가적인 정보를 얻을 수 있습니다.

Context

사람이 대화를 할땐 주로 Multi-turn 으로 대화가 이루어집니다. 아래의 예시는 날씨에 관한 Multi-turn 대화 형식입니다.

사람 : 날씨 어때?
챗봇 : 어디?
사람 : 부산!
챗봇 : 언제?
사람 : 오늘!
챗봇 : 오늘 부산 날씨는 ......

위에 설명 했던 Intent, Entity 도 챗봇을 만들 수 있는 강력한 도구들이지만 이것들만으로는 Multi-turn 챗봇을 만들기 힘듭니다. 이렇게 Multi-turn 대화를 만들기 위해선 이전의 대화의 내용들을 잘 기억하고 있어야합니다. 여기서 도움을 줄 수 있는 것이 Context 입니다.

설명 전 ASKING WEATHER 은 날씨를 물어보는 질문(ex. How's the weather?)을 학습시켜 비슷한 질문을 하면 해당 Intent 로 잡히고 Where? 을 대답하게끔 설정했습니다.

Intent 로 돌아가 임의의 Intent 에 마우스를 가져가 대보면 Add follow-up intent 가 나오는걸 확인할 수 있습니다. 버튼을 클릭한뒤 custom 을 눌러줍니다. 그럼 바로 아래에 ASKING WEATHER - custom 이라고 나와 있는것을 확인할 수 있습니다.(저는 Intent 의 이름을 ASKING WEATHER - region 으로 수정해주겠습니다.)

Intent 를 클릭해서 들어가보면 ContextsInput context ASKINGWEATHER-followup 이라는 Context 가 있는것을 확인할 수 있습니다.

바로 이 부분이 우리가 사용할 Context 입니다. Context 의 주된 사용처는 이전 대화의 흐름과 함께 이전 대화에 나왔던Entity 를 사용할 수 있다는 것 입니다. 여기선 지역에 대한 정보를 물어볼 것이기 때문에 사용자가 특정 지역(ex. I want to know about Pusan) 과 같은 발화를 하면 When? 을 응답하도록 설정해주었습니다.

여기서 똑같은 방법으로 ASKING WEATHER - regionAdd follow-up intent 를 눌러 ASKING WEATHER - region - time 을 만들어줍니다.

ASKING WEATHER - region - time 에선 시간(ex. Yesterday, Tomorrow)에 대한 대답을 잡아내도록 학습을 하였습니다. 그 후 Responses#ContextName.ParameterName 형식으로 이전 Context 에서 나온 Parameter 정보를 가져와 대답하도록 하였습니다.

참고로 ContextInput ContextOutput Context 로 나눠져 있습니다.

Input Context : 해당 Intent 에서 입력으로 받는 Context 1개 이상 가질 수 있습니다.

Output Context : 해당 Intent 에 뒤에올 Intent 에게 줄 수 있는 Context 입니다 1개 이상 가질 수 있습니다.

또한 Output ContextLifeCycle 을 가지고 있습니다. 한번 대화가 진행될 때마다 Life Cycle 이 1씩 감소하고 만약 Life Cycle 이 0이되면 더이상 해당 Output Context 를 참조할 수 없습니다.

그럼 이제 Context 를 활용한 응답이 잘 되는지 확인해 보겠습니다. 보다 원활한 대화를 위해 IntegrationsWeb Demo 를 활용하겠습니다. Web Demo 를 누른뒤 해당 Url 로 이동하면 됩니다.

대화를 진행해보면 아래와 같이 올바르게 Context 를 이해하여 대화를 진행하고 있는 모습을 볼 수 있습니다.

 

파이선 코드

from google.cloud import dialogflow

import os


def detect_intent_texts(project_id, session_id, texts, language_code):
    """
    Returns the result of detect intent with texts as inputs.
    Using the same `session_id` between requests allows continuation
    of the conversation.
    """
    session_client = dialogflow.SessionsClient()
    session = session_client.session_path(project_id, session_id)
    print("Session path: {}\n".format(session))

    for text in texts:
        text_input = dialogflow.TextInput(text=text, language_code=language_code)
        query_input = dialogflow.QueryInput(text=text_input)
        response = session_client.detect_intent(
            request={"session": session, "query_input": query_input}
        )

        print("=" * 20)
        print("Query text: {}".format(response.query_result.query_text))
        print(
            "Detected intent: {} (confidence: {})\n".format(
                response.query_result.intent.display_name,
                response.query_result.intent_detection_confidence,
            )
        )
        print("Fulfillment text: {}\n".format(response.query_result.fulfillment_text))

        if response.query_result.intent.display_name == "Photo or URL send intention":
            print("나는 지금 텍스트로된 말을 빼고 보낼 수 없어 언제든 편하게 말해줘!")


# Press the green button in the gutter to run the script.
if __name__ == "__main__":
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = # key 경로
    detect_intent_texts(
        project_id="test-ufws",
        session_id="123456789",
        texts=[
            "Can you share your picture?",
            "Please don't send me your photo",
            "I would like to get some picture of you",
            "How the weather today?",
        ],
        language_code="en-US",
    )