まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:bluemix

久しぶりに The Weather Company API を使ってみました。なお以下で紹介するサンプルのソースコードはこちらで公開しています:
https://github.com/dotnsf/twc_api

2018081400


使い方は Node.js が導入されているシステムに git clone(またはダウンロード&展開)して、npm install して、node app.js します。成功すると "server starting on XXXX ..." というメッセージが表示されます(この XXXX が動的に決まるポート番号です。以下の例では 6039 番):
$ npm install
$ node app.js
server starting on 6039 ...

ウェブブラウザで上記のポート番号を指定してサンプルアプリケーションに接続します。成功すると東京周辺の(OpenStreetMap の)地図が表示されます:
2018081401


2018/08/14 時点では、このサンプルアプリケーションで3つの The Weather Company API を試すことができ、それぞれ画面上部にある3つのボタンで実行可能です:
2018081402


一番左の "alertsByCoundryCode" ボタンは GET /v1/country/{countrycode}/alerts.json を countrycode = "US" で実行します。つまり "US"(アメリカ合衆国)で現在発生している天候に関する警報の一覧を取得します:
https://twcservice.mybluemix.net/rest-api/#!/Weather_Alerts/v1ccalertheadln


このサンプルアプリケーションでは上位30個の警報を取り出して、その発生位置にマーカーを置き、各警報の内容を参照できるようにしています:
2018081403
(↑この API は以前には存在してなかったような・・・)


真ん中の "currentConditions" ボタンをクリックすると、そのタイミングで地図の中心にある地点を座標を使って GET /v1/geocode/{latitute}/{longitude}/observations.json を実行します。地図の中心にある地点の現在の天候の様子を取得して表示するので、最初に地図をある程度スクロール(マウスドラッグでスクロールします)してから実行し、このサンプルアプリケーションではその情報の一部を表示します:
2018081404


一番右の "historicalData" ボタンをクリックすると、そのタイミングで地図の中心にある地点を座標を使って GET /v1/geocode/{latitute}/{longitude}/observations/timeseries.json を実行します。地図の中心にある地点の過去 24 時間の天候の移り変わりの様子を取得します。このサンプルアプリケーションでは過去 24 時間の気温の移り変わりを表示するようにしています:
2018081405


実装方法など、詳しくは Github 上のソースコードと、The Weather Company API の API Reference を参照ください。

以前に express-ipfilter ライブラリを使って、Node.js アプリの IP アドレスフィルタリングを行うサンプルを紹介しました:
http://dotnsf.blog.jp/archives/1066182158.html

↑ここで紹介したサンプルは一応動くものですが、アプリケーションを IBM Cloud の Cloud Foundry アプリとしてデプロイすると( IP アドレスフィルタリングが)正しく動かないことがわかりました。原因は Cloud Foundry 内のルーティングで x-forwarded-for ヘッダの情報が変わってしまい、正しい IP アドレスを取得できなくなってしまうようでした。

IBM Cloud の Cloud Foundry 環境でもこの IP アドレスフィルタリングを有効にするには、フィルタリングを行う前に Express() の use メソッドを使って、
app.use( 'trust proxy', true );

を呼び出してからフィルタリングを行う必要があります。

(解説)
http://expressjs.com/ja/api.html



 

IBM Cloud から提供されているコグニティブエンジン IBM Watson を使って、
 1. MNIST の手書き数字サンプルデータを学習させて、
 2. 実際に手書き数字データを送信して、認識させる
という、「学習」と「問い合わせ」のコグニティブエンジン一連の作業を再現させてみます(した)。


今回紹介する一連の作業では、IBM Cloud の以下のサービスを連動させて使います:
 ・IBM Watson Studio
 ・IBM Machine Learning
 ・IBM Cloud Storage
 ・SDK for Node.js ランタイム(上記2のサンプルをクラウド上で稼働させる場合)

以下で紹介する手順は IBM Cloud の無料版であるライトアカウントを使っても同様に動かすことができるようにしているので、興味ある方は是非挑戦してみてください。


1. MNIST の手書き数字サンプルデータを学習させる

人工知能とか機械学習とかを勉強していると、そのチュートリアルとして "MNIST" (Modified National Institute of Standards and Technology)を目にする機会があると思っています。機械学習のサンプルとして手書きで描かれた数字の画像データと、そのラベル(何の数字を描いた画像なのか、の答)が大量にサンプルデータとして公開されており、機械学習を説明する際の様々な場面で使われています:
2018050800


今回、この MNIST データを IBM Watson StudioIBM Watson Machine Learning を使って学習させ、かつ問い合わせ用の REST API を用意します。

・・・と、偉そうに書いていますが、この部分の手順については私の尊敬する大先輩・石田剛さんが Qiita 上でわかりやすく紹介していただいています。今回の学習部分についてはこの内容をそっくりそのまま使わせていただくことにします(石田さん、了承ありがとうございます):
Watson Studioのディープラーニング機能(DLaaS)を使ってみた 

2018050801

↑この作業で MNIST の手書き画像を IBM Watson Machine Learning を使って学習させ、その問い合わせ API を REST API で作成する、という所までが完了します。


2. 手書き数字データを送信して、認識させる

マウスやタッチ操作で画面に手書き数字を描き、その内容を 1. の作業で用意した REST API にポストして何の数字と認識するか、を確認できるようなアプリケーションを作成します。

・・・というか、しました(笑):
2018050804


PC またはスマホでこちらのサイトにアクセスすると体験できるようにしています:
https://dotnsf-fingerwrite-mnist.us-east.mybluemix.net/


フロントエンドはもともと以前に「イラツイ」という手描きイラスト付きツイートサービスを作った際のものを丸パク応用し、問い合わせ API を呼び出すバックエンド部分はデプロイしたモデルの Implementation タブ内にある JavaScript の Code Snippets を参考に作りました。この Code Snippents は各種言語のサンプル(アクセストークンを取得してエンドポイントにリクエストするサンプル)が用意されていて、とても便利です:
2018050809


アプリケーションの使い方はマウスまたは指でキャンパス部分に数字を描いて、"fingerwrite" ボタンを押すと、その描いた数字データを上記 1. で作成した REST API を使って識別し、最も可能性が高い、と判断された数値とその確率が表示される、というものです:
2018050805


PC 画面の場合に限りますが、デバッグコンソールを表示した状態で上記を実行すると、可能性が最も高いと思われた結果だけでなく、全ての数値ごとの確率を確認することもできます:
2018050806

↑常に「2」の確率が高くなってる気がする。。原因は学習の調整不足だろうか??それともデータを渡すフロントエンド側??(2018/May/09 ピクセル毎のデータを取り出すロジックに不具合があったので、修正しました)


なお、この 2. のサンプルアプリは Node.js のソースコードを公開しているので、興味ある方は自分でも同様のサイトを作成してみてください:
https://github.com/dotnsf/fingerwrite-mnist

2018050807


このソースコードから動かす場合、事前に settings.js ファイルを編集しておく必要があります:
2018050808


まず上の3つ、 exports.wml_url, exports.wml_username, exports.wml_password の3つの変数の値は 1. で MNIST データを学習した際に使った IBM Watson Machine Learning サービスのサービス資格情報を確認して、その中の url, username, password の値をそれぞれコピー&ペーストしてください(最初の exports.wml_url だけはおそらくデフォルトで url の値になっていると思います。異なっていた場合のみ編集してください):
2018050803


また一番下の exports.ws_endpoint の値は同様に 1. で使った IBM Watson Studio の Web サービスのエンドポイント(学習モデルをデプロイした時に作成した Web サービス画面の Implementation タブから確認できる Scoring End-point の値)をそのまま指定します:
2018050802


ここまでの準備ができた上でアプリケーションを実行します。ローカル環境で動かす場合は普通に npm install して node app で起動します:
$ npm install
$ node app

IBM Cloud (の SDK for Node.js)を使って動かす場合は、cf ツールbx ツールを使って、そのまま cf push で公開されます:
$ cf push (appname)


今回紹介した方法では IBM Watson Studio と IBM Watson Machine Learning を使って画像データを学習させ、その学習結果に対して REST API で問い合わせをする、という機械学習の一連の流れを体験できます。また学習データ(とモデリング)を変更することで、異なる内容の学習をさせる応用もできますし、学習した内容に問い合わせを行う API も自動生成されるので、フロントエンドの開発も非常に楽でした。
 

 は IBM CloudantApache CouchDB と API 互換のある NoSQL データベースです。JavaScript で操作することができます。npm を使ってサーバーサイドで動かすこともできますが、ブラウザから JavaScript ライブラリをロードして、個々のブラウザ内で使うことも可能です。

特にこれをブラウザから使う場合、マスターデータはクラウドの IBM Cloudant で保持しつつ、必要なデータをユーザーのブラウザと同期して、ほぼすべての処理をローカルブラウザ内で完結させる(=時間のかかるDBアクセスをローカルDBだけを対象に行えばよくなるので、アプリケーションとしての安定性やパフォーマンス向上も期待できる)、ということが可能になります。とても強力な同期機能を持ったデータベースエンジンと言えます。
2018040900


この「同期」を具体的にどうやるか、という内容が今回のブログエントリです。


今回の説明では、サーバー側の DB を IBM Cloudant で用意することにします。IBM Cloud のライトアカウントを作成すると容量 1GB まで使えるライトプランの DBaaS が無料で用意できます。

で、この Cloudant DB をブラウザに同期・・・するわけですが、IBM Cloudant を使う場合はその前に1つ設定が必要です。標準状態の IBM Cloudant はクロスオリジンからのアクセスを許可していません。そのため、標準設定のままウェブブラウザから同期をとろうとするとクロスオリジンアクセスになってしまい、エラーとなってしまいます。したがってクロスオリジンアクセスを許可するよう、設定を変更する必要があります。詳しくはこちらにも記載していますが、curl コマンドと IBM Cloudant の API を使って IBM Cloudant の CORS アクセスを有効にするための設定を行います:
$ curl -i -u 'db_username:db_password' -XPUT 'https://db_username.cloudant.com/_api/v2/user/config/cors' -H 'Content-type: application/json' -d '{"enable_cors":true,"allow_credentials":true,"origins":["*"]}'

↑db_username, db_password は IBM Cloudant のインスタンスにアクセスするためのユーザー名およびパスワードです。


次にブラウザ内の JavaScript で DB の同期を行います。普通に「データベースそのものの同期をとる」のであれば話は単純で、以下のようなコードを記述するだけです:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

remoteDB.sync( localDB, {
live: true, retry: true });
: :

まず CDN を指定して PouchDB のライブラリをロードします。そして IBM Cloudant のユーザー名(db_username)とパスワード(db_password)を指定して、'testdb' という名前のデータベースインスタンスを remoteDB 変数に代入します。これで IBM Cloudant 上のリモートデータベースを remoteDB から操作できるようになりました。同時にローカルブラウザ内に(同じ名前の)'testdb' という名前のデータベースインスタンスを作って localDB 変数に代入しています(こちらは作成時点では空です)。 この2つのリモート/ローカルデータベースを sync() 関数で同期するように指定しています。これによってこれら2つのデータベースインスタンス変数の内容は自動同期され、一方(例えばブラウザ内の localDB)に変化が起こるともう一方(サーバーの remoteDB)にその変化内容が勝手に反映される、という仕組みが実現できます。

ちなみに sync() 実行時の live:true オプションはリアルタイム同期の指定で、retry:true オプションは一度接続が切れた後に自動的にリトライして接続が戻った時に同期も復活させるための指定です。ここまでは超簡単です。


さて、本来やりたかったのはデータベースをまるごと同期するのではなく、データベースの一部だけを同期する、というものです。上記例だと remoteDB はサーバー側のものなので、全部で数ギガバイトになったりそれ以上になったりすることも想定しないといけないのですが、localDB はブラウザ内で作るものなのであまり大きくなっては困ります。そこで(一般的には部分同期とか Partial Sync とか呼ばれる方法で)特定条件を満たす一部の文書だけを対象にローカルに同期し、ローカルでの変更・追加・削除といった処理をサーバー側のマスターに同期し直す方法を紹介します。

PouchDB にも部分同期機能は存在しています。ただそこで「この条件を満たす文書だけ」を指定する方法がかなり限られていて、現時点では文書 ID を配列で指定する方法しかないように見えます(このフィールドがこの値で・・・みたいなクエリーではできないっぽい)。具体的にはこんな感じ:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

var doc_ids = [ 'xxx', 'yyy', 'zzz' ];  //. 同期対象文書の _id 値配列

remoteDB.sync( localDB, {
doc_ids: doc_ids,
live: true, retry: true }); : :

上記例では 'xxx', 'yyy', 'zzz' という3つの文書ID(Cloudant 内だと文書の _id の値)を指定して、sync() 関数を doc_ids パラメータで配列指定しています。これだけで localDB には指定された3文書だけが同期され、削除を含めた変更があるとリモートにも即時に反映されるようになります。

したがって実装する場合はページロード時の最初に IBM Cloudant に対してクエリー API を実行して同期の対象となる文書(の ID 配列)をまとめて取得し、その ID 配列を指定して PouchDB と部分同期する、という流れになると思っています。この方法なら最初のロード時のネットワーク接続は必須になりますが、どのみちページをロードするにもネットワークは必須だし、同期をとった後はネットワークが切れても平気、というシステム構成が可能になります。


なお、一点だけ注意が必要なことがあります。この方法で部分同期した後に localDB に新たに文書を追加した場合です。部分同期の条件は doc_ids に指定された文書ID配列だったので、ここに含まれない新しい文書を追加してもサーバー側には同期されません。その場合は新たに doc_ids を指定しなおして(新しい文書の ID を追加して)改めて sync() 関数を実行する必要があります:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

var doc_ids = [ 'xxx', 'yyy', 'zzz' ];

remoteDB.sync( localDB, {
doc_ids: doc_ids, live: true, retry: true }); : : function add( doc ){ //. ドキュメントを追加する処理 localDB.put( doc ).then( function( res ){ //. localDB に文書(doc)追加 doc_ids.push( doc._id ); //. 文書の _id 値を配列に追加 remoteDB.sync( localDB, { //. sync() 再実行 doc_ids: doc_ids, live: true, retry: true }); }).catch( function( err ){ console.log( err ); }); }

例えば上記例では add( doc ) 関数の中で doc に指定されたオブジェクトを localDB に追加する想定で処理を記述していますが、localDB.put() が成功したら doc_ids 配列を更新した上で remoteDB.sync() を再実行して同期条件を変えるようにしています。

現実問題としては追加なのか更新なのか(どちらも put() 関数を使う)、更新だとすると _rev の値も必要になって・・・とか、多少細かい実装が必要になることも事実ですが、一応これだけでローカルDBとその部分同期を使ったアプリの実装はできることになります。

問題は上述したローカルDBに同期したい文書の ID をどうやって調べるかですが、そこはやっぱり一度サーバーにクエリー投げるしかないのかなあ・・・


なお、PouchDB の API Reference はこちらを参照ください:
https://pouchdb.com/api.html


IBM Cloud(Bluemix) から提供されている NoSQL データベースの DBaaS である Cloudant は読み書きのための REST API が公開されています。各種プログラミング言語から HTTP ベースの API を実行してデータを読み書きすることが可能です。

ただ、これらの API には一般的な CORS(Cross-Origin Resource Sharing) の制限がかかっており、ウェブブラウザの JavaScript からは読み書きができないように設定されています。この CORS 制限を無効にする方法が分かったので、ブログで紹介する形で手順等を紹介します。

まず今回ブラウザからアクセスする対象とするデータベースをこちらとします。pouchdb というデータベースで、現在4件のドキュメントが登録されています:
2018040900


このデータベースに簡単にアクセスするため、今回は PouchDB ライブラリを使うことにします。PouchDB は軽量かつ CouchDB(Cloudant) 互換のデータベースです。この PouchDB を CDN(//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js) からロードして、データベースオブジェクトを作り、その中の全文書を取り出して表示する、という処理を実装すると↓のような感じになります:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>CORS check</title>
<script src="//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var cloudant_db_url = 'https://USERNAME:PASSWORD@USERNAME.cloudant.com/pouchdb';

var db = new PouchDB( cloudant_db_url );

db.allDocs( { include_docs: true } ).then( function( docs ){
  console.log( docs );
}).catch( function( err ){
  console.log( 'error' );
});
</script>
</head>
<body>
</body>
</html>

主な処理内容を簡単に解説します。まず CDN から PouchDB ライブラリをロードし、Cloudant のデータベース URL を指定して、データベースオブジェクトを作ります(上記の USERNAME は Cloudant のユーザー名、PASSWORD は同パスワードです)。そして allDocs() メソッドで全文書を取り出して結果を console.log() でコンソールに表示する、という内容の JavaScript を含む HTML になっています。


この HTML を HTTP サーバー上に配置してウェブブラウザからアクセスします。取得結果はコンソールに表示されるので、あらかじめコンソール画面を表示(FireFox であれば F12 キー)しておきます。その状態でブラウザから同ページにアクセスすると・・・コンソールには「クロスオリジン要求・・」というエラーが表示されます。これはつまり Cloudant 側でクロスオリジンからのアクセスを許可していないため、アクセスは拒絶され、そのエラーが表示されています。これが Cloudant のデフォルトでの挙動です:
2018040901


では Cloudant の CORS アクセス(クロスオリジンからのアクセス)を有効にしてみます。curl コマンドの使えるターミナルから、以下のコマンドを実行します:
$ curl -i -u 'USERNAME:PASSWORD' -XPUT 'https://USERNAME.cloudant.com/_api/v2/user/config/cors' -H 'Content-type: application/json' -d '{"enable_cors":true,"allow_credentials":true,"origins":["*"]}'

このコマンドでは認証用の ID とパスワード、HTTP ヘッダの Content-Type: application/json を指定し、/_api/v2/user/config/cors パスに対して、CORS アクセスを有効にするようデータを POST して実行しています:

2018040902


上記のように {"ok": true} という結果が返ってくればコマンドは成功し、クロスオリジンからのアクセスも許可されています。試しに再度同じ HTML ページを(リロードするなどして)表示すると、今度は allDocs() メソッドが成功し、期待通りに(4件の)データを取得し、コンソールに表示できているはずです:
2018040903


これで Cloudant の API をブラウザ(の JavaScript )からも直接実行する術が確保できました。これでウェブブラウザの HTML から直接 Cloudant を操作したり、ウェブブラウザ内の PouchDB と連携することもできるようになります。



(参考)
How to use CORS with a Cloudant account

CORS


このページのトップヘ