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

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

2015年12月

先日紹介した、この↓ IBM Watson の画像認識機能(のバージョンアップ)を Java で実装して、マンホールマップに組み込んでみました:
IBM Watson の Visual Recognition(画像認識)サービスにマンホール画像を学習させる


実を言うと、この画像認識機能自体は以前からマンホールマップのベータ機能として実装していました。が、期待通りの認識結果にならず、むしろ自虐的な「ネタ機能」としての位置付けだったのですが、上記ページで紹介した学習機能のバージョンアップとその実装により、とりあえず「マンホール画像」だと認識できそうなレベルにすることができたので、その報告です。

まず、実際の認識結果が今回の改良前後でどのように変わったかを紹介します。試したのはこのつくば市のスペースシャトル・マンホール蓋画像です:
2015122201


以前のバージョン(v1)でこの画像を認識させた時の結果がこちらでした。「Brown(茶色)」とか「Food(食べ物)」とか「Arthropod(節足動物)」とか・・・ Brown はともかくとして、それ以外に正解といえるものがないような状態でした。もちろん「マンホール」とは判定されていません:

2015122202


このロジックを変更して、前回紹介した方法でマンホールを学習させた上で新しい v2 の API を呼び出すようにした結果がこちらです。1位は見事「Manhole(マンホール)」、それ以外にも「Carpenter_Plane(手作り飛行機)」とか近い結果も出ていますし、「Catacomb(カタコンブ=地下納骨堂)」のようなマニアック(笑)な学習もされている様子が確認できます:

2015122203


この機能はマンホールマップの個別ページで確認することができます。上記のスペースシャトル蓋であればこれです:
http://manholemap.juge.me/page.jsp?id=84004


PC版であれば画面右下の "Visual Recognition" と書かれた箇所をクリックすると認識結果が展開表示されます(認識に少し時間がかかるので、ロード後少しだけ待ってからクリックしてください):
2015122204


スマホ版であれば個別詳細ページ下のここをクリックすると認識結果が展開されます:
2015122205



この機能を実装するには、まずこちらの記事を参考にマンホールを学習させておく必要があります。 その上で /api/v2/classify に対して目的の画像データを POST して、その結果の JSON を取得して解析する、という内容になります。Java であればこんな感じになります:
byte[] img = null;
    :
    :
(img に目的画像のバイナリを読み込む)
    :
    :

PostMethod post = new PostMethod( "https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classify?version=2015-12-02" );
Part[] parts = new Part[]{ 
	new FilePart( "images_file", new ByteArrayPartSource( "imgFile.jpg", img ) )
};
byte[] b64data = Base64.encodeBase64( ( "(username):(password)" ).getBytes() );
post.setRequestHeader( "Authorization", "Basic " + new String( b64data ) );
post.setRequestEntity( new MultipartRequestEntity( parts, post.getParams() ) );

int sc = client.executeMethod( post );
String json = post.getResponseBodyAsString();
    :
    :

この方法を実装すると、String 型の json 変数には以下のような値が入ってきます:
{"images":[
	{"image":"imgFile.jpg",
	 "scores":[
		 {"classifier_id":"Manhole_1277505833","name":"Manhole","score":0.799763},
		 {"classifier_id":"Brown","name":"Brown","score":0.634311},
		 {"classifier_id":"Carpenter_Plane","name":"Carpenter_Plane","score":0.620247},
		 {"classifier_id":"Wallet","name":"Wallet","score":0.599018},
		 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.582803},
		 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.582803},
		 {"classifier_id":"Diving","name":"Diving","score":0.570293},
		 {"classifier_id":"White","name":"White","score":0.554109},
		 {"classifier_id":"Desert","name":"Desert","score":0.532132},
		 {"classifier_id":"Cliff_Diving","name":"Cliff_Diving","score":0.531722},
		 {"classifier_id":"Zoo","name":"Zoo","score":0.524164},
		 {"classifier_id":"Bottle_Storage","name":"Bottle_Storage","score":0.518594},
		 {"classifier_id":"Catacomb","name":"Catacomb","score":0.512115}]}]
}

あとはこの JSON をデコードして、images 配列最初の要素の、scores 配列の中身を順に調べていけば目的の結果を取り出せる、ということになります。上記のマンホールマップ内ページではその結果を表形式にして表示しています。

画像を扱うウェブページやウェブサービスを運用している皆さま、是非色々試してみてください。


IBM Bluemix から提供されているコグニティブエンジン Watson Visual Recognition サービスは画像をインプットとして与えると、その画像が何の画像なのか?をその確実度合いと併せてアウトプットしてくれる、という画像認識 API です(このページからサンプルデモアプリを動かして確認することができます):
2015122101

この API はあらかじめ学習済みのモデルを素に、確率の高そうな認識結果を返してくれる、という API ですが、この仕様の裏を返すと、既に学習済みの一般的な判断結果しか返してくれない、ということになります。「この画像、本当は○○という結果を返してほしいんだよなあ」と思っても、その○○を学習していない状態では(ワトソンからすればそもそも知らない選択肢ということになるので)そのような判断をすることもできないのでした。

しかし、12月に Visual Recognition API が V2 にバージョンアップし、これまでの V1 API には存在しなかった学習 API も使えるようになりました。この API で自分に都合よい画像サンプルとその分類結果を学習させておけば、自分の望む識別結果を返してくれるようになる、、、かもしれません。


なお、Visual Recognition V2 の詳しい API の仕様については以下のリファレンスページを参照してください:
https://watson-api-explorer.mybluemix.net/apis/visual-recognition-v2-beta


改めて、ではその新機能である学習 API の使い方と、学習させた結果の認識がどのように変わるのか、を調べた様子を紹介します。

まず「画像を学習させる」とはどのようなことなのかを理解する必要があります。例えば上記デモ結果画像のトラの画像を認識させた結果を見ると、以下のようになっています:
認識結果その確実度
Tiger77%
Wild_Cat76%
Brown69%
Indoors64%
Cat63%


この認識結果として表示されている候補のテキスト("Tiger" や "Wild_Cat")それぞれのことを classifier(分類カテゴリ)と呼びます。Visual Recognition API にはあらかじめ複数の classifiers が学習済みで用意されており、カスタマイズする前であれば、あらかじめ学習済みの内容だけを対象に認識・識別を行います。

Visual Recognition API V2 で新たに追加された機能とは、この classifier を独自に追加できるようになる、というものです。追加の際には以下の3つの情報が必要となります:
(1) 追加する classifier の名前(上記の "Tiger" や "Wild_Cat" に相当する部分)
(2) 追加する classifier に分類される画像例
(3) classifier に分類されない画像例


(1) は認識結果としてどのように表示してほしいか、という名称なので、これは任意のテキストを指定できます。では例えばここでは "Manhole"(マンホール)というカテゴリを認識できるようにしたいとしましょう。 

そして (2) はその目的の名称として認識される画像のサンプルです。画像サンプルは 200x200 以上の解像度を持ったイメージで、学習には 50 枚以上が必要となります(枚数が多いほど精度が高くなります)。つまりこの場合であれば 200x200 以上の解像度を持った典型的なマンホールの画像を 50 枚以上用意する必要がある、ということになります。

その画像をどうやって集めるか、が問題になりますが、ここではマンホールマップの人気画像 100 個を集めてきました。「人気がある」=「典型的」かどうかは何とも言えませんが、マンホールとして分類してほしい画像のサンプルになりえる画像集、です:
2015122101
  ↑マンホール画像100枚


今回は 100 枚の画像を集めました。API として学習させる際には zip ファイルとしてまとめて転送することになるので、これら 100 枚の画像を posi.zip というファイルにまとめておきました。

次に (3) の「マンホールではない画像のサンプル」を用意します。これは正確には「マンホールに見間違えるかもしれないけど、マンホールではない画像のサンプル」です。「マンホールに間違えるかもしれない」という部分をどう考えたらよいのかよくわかりませんが、とりあえず「丸い画像」を 100 枚集めてみました:
2015122102
  ↑丸いけどマンホールではない画像100枚


これらのファイルも API 利用時には zip ファイルとして指定する必要があるため、nega.zip という zip ファイルにまとめました。

実際にはこの「サンプル画像を用意する」のがかなり大変な作業だと思います。認識して欲しい画像のサンプルは比較的容易に集められるかもしれませんが、「似てるけど認識して欲しくない画像」とはそもそも何を探せばよいのかも難しいのです。しかも 200x200 以上の画像で、50 枚以上用意する必要があります。現実にはこの部分の作業には手間もかかるだろうし、かなり大変だと思っています。


(1), (2), (3) 全て用意できたら、実際に学習させてみましょう。IBM Bluemix に Watson Visual Recognition サービスを追加し、その資格情報から username と password を確認しておきます(username と password が、それぞれ (username) と (password) であると仮定して以下を説明します):
2015122101


まずは学習前の classifiers 一覧を確認してみましょう。curl を使って以下のコマンドを実行します(青字が実行結果です)。なお指定するエンドポイント URL に必ず version パラメータを付与する(値は 2015-12-02 を指定する)ことに注意してください:
# curl -u "(username)":"(password)" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classifiers?version=2015-12-02
{ "classifiers":[
        {"classifier_id":"Black","name":"Black"},
        {"classifier_id":"Blue","name":"Blue"},
        {"classifier_id":"Brown","name":"Brown"},
        {"classifier_id":"Cyan","name":"Cyan"},
        {"classifier_id":"Green","name":"Green"},
        {"classifier_id":"Magenta","name":"Magenta"},
        {"classifier_id":"Mixed_Color","name":"Mixed_Color"},
        {"classifier_id":"Orange","name":"Orange"},
          :
          :
        {"classifier_id":"Sunset","name":"Sunset"}]
}

実行結果には classifiers という名前の配列が含まれており、その中が現時点での classifiers 一覧になっています。要するに何も学習させていないこの段階で Visual Recognition API V2 で画像を認識させると、この一覧の中のどの属性に近い画像であったのかを調べて、その結果が出力される、ということです。この一覧の中には "name" 値が "Manhole" という classifier はありません。つまり少なくとも "Manhole" という結果が返されることはないわけです。

いったん、この(学習前の)状態でマンホールの画像をポストしてどのように認識されるかを確認してみましょう。題材に使うのはこの画像とします(先程の posi.zip には含まれていない画像です):



この画像を file.png という名前で保存した上で、以下のコマンドを実行して画像認識を実行してみます:
# curl -u "(username)":"(password)" -X POST -F "images_file=@file.png" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classify?version=2015-12-02
{"images":[
        {"image":file.png",
         "scores":[
                 {"classifier_id":"Fashion_Accessory","name":"Fashion_Accessory","score":0.645613},
                 {"classifier_id":"Meter","name":"Meter","score":0.618349},
                 {"classifier_id":"Mixed_Color","name":"Mixed_Color","score":0.608368},
                 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.595259},
                 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.595259},
                 {"classifier_id":"Disturbance","name":"Disturbance","score":0.58813},
                 {"classifier_id":"Subway_Platform","name":"Subway_Platform","score":0.572388},
                 {"classifier_id":"Skibob","name":"Skibob","score":0.556545},
                 {"classifier_id":"Road_Traffic_Scene","name":"Road_Traffic_Scene","score":0.549438},
                 {"classifier_id":"Auto_Factory","name":"Auto_Factory","score":0.53405},
                 {"classifier_id":"Kayaking","name":"Kayaking","score":0.529446},
                 {"classifier_id":"Gym","name":"Gym","score":0.512764}]}]
}

学習前のデフォルト状態では "Fashion_Accessory" や "Meter" の可能性が高い、という認識結果になりました。まだ "Manhole" を学習してない状態ではこのような結果になりましたが、ではこれを "Manhole" と認識してもらえるように先程用意したポジティブ&ネガティブ画像を使って学習させることにします。

(2) で用意した posi.zip と、(3) で用意した nega.zip を同じディレクトリに用意したら、実際に API を実行して学習させてみます。2つの zip ファイルをカレントディレクトリに用意した状態で以下のコマンドを実行します:
# curl -X POST -u "(username)":"(password)" -F "positive_examples=@posi.zip" -F "negative_examples=@nega.zip" -F "name=Manhole" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classifiers?version=2015-12-02
{
  "name" : "Manhole",
  "classifier_id" : "Manhole_1277505833",
  "created" : "2015-12-21T05:59:30.000Z",
  "owner" : "de3f195e-890a-4e87-834f-9d728abd863c-us-south"
}

↑コマンド実行後、しばらく学習が続きますが、その間はプロンプトが戻ってきません(ハングったように見えます)。しばらく待った後に上記の青字部分が実行結果として返ってきます。上記のように classifier_id と name が含まれる結果が返っていれば学習が完了したことを示しています。

"Manhole" を覚えたはずなので、この状態で再度 classifiers 一覧を取得してみましょう:
# curl -u "(username)":"(password)" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classifiers?version=2015-12-02
{ "classifiers":[
        {"classifier_id":"Manhole_1277505833","name":"Manhole"},
        {"classifier_id":"Black","name":"Black"},
        {"classifier_id":"Blue","name":"Blue"},
        {"classifier_id":"Brown","name":"Brown"},
        {"classifier_id":"Cyan","name":"Cyan"},
        {"classifier_id":"Green","name":"Green"},
        {"classifier_id":"Magenta","name":"Magenta"},
        {"classifier_id":"Mixed_Color","name":"Mixed_Color"},
        {"classifier_id":"Orange","name":"Orange"},
          :
          :
        {"classifier_id":"Sunset","name":"Sunset"}]
}

先程の実行結果に含まれていなかった "Manhole" の classifier が含まれています。つまりこの段階で画像認識 API を使うと、"Manhole" という結果が帰ってくる可能性がある状態にできました。

では改めてこの状態で先程試した画像を使って画像認識を実行してみます:
# curl -u "(username)":"(password)" -X POST -F "images_file=@file.png" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classify?version=2015-12-02
{"images":[
        {"image":file.png",
         "scores":[
                 {"classifier_id":"Manhole_1277505833","name":"Manhole","score":0.776738},                 {"classifier_id":"Fashion_Accessory","name":"Fashion_Accessory","score":0.645613},
                 {"classifier_id":"Meter","name":"Meter","score":0.618349},
                 {"classifier_id":"Mixed_Color","name":"Mixed_Color","score":0.608368},
                 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.595259},
                 {"classifier_id":"Chocolate_Mousse","name":"Chocolate_Mousse","score":0.595259},
                 {"classifier_id":"Disturbance","name":"Disturbance","score":0.58813},
                 {"classifier_id":"Subway_Platform","name":"Subway_Platform","score":0.572388},
                 {"classifier_id":"Skibob","name":"Skibob","score":0.556545},
                 {"classifier_id":"Road_Traffic_Scene","name":"Road_Traffic_Scene","score":0.549438},
                 {"classifier_id":"Auto_Factory","name":"Auto_Factory","score":0.53405},
                 {"classifier_id":"Kayaking","name":"Kayaking","score":0.529446},
                 {"classifier_id":"Gym","name":"Gym","score":0.512764}]}]
}

一応、最も確率の高い結果として "Manhole" を得ることができました。2位に先程まで1位だった "Fasshon_Accessory" が入っていて、学習に使った 100 枚のマンホールが有効に働いて今回のこの結果が得られているようです。

ではもう一つ。これも先程の学習に使わなかった(nega.zip に含まれていない)この画像を問い合わせて見ることにします:



デザイン的にはちょっとマンホールっぽく見えないこともないこの画像を Visual Recognition に問い合わせるとどういう結果になるのでしょうか?やはり file.png という名前で保存して実行してみました。実際の結果がこちらです:
# curl -u "(username)":"(password)" -X POST -F "images_file=@file.png" https://gateway.watsonplatform.net/visual-recognition-beta/api/v2/classify?version=2015-12-02
{"images":[
        {"image":file.png",
         "scores":[
                 {"classifier_id":"White","name":"White","score":0.707564},
                 {"classifier_id":"Snow_Sport","name":"Snow_Sport","score":0.687052},
                 {"classifier_id":"Winter_Scene","name":"Winter_Scene","score":0.648243},
                 {"classifier_id":"Skiing","name":"Skiing","score":0.648041},
                 {"classifier_id":"Outdoors","name":"Outdoors","score":0.64775},
                 {"classifier_id":"Winter_Sport","name":"Winter_Sport","score":0.636656},
                 {"classifier_id":"Blow_Dryer","name":"Blow_Dryer","score":0.636557},
                 {"classifier_id":"Sky_Scene","name":"Sky_Scene","score":0.616727},
                 {"classifier_id":"Appliance","name":"Appliance","score":0.615328},
                 {"classifier_id":"Ski_Jumping","name":"Ski_Jumping","score":0.587443},
                 {"classifier_id":"Gray_Sky","name":"Gray_Sky","score":0.582471},
                 {"classifier_id":"Tool","name":"Tool","score":0.580808},
                 {"classifier_id":"Nature_Scene","name":"Nature_Scene","score":0.573698},
                 {"classifier_id":"Belt","name":"Belt","score":0.572148},
                 {"classifier_id":"Snow_Scene","name":"Snow_Scene","score":0.563831},
                 {"classifier_id":"Statue_of_Liberty","name":"Statue_of_Liberty","score":0.514912},
                 {"classifier_id":"Black_and_white","name":"Black_and_white","score":0.505201},
                 {"classifier_id":"Manhole_1277505833","name":"Manhole","score":0.5009}]}]
}

"White" や "Snow_Sport", "Winter_Scene" といった辺りの分類が高い可能性として挙げられています。画像全体が白いとそういう結果になるんですかね。 そして "Manhole" はリストの最後に出てきてはいますが、あまり確率が高いとはいえないことがわかります。これもある意味で正しく Negative の学習ができていると判断できるかもしれません。


以上のように、新しくなった Watson Visual Recognition API ではサンプル画像を用意して新しい認識結果の学習が出来ることがわかりました。その結果も少なくともとんちんかんなものではなさそうな印象です。 画像のパターン認識について意識することなく学習させることができる、というのは便利に使えそうです。


ともあれ、それなりに正しく動きそうなマンホール画像学習ができました。これは近日中にマンホールマップに実装予定です。


(参考)
http://qiita.com/mfujita/items/a6bfcffae8097807f6a0


この記事の続きです:
2015 マンホーラー実態調査

恒例となりましたが、2015 年にマンホールマップで最も人気のあったマンホール蓋をベスト10と、今年新たに投稿された中で最も人気のあった蓋の「新人賞」を公開します。

ちなみに「人気があった」という基準は、2015/Jan/01~2015/Dec/19 の間にPC版およびスマホ版のマンホールマップにおいて、単独ページでの通算アクセス数でソートした結果をランキングとしています。

なお、昨年の結果についてはこちらを参照ください:



2015 年新人賞

今年マンホールマップに投稿された蓋の中で、最も多くのアクセス数を記録したのはこの蓋画像でした:
市区町村投稿者画像
中華人民共和国内蒙古自治区42ER03


実はこのモンゴルの蓋は今年の12月に投稿されたもので、通算アクセスランキング的には非常に不利な条件だったにも関わらずに最も多いアクセス数を記録しました。なお、この蓋の人気は全体での総合ランキングでも11位でした。ということはこの1ヶ月間だけでものすごいアクセスを稼いだことになります。 42ER03 さま、おめでとうございます。


2015 年総合ランキングベスト10

では 2015 年アクセスランキングのトップ10を発表します。まずは10位から4位まで:

順位昨年順位市区町村投稿者画像
10-秋田県南秋田郡tainoshippo

↑秋田県八郎潟町の「北前船マンホール」です。いきなり何故これが人気あったのか分からない蓋が出てきました(苦笑)。これだからマンホールは奥が深い(マンホールだけに)。


順位昨年順位市区町村投稿者画像
82東京都三鷹市kakuyodo

↑第8位は同数で2つありました。1つ目は三鷹にある「ジブリ博物館マンホール」でした。ここは東京の中でも比較的交通の便があまり良くない所にあるのですが、マンホール蓋の人気はコンスタントに高いようです。

なお、昨年も同じことを言いましたが、なぜこのデザインがジブリに関係があるのか、私は1年経っても答を調べることができませんでした。登場キャラクターじゃないよね?


順位昨年順位市区町村投稿者画像
8-神奈川県鎌倉市EkikaraManhole

↑もう1つの8位は鎌倉市が鎌倉町だったころの「月&星」の町章デザインマンホールです。鎌倉市が生まれたのが昭和14年なので必然的にその前に設置されたことになり、歴史的文化価値のあるものであると推測されます。 

ただ、残念ながらマンホールマップ上で見ることのできるこの蓋は現在は新しい別の蓋に交換されていることがわかっています。その意味でも資料価値の高い、幻のマンホールです。


順位昨年順位市区町村投稿者画像
7-東京都台東区EkikaraManhole

↑7位は蔵前水の館に展示されている東京都のカラーマンホールでした。サクラ(ソメイヨシノ)とイチョウとユリカモメに色を塗ったらこんな毒々しいデザインになるとは・・・


順位昨年順位市区町村投稿者画像
6-茨城県つくば市kenitirokikuti

↑6位はこれもコンスタントに人気の高い、つくば市のスペースシャトル蓋でした。背景にはシンボルとも言える筑波山と、そして謎の星(!?)がデザインされています。なんというか、理系っぽい感じのデザインです。


順位昨年順位市区町村投稿者画像
57石川県河北郡内灘町keymoyaking

↑5位は現在のマンホールマップにおける最高「ナイスマンホ」数を誇る石川県河北郡内灘町のマンホールでした。昨年も7位でしたが、現在の登録数が5000を超えるマンホールマップにおいて、2年連続でトップ10に入るというだけで「コンスタントに人気がある蓋」といえるのではないでしょうか? 皆さんも「推し蓋へのナイスマンホ!」を積極的によろしくお願いします。


順位昨年順位市区町村投稿者画像
4-埼玉県川越市EkikaraManhole

↑4位は川越市の「時の鐘マンホール」でした。このデザイン自体は特別に目新しいわけではないのですが、「雨水吸込槽」と書かれたマニアックな蓋でした。


ではランキングトップ3の発表です。

第3位

順位昨年順位市区町村投稿者画像
38大分県豊後大野市ueda63682587

昨年8位だった豊後大野市の公式ゆるキャラ「ヘプタゴンちゃん」のカラーマンホールが今年はトップ3入を果たしました。 実はこの蓋も何故人気があるのか、私は全く理解できていません(苦笑)。

第2位

順位昨年順位市区町村投稿者画像
2-群馬県高崎市TMW_papa

↑ぐんまちゃんがサッカーをしている高崎市の排水蓋が2位でした。ぐんまちゃんはこのデザイン以外でも何種類かのマンホールのデザイン内で使われています。大活躍です。


さて、昨年は第一位を残した時点でメジャーな蓋が2~10位に出てしまっていたのですが、今年はここまでにあまり顔を出していません。昨年のベスト10蓋は3つしか出ていません。そんな中での1位、予想できますか??

では栄えある 2015 MVM(Most Variable Manhole) に輝いた今年のアクセス数第一位のマンホールは、今年も超大穴(マンホールだけに)のこれでしたっ!





























順位昨年順位市区町村投稿者画像
16北海道岩見沢市yanapong


昨年6位だった旧栗沢町のマスコット「リスのクリちゃん」マンホールが 2015 MVM となりました。 このマンホールデザインも栗沢町が岩見沢市と合併したこともあり、現在では新たに設置されることのない幻のマンホールとなっています。

このマンホールは特別にアクセスが集中した日があったわけではなく、1年間を通じてコンスタントなアクセスが記録されていました(昨年のアクセス数一位だった蓋にも同じ傾向がありました)。その地道な結果が年間アクセス数第一位に結びついた、と考えられます:
2015122101


ただ、実はこのマンホールの特徴として、マンホールマップのゲーム機能の1つである「スライドパズル」でのアクセス数が非常に多く、これが票を稼いでいた、という背景がありました。 

そして結果的にですが、yanapong 様は2年連続での 2015 MVM 受賞となりました。おめでとうございます!



最後になりましたが、マンホールの活動をしていて、昨年くらいから少しずつ一般への認知度が高まりつつあることを感じています。「マンホールの写真を集めてます」と言うと、「ああ、色んなデザインがあるんですよね」とか言われることが確実に多くなりました。テレビや雑誌で紹介される機会も増え、マンホーラーの仲間たちを始めとする地味~な啓蒙活動が着実に実を結びつつあると実感しています。 また昨日の記事でも紹介しましたが、海外から日本のマンホールが注目されつつあることも非常に嬉しいことです。実際、ものすごく Cool だと思ってますし、日本独自の文化として世界に誇れる内容だと感じてます。マンホールマップがその手助けの一因になっていけたら嬉しいです。

今後もより便利なサービスを目指していきます。来年以降も引き続きマンホールマップをよろしくお願いします。

昨年、一部の皆様の熱狂的支持を受けたマンホーラー実態調査、2015 年の今年もやります! 世界最大の位置情報付きマンホール画像サービス「マンホールマップ」の 2015 年分の利用実態を調べました。

以下のデータは Google Analytics を使った 2015/Jan/01 から 2015/Dec/19 までのアクセスデータを元に作成しています。信じられない人がいるかもしれませんが、かなりのビッグデータ解析です。


2015年 マンホールマップ利用概要

マンホールサミットとマンホールナイト、2つの大きなイベントとその直後にアクセス数のスパイクが見られます。ある意味で「想定通り」の利用結果になりました:
2015122001


なお、1回のセッションで平均 4.65 ページが参照されています。直帰率は 56.77% で、平均セッション時間は4分22秒。つまり「1回見に来た人は、その後4分22秒間滞在して、その間に4~5ページ見てくれる」ようなサイトになっています。ウェブサービスページとしてはなかなかの滞在時間と近隣ページへの誘導が実現できている方だと思っています。


利用者の傾向

グーグルのプロフィール予測に基づいたマンホールマップ利用者の推定年齢はこのようになりました:
2015122002


↑順位は昨年と変わっていません。意外と(?)若い層がマンホールマップを使っていると見るべきか、マンホールマップを使っている人は(グーグル的に)「若い!」と推測・分類されるような行動プロフィールなのか、その辺りは謎ですが、マンホールマップおよびマンホーラーの将来は明るいと言えます。

次に利用者の性別です:
2015122003


↑性別に関しても若干男性が多いままで、「合コンが成立するくらいの男女比」になっています。非常にバランスよく推移しています。ただし、出会いの場になっているかどうかは知りません。

既存ユーザーと新規ユーザーとのバランスもいい感じでした:
2015122007


↑既存ユーザー 41.3% に対して、新規ユーザーが 58.7% 。この差は昨年よりも開きました。新しいユーザーの開拓にも成功し続けていることが分かります。

最後に利用者の趣味・興味分野を見てみましょう:
2015122004


↑これは先程の推定年齢にも関係しているかもしれませんが、マンホールマップ利用者の趣味・興味分野が比較的アクティブなものになっています。それもあって、実年齢はともかく若いと判断される人の趣味になっているのかもしれません。加えて、マンホーラーの IT 率は相変わらず高いようです。


利用者の国、言語

マンホールマップのアクセスログに残されたユーザーエージェント情報を元に、この一年間でどのような国から利用されているのかを図示しました。1回でもアクセスが記録されていた国には青っぽい色が付けられています:
2015122005


主要国はほぼ含まれているといっても過言ではありません!

実は日本を含めて65ヶ国からのアクセスが記録されていました。国別アクセス数では日本がダントツですが、では2位以下はどのようになっていたのでしょうか?

2015122006


↑2位はアメリカ(昨年7位)でした。3位は(おそらくクローラーボットなど、ヘッダ情報のないアクセスだったため)国名が取得できないアクセスだったので無視します。昨年2位のロシアは実質3位でした。 なんというか、世界中から参照されていることを実感します。

ということはマンホールマップの国際対応を意識する必要が出てきます。マンホールマップはリリース当初から英語対応されていますが、更なる国際化を急ぐ必要があるのでしょうか? ではどの言語に対応すべきか、という問題をアクセスログに残された言語設定から見てみると、こんな結果になりました。ロケールの設定方法が統一されていないので少し見にくいかもしれませんが、生データだとこんな感じです:

1ja-jp日本・日本語
2ja日本語
3en-us米国・英語
4(取得不可) 
5en英語
6pt-brブラジル・ポルトガル語
7zh-tw台湾・中国語
8ruロシア語
9zh-cn中国・中国語
10cCロケール(英語)
11en-gb英国・英語
12esスペイン語
13de-deドイツ・ドイツ語
14en-au豪・英語
15es-esスペイン・スペイン語
16frフランス語
17fr-frフランス語
18it-itイタリア・イタリア語
19ko-kr韓国・韓国語
20sv-seスウェーデン・スウェーデン語


↑1位と2位は日本語、3位と5位は対応済みの(米)英語、4位は取得できないものだったので無視します。 残りの順位を見ると次はポルトガル語、中国語、ロシア語対応が必要なのでしょうか? ヤバい、知らない言語ばかりだ・・・


日本国内の市区町村別アクセスランキング

次は日本国内の市区町村別アクセスランキングです:

1大阪市
2港区
3新宿区
4横浜市
5名古屋市
6京都市
7(東京都)中央区
8札幌市
9千代田区
10千葉市
11渋谷区
12さいたま市
13(取得不可)
14福岡市
15世田谷区
16川崎市
17神戸市
18新潟市
19加古川市
20相模原市


なんと1位は昨年はベスト10圏外だった大阪市! 関西マンホールサミットの影響なのでしょうか?今回のデータで、これが個人的に一番驚きました。

なお上記表では20位まで載せていますが、21位は私の家がある船橋市でした。家からも結構見ていたつもりだったのですが、20位にも入ってなかったとは・・・ マンホーラーが東京周辺に集中する傾向が少しずつ崩れて、全国的に広まりつつある感触を感じます。


利用環境

次にマンホーラーの利用環境です。まずはブラウザから。実質的に上位6つまでが順位といえる結果でした:

1Chrome30.08%
2Internet Explorer28.01%
3Safari16.08%
4Firefox8.49%
5モバイルSafari7.55%
6モバイルChrome7.14%
7(不明)0.90%
8Opera0.87%
9Edge0.55%
10Amazon Silk0.06%


1位は推奨環境でもある Chrome でした。昨年は(非推奨環境の)Internet Explorer が1位だったのですが、地道な啓蒙活動が実りつつあるとポジティブに解釈させていただきます。とはいえまだ2位。あと Edge も9位に顔を表しています。10位のは知らないな・・・

そして OS 別だとこんな感じです。実質は上4つが順位で、5位以下は1%未満です:

1Windows49.26%
2iOS21.39%
3Android17.61%
4Macintosh9.75%
5(不明)0.96%
6Linux0.90%
7Nintendo Wii0.04%
8Chrome OS0.02%
9NTT DoCoMo0.02%
10BlackBerry0.01%


ある意味想像通りで、上位4つは昨年と変わっていません。ただ Linux デスクトップからのアクセスが思っていたよりも少なかった印象です。あとは Wii やドコモのガラケーから使っている人もいるんですね。ちゃんと見れてるのかな・・・

※BlackBerry のアクセスログはたぶん自分です


まとめ

以上の内容から、2015 年のマンホーラーの実態はこのようなものであったと推測されます:

・相変わらず女性に人気があり、新規ユーザーも増え続けている
・「若いですねぇ」とか「そんな歳に見えませんよぉ」と言われているような人がマンホーラー
・マンホーラーは世界中にいるし、日本国内でも東京一極集中から分散しつつある
・マグロが止まったら死ぬように、InternetExplorer を使ってないと死ぬ人がいる



さて、次回のブログでは 2015 年のマンホール蓋別年間アクセスランキングを発表します! 2015 MVM(Most Variable Manhole) に選ばれるのはあの人気蓋なのか!?それとも昨年のような伏兵が現れるのか!? そして期待の新人賞の行方は!!??  お楽しみに!


(追記 2015/Dec/22)
続きはこちら


サーバーサイド JavaScript サーバーである StrongLoop をローカル環境に作成(&実行)する手順を以前に紹介しました:
CentOS に StrongLoop をインストールする
上記記事では、データストア先として標準のメモリ DB を使う前提で手順を紹介していますが、実際には外部のリレーショナル DB を使いたいケースも多いと思っています。というわけで、データストアに MySQL を使う場合の手順を紹介します。

まずは上記リンク先を参照して、StrongLoop のインストールと、StrongLoop アプリケーションの作成(上記リンク先ページだと slc loopback コマンドで myapp アプリを作るところ)までを実行しておいてください。以下、アプリケーションの名前は myapp として、myapp ディレクトリが出来ている前提で以下を紹介します。違うアプリケーション名で作成した場合は適宜読み替えてください:



また、このアプリケーションのデータストア先となる MySQL データベース、および接続情報は以下のようになっているものと仮定します:
属性
MySQL サーバー名(HOSTNAME)
MySQL ポート番号3306
データベース名(DBNAME)
ユーザー名(USERNAME)
パスワード(PASSWORD)


ではこのアプリケーションのデータストア先が上記の MySQL データベースになるようカスタマイズを開始します。まずは LoopBack MySQL コネクタをインストールします:
# cd myapp
# npm install --save loopback-connector-mysql

LoopBack MySQL コネクタのインストールができたら、LoopBack としてのデータソースを作成します。ここでは "mydb" という名前でデータソースを作成しています。データソース名の指定ではデフォルトの mydb のまま、コネクタタイプの指定ではカーソルを MySQL に合わせて選択します:
# slc loopback:datasource mydb
  :
  :
? Enter the data-source name: mydb
? Select the connector for mydb: MySQL (supported by StrongLoop)

今の手順でデータソースファイル(server/datasource.json)が生成されています。このファイルを編集して、目的のリモート MySQL データベースに接続するよう上記の接続情報を指定します(青字部分を追加します):
# vi server/datasource.json


{
  "db": {
    "name": "db",
    "connector": "memory"
  },
  "mydb": {
    "host": "(HOSTNAME)",
    "port": 3306,
    "database": "(DBNAME)",
    "user": "(USERNAME)",
    "password": "(PASSWORD)",
    "name": "mydb",
    "connector": "mysql"
  }
}

ここまでくれば後は前回と同様です。API で CRUD を行うモデルとして、以下の様な item モデルを定義しましょう:
列名列型必須条件
namestringYES
codestringYES
pricenumber 


モデルの名称は item、データソースは先程定義した(MySQL 上の)mydb、PersistedModel で REST API の公開対象とします。また Common model を指定します:
# slc loopback:model item
  :
  :
? Enter the model name: item
? Select the data-source to attach item to: mydb (mysql)
? Select model's base class PersistedModel
? Expose item via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? common
Let's add some item properties now.

続けて3つのフィールドの名前、型、必須条件をそれぞれ指定していきます。最後に名前指定の所でそのまま Enter を押すとモデルの定義も終了です:
  :
  :

Enter an empty property name when done.
? Property name: name
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
   invoke   loopback:property
? Property type: string
? Required? Yes

Let's add another item property.
Enter an empty property name when done.
? Property name: code
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
   invoke   loopback:property
? Property type: string
? Required? Yes

Let's add another item property.
Enter an empty property name when done.
? Property name: price
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
   invoke   loopback:property
? Property type: number
? Required? No

Let's add another item property.
Enter an empty property name when done.
? Property name: (ここでそのまま Enter で終了)


最後にこのモデルを DB スキーマとして登録しましょう。MySQL にコマンドラインでログインし、以下のような create table コマンドを実行して item テーブルを作ります:
> create table item( id int primary key auto_increment, name text, code text, price integer );

これで動く状態ができました。実際に動かしてみましょう:
# node .

で、ウェブブラウザを起動して、この開発環境の 3000 番ポートの /explorer パスにアクセスしてみます:
2015121802


作成した myapp アプリケーションの中で、定義した item モデルの CRUD API が公開されています!


実際にデータを作成してみましょう。item モデルを展開後の /items の POST メソッドを開き、data フィールドに以下の内容を入力して "Try it out!" ボタンをクリックしてみます:
2015121803

{
  "name": "コーラ",
  "code": "AA001",
  "price": 1000
}

実行結果が以下のようになっていれば POST コマンドは成功して、テーブルに1レコードが追加されたことになります:
2015121901


ちなみにこの段階で /items の GET コマンドを実行すると、今作成したデータが返されるはずです。
2015121902


念のため、MySQL サーバーにログインして item テーブルの中身を確認すると、このコマンドで POST したレコードが作られているはずです。リモートの MySQL と連携する StrongLoop 環境が作れたことになります:
> select * from item;
+----+-----------+-------+-------+
| id | name      | code  | price |
+----+-----------+-------+-------+
|  2 | コーラ    | AA001 |  1000 |
+----+-----------+-------+-------+
1 row in set (0.18 sec)

なお、この状態を cf コマンドで IBM Bluemix 上の Node.js ランタイムにプッシュすると、そっくりそのまま Bluemix 環境で動かすことも可能です:
2015121903


以上、StrongLoop と MySQL との連携方法の説明、および Bluemix 環境への移行方法の紹介でした。


このページのトップヘ