かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

LUISで自然言語処理を試してみよう

Bot Frameworkは自然言語解析エンジンは提供してくれていません。 ただし、LUISとの連携機能は提供しています。

ということでBot Frameworkで自然言語解析をしたければLUISを使うのが一番自然な流れになります。

LUIS

LUISはLanguage Understanding Intelligent Serviceの略で、以下のページからアカウントが作成できます。

LUIS: Language Understanding Intelligent Service (beta)

アプリケーションを作りましょう、気を付けるのはJapaneseを選ぶところくらいですかね。

f:id:okazuki:20161001142210p:plain

アプリケーションを作成したら、そこにIntentとEntityを作っていきます。

  • Intent: 認識結果の種類みたいなの。
  • Entity: 文章から抜き出すパラメータみたいなもの。

ということで、基本的にIntentにはEntityが含まれてます。

今回は、「こんにちは〇〇」というというとhelloというIntentになって〇〇の部分をnameというEntityとして抜き出すようにしてみたいと思います。

まずnameというEntityを追加します。

f:id:okazuki:20161001142502p:plain

次に、helloというIntentを追加します。例題となるサンプル文章を打ち込んで作ります。

f:id:okazuki:20161001142625p:plain

New utterancesに画面が遷移して文章が文節ごとに区切られた感じに表示されます。

f:id:okazuki:20161001142735p:plain

ここにEntityを指定していきます。今回は世界の部分をnameとしたいので世界を選択してnameを割り当てます。

f:id:okazuki:20161001142857p:plain

「こんにちは〇〇」以外にも「こんばんは〇〇」とか「おはようございます〇〇」もついでに同じ要領で覚えさせてみましょう。

f:id:okazuki:20161001143103p:plain

こういう風に延々と満足いくまで学習させていきます。

publishをするとWebAPIになります。

f:id:okazuki:20161001143508p:plain

URLにidとsubscription-keyが含まれてます。こいつがアクセスするのに必要になります。テキストボックスに何か文字列を入れてEnterを押すと結果のJSONが取得できます。

f:id:okazuki:20161001144306p:plain

{
  "query": "こんにちは世界",
  "intents": [
    {
      "intent": "hello",
      "score": 0.9999995
    },
    {
      "intent": "None",
      "score": 0.0215021987
    }
  ],
  "entities": [
    {
      "entity": "世界",
      "type": "name",
      "startIndex": 5,
      "endIndex": 6,
      "score": 0.9617552
    }
  ]
}

intentがhelloでentityのnameが世界になってることが確認できます。

Bot Frameworkとの連携

LUISで言語解析するAPIができたのでBot Frameworkと連携してみたいと思います。 「こんにちは世界」という感じで話しかけると、「世界さん!!ようこそ!!」と返答するようにしたいと思います。 わからないケースの場合は、「すいません。わかりません」と回答したいと思います。

LuisDialog<T>というクラスが提供されているので、これを継承することでLUISと連携ができます。LuisDialogの型引数には、このダイアログが返す値の型になります。 今回は、LuisDialogで延々と処理を回したいと思うのでobjectを指定しています。 ダイアログにはLuisModel属性を指定して、先ほどのLUISのURLに埋め込まれてるidとsbscription-keyの値を設定します。 Intentに応答したかったらLuisIntent属性にIntent名を指定することで、そのメソッドがIntentに応答するようにできます。

コードは以下のようになります。

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using System;
using System.Threading.Tasks;

namespace HelloBot.Dialogs
{
    [LuisModel("idの値", "subscription-keyの値")]
    [Serializable]
    public class HelloLuisDialog : LuisDialog<object>
    {
        // noneの場合
        [LuisIntent("")]
        public Task None(IDialogContext context ,LuisResult result)
        {

        }

        // helloの場合
        [LuisIntent("hello")]
        public Task Hello(IDialogContext context, LuisResult result)
        {

        }
    }
}

LuisIntentに空文字を渡すとnoneの場合になります。

簡単なNoneメソッドから作っていきましょう。

// noneの場合
[LuisIntent("")]
public async Task None(IDialogContext context ,LuisResult result)
{
    await context.PostAsync("すいません。わかりません");
    context.Wait(this.MessageReceived);
}

メッセージを返した後に、MessageReceivedメソッドの処理を待つことで再度LUISとの連携待ちに入ります。

次に、Helloメソッドです。こいつは、nameというEntityを抜き出す必要があります。 抜き出すにはLuisResultTryFindEntityメソッドで抜き取れます。

// helloの場合
[LuisIntent("hello")]
public async Task Hello(IDialogContext context, LuisResult result)
{
    EntityRecommendation nameEntity;
    if (result.TryFindEntity("name", out nameEntity))
    {
        await context.PostAsync($"こんにちは!!{nameEntity.Entity}さん!!");
    }
    else
    {
        await context.PostAsync("すいません。わかりません");
    }

    context.Wait(this.MessageReceived);
}

EntityRecommendationを使って抜き出したEntityを操作できます。Entityプロパティでテキストにアクセスできます。 なので、基本的に文字列を組み立ててユーザーにフィードバックをしています。 名前がとれなかった場合は、すいません。わかりませんと言っています。

そして、context.Wait(this.MessageReceived);でLUISとの連携を待ってます。

最後にMessagesControllerで作成したDialogを使うように変更します。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        await Conversation.SendAsync(activity, () => new HelloLuisDialog());
    }
    else
    {
        HandleSystemMessage(activity);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK);
    return response;
}

動作確認

実行してエミュレータでつないでみましょう。

f:id:okazuki:20161001151422p:plain

ちゃんと動いてますね!ということでLUISを使って自然言語で最初のトリガーを識別して、そのあとFormFlowを使って必要な情報を集めてアクションをするといった流れとかができますね。