駅すぱあと x Cognitive Services で 画像DE路線当てBOT を作ろう! [BOT実装(Node.js ver) 編]

  • 3
    Like
  • 0
    Comment

Microsoft Cognitive ServicesCustom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。また、駅すぱあとwebサービス は、国内初の乗換案内ソフト 駅すぱあと がもつ様々な情報をWebAPIで提供しているサービスです。

今回は Custom Vision Service で作成した画像判定エンジンを利用して、
鉄道の路線を画像から判別し、 駅すぱあとwebサービス を利用して路線に含まれる駅を検索し、それを返す BOT を作成する方法を紹介します。

Microsoft Cognitive Services の Custom Vision Service は、オリジナルの画像判定エンジンを作成して API で推定値を取得できるサービスです。
機械学習などで画像判定ロジックを構築しなくても、画像をアップロードしてタグ付けを行うことで、画像判定エンジンを構築できます。

駅すぱあとwebサービス は、国内初の乗換案内ソフト 駅すぱあと がもつ様々な情報をWebAPIで提供しているサービスです。
経路探索はもちろん、駅データや時刻表などの情報を取得することができます。
無料で使えるフリープランを用意しており、企業にかぎらず個人でもご利用いただけます。

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

駅すぱあと x Cognitive Services で 画像DE路線当てBOT を作ろう!
(1) Custom Vision 編
(2) BOT実装 編
- Node.js ver 編 ※このページ
- C# ver 編

Custom Vision 編、BOT実装 編を通して作成できる 画像DE路線当てBOT↓

スクリーンショット 2017-09-22 18.26.46.png

この資料では、 Bot Builder を用いてBOTを実装する部分を紹介します。

必要な環境、サブスクリプションの準備

開発環境

Bot Framework Channel Emulator

BOTの挙動をローカルで確認する際に必要となります。
https://github.com/Microsoft/BotFramework-Emulator/releases/ からダウンロードすることができます。

Node.js

Node.jsを利用してBOTを開発します。
https://nodejs.org/en/ からダウンロードすることができます。

ngrok

ローカルホストを外部からでも叩けるようにするツールです。
今回は、BOTを経由して Custom Vision API に 画像を添付してPOSTリクエストを行うのですが、その際に必要となります。
https://ngrok.com/download からダウンロードできます。

駅すぱあと Webサービス のアクセスキー

駅すぱあと Webサービス のフリープランをお申込みいただき、アクセスキーを取得してください。
https://ekiworld.net/service/sier/webservice/free_provision.html
(即時発行ではないので少しばかしお時間かかります・・すいませんorz)

Custom Vision 編 を実施していない場合

Custom Vision 編 を実施していない場合はMicrosoft アカウントを準備してください。([Custom Vision 編] と同じもので問題ありません。)

Microsoft アカウント

Cognitive Services (と必要に応じて Azure) サブスクリプションの申し込みに必要ですので、持っていない場合は取得しておきます。

Microsoft アカウント登録手続き

BOT アプリの実装

BOTを動かす

まず最初に、BOTが自分の環境でちゃんと動くかどうかを確認します。
BOTを実装する作業ディレクトリに移動し、以下のコマンドを実行してください。

$ npm init

ぽちぽちエンターを押して設定していきますが、entry pointの項目だけは、デフォルトではなくapp.jsに設定することをオススメします。
設定が完了したら、BOT開発に必要な以下のモジュールをインストールします。

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

作業ディレクトリ直下に、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);
});

ファイルを保存したら、挙動の確認を行います。
以下のコマンドを打ち込み、サービスを起動させます。

$ node app.js

サービスが起動すると、リクエストを受け付けるポート番号がターミナルに表示されます。

エミュレータを立ち上げます。
ウィンドウ上部のEnter your endpoint URLをクリックし、以下のURLを打ち込みます。
{{ポート番号}}の部分は、サービスを起動した時に表示されるポート番号に置き換えてください。

http://localhost:{{ポート番号}}/api/messages

Microsoft App IDMicrosoft App Passwordを指定する画面が表示されますが、それは無視して、Connectをクリックします。
メッセージ入力欄に適当に何かメッセージを書き込んでみましょう。
例えばHelloと入力したら、You said: Helloと返れば成功です。

20170901102355.png

Custom Vision の呼び出し

さて、自分の環境で無事にBOTが動かせることが確認できたら、Custom Vision APIをBOT内で呼び出してみましょう。

必要なモジュールをインストールします。

$ npm install --save dotenv request
  • dotenv...環境変数を扱う際に便利なモジュールです。
  • request...HTTPリクエストを扱う際に便利なモジュールです。

先ほど作成したapp.jsを開きます。
app.jsの上部に、app.js内でdotenvrequestが使用できるように、モジュールの読み込み記述を行います。

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

// 追記する部分 ここから
require('dotenv').config();
var request = require('request');
// ここまで

:
中略
:

画像をURLとして取得し、Custom Vision を呼び出します。
var bot = new builder.UniversalBot(connector, function (session) {から以下の行を、まるっと下記のコードに書き換えます。

:
中略
:

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

  // メディア(画像)が添付されているか
  if(session.message.attachments.length > 0) {

    var tag = ""; // 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 API へのPOSTリクエスト
    request.post(customVisionApiRequestOptions, function (error, response, body) {
      if(!error && response.statusCode == 200) {

        // Probability (≒信頼度) の高いものからリストされるため、0番目を取得
        tag = response.body.Predictions[0].Tag;

        session.send("%s に、いちばんにてるね!", tag);

      } else {
        console.log("error: " + error);
        session.send("はんていにしっぱいしちゃった。もういちどがぞうを送ってください。");
      }
    })
  } else {
    session.send("でんしゃのしゃしんを送ってね!");
  }
});

process.env.環境変数名で、指定された環境変数名から値を取得することができます。
Custom Vision API のPrediction-KeyやURLは、環境変数で扱いますので、環境変数に値をセットします。

app.jsと同じ階層に、.envという名前でファイルを生成し、その中に環境変数を記述していきます。

CUSTOM_VISION_API_URI={{ご自身の Custom Vision API のURL}}
CUSTOM_VISION_PREDICTION_KEY={{ご自身の Custom Vision API のPrediction Key}}

※windows10の場合、拡張子の前に名前がないとファイルを作らせてくれないようで、ファイルエクスプローラから.envファイルを新規作成できないことが分かりました。こちら( [Win10] Windows上で拡張子だけのファイルを作成する方法 )のページにコマンドプロンプトで作成する方法が挙げられています。

{{ご自身の Custom Vision API のURL}}{{ご自身の Custom Vision API のPrediction Key}}の値は、Custom Vision 編のAPIアクセス情報画面から取得できます。
Prediction URLの画面の、If you have an image URL:と書かれた方の値を利用します。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f37393537372f63373531333030352d333661342d643261322d363662392d3065346263383137366433312e706e67.png

挙動を確認します。
エミュレータを開き、メニューボタンから、App Settingsを開きます。

スクリーンショット 2017-09-22 18.34.06.png

ngrokをダウンロードしてzipファイルを解凍すると、ngrokという名の実行ファイルが入っています。
その実行ファイルが置いてあるパスを指定します。
Bypass ngrok for local addressesと書かれたチェックボックスがありますが、チェックを外してください(チェックを入れてしまうと、意図しないエラーが発生してしまいます)。
最後にSAVEをクリックします。

スクリーンショット 2017-09-22 18.54.45.png

サービスを起動します。

$ node app.js

入力エリアの画像アイコンをクリックして、分析した画像を選択し、BOTに送信すると、判定されたタグが返信されるのを確認してください。

スクリーンショット 2017-09-26 11.12.15.png

駅すぱあとwebサービス の呼び出し

画像を判定して取得できたタグに応じてメッセージを変更します。
今回は、 駅すぱあとWebサービス を利用して、路線名から停車駅を取得し、表示するようにします。

駅すぱあとwebサービスの詳しい使い方は、駅すぱあとwebサービス フリープラン ドキュメントに掲載しています。

路線名、停車駅はこちらのAPIを利用して取得できます。
今回は予め路線コードはアプリ内に保持し、停車駅のみをAPIで取得しています。
- 路線名の取得: http://api.ekispert.jp/v1/xml/operationLine?name=路線名&key=アクセスキー
- 停車駅の取得: http://api.ekispert.jp/v1/json/station?&operationLineCode=路線コード&key=アクセスキー

app.jsを編集します。
var bot = new builder.UniversalBot(connector, function (session) {から以下の行を、まるっと下記のコードに書き換えます。

:
中略
:

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

  // メディア(画像)が添付されているか
  if(session.message.attachments.length > 0) {

    var lineName = ""; // 駅すぱあと運航路線名
    var lineCode = ""; // 駅すぱあと運航路線コード
    var tag = ""; // custom vision で定義したカテゴリータグ
    var stations = []; // 路線停車駅情報(名前、緯度経度、都道府県..etc)リスト
    var stationNames = []; // 路線停車駅名リスト

    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 API へのPOSTリクエスト
    request.post(customVisionApiRequestOptions, function (error, response, body) {
      if(!error && response.statusCode == 200) {

        // Probability (≒信頼度) の高いものからリストされるため、0番目を取得
        tag = response.body.Predictions[0].Tag;

        switch (tag) {
          case "Chuo_Sobu":
            lineName = "JR中央・総武線各駅停車";
            lineCode = "110";
            break;
          case "Chuo_Ex":
            lineName = "JR中央線快速";
            lineCode = "109";
            break;
          case "Keihin-Tohoku":
            lineName = "JR京浜東北線";
            lineCode = "115";
            break;
          case "Tokaido":
            lineName = "JR東海道本線";
            lineCode = "117";
            break;
          case "Yamanote":
            lineName = "JR山手線";
            lineCode = "113";
            break;
          case "Yokosuka_SobuEx":
            lineName = "JR横須賀線";
            lineCode = "116";
            break;
        }

        session.send("%s に、いちばんにてるね!", lineName);

        var ekispertRequestOptions = {
          url: encodeURI("https://api.ekispert.jp/v1/json/station?operationLineCode=" + lineCode + "&key=" + process.env.EKISPERT_ACCESS_KEY),
          json: true
        }

        // 駅すぱあとWebサービスへのGETリクエスト
        request.get(ekispertRequestOptions, function(error, response, body) {
          if (!error && response.statusCode == 200) {

            session.send("%s は、以下のえきをはしるよ。", lineName);

            stations = response.body.ResultSet.Point;

            // 駅名称を列挙した配列を作成
            for(var i=0; i<stations.length; i++) {
              stationNames.push(stations[i].Station.Name);
            }
            session.send("%s", stationNames.join(' → '));
          } else {
            console.log("error: " + error);
          }
        })
      } else {
        console.log("error: " + error);
        session.send("はんていにしっぱいしちゃった。もういちどがぞうを送ってください。");
      }
    })
  } else {
    session.send("でんしゃのしゃしんを送ってね!");
  }
});

.envファイルに、 駅すぱあとwebサービス のアクセスキーを追加します。

:
中略
:

EKISPERT_ACCESS_KEY={{ご自身の 駅すぱあとwebサービス のアクセスキー}}

{{ご自身の 駅すぱあとwebサービス のアクセスキー}}は、駅すぱあとwebサービスにアクセスする為に必要な値です。
駅すぱあとwebサービス フリープラン 申し込みページから無料でお申し込みいただけます。

挙動を確認します。

$ node app.js

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

スクリーンショット 2017-09-22 18.26.46.png

Appendix

完成形のソースコードを GitHub にて公開しました。
https://github.com/hmaruyama/train-finder-bot

終わりに

様々なサービスを組み合わせることで、簡単にBOTを実装することができました。
作成したBOTをベースに、画像判定の精度をあげるもよし、対応路線のバリエーションを増やすもよし、会話のバリエーションを増やすもよし・・。
パブリックに公開するなら、こちらの手順(Microsoft Bot Framework、Azureを利用したBot開発!Bot作成からWeb上で公開するまで - hi, hikaru)が参考になると思います。