Google Homeから適当にしゃべってLUISに自然言語処理させ、カレンダーに予定を登録させるというお話(其の一)

しばらくぶりのブログでございます。

ネタはいろいろあるんですが、ついついクオリティにこだわりすぎるあまりまとめきれず・・・

言い訳は見苦しいのでさっさと本題に参りましょう。

今回は、「Cogbot! – Cognitive Services, Bot Framework, Azure ML, Cognitive Toolkit(CNTK) Advent Calendar 2017」
のためのエントリーでございます。

https://qiita.com/advent-calendar/2017/cogbot

ということで、最近話題の機械学習についてのエントリー。
技術屋といいつつ全然技術屋らしくないことばっかり普段ボヤいているので、今回はちょっと頑張ろうかと思います。

最新のGoogle HomeとGAしたてのLUISで最先端!

今回の主役は「LUIS」。

正式には「Language Understanding Intelligent Service」といいます。
2017年12月13日にGAを迎えたばかりの、ホットなサービスですね。

何をしてくれるかというと、人間が投げかけた普段話している文章を、全体の文脈や文章の中に含まれている単語などから意味を理解し、理解した意味に沿って解析してくれるというものです。

(やってくれるのは文章の解析までで、その後何をするかはこちらで用意してやる必要があります。)

例えば・・・
明日の天気を知りたい場合、丁寧に言えば
「明日の名古屋の天気を教えてください」
でしょうが、実際に人との会話の中ではもっとフランクに

  • 「明日の天気教えて。名古屋の。」
  • 「明日の天気知ってる?」
  • 「明日天気どうかな」
  • 「明日名古屋晴れるかな」

といろいろです。

これまでのITサービスでは、このような自然な言葉の”揺らぎ”を吸収することができず決まった言葉でサービスに投げかける必要がありました。

それを解決し、自然な口語でコンピュータと対話できるようにする技術・自然言語処理が「LUIS」なのです。

今回は、このLUISを使って、もう一つ話題のGoogle homeから投げかけた言葉を解釈してカレンダーに予定を登録するという仕組みを作ってみたいと思います。

ここまで前置き。

LUISは人間とコンピュータの間の翻訳家みたいなもの

さて、全体の構成はこのようになります。

image

メインとなるLUIS以外にも様々な要素が絡んでますが、それを一つ一つ説明していると大変なので、本日は主にLUISの部分について解説していきます。

LUISは・・・

  1. 予定登録のためのいい加減な文章のテキストデータ(※インテント)を入力とし
  2. 予定登録のために必要な、日時や場所、予定の内容などの情報(※エンティティ)に分ける

ことをやってくれます。

※インテント 平たく言えば文章(特定の回答を持つ文章群)

  •  天気予報の回答を求める文章
  •  時刻の回答を求める文章
  •  ただの挨拶

※エンティティ 平たく言えば単語(特定の意味を持つ単語の集合)

  •  駅名(名古屋、大阪、東京、福岡、札幌)
  •  天気(晴れ、曇り、雨、雪、槍)
  •  時間の概念(今日、明日、何日、何時)

なので、LUISを利用する場合・・・

  • 自分が何を求めたいのか
  • ユーザーのどんな要望に回答させたいのか

を明確にすることが重要になります。

今回は「予定の登録」ということに的を絞っていますが、一般の人が想像するような賢いAI・・・
マルチなBOTを作ろうと思うと、無限のインテント登録とエンティティ登録が必要になります。
これはすごく大変。

自然言語処理の難易度をわかりやすいかどうかは別として例えてみると・・・

1.あいさつ・・・・容易

2.特定の回答・・・まぁまぁ容易
(天気予報とか、時刻の回答)

3.特定のジャンルのチャットボット(アニメで例えれば「四畳半神話大系」に関するBot)・・・やや困難
(ジャンルに関するインテントの準備とエンティティの準備がソコソコ多い)

4.広いジャンルのチャットボット(ガンダムに関するBot(作品は単発だが壮大なシリーズ構成))・・・かなり困難
(ジャンルに関するインテントの準備とエンティティの準備がかなり多い)

5.かなり広いジャンルのチャットボット(アニメ全般)・・・滅茶苦茶困難
(ジャンルに関するインテントの準備とエンティティの準備が半端なく多い)



100.人間のエミュレーション(無限大)

これを自動的にやる手法としては、昔よくあった人工無能のように、わからない質問に「それはなに?」と質問に質問で返して学習でしょうか。

今時な手法としては、不明な質問群を再帰的にAIに食わせて自己学習させていく感じでしょうか。夢は膨らみます。

ということなので、インテントとエンティティの材料集めの仕方や攻略アルゴリズムを得た人が勝ちということですね。

それをさらにAIに処理させることによって指数関数的に賢くなれるということになります。
もうこれは数多くこなしたほうがいいに決まっているので、やるなら早くやったほうがいいということです。

では予定登録のLUISを実装してみましょう

閑話休題。

では早速今回のケースを実装してみたいと思います。

LUISの基本的なサービス利用方法については、以下のブログがこんなブログより大変ためになるので是非参考にしてみてください。

LUIS 入門(Cognitive Services – 2017年12月版 – 1/2)
http://beachside.hatenablog.com/entry/2017/12/07/000000

LUIS 入門(Cognitive Services – 2017年12月版 – 2/2)
http://beachside.hatenablog.com/entry/2017/12/11/140000

お話は、LUISのサービス利用準備が整い、アプリを作る手前の画面から始まります・・・。

2017-12-23_00h38_45
「Create new app」をクリックし

2017-12-23_00h40_07
「Name」には任意のアプリ名を入力します。
「Culture」はかならず”Japanese”を選択しましょう。
「Description」はアプリに関する説明です。特に入力は不要です。
入力できたら「Done」をクリック。

2017-12-23_00h40_19
入力した通りのアプリが作成されたことを確認しましょう。

インテントの登録

2017-12-23_00h49_08
早速インテントを登録していきます。
エンティティから入力しても良いですが、まずは人間が発話する自然な文章を片っ端から入力していくのが良いのではないかと思います。
エンティティに引っ張られず、よい例文がたくさん並ぶと思います。

2017-12-23_01h02_30
こんな感じで、スケジュールの登録に必要な文章を入力していきます。
例文はちょっと整いすぎてますね・・・
もっと表現をバラしたほうが、最終的な学習精度が良いと思います。

この時点ですでに、名詞や助詞などに文章が区切られています。
単語の間に微妙にスペースがあるのが分かり、カーソルで単語にフォーカスすると単語ごとに[]で分割されていることが分かります。

エンティティの登録と関連付け

2017-12-23_01h04_20
続いてエンティティを登録します。
エンティティはエンティティの要素を一つ一つ登録してくのではなく、エンティティを包括する種類・カテゴリを登録します。

2017-12-23_01h04_35
例では「場所」のエンティティを登録しています。
これは、インテントの中にある「名古屋」や「半田」といった地名をまとめるエンティティです。

2017-12-23_01h07_22
同様に、スケジュールにおいてどんなことをするのかという「予定の内容」というエンティティを作成します。
今回のケースでは「打合せ」や「ミーティング」「出張」などがこれにあたります。
ちょっと例が悪く、このエンティティは相当な量を登録しないと精度が上がらなさそうですね・・・。

2017-12-23_01h09_19
さてここで一旦、インテントの中にある単語とエンティティをひもづけてみましょう。

文章の中にある地名に相当する単語をクリックすると単語の下に
「Browse prebuilt entities」
「予定の内容」
「場所」
と、登録したエンティティが表示されるので該当するエンティティをクリックして関連付けを行います。

2017-12-23_01h09_31
関連付けが終わると、エンティティ名に置き換わります。

2017-12-23_01h11_12
同様に「予定の内容」エンティティも関連付けます。

2017-12-23_01h13_37
エンティティに登録したい語句が複数の単語にまたがる場合、最初の単語をクリックした後登録したい単語までカーソルを移動し、末尾の単語でもう一度クリックすることで連続した単語でエンティティに登録することもできます。2017-12-23_01h13_51
出来上がりはこんな感じ。

が、これも非常に多くのパターンを登録しなければ精度は上がらないと思われます。
可能な限りエンティティは小さくまとめるようにしたほうが良いかと思います。

2017-12-23_01h16_20
ということで場所と予定の内容のエンティティの関連付けができました。

日付に関するエンティティの問題

肝心な日時が残っていますが、これが実はなかなか大変・・・。

LUISには、多くの方が利用するようなエンティティに関しては、学習済みの多くのプリセットエンティティが用意されており、日時についても相当するものがあります。

それが「Datetime」です。

2017-12-23_03h16_14

認識精度が高いことを期待して当初このPrebuiltエンティティを使っていたのですが、日本語で表現される日時のフォーマット(2018年1月22日15時など)はうまく認識してくれず、月だけエンティティ化されたり、時間だけエンティティ化されたりと最後まで癖がつかめませんでした
(諸先輩方、よい方法あったら教えてください)

故に最終的な結果もあまり芳しくない状態になってしまっています。無念。

2017-12-23_01h21_08
ということで、とりあえず日時の範囲だけでも正確に取得するために「日時」エンティティを作成し、関連付けしました。
このぼんやりとしたエンティティ・・・エンジニア的にはなんとも歯がゆいところであります・・・。
(推奨フォーマットはISO 8601 format)

インテントとエンティティの登録が終わったら学習させよう

2017-12-23_01h25_40
さて、インテントをいくつか(※)登録しエンティティとの関連付けが終わったら、登録したインテントをもとに学習させます。
画面右上の「Train](電車じゃない)をクリックします。

※長い文章、複雑な文章ほどインテントを多くしたほうが精度が上がります。

2017-12-23_01h26_04
学習が始まり・・・・

2017-12-23_01h26_11
学習が終了しました。

2017-12-23_01h26_24
では実際にきちんと認識してくれるか試してみましょう。
「Test」ボタンをクリックします。

2017-12-23_01h27_53
テスト用に文章を試すことができる入力フォームが現れるので、インテントを登録したように文章を入力してEnterキーを押します。
すると反転した文章が表示されます。
反転した文章の右下「Inspect」をクリックすると、入力したテストデータの解析結果を見ることができます。

今回は日時、場所、予定の内容について、日時のみ正確でした。
(個人的に内容は”パーティの打ち合わせ”まで認識してほしい・・・が登録インテント数が少ないので難しい)

2017-12-23_01h29_46
失敗した文章はインテントに登録し、エンティティの関連付けを行います。
これを繰り返して精度を上げていきます。
LUISはAPIも公開されているので、判定スコアの低い結果については自動的に学習用のインテントに取り込んで、管理者にエンティティ付けを行わせるようにすることで効率的に解釈の精度を上げていくことができるように思います。

2017-12-23_01h30_31
リトライ。正確に認識してくれました。
(インテントまんまだからそりゃそうでしょ・・・)

外部連携のためにアプリを公開

さて、ある程度学習とテストを繰り返したら、公開しましょう。
今回はこれだけでは終わりません。
入力はGoogle Homeからもらい、処理した後カレンダーの登録を終えなければいけないのですから!!!!

2017-12-23_01h31_18
画面右肩のタブ「PUBLISH」をクリックし、「Publish to production slot」をクリックします。

image
インテントの登録後学習が十分でないと、グレーアウトしてボタンが押せなくなっている場合がありますが、その場合は再度学習させましょう。

2017-12-23_01h31_30
公開プロセスが進み・・・

2017-12-23_01h31_33
公開プロセスが完了しました。

これで外部のサービスからも利用することができるようになりました。

2017-12-23_03h39_05
この時、後々サービス間での関連付けに必要になるKey情報をコピーしておきます。

2017-12-23_03h00_50_2
現在終わったのはこれだけ。
紙面の都合で、その他の部分は改めて別のエントリーとします。
(そもそもここまでの手順って、上述のブログであったような・・・orz)

Logic Appsで全体の処理の実装

さて、ではその他の仕組みと連携させましょう。
AzureのLogicAppsで全体を組み立てていきます。

2017-12-23_01h34_55
LogicAppsの手前、Google Homeから取得する、解析元になる文章は、WebHookでIFTTTから取得します。
そのあとの変数とか日時データの取得は苦し紛れの対応なので、とりあえず無視してください。
みなさんはもっとスマートな実装を目指しましょう。
(いい方法あったら教えてください)

2017-12-23_01h36_14
LUISの設定は、アクションの登録から「LUIS」で検索して「LUIS – Get prediction」を選択します。

2017-12-23_01h37_04
まずはどのLUISサービスに接続してアプリを利用するのか判別するために、先ほどコピーしておいたKey情報を「API Key」エリアに入力します。
「接続名」は任意の文字列でOK。
「作成」をクリックします。

2017-12-23_01h40_41
「LUIS – Get prediction」アクションが作成されると、また入力フォームが現れます。
「App Id」はクリックすると、Keyに紐づく先ほど作成したLUISアプリが表示されるので、選択するだけです。
「Utterance Text」は解析させたい文章・文字列なので、Webhookから送られてきた文章を指定します。
(本来はその他の処理も考えてJSONで送りたいところですが、今回は簡潔にするためプレーンテキストで送っているため本文をそのまま処理しています。)
「Desired Intent」は、処理させたいインテントを指定することができます。
前処理で確実に「予定の登録」インテントで処理することが分かっている場合はプルダウンから「予定の登録」を選択しても良いですが、通常はデフォルトの「Desired top scoring intent」が良いでしょう。

インテントが複数登録されていた場合、文章の解析中に判定されたスコア(最も適切なインテントである確率)が一番高いインテントが自動的に利用されます。

LUISの処理登録はこれだけです。

続いて、処理後の値をカレンダー登録させるための処理を設定します。

LUISで処理後は、以下のようなJSONが返っていています。

{“query”:”2018 年 1 月 22 日 16 時 名古屋 で 打ち合わせ”,”topScoringIntent”:{“intent”:”予定表の登録”,”score”:1.0},”intents”:[{“intent”:”予定表の登録”,”score”:1.0},{“intent”:”None”,”score”:0.008573224}],”entities”:[{“entity”:”2018 年 1 月 22 日 16 時”,”type”:”日時”,”startIndex”:0,”endIndex”:19,”score”:0.916193664},{“entity”:”名古屋”,”type”:”地名”,”startIndex”:21,”endIndex”:23,”score”:0.99994874},{“entity”:”打ち合わせ”,”type”:”予定の内容”,”startIndex”:27,”endIndex”:31,”score”:0.9998853}]}

2017-12-23_01h44_26
Logic Appsが解釈してオブジェクトごとに分けた結果

この中で必要なデータは、解析後のエンティティのカタマリなので、「Entities Array」を「For each」アクションでエンティティのアイテムごとに取得し、エンティティのtypeで判別してそれぞれ予め準備しておいた、エンティティに対応する変数へ格納します。
(もうちょっとスマートにならんかなコレ・・・)

2017-12-23_01h43_01

で、準備できたエンティティのデータをようやくカレンダーに登録します。

2017-12-23_01h44_43
が、ここで問題が・・・。
Google Homeからやってくる日付データはまんま日本語の文字列です。最近はある程度のフォーマットでまとまっている場合、うまいことパースしてくれるシステムもあるのですが、Logic Appsではそれがうまくできず、取得した日付データを用いてのカレンダ登録は断念しました。ぐぬぬぬ無念・・・

LUISを使うことが主テーマなので、自然言語処理で得られた日付のエンティティデータを後続処理でISO 8601規定に則ってフォーマットしてからGoogleのAPIに投げるなどしないといけないですね。

お恥ずかしながら、今回は現在時刻で「仮登録のスケジュール」として登録し、あとで予定表を手動で治すというクソオペレーションでお茶を濁すことと相成りました。
絶対直してやる。
(「終了時刻」と「開始時刻」の”デフォルト開”とか”デフォルト閉”は現在時刻の取得アクションから取得したデータを投げてるだけですスミマセン)

ということで、一通りの処理が作成できたのでテストです。
(例によって細かいチューニングはありますが・・・)

今回の動画で実際に取得したEntities Arrayのデータ。

Entities Array
[
{
“entity”: “2018 年 1 月 22 日 16 時”,
“type”: “日時”,
“startIndex”: 0,
“endIndex”: 19,
“score”: 0.916193664
},
{
“entity”: “名古屋”,
“type”: “地名”,
“startIndex”: 21,
“endIndex”: 23,
“score”: 0.99994874
},
{
“entity”: “打ち合わせ”,
“type”: “予定の内容”,
“startIndex”: 27,
“endIndex”: 31,
“score”: 0.9998853
}
]

で。登録された結果です。

2017-12-23_02h20_08

肝心の日付が・・・

さて、長くなりましたので、一旦本日はここでお開きといたします。

LUIS前後のLogic Appsの処理の解説や、日付処理のリベンジはまた改めてやりたいと思います。

長い時間お付き合いいただきありがとうございました。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です