[Microsoft Bot Framework] LUISに日本語を解析させる

## 概要

MicrosoftのLUISは自然言語を解析してくれるサービスですが、現状 英語/フランス語/イタリア語/スペイン語/中国語 にしか対応していないという問題があります。

【参考記事】
『Build2016 : LUIS による自然言語入力解析』
https://blogs.msdn.microsoft.com/bluesky/2016/04/05/build2016-luis-natural-language-understanding/

しかし、Bot Connectorを通すことでその自動翻訳機能を利用することができ、結果的に「自然な日本語で受け答えができるボット」を開発することができます。

今回は「日付を入力したらその曜日を答える」ボットを作ってみます。


## ボットを作成し、Bot Connectorに登録する

下記記事にMicrosoft Bot Frameworkを使って作成したボットをBot Connectorに登録するまでの流れが紹介されています。

『SlackとWeb上のチャットをつなぐ / MS謹製のbot作成フレームワーク「Bot Builder」で遊んでみた！』
http://qiita.com/o0h/items/c10491a48cf014195338

記事中に公式ドキュメントから引用された図が貼られていますが、"Bot Connector"の欄に"Automatic translation to 30+ languages"という記述があります。今回はこれのお世話になろうというわけです。

コードは「Bot Builderを利用して」に貼られている方をベースとします。以下、転載となります。

```
var restify = require('restify');
var builder = require('botbuilder');

var bot = new builder.BotConnectorBot({ appId: process.env.appId, appSecret: process.env.appSecret});
var dialog   = new builder.CommandDialog();

dialog.matches(['Hi', 'Hello', 'こんにちは'], function (session) {
  session.send('こんにちは');
});
bot.add('/', dialog);

var server = restify.createServer();
server.post('/v1/messages', bot.listen());
server.listen(process.env.port || 8080, function () {
    console.log('%s listening to %s', server.name, server.url);
});
```

これをAzure(App Service)あたりに上げます。
よろしければ、下記記事もご覧ください。

『Macで開発したボットをAzureで運用する』
http://qiita.com/fullkawa/items/5a661d55311d45279aaf

Bot Connectorに登録する際、エンドポイントとして用意したパス(上記コードで言うと `server.post('/v1/messages', bot.listen());` の部分)を登録するのがポイントです。

![スクリーンショット 2016-06-15 10.12.40.png](https://qiita-image-store.s3.amazonaws.com/0/11338/167fd42a-3d75-6542-a88d-48634bb3311c.png)

appId, appSecret は「アプリケーション設定 > アプリ設定」から渡します。

![スクリーンショット 2016-06-15 10.25.08.png](https://qiita-image-store.s3.amazonaws.com/0/11338/29b88ff5-7666-5587-b158-af32e6474fd8.png)

OK！ 無事、Bot Connectorを通して受け答えができるようになりました。

![スクリーンショット 2016-06-15 10.37.35.png](https://qiita-image-store.s3.amazonaws.com/0/11338/13632a15-8e3d-b497-9896-2036ca5189ad.png)


## LUISにIntent, Entityを登録する

Microsoft LUISに関する説明、簡単な使い方は下記記事が参考になります。

『Build2016 : LUIS による自然言語入力解析』
https://blogs.msdn.microsoft.com/bluesky/2016/04/05/build2016-luis-natural-language-understanding/

このあたりから少し詳しく説明していきます。
まず、新規アプリケーションを作成します。

![スクリーンショット 2016-06-15 10.44.21.png](https://qiita-image-store.s3.amazonaws.com/0/11338/10a632cc-25cf-d9f8-8658-d6e040528fb8.png)

ご覧の通り、選択できる言語のリストに日本語はありません。
ここは"English"を選択しておきます。

今回は「日付を入力したらその曜日を答える」ボットを作るのが目的だったわけですが、ここで具体的な問答を考えてみましょう。

問：今日は何曜日ですか？
答：その日は水曜日です。

上記問の「今日」の部分はいろいろな日付表現を受け付けたいところです。これが今回の"Entity"となります。
LUISには組み込みのエンティティ(Pre-built Entities)に日付(datetime)があるので、それを利用しましょう。

左メニュー「Pre-built Entities」横の追加ボタンを押し、"datetime"を選択し、[OK]を押します。

![スクリーンショット 2016-06-15 11.01.00.png](https://qiita-image-store.s3.amazonaws.com/0/11338/0decac53-2b5b-d3e6-11ee-660b3e6353cb.png)

"datetime"が利用可能になりました。

![スクリーンショット 2016-06-15 11.01.35.png](https://qiita-image-store.s3.amazonaws.com/0/11338/70e5a064-d6ad-eaf5-df75-471fc3fa7bf2.png)

次にIntentを登録します。
例文を英語で入力する必要がありますが、Bot Connectorで自動翻訳された英文がLUISに渡されるのであれば、Bing翻訳の英訳で十分でしょう。これで英語に自信がない人でも大丈夫です。

<img width="852" alt="スクリーンショット 2016-06-15 11.05.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/11338/ea8a350b-b66d-9375-3a0a-3689d7a9cc57.png">

左メニュー「Intents」横の追加ボタンを押し、インテント名(任意)と先程翻訳した例文を登録し、[Save]を押します。

![スクリーンショット 2016-06-15 11.08.02.png](https://qiita-image-store.s3.amazonaws.com/0/11338/e1d9ba71-e0c9-5a67-a7fb-e8f16b110e3f.png)

Entity候補として自動的に"today"の部分が選択されました。これで問題ありませんので、[Submit]を押します。

![スクリーンショット 2016-06-15 11.08.23.png](https://qiita-image-store.s3.amazonaws.com/0/11338/85dc441e-82db-9a72-ff6c-8606e11b2b12.png)

"what_day"が登録されました。
"None"は最初から登録されているIntentで、どれにもマッチしない場合を表します。

![スクリーンショット 2016-06-15 11.10.48.png](https://qiita-image-store.s3.amazonaws.com/0/11338/0e5ff1b2-4453-d4ac-2f39-b182be8b00b3.png)

左下"Train"ボタンを押して、例文を学習させます。

![スクリーンショット 2016-06-15 11.25.41.png](https://qiita-image-store.s3.amazonaws.com/0/11338/0dd3cf11-be16-e341-b077-9fa1f9a2b39c.png)

左メニュー「Publish」から[Publish web service]を押すとURLが表示されます。
ここから、idとsubscription-keyを取得します。

![スクリーンショット 2016-06-15 11.29.09.png](https://qiita-image-store.s3.amazonaws.com/0/11338/ab65bdde-9079-65b0-ca0e-5d8f11353678.png)


## Intentに対する返答を実装する

ここまでで以下の流れができるようになっています。

```
[chat] 今日は何曜日ですか？
　↓
[Bot Connector] Today what is the day of the week?
　↓
[LUIS] Intent="what_day", Entity(datetime)="today"
```

これに対する返答をNode.jsで実装してやります。
サンプルコードは下記の通りです。
CommandDialogの代わりにLuisDialogを利用しています。`id`, `subscription-key`は「アプリケーション設定 > アプリ設定」から渡すようにします。
単に返答を返すだけでなく、どのようなデータが受け渡されているかをログに出力してみました。

```
var restify = require('restify');
var builder = require('botbuilder');

var bot = new builder.BotConnectorBot({ appId: process.env.appId, appSecret: process.env.appSecret});
var url = 'https://api.projectoxford.ai/luis/v1/application?id=' + process.env.luisId
    + '&subscription-key=' + process.env.luisSubscriptionKey
var dialog = new builder.LuisDialog(url);

bot.add('/', dialog);

// Intent="what_day"の場合の処理
dialog.on('what_day', function(session, args) {
    console.log('message:');
    console.log(session.message);
    
    var date = builder.EntityRecognizer.findEntity(args.entities, 'builtin.datetime.date');
    console.log('date:');
    console.log(date);
    
    if (date != undefined && date.resolution != undefined) {
        var d = new Date(date.resolution.date);
        var day = '日月火水木金土'[d.getDay()];
        session.send('その日は「' + day + '曜日」です。');
    } else {
        session.send('日付を取得できませんでした。');
    }
});

// Intent="None"の場合の処理
dialog.onDefault(function(session, args) {
    console.log('args:');
    console.log(args);
    
    console.log('message:');
    console.log(session.message);
    
    session.send("質問を理解できませんでした。もう一度、少し表現を変えて質問してみてください。")
});

var server = restify.createServer();
server.post('/v1/messages', bot.listen());
server.listen(process.env.port || 8080, function () {
    console.log('%s listening to %s', server.name, server.url);
});
```

## 動作確認

![スクリーンショット 2016-06-15 12.19.13.png](https://qiita-image-store.s3.amazonaws.com/0/11338/18ce02b9-3f1a-5ef7-6015-0ca240b35ae9.png)

入力した文章を理解してもらえませんでした。

```
args:
{ intents: 
   [ { intent: 'None', score: 0.57440114 },
     { intent: 'what_day', score: 0.0471038148 } ],
  entities: [] }
message:
{ type: 'Message',
  id: '9UcWTbjioJI',
  conversationId: 'DFS1VbGtB9ae5Ez4m49dp5TFGUj6kP5ol81I6nu7AVEEJ3Il',
  created: '2016-06-15T03:15:44.8295028Z',
  language: 'en',
  text: '今日は何曜日ですか？',
```

ログを確認すると、LUISはこの文を'what_day'ではなく'None'だと解釈したようです。
Messageの内容を見ると、`language: 'en'`となっています。入力をそのまま英文として解釈したようですね。

ボットに対してこれから日本語を使うことを伝え、再度同じ質問をすると今度は期待した答えをくれました。

![スクリーンショット 2016-06-15 12.18.01.png](https://qiita-image-store.s3.amazonaws.com/0/11338/bee56050-800c-82cd-cb19-c699d02a3bd9.png)

```
message:
{ type: 'Message',
  id: 'GuWITxWtRCb',
  conversationId: '7Uj2OT5w85OUFhL9UNfzG2p8fF5wR78wFwaAVlDgCLs7om',
  created: '2016-06-15T03:17:29.3002618Z',
  sourceText: '今日は何曜日ですか？',
  sourceLanguage: 'ja',
  language: 'en',
  text: 'Today what is the day of the week?',
(中略)
date:
{ entity: 'today',
  type: 'builtin.datetime.date',
  startIndex: 0,
  endIndex: 4,
  resolution: { date: '2016-06-15' } }
```

sourceText → text と自動翻訳されているのが分かります。
また、翻訳された"today"がEntityとして解釈され、それがLUISによって"2016-06-15"と解決されていることも分かります。

Bot Connectorの設定項目に"Default Conversation Language"というのがあり、これに"ja"と入れることで言語切替なしでいけることを期待したのですが、そうなりませんでした。他の設定手段があるのでしょうか？

![スクリーンショット 2016-06-15 11.48.18.png](https://qiita-image-store.s3.amazonaws.com/0/11338/371acb74-221b-387c-2dcb-1e4bf232e955.png)

それはさておき、他のパターンも試してみましょう。

![スクリーンショット 2016-06-15 12.29.42.png](https://qiita-image-store.s3.amazonaws.com/0/11338/113f89c0-3211-abaa-9b70-a50fd8e17793.png)

和暦も理解してくれるのはありがたいですね！

```
message:
{ type: 'Message',
  id: 'GQPdJkOyxrc',
  conversationId: '7Uj2OT5w85OUFhL9UNfzG2p8fF5wR78wFwaAVlDgCLs7om',
  created: '2016-06-15T03:31:41.7877018Z',
  sourceText: '平成28年6月16日は何曜日だっけ？',
  sourceLanguage: 'ja',
  language: 'en',
  text: '6/16/2016, what is the day of the week?',
(中略)
date:
{ entity: '6/16/2016',
  type: 'builtin.datetime.date',
  startIndex: 0,
  endIndex: 8,
  resolution: { date: '2016-06-16' } }
```

では、デタラメな質問を投げると…？

![スクリーンショット 2016-06-15 13.31.58.png](https://qiita-image-store.s3.amazonaws.com/0/11338/fbe13c7e-ccd8-764e-014f-ec4fbef6eadf.png)

Intentの判定には成功しましたが、Entityは取得できませんでした。
妥当な結果だと思います。

```
message:
{ type: 'Message',
  id: '7ZrZJP1tEcH',
  conversationId: '7Uj2OT5w85OUFhL9UNfzG2p8fF5wR78wFwaAVlDgCLs7om',
  created: '2016-06-15T04:31:46.4026778Z',
  sourceText: '私は何曜日でしょうか？',
  sourceLanguage: 'ja',
  language: 'en',
  text: 'I what what day of the week?',
(中略)
date:
null
```

このクオリティであれば、十分実用に耐えうるのではないでしょうか。