人工知能パーツ Microsoft Cognitive Services で食べ物画像判定 BOT を作ろう！ [Bot Framework (Node.js) 編]

Cognitive Services の Custom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。機械学習などで画像判定ロジックを構築しなくても、画像をアップロードしてタグ付けを行うことで、画像判定エンジンを構築できます。
今回は Custom Vision Service で作成した画像判定エンジンを利用して、画像を判定した結果によって異なる返答を返す Bot を作成する方法を紹介します。

Cognitive Services Custom Vision (Prediction API) を HTTP リクエスト(POST)で利用します。

この BOT アプリは Bot Framework Channel Emulator を使ってローカル環境で稼働確認することが可能です。また、Web 公開 & Bot Framework に登録すると、埋め込み可能な Web Chat が利用できます。

>**人工知能パーツ Microsoft Cognitive Services で食べ物画像判定 BOT を作ろう！**
>(1) [Custom Vision 編](http://qiita.com/annie/items/293525901020685ad5f6)
>(2) Bot Framework 編 : [C# 版](http://qiita.com/annie/items/bd232f67e06c83586ca5) / Node.js 版 ※このページ

Custom Vision 編、Bot Framework 編を通して作成できる食べ物画像判定 BOT↓
![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_04-e1512022726111.png)

#Bot Framework & Cognitive Services 利用に必要な環境、サブスクリプションの準備

##開発環境
###Node.js 環境、Visual Studio Code (or お好みの開発ツール)、Bot Framework Channel Emulator & ngrok
- Node.js
 - https://nodejs.org/en/ からダウンロードすることができます。
- 開発ツール
 - お好みの開発ツールがなければ、無償の Visual Studio Code でOKです。[Visual Studio サイト](https://www.visualstudio.com/) から Visual Studio Code を選択してダウンロード＆インストールします。
- [Bot Framework Channel Emulator (Windows版)](https://aka.ms/bf-bc-emulator) ※Mac/Linux は [Console版](https://aka.ms/bfemulator) をご利用ください
>Bot Framework Node.js 版 には C# 版のようなテンプレートはありません。

- ngrok
 - Bot (Webアプリ) をローカルで稼働させたまま、あたかもパブリックな場所に公開しているように偽装できるツールです。[ngrok のダウンロードサイト](https://ngrok.com/download) からダウンロードして、解凍して適当な場所に配置しておきます。Bot Framework Emurator の App Settings で ngrok を指定します。

>ngrokの配置先を [Path to ngrok] に記載します。
また、ローカル環境でテストを行っている場合、ユーザーが送信する画像URLを "localhost" として取得してしまいます。localhost をパブリックアドレスに偽装するため、*Bypass ngrok for local addresses* の✓(チェック)を外しておきます。
>![](https://msdnshared.blob.core.windows.net/media/2018/03/bluesky_20180321_01-300x225.png)

#BOT アプリの実装
##Bot アプリケーションの作成
BOTを実装する作業ディレクトリに移動し、以下のコマンドを実行してアプリケーションを作成します。
entry pointはデフォルトではなく app.js に設定します。

```
$ npm init
```
設定が完了したら、BOT開発に必要な以下のモジュールをインストールします。

```
$ npm install --save botbuilder restify
```

>botbuilder: MicrosoftBotFrameworkで動くBOTを作成できるSDKです。
restify: REST形式のwebサービス構築に特化したフレームワークです。

作業ディレクトリ直下に、app.js ファイルを作成します。app.js に以下のコードをコピーします。
これでが Bot Framework による Bot アプリケーションの "Hello World" 的な動作確認を行うことができます。

```app.js
var restify = require('restify');
var builder = require('botbuilder');

var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
   console.log('%s listening to %s', server.name, server.url); 
});

var connector = new builder.ChatConnector({
    appId: process.env.MICROSOFT_APP_ID,
    appPassword: process.env.MICROSOFT_APP_PASSWORD
});

server.post('/api/messages', connector.listen());

var bot = new builder.UniversalBot(connector, function (session) {
    session.send("You said: %s", session.message.text);
});
```
>ユーザーのメッセージは session.message として取得できます。

*app.js* ファイルを保存しておきます。
Bot アプリケーションの動作確認を行います。以下のコマンドでサービスを起動します。

```
$ node app.js
```

Bot Framework Channel Emulator を起動してアクセスを行います。
Bot Framework Channel Emulator の上部中央にある Bot Url に、http://localhost:ポート番号/api/messages と入力します。(ポート番号は、起動しているサービスに表示されているものを使用してください)

メッセージ入力欄に何か入力すると「You said: (入力したメッセージ)」という返答がBotから送られてくれば OK です。

![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_01-e1512022623530.png)

##会話のハンドリングの記述 (基本)
以下のコマンドでモジュールを追加します。

```
$ npm install --save dotenv request
```

>dotenv: 環境変数を扱う際に便利なモジュールです。
request: HTTPリクエストを扱う際に便利なモジュールです。


先ほど作成した *app.js* を開きます。
冒頭に、app.js 内で dotenv、requestが使用できるように、モジュールを読み込むコードを追加します。

```app.js
var restify = require('restify');
var builder = require('botbuilder');
// モジュール追加
require('dotenv').config();
var request = require('request');
```

会話をハンドリングするコードを追加していきます。
`var bot = new builder.UniversalBot(connector, function (session) { ・・・ }});`　の中身を以下のように書き換えます。

```app.js
var bot = new builder.UniversalBot(connector, function (session) {

    // デフォルトのメッセージをセット
    var msg = "こんにちは！ドリンクおすすめ Botです。食べ物の写真を送ってね♪";

    // 画像が送られてきたか確認
    if (session.message.attachments.length > 0) {
        // 変数定義
        // Custom Vision を使う準備        
        // Custom Vision を呼び出してタグを取得
            // 結果取得OKの場合
                // food タグ および カテゴリーを取得
                // 取得したタグに対応してメッセージをセット
                // メッセージ送信
            // 結果取得NGの場合
    // ※次以降の項目で作成します

    // 画像がない場合
    } else {
        session.send(msg);
    }

});
```
>デフォルトのメッセージとして msg を設定し、ユーザーの入力に対して msg の内容を返答します。
また、ユーザーから送られてくる画像は session.message.attachments として取得できます。


###Custom Vision を呼び出す準備
作業フォルダー(app.jsと同じ階層)に .env ファイルを作成し、以下の内容を記載します。


```.env
CUSTOM_VISION_API_URI="YOUR_ImageUrlPREDICTION_URL"
CUSTOM_VISION_PREDICTION_KEY="Prediction-Key"
```

YOUR_ImageUrlPREDICTION_URL には Custom Vision の Prediction URL 表示画面で表示される imageURL 判定用の URL、YOUR_PREDICTION_KEY には Prediction-Key をコピーします。
[Custom Vision 編](http://qiita.com/annie/items/293525901020685ad5f6#%E7%94%BB%E5%83%8F%E5%88%A4%E5%AE%9A%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B) の API アクセス情報画面から取得できます。Prediction が [Already Default] になっているのを確認してください。
![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_02-e1512022681472.png)


app.js に Custom Vision の画像分析エンジンを呼び出すための設定を追加していきます。

```app.js
var bot = new builder.UniversalBot(connector, function (session) {

    // デフォルトのメッセージをセット
    var msg = "こんにちは！ドリンクおすすめ Botです。食べ物の写真を送ってね♪";

    // 画像が送られてきたか確認
    if (session.message.attachments.length > 0) {
        // 変数定義        
        var tag = "";      // 食べ物カテゴリータグ
        var food = false;  // "food" タグの有無

        // Custom Vision を使う準備
        var customVisionApiRequestOptions = {
            uri: process.env.CUSTOM_VISION_API_URI,
            headers: {
                "Content-Type": "application/json",
                "Prediction-Key": process.env.CUSTOM_VISION_PREDICTION_KEY
            },
            json: {
                "Url": session.message.attachments[0].contentUrl
            }
        };

        // Custom Vision を呼び出してタグを取得
            // 結果取得OKの場合
                // food タグ および カテゴリーを取得
                // 取得したタグに対応してメッセージをセット
                // メッセージ送信
            // 結果取得NGの場合
    // ※次以降の項目で作成します

    // 画像がない場合
    } else {
        session.send(msg);
    }

});
```



###Custom Vision の呼び出し、結果の取得
画像を URL として取得し、Custom Vision を呼び出します。Probability (≒信頼度) の高いものから参照されるため、Probability > 0.8 となるタグのみを取得します。"food" とそれ以外のタグ (のうち信頼度が一番高いもの) は分けてセットします。

```app.js
var bot = new builder.UniversalBot(connector, function (session) {
    :
    中略
    :
        // Custom Vision を呼び出してタグを取得
        request.post(customVisionApiRequestOptions, function (error, response, body) {

            // 結果取得OKの場合
            if (!error && response.statusCode == 200) {

                // food タグ および カテゴリーを取得
                if (response.body.predictions[0].tagName != "food") {
                    if (response.body.predictions[0].probability > 0.8) {
                        tag = response.body.predictions[0].tagName;
                    }
                } else {
                    if (response.body.predictions[0].probability > 0.8) {
                        food = true;
                    }
                    if (response.body.predictions[1].probability > 0.8) {
                        tag = response.body.predictions[1].tagName;
                    }
                }

                // 取得したタグに対応してメッセージをセット
                // メッセージ送信

            // 結果取得NGの場合
            } else {
                console.log("error: " + error);
                session.send("画像を判定できませんでした。もう一度食べ物の写真を送ってね♪");
            }
        });

    // 画像がない場合
    } else {
        session.send(msg);
    }

});
```



###返答の作成
以下のロジックでメッセージをセットします。
 - "food"＞0.8 & "tag"(他のタグ)＞0.8 → ("food" 以外の)タグの食べ物
 - "food"＞0.8 & "tag"(他のタグ)≦0.8 → "食べ物
 - すべてのタグ≦0.8 → 食べ物でないと判定

```app.js
var bot = new builder.UniversalBot(connector, function (session) {
    :
    中略
    :
                // 取得したタグに対応してメッセージをセット
                if (tag != "") {
                    msg = "この写真は " + tag + " だね♪";
                }
                if (tag == "" && food == true) {
                    msg = "この食べ物は分からないです．．．日本の冬は番茶だね！";
                }

                // メッセージ送信
                session.send(msg);

            // 結果取得NGの場合
            } else {
                console.log("error: " + error);
                session.send("画像を判定できませんでした。もう一度食べ物の写真を送ってね♪");
            }
        });

    // 画像がない場合
    } else {
        session.send(msg);
    }

});
```

忘れずに *app.js* を保存しておきます。


##アプリケーションの動作確認
ここで一旦 BOT の動作確認を行います。Node.js のサービスを起動して、Bot Framework Channel Emulator の上部中央にある Bot Url に、http://localhost:ポート番号/api/messages と入力して接続します。(ポート番号は、起動しているサービスに表示されているものを使用してください)

何か文字を入力して送信すると、msg で指定したデフォルトの回答が返信されることを確認してください。
入力エリアの画像アイコンをクリックして、分析した画像を選択、BOT に送信すると、判定されたタグ、または判定できなかったメッセージが返信されるのを確認してください。

![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_03-e1512022702307.png)


##会話のハンドリングの記述 (応用)
画像を判定して取得できたタグに応じてメッセージを変更します。

```app.js
var bot = new builder.UniversalBot(connector, function (session) {
    :
    中略
    :
                // 取得したタグに対応してメッセージをセット
                // if (tag != "") {
                //     msg = "この写真は " + tag + " だね♪";
                // }
                switch (tag) {
                    case "curry":
                        msg = "カレーおいしそう！甘いチャイでホッとしよう☕";
                        break;
                    case "gyoza":
                        msg = "やっぱ餃子にはビールだね🍺";
                        break;
                    case "pizza":
                        msg = "ピザには刺激的な炭酸飲料★はどうかな？";
                        break;
                    case "meat":
                        msg = "肉、にく、ニク♪ 赤ワインを合わせてどうぞ🍷";
                        break;
                    case "ramen":
                        msg = "やめられないよねー。ラーメンには緑茶でスッキリ☆";
                        break;
                    case "sushi":
                        msg = "今日はちょっとリッチにお寿司？合わせるなら日本酒かな🍶";
                        break;
                }

                if (tag == "" && food == true) {
                    msg = "この食べ物は分からないです．．．日本の冬は番茶だね！";
                }
    :
    後略
    :
```

#アプリケーションの動作(再)確認
Nodeのサービスを起動したら、Bot Framework Channel Emulator からアクセスを行います。

入力エリアの画像アイコンをクリックして、分析した画像を選択、BOT に送信します。
設定したメッセージが返答されたら完成です。

![](https://msdnshared.blob.core.windows.net/media/2017/11/bluesky_20171130_04-e1512022726111.png)


#Appendix
完成形のソースコードを GitHub にて公開しました。
https://github.com/a-n-n-i-e/CognitiveCustomVision-DrinkPairingBot/tree/master/NodeJS