かれ4

かれこれ4個目のブログ

Azure Advent Calendar - 三倉姉妹(マナカナ)をAzureを使って見分ける

Microsoft Azure Advent Calendar 2015 の20日目の記事です。


今回はProject Oxfordを使って三倉姉妹(マナカナ)の判別を行いたいと思います。

三倉姉妹とは?

三倉姉妹とは、三倉茉奈三倉佳奈の双子の女優・タレント・歌手です。
通称マナカナと呼ばれている人たちです。

「左がマナで、右がカナ。」という言葉が出来るほどに判別は難しく、人類がこれまで幾度と無く挑戦し失敗し諦めてきた大きな課題です。



準備としては、
まずBing Search APIを利用してマナカナの画像を集め、
blog.tottokug.com
Project OxfordのFace APIを使ってかおの画像を切り出しました。
blog.tottokug.com

今日はついにマナカナを判別する所を実装していきます。

実装するは言い過ぎました。実装は特にしません。

Project Oxfordの機能を使って、

といった感じに、Face APIに全てを任せます。

マナカナ判別用教師データの作成。

今回使うのもProject OxfordのFace APIです。
Face APIを使いはじめるところまでは、前回のマナカナの画像からProejctOxfordとimagemagickで顔を切り出す。 - かれ4で書いた通りで、
今回はここからさらにマナカナをマナとカナに分類することをしていきます。

今回は顔を切り出す必要はないので、ImageMagickは使わずにただBounding Boxを一覧化するところまでをおこえば良いのかなと。
気をつけないと行けないのはfaceAPIは秒間1回くらいの利用しか認めておらず、それを超えるとエラーが帰ってくるので要注意です。
なので、sleep 1などを入れて1秒待機させる事にすると回避出来るので、スリープはぜひ入れましょう。

#!/bin/bash
IFS='
'
APIKey=***************************************
[ -d images/detected ] || mkdir -p images/detected
for f in $(find ./images/manakana -type f);
do
  curl -X POST  -H "Content-Type: application/octet-stream" --data-binary @${f} \
  "https://api.projectoxford.ai/face/v0/detections?subscription-key=${APIKey}"  \
  |jq -r ".[]  | [\"$f\",.faceId,.faceRectangle.width,.faceRectangle.height,.faceRectangle.left,.faceRectangle.top] | @csv"  >> faceindex.csv
  mv "${f}" images/detected/
 sleep 1
done

これで faceindex.csvに下記のような

画像ファイル名,faceId,幅,高,x,y

という形式でのCSVファイルが吐き出せました。

"./images/manakana/00011738_02A.jpg","a6415064-2720-4c84-8dff-7701f006f36d",81,81,123,93
"./images/manakana/00011738_02A.jpg","3d957436-a206-4d78-839a-bc0174345f77",77,77,546,144
"./images/manakana/00011738_02A.jpg","2bf5f7dd-c9c5-45e5-acab-6a58b13da0ad",75,75,415,136
"./images/manakana/000504.jpg","0913d773-c52f-45f4-8dce-16ba86b26d42",115,115,28,75
"./images/manakana/001-199x300.jpg","94f18b64-1faf-425f-a064-1a203b8c4140",42,42,87,26
"./images/manakana/001-199x300.jpg","fc8407f4-4722-4e58-ad10-f5e1d1efb733",39,39,34,54
"./images/manakana/001.jpg","87de0b21-ee19-4072-bf7d-b6d14895578c",76,76,96,104
"./images/manakana/0014d0d9.jpg","ca4d66a1-2243-44ac-b084-c1703d419ff6",133,133,145,368
"./images/manakana/0014d0d9.jpg","7a71404c-81b3-4562-8f24-3d78e455b480",122,122,419,370
"./images/manakana/001l.jpg","a967b8e8-a50d-43be-beb7-5d718e0577bd",90,90,199,89

この処理はスリープが入っているので、時間がかかります。
待っている間に、先にPersonGroupの作成とPersonの作成をしてしまいます。
どちらも、繰り返し行う動作ではないのでブラウザでさくっとやってしまいます。
API Referenceから実行出来るようになっているので、そこから実行します。
最近API Referenceから実行出来るようなのが増えていますね。


Person Groupの作成

f:id:tottokug:20151220231830p:plain

f:id:tottokug:20151220231834p:plain

Person の作成

f:id:tottokug:20151220231418p:plain
f:id:tottokug:20151220231421p:plain
f:id:tottokug:20151220231436p:plain

Personができていることの確認

f:id:tottokug:20151220231438p:plain
f:id:tottokug:20151220231442p:plain


学習させる

ここまでで準備は整ったので、ついに学習させていくことになります。
学習させるためには、Face APIのPersonにあるAdd Faceを利用してまずは顔を登録していきます。
今回は顔を検出した結果、2人で写っているものだけを学習用に使い、1人もしくは3人以上で写っている画像を検証用に使います。

2人で写っているものを使う理由として、マナカナには「左:マナ、右:カナ」というルールがあるためです。
ここが今回一番大切な所です。
他にもカナの左目の下にほくろがあるとか、鼻の穴の角度が違うとかそんな見分け方も有りますが、それは今回は使いません。

大切な事なので、もう一度。
「左がマナで、右がカナです。」

言い換えると、Bounding BoxのLeftの値が小さい方がマナ、大きい方がカナです。
というわけで、2人で写っている画像を探し出してきてその画像と一緒に、顔の位置の情報を送ります。


#!/bin/bash
IFS='
'
APIKey=**************************************
personGroupId="manakana"
mana="4314b829-559c-4350-81d5-7bb24b4041e7"
kana="6eb1744b-a7e6-4782-acb4-6a35ec74a8a1"

for file in $(cat faceindex.csv|cut -d',' -f1  |sort |uniq -c  |grep "^\s*2"|perl -pe "s|.*images/manakana(.*?)\"|images/detected\1|g ");
do
  personId=$mana
  for bb in $(cat faceindex.csv |grep  "${file##*/}" |perl -pe "s|.*,(\d+),(\d+),(\d+),(\d+)|\3,\4,\2,\1|g")
  do
    echo "========="
    target=$bb
    eretry=1
    while :
    do
      URL="https://api.projectoxford.ai/face/v1.0/persongroups/${personGroupId}/persons/${personId}/persistedFaces?targetFace=${target}"
      faceId=$(curl -X POST "$URL" \
      -H "Content-Type: application/octet-stream" \
      -H "Ocp-Apim-Subscription-Key: ${APIKey}" \
      --data-binary @./${file} \
      )
      if [ ! -z "$(echo $faceId |grep RateLimitExceeded)"  ]
      then
        eretry=$(expr "$eretry" + "$eretry" )
        echo "retry ${eretry} sec ago"
        sleep ${eretry}
        continue
      fi
      echo "$file,$faceId,$bb" >> registIndex.csv
      break;
    done
    personId=$kana
    sleep ${eretry}
  done
done

ここで指定している

mana="4314b829-559c-4350-81d5-7bb24b4041e7"
kana="6eb1744b-a7e6-4782-acb4-6a35ec74a8a1"

はpersonを作った時に付与されたPersonIdになっています。
また、このAPIは1秒のSleepを入れても、RateLimitExceededが出てしまうので、出てしまった場合は1秒、2秒、4秒と増やしながらリトライする処理が入っています。
ひとまずこれで顔の登録が完了しました。

学習の開始

学習の開始は非常に簡単です。
これも繰り返し行う動作ではないのでブラウザ上で行ってしまいます。
https://dev.projectoxford.ai/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395249/console
こちらから
personGroupIdを指定してsendで完了です。
f:id:tottokug:20151221005302p:plain
Queueに登録するだけなので、202 Acceptedが帰ってきていれば成功です。
f:id:tottokug:20151221005733p:plain

学習の結果確認

こちらも繰り返し行うものではないので、ブラウザ上で確認します。
f:id:tottokug:20151221005736p:plain

f:id:tottokug:20151221005739p:plain

無事に成功しているようです。
今回顔画像をそれぞれ250枚ほど登録していますが、
約1分程度でステータスはSucceededになっていました。

検証

それではドキドキのマナカナが見分けられるかどうかのテストになります。

テストにはファイルを指定して、そのままどっちなのか判定出来るように以下のスクリプトを使います。

#!/bin/bash
IFS='
'
APIKey=****************************************

faceId=$(curl -s -X POST "https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true" \
-H "Content-Type: application/octet-stream" \
-H "Ocp-Apim-Subscription-Key: ${APIKey}" \
--data-binary @${1} \
| jq -r  .[].faceId)

echo "FACE ID = $faceId"
curl -s -X POST "https://api.projectoxford.ai/face/v1.0/identify" \
-H "Content-Type: application/json" \
-H "Ocp-Apim-Subscription-Key: ${APIKey}" \
--data-ascii "{\"personGroupId\":\"manakana\",\"faceIds\":[\"${faceId}\"],\"maxNumOfCandidatesReturned\":1 }" \
| jq -r '.[].candidates[] |[.personId,.confidence] | @csv' \
|perl -pe "s/4314b829-559c-4350-81d5-7bb24b4041e7/mana/g; s/6eb1744b-a7e6-4782-acb4-6a35ec74a8a1/kana/g"

マナの判定

ではまずは、こちらの画像から
f:id:tottokug:20151221012106j:plain

$ ./identify.sh images/detected/mikuramana.jpg
FACE ID = efd9608a-b6ff-47a9-84cc-4107f9789d2d
"mana",0.6302

無事にmanaと判定されました。

カナの判定

次に,この画像から、kanaと判別されれば成功です。

f:id:tottokug:20081227164536j:plain

$ ./identify.sh images/detected/kana.jpg
FACE ID = 6ff3601d-a2f6-4ba5-81e0-0e6a39df2a16
"kana",0.73804


無事に認識されました。


これで、マナカナを見分ける事が出来るようになりました。

クローラを生まれ変わらせるにあたっての思考

Advent Calendar 2015の13日目の記事です。

今うちで動いているCrawlerは数年前に作られたもので、
仕組みとしてはAzureのService Fabricの上でAWSのLambdaを動かしているのと似たアーキテクチャになっている。

そろそろ生まれ変わりの頃かなと思う。

生まれ変わるにあたって、これからのアーキテクチャをどうしようかと悩むわけだけど、
そんな時には過去に遡っていくと次時代へのヒントが転がっていることがよくある。
特に、過去に敗北した素晴らしいアーキテクチャが今の技術であれば一般時にも実現可能になっていたりする。

昔話

昔々世界にはInktomiという会社と
AltaVistaという会社があった。
Inktomiという会社はソフトウェア開発会社で、2002年に米Yahoo!に買収されている。
AltaVista検索エンジンの会社だったが、2003年にOverture社が買収し、
2003年にOverture社を米Yahoo!に買収している。

詳しい歴史等はWikipediaを見てもらえば良いとして、この2社はざっくり言うとどちらもクローラを持ち、
検索サイトとして、もしくは検索エンジンAPI提供として一時代を築いた2つの企業。

この二つの企業では、crawlingとindexingについて大きく違う思想を持ってやっていた。

AltraVistaはDEC Alphaの高性能差をアピールするために生まれたという経緯があり、
1台で完結するように作られたCrawlerが動いていた。
それに対し、Inktomiは完全に分散された環境でスケールする事を意図して作られている。

この2つの会社の事を心に止めておきながら、本題に入っていきたい。

原子的なCrawler

Crawlerについて順を追って、どのようにしていくべきなのかを考えてみる。

その過程でCrawlingとScrapingを分けて考えていかないと行けない。
Crawlingに関して言えば、初期ロボット型検索サイトを作りたいのであればScrapingはせずに、
シンプルに正規表現なんかでタグをはずし、
テキスト部分だけで転置インデックスを作って行くだけでも大規模でなければ実現できる。

それに対してScrapingをする必要があるのは、インデックスを作りたいのではなく、
構造化されたデータとして使いたい場合。

とはいえ、とりあえずCrawlingの話からしよう。
Crawlingと言っても、シンプルな方法からまるで人間がアクセスしているかのような動きをするとか
様々なレイヤーがある。

HTTPでファイルを取得する。

まずシンプルにHTTPでリクエストを送りファイルを保存する事を考えてみる。

  +-------------+    +-----------+
  | HTTP Client | -> | HTML File |
  +-------------+    +-----------+

このようにCrawlerがHTTPRequestを送り、シンプルにHTML、もしくはJsonなどをファイルに保存する方法。
ただ、これでは、CrawlerではなくただのHTTPClientでしかない。

これを実現するためにはwgetというコマンドがある。

再帰的に取得する。

次に再帰的に取得する場合を考えてみる。

  +-------------+    +-----------+    +------------+
  | HTTP Client | -> | HTML File | -> | html parse |
  +-------------+    +-----------+    +------------+
       ^                                v
       +---------------------------------
  1. HTTP ClientがHTMLファイルをダウンロード
  2. そのHTMLをパース
  3. Aタグの文字列を絶対パス(URL)に変換
  4. URLをHTTP Clientが

というループを繰り返すことになる。

そして、これを実現するためにはwgetというコマンドがあり、

  • rというオプションをつけることで可能。

不要なアクセスをなくす。

次に考える事は、どこをたどるかという事。
不要なところにアクセスして、ファイルを取得しても仕方がないので、必要なところだけに絞って次のアクセスをする。

  +-------------+    +-----------+    +------------+
  | HTTP Client | -> | HTML File | -> | html parse |
  +-------------+    +-----------+    +------------+
       ^              +--------+                   v
       +------------- | Filter | -------------------
                      +--------+

HTMLをパースして、Aタグを抜き出すところまではさっきと同じだけれども、その後、HTTP Clientに行く前に
Filterを介すことにする。
そうすることで、不要なアクセスをなくし、効率的にクローリングすることが可能になる。


そして、これを実現するためには、wgetというコマンドがあり、これだけのフィルタオプションがある。

再帰ダウンロード時のフィルタ:
  -A,  --accept=LIST               ダウンロードする拡張子をコンマ区切りで指定する
  -R,  --reject=LIST               ダウンロードしない拡張子をコンマ区切りで指定する
       --accept-regex=REGEX        許容する URL の正規表現を指定する
       --reject-regex=REGEX        拒否する URL の正規表現を指定する
       --regex-type=TYPE           正規表現のタイプ (posix|pcre)
  -D,  --domains=LIST              ダウンロードするドメインをコンマ区切りで指定する
       --exclude-domains=LIST      ダウンロードしないドメインをコンマ区切りで指定する
       --follow-ftp                HTML 文書中の FTP リンクも取得対象にする
       --follow-tags=LIST          取得対象にするタグ名をコンマ区切りで指定する
       --ignore-tags=LIST          取得対象にしないタグ名をコンマ区切りで指定する
  -H,  --span-hosts                再帰中に別のホストもダウンロード対象にする
  -L,  --relative                  相対リンクだけ取得対象にする
  -I,  --include-directories=LIST  取得対象にするディレクトリを指定する
  --trust-server-names             ファイル名としてリダイレクト先のURLの最後の部分を使う
  -X,  --exclude-directories=LIST  取得対象にしないディレクトリを指定する
  -np, --no-parent                 親ディレクトリを取得対象にしない

スクレイピングする

ここまでで、HTMLファイルを再起的に収集することは出来た。

次は取得したHTMLから必要部分を抽出しくことで、やっとクローリングに意味が出てくる。
このHTMLから必要部分を抽出していくことが、スクレイピングなわけだけど、どのように実施していくのが効率的なのだろうか?

これは大きく2つの方法が考えられる。

後になってやっぱり、この項目も抽出したいなんて感じになった場合はCrawlingしたタイミングでのHTMLが必要になる。
もう一度クローリングし直すでも良いかもしれないが、時系列データを収集したい場合なんかはそれではいけない。
その分ストレージ容量を大量に消費するというのがトレードオフになり、それぞれのメリットとデメリットとして存在する。
ここは用途に併せて作るべきと言いたいところだけれども、ストレージをクラウドに任せる事によって
なんとかなるかもしれない。

そして後は抽出後のデータをどこに格納するかという事を考えないと行けない。
今回はCrawlerとScrapingの事を考えないと行けないので、データをどこに保存するのかは
おいておく。

Crawlerをスケールさせる事を考える

次にスケールする場合について考えてみる。
とりあえずオンメモリでやる場合は、メモリ上にしかHTMLが保持されていないので、マシンやプロセスをまたぐことは出来ないと思ったほうがよい。
RDMAなんかでやってやれない事はないが、そんな事を考えるほうがめんどくさい。
というわけでスケールの単位としては以下の図のようになる。

+-- PROCESS -----------------------------------+ +------+
|  +-------------+    +------+    +----------+ | |+----+|
|  | HTTP Client | -> | HTML | -> | Scraping | |-|| DB ||
|  +-------------+    +------+    +----------+ | |+----+|
+----------------------------------------------+ +------+

このような感じでPROCESSを増やすなり、マシンを増やすなりすれば良いので、
コンテナなんかとの相性が良さそうだ。

しかし、ここで考えないと行けないのが、HTTP Client部とScraping部を非同期にするか同期にするか。
一つのProcessがHTTPでの通信とScapingの両方をやる場合、どちらの速度も同じ場合のみ、
非同期でも問題はない。
しかし、HTTPの通信速度は安定するものではないし、HTMLのボリュームによってはScrapingの速度も安定しない。
並列、非同期にするメリットがなく、直列の同期処理にするのが良い。


次に、HTMLを一度ファイルに書き出す方法の場合はどうだろうか。
ここでの前提としては、ファイルはローカルのストレージではなく、クラウド上のObjectStorageに保存するものとする。
この場合のスケールは下の図のようになる。

+---------------+  +--------+  +-------------+
|+-------------+|  |+------+|  |+----------+ |
|| HTTP Client ||->|| HTML ||->|| Scraping | |
|+-------------+|  |+------+|  |+----------+ |
+---------------+  +--------+  +-------------+

HTTP ClientとScraping部が完全に切り離されている。
これによって、HTTP ClientとScrapingのProcessを分離することが可能になっている。
HTMLのファイルを取得するためのIOコストがあるが、HTTP ClientのHTTP通信のコストよりも
低いコストである確率は高い。(ScrapingのプロセスがObjectStrageと同一クラウドに存在する場合。)


しかし、この場合再帰的なCrawlingをする時にScrapingが終わるまで、次のページの取得が始まらない。
それではあまりスケーリングのメリットが享受出来ない可能性が高い。

なので、この構造から少し改良をして、再帰的なクローリングを効率的に行うようにする。
リンクをたどるためにHTMLのParserをHttp Client内部に持ち、Crawlingだけを先に進めていき
Scrapingを非同期に動かすという方法。 下の図のようになる。

+---------------+  +--------+  +-------------+
|+-------------+|  |+------+|  |+----------+ |
|| HTTP Client ||->|| HTML ||->|| Scraping | |
|+-------------+|  |+------+|  |+----------+ |
|    ^    V     |  +--------+  +-------------+
|+-------------+| 
|| HTML Parser ||
|+-------------+|
+---------------+

これによって、HTTPClientとScrapingを完全に分離出来てはいるが、非常に無駄の多い事はすぐに分かると思う。
HTTP Client部とScraping部で、2回HTML Parserが動くことになるからだ。

ここで言うParserと言うのは、きちゃないHTMLを綺麗なHTMLにしてDomParserがすんなり読み込める形にすることをいう。
具体的には、HTMLの整形と文字コードの変換になる。

この無駄に対してのアプローチとしては、非常にシンプルに解決できる。
ParseされたHTMLを保存しておけばいい。

+---------------+
|+-------------+|
|| HTTP Client ||
|+-------------+|
|    ^    V     |  +---------------+  +-------------+
|+-------------+|  |+-------------+|  |+----------+ | 
|| HTML Parser ||->|| Parsed HTML ||->|| Scraping | |
|+-------------+|  |+-------------+|  |+----------+ |
+---------------+  +---------------+  +-------------+

ここで、さっきのwgetでやっていた、無駄なアクセスを減らす動きも加えていきたい。
ここまでの構成だと、HTMLをParseし、そのままHTTP Client部に渡しているので、
間にFilterを挟めば良いだろう。


これによって、無駄なアクセスも省略できるようになってきた。
ここまで来ると、今度はこのParserすら非同期化したくなってくる。

+---------------+
|+-------------+|
|| HTTP Client ||
|+-------------+|
+---^-------v---+
| Queue | Queue |
+---^-------v---+
|+--^---+   v   |
||Filter|   v   |
|+--^---+   v   |
+---^-------v---+  +---------------+  +-------------+
|+--^-------v--+|  |+-------------+|  |+----------+ | 
|| HTML Parser ||->|| Parsed HTML ||->|| Scraping | |
|+-------------+|  |+-------------+|  |+----------+ |
+---------------+  +---------------+  +-------------+

HTTP ClientとParserの間をQueueでつなぎ、Filterを通したものをQueueを通してHTTP Clientに戻す形にする。

ここまで来ると勘の良い人は気づいているかも知れないけれど、
Scraperの準備をする前に、Crawlingだけ開始することができる。

Parsed HTMLはObjectStorage上に保管されているので、定点観測的に使う場合にはすぐにでも
データを収集しておくことが可能だ。

そして、この構造にするともう一つメリットがある。
Scrapingをしている人なら、わかると思うけれど、XPATHで抜くにしろ正規表現で抜くにしろ
何度も試行錯誤をすることが多いと思う。
そのたびにサイトにHTMLを取得しにいっていないだろうか?

もしインメモリでやっていたら、確実にHTMLを何度も取得しにいっているだろう。
この構造になることで、一度HTMLを収集していれば、何度でも試行錯誤し放題になる。



StorageとScrapingの関係。

最後にStorageとScrapingについてもう少し深く掘り下げていきたい。
もし、Scrapingをストリーム処理のように、Crawlingしてきたそばからやっていきたい場合は
Storageに保存されたことにHookさせる事ができるサービスもあるので、随時Scrapingを開始することも可能だ。

Scrapingをストリーム処理する必要がない場合は更に自由度がます。
必要がなくてもストリーム処理しても良いし、蓄積されたParsedHTMLに対して一気にScrapingすることも可能になる。

その場合は、未処理のScraping量に併せてProcessの数を増減させて行けばよい。
Scrapingをしなくていい時は、ScrapingのProcessすべてを停止しておけば良いので、
クラウド上のコンピューティングリソースを使う場合コストメリットも出てくる。
StorageがHDFSなんかだったりしたら、Hadoopで一気にScrapingする事も可能になったりもしてくる。
例えば、

SELECT `span.name`, `span.price` FROM `STORAGEPREFIX.div#listtable ul li `

なんて事も可能に

また、StorageにHTMLを保存し、Scrapingを分離することによってデータ活用のタイミングをずらす事もできるので、
インターネット上の統計的な分析には無駄なコンピューティングリソースを使う事なく、機をうかがう事も可能になる。
半年分の動向とか見たい時なんかには効率的だ。



まとめ

ここまで、クローリングについてどのようなアーキテクチャにしていくのが良いか考えていた事をうぁーと書いてみたけれど、結果どうしたかというと、Crawling部とScraping部を分けた。
それによって、データの使い回しがきくようになったことと、クローリングだけを先に始めることができる事によって、やりたいことを実現するにあたって、
情報の取り逃しを減らすことが可能になった。
Storageコストはかかるが、そこは圧縮するなりコールドストレージに落とすなり、最適化の方法が尽きたわけではない。
というわけで、結局Microserviceのような形に落ち着いた。

で、冒頭に出てきた2つの会社だけれども、特に意味はなく意味深な感じにしたかっただけでした。

やたらmds_storesがCPUを喰っている時に確認すること

f:id:tottokug:20151110103508p:plain

gitを使っている場合、大きめのcommitをしたり、大きめのリポジトリをcloneしてきたりすると.gitの中がspotlightの検索対象になってしまう。
このせいでmds_storesがCPU喰ってしまう事がある。


心当たりがあれば、以下の方法でspotlightのindexから.gitを除外すると良いと思う。

spotlightの除外の設定は

/.Spotlight-V100/VolumeConfiguration.plist

に書いてある

        <key>Exclusions</key>
        <array/>

が対象になる。

plistファイルなので、直接書き換えてもいいが、
defaults で

$ sudo defaults write /.Spotlight-V100/VolumeConfiguration.plist Exclusions -array-add 'path/to/exclude'

これで設定が可能になる

まずは今の設定の確認をすると空っぽなことも確認出来る。

$ sudo  defaults read /.Spotlight-V100/VolumeConfiguration.plist Exclusions
(
)

一個一個設定するのも大変なので、

$ sudo locate .git |grep "/.git$"  | xargs -I {} defaults write /.Spotlight-V100/VolumeConfiguration.plist Exclusions -array-add  {}
$ 
(
    "/Users/user/repos/github.com/OurCompany/tools/.git",
    "/Users/user/repos/github.com/OurCompanylab/OurCompanylab-sdk-java/.git",
    "/Users/user/repos/github.com/OurCompanylab/commons/.git",
    "/Users/user/repos/github.com/OurCompanylab/master-db/.git",
    "/Users/user/repos/github.com/OurCompanylab/matching/.git",
    "/Users/user/repos/github.com/env-OurCompany-router/.git",
    "/Users/user/repos/github.com/machine-learning/.git",
    "/Users/user/repos/github.com/OurCompany/OurService/.git",
    "/Users/user/repos/github.com/OurCompany/OurService/OurService_kaizen_batch/.git",
    "/Users/user/repos/github.com/OurCompany/OurService/OurService_learn/.git",
〜略〜

無事に.gitディレクトリがExcludeに入った。

リセットしたいときは

$ sudo defaults write /.Spotlight-V100/VolumeConfiguration.plist Exclusions -array

S3経由 codedeploy でデプロイするスクリプト

#!/bin/bash

APPLICATION_NAME= #codedeployのapplication name
DEPLOYMENT_GROUP= #codedeployのdeployment group
DEPLOYMENT_CONFIG=CodeDeployDefault.OneAtATime # codedeployのdeployment config
BUCKET= # ソースコードをおいておくS3のバケット
PROFILE=default #aws cli のprofileを指定する場合

## ===========================================
PROJECT_NAME=$(cd $(dirname $0); pwd | perl -pe "s|.*/(.*?)/?|\1|" )
VERSION=$(date '+%Y%m%d%H%M%S')
echo "Project Name = $PROJECT_NAME"
FILENAME=${PROJECT_NAME}-${VERSION}.tar.gz
KEY=${PROJECT_NAME}/$FILENAME

tar zcf  /tmp/${FILENAME} ./
aws --profile ${PROFILE} s3 cp /tmp/${FILENAME}  s3://${BUCKET}/${KEY}
ETag=$(aws --profile ${PROFILE} s3api head-object --bucket ${BUCKET} --key ${KEY} | jq -r .ETag |perl -pe "s/\"(.*?)\"/\1/g") # jq < 1.5対策
echo "uploaded ETAG = ${ETag}"

aws --profile ${PROFILE} deploy \
create-deployment \
--description "deploy with shell. --  ${VERSION}" \
--application-name ${APPLICATION_NAME} \
--deployment-group-name ${DEPLOYMENT_GROUP} \
--deployment-config-name ${DEPLOYMENT_CONFIG} \
--s3-location '{ "bundleType":"tgz", "eTag": "'${ETag}'","bucket": "'${BUCKET}'", "key": "'${KEY}'"}'

MacとiPhoneでなかなかテザリングがつながらない時の対処法

外でMaciPhoneでなかなかテザリングがつながらない。
よくあるのが、実は裏でWi2とかに接続されているのに、メニューバーではWifiに接続されていないように見えていたりする。

WifiのOff ,Onを行っても、結局裏でwi2等に繋がってしまっている為、なんかうまくいかない。

そんな時には
SystemUIServerを再起動する事で解決することがよくある。

再起動の方法は

ps -ax |grep /SystemUIServer | perl -pe "s/^\s*(\d+).*/\1/g" |head -n 1  |xargs kill

これをファイルとして保存しておいてSpotLightから呼び出すと結構楽

SORACOM Airで色々と検証してみる。

はじめに

ついに今まで謎にされてきたSORACOMのサービスが明らかになりました。

今までIoTのサービスだということだけを聞いていましたが、それ以外のことは一切謎に包まれていました。


ラッキーなことに事前に使わせて貰っていたので、公開しても良い9/29が過ぎた今日このブログを公開します。

SORACOMって?

SORACOMのサービスは簡単にいうと、IoTのための世界で一番柔軟な通信回線を提供するというサービスだと思ってます。
そんなSORACOMに対して一番興味を惹かれたのはこの一枚のスライドです。

f:id:tottokug:20150930215716j:plain
提供:SORACOM

正直AWSでIoTということで、L4以上のレイヤーを想定していたので、まさかL2で!?というのには胸が熱くなります。

柔軟な通信環境と通信料金を設定出来るようになっている事がビジネスの肝になっていそうで、これは今までの通信の世界では破壊的で、CTOの安川さんらしいなという感じがしました。

料金は?

ちなみに料金プランを見てみると
https://soracom.jp/services/air/price/soracom.jp

料金を見るとUPの方が安い事がわかります。
SIMフリースマホとかタブレットにさして使うとかも考えられますがそういった使い方であれば、正直IIJmioの方が安いと思います。

IoTのための通信回線というのがここの料金からも読み取れますね。

アップロードは安いのでセンサーなどのデータのアップロードや、カメラの映像を飛ばすとかであれば非常にありがたいことになります。

料金プランもs1.とかあるので、
たぶんそのうち、アップロードだけ早いu1.とかダウンロードだけが早いd1.とか出てくるのでしょう。

触ってみる

スピードテスト(
SORACOM Airのスピードテストをしてみた。 | DevelopersIO
)とか、使い勝手とか、APIの話とか、UIの話しは別の人が多分書いてくれるので今回はおいておいて、今回はそのネットワーク周りを探っていきたいと思います。

端末側のIPアドレスは?

まずはSIMをさして、自分のIPアドレスを確認するとどうなっているのかを確認してみます。
今回いろいろと検証に利用したのはこの端末

http://amzn.to/1M1r3Qpamzn.to

まずは単純にipconfigの値を
モバイル ブロードバンド アダプター モバイル ブロードバンド:

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::ad68:fe85:f4cc:20c3%11
   IPv4 アドレス . . . . . . . . . . . .: 10.173.101.69
   サブネット マスク . . . . . . . . . .: 255.255.255.0
   デフォルト ゲートウェイ . . . . . . .: 10.173.101.1


SIM経由でふられたIPアドレスは10.173.101.69/24というプライベートIPアドレスが振られました。
ここでふと思った事が二つ。
1つ目はプライベートIPだという事です。
もう一つは /24?ということでした。
とは言え、L2でAWSまで接続されているので、マスクは端末が適当に設定していて、正直どうでも良いことなのでしょう。
それかもしかしたら、接続するごとにネットワークアドレスが変わるんじゃないかという事で試してみました。

2回目、10.173.101.69
3回目、10.173.101.69
4回目、10.173.101.69
5回目、10.173.101.69

何回やってもアドレスが変わらないので、楽しくなってきました。

次に速度を変えてみます。
s1.standardだった所からs1.fastに変えてみます。そして切断、接続を繰り返してみます。
やはり変わりません。

次はステータスを休止にして開始にし直してみます。
ちなみに休止のステータスにした途端にネットワークは切られました。素晴らしい速さです。


休止から使用開始とまたステータスを変更しましたが、ここでもIPアドレスが変更されませんでした。
もしかしたら、MSISDN、IMSI当たりとIPアドレスを紐付けているのかもしれません。
そうなると、SORACOM AirのSIMはたった16,581,375枚しか発行出来ない事になってしまいます。
良い風に捉えると、SORACOM AirIPアドレスが固定されるという事であれば、SORACOM Air同士の通信が出来るんじゃないかなんていう夢も広がります。蟻です。蟻のネットワークが作れます。
しかし、もしIPアドレスが固定されているのであれば、consoleや、APIでそれが見れるようになっていてもおかしくないはずです。
ここはただアドレスを割り振ったキャッシュが数時間残っているというのが一番可能性として高そうです。
(きっと、APIでSIMに対してなにか送信できるサービスができてくるに1票 SORACOM controlみたいな)


参考までに:IIJmio

 接続固有の DNS サフィックス . . . . .:
 IPv6 アドレス . . . . . . . . . . . .: 2001:240:2401:9bfa:ad68:fe85:f4cc:20c3
 一時 IPv6 アドレス. . . . . . . . . .: 2001:240:2401:9bfa:1847:8bce:a8b8:2a30
 リンクローカル IPv6 アドレス. . . . .: fe80::7:3a43:1401%11
 リンクローカル IPv6 アドレス. . . . .: fe80::ad68:fe85:f4cc:20c3%11
 IPv4 アドレス . . . . . . . . . . . .: 100.92.169.196
 サブネット マスク . . . . . . . . . .: 255.255.255.0
 デフォルト ゲートウェイ . . . . . . .: fe80::7:3a43:1402%11
                                        fe80::7:3a43:1440%11
                                        100.92.169.1
外側のIPアドレス

次にこのSIMからインターネットにアクセスした時のGlobal IPをしらべてみます。
What Is My IP Address - Online Privacy and Safety Experts

141.0.9.62
でした。
このIPアドレス

$ dig -x 141.0.9.62
〜略〜
62.9.0.141.in-addr.arpa. 79173	IN	PTR	s20-16.opera-mini.net.

まさかのここでOperaTurbo

外側のIPアドレス(テイク2)

次にこのSIMからインターネットにアクセスした時のGlobal IPをしらべてみます。
What Is My IP Address - Online Privacy and Safety Experts

54.65.46.149
でした。
このIPアドレス

$ dig -x 54.65.46.149
〜略〜
149.46.65.54.in-addr.arpa. 300	IN	PTR	ec2-54-65-46-149.ap-northeast-1.compute.amazonaws.com.

EC2の東京リージョンのIPアドレスがアクセス元のGlobal IP として出てきました。
一安心です。
AWSIPアドレスからBANするのが好きなサイトとして有名なiTownpageも見てみましたが、無事に見れました。(OperaTurboもOff)


Traceroute

続いてTracerouteで、どのようなネットワーク経路を通ってインターネットに接続されていくのかを見ていきます。

 PS C:\Users\ope> tracert google.com

google.com [173.194.126.227] へのルートをトレースしています
経由するホップ数は最大 30 です:

  1   502 ms   454 ms   556 ms  ec2-175-41-192-130.ap-northeast-1.compute.amazonaws.com [175.41.192.130]
  2   308 ms   231 ms   125 ms  27.0.0.154
  3   608 ms   530 ms   623 ms  27.0.0.136
  4   241 ms   294 ms   423 ms  15169.tyo.equinix.com [203.190.230.31]
  5   582 ms     *      579 ms  72.14.236.82
  6   426 ms   570 ms   585 ms  72.14.232.109
  7   550 ms   387 ms   546 ms  nrt04s08-in-f3.1e100.net [173.194.126.227]


1hop目からいきなりEC2きました。AWSまでL2で来ている事が確認出来ました。

その次のHOPも見てみましたが、
http://www.speedguide.net/ip/27.0.0.154
http://www.speedguide.net/ip/27.0.0.136
http://www.speedguide.net/ip/203.190.230.31
http://www.speedguide.net/ip/72.14.236.82

このTracerouteを何回か取ってみることにことにしました。

$ tracert -h 1 amazon.co.jp
  1   559 ms   612 ms   697 ms  ec2-175-41-192-230.ap-northeast-1.compute.amazonaws.com [175.41.192.230]
$ tracert -h 1 hatena.ne.jp
  1   559 ms   612 ms   697 ms  ec2-175-41-192-232.ap-northeast-1.compute.amazonaws.com [175.41.192.232]
$ tracert -h 1 www.soracom.jp
  1   559 ms   612 ms   697 ms  ec2-175-41-192-234.ap-northeast-1.compute.amazonaws.com [175.41.192.234]

なんかコンプリートしたくなってきました。

$ tracert -h 1 google.com
  1   559 ms   612 ms   697 ms  ec2-175-41-192-236.ap-northeast-1.compute.amazonaws.com [175.41.192.236]

色々なドメインに対してのtracerouteを試してみましたが、今ここを書いている時点では第4オクテットが230,232,234,236が出てきました。

tracerouteの先がs3-website.****の時には少し変わって、12とか20、130が出てきました。

  1   498 ms   467 ms   629 ms  ec2-175-41-192-12.ap-northeast-1.compute.amazonaws.com [175.41.192.12]
  1    35 ms    44 ms    56 ms  ec2-175-41-192-130.ap-northeast-1.compute.amazonaws.com [175.41.192.130]
$ 
  1   559 ms   612 ms   697 ms  ec2-175-41-192-20.ap-northeast-1.compute.amazonaws.com [175.41.192.20]

とはいえ、ドメイン200個程試してみましたが、この7個しか見つける事しか出来ませんでした。

さいごに

今回はSORACOM Airについて色々といじってみましたが、私としては手元にAWSIPアドレスを引き込める事に非常に魅力を感じました。
さらにSORACOM Beamという別サービスとしてL3以上で付加価値をつけるというのも非常に興味津津です。最近はレイヤーの高い技術ばかりだったので、久しぶりのL3以下の技術にふれられて今日は興奮で寝れないかもしれません。



昨日ははてなのCTO 田中さんでした。
blog.stanaka.org


明日以降もSORACOM リリース記念リレーブログは続きます。
blog.soracom.jp

マナカナの画像からProejctOxfordとimagemagickで顔を切り出す。

前回マナカナの画像をBingAPIを使って収集しましたが、
今回はマナカナの画像から、顔だけを切り出して行きます

顔の切り出しをするには、顔の位置を特定してとか色々必要層ですが、
今回はまとめてProjectOxfordのFaceAPIを使います。

f:id:tottokug:20150731062146p:plain

Microsoft Project Oxford Face APIs

使うためにはAzureのPortalからMarketplace

f:id:tottokug:20150731063229p:plain

FaceAPI
f:id:tottokug:20150731063234p:plain

後は必要そうな情報をいれて数クリックで使えるようになりました。

Sign Upが完了したら
https://dev.projectoxford.ai/Developer
にアクセスして、Keyを取得します

f:id:tottokug:20150731063627p:plain



Keyが取得出来たら後は使うだけで、顔の切り出しが出来るようになります。

カレントディレクトリに写真があるとして、
以下のスクリプトを実行です。
(実行にはjqimagemagickが必要です)

APIKeyにはFaceAPIのKeyが入ります。PrimaryでもSecondaryでも良いと思います。

#!/bin/bash
IFS='
'
APIKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[ -d face ] && mkdir face
for f in $(find ./ -type f); 
do
  curl -X POST  -H "Content-Type: application/octet-stream" --data-binary @${f} \
  "https://api.projectoxford.ai/face/v0/detections?subscription-key=${APIKey}"   \
  | jq '.[].faceRectangle | "\(.width)x\(.height)+\(.left)+\(.top)"' 
  | xargs -I{}  convert -crop  {} ${f} face/$(uuidgen)-{}-${f##*/};  
done


face/というディレクトリに顔の部分だけを切り出した画像が出来上がって来ます。
f:id:tottokug:20150731064415p:plain

なんだか、明らかにマナでもカナでも無い人がいる。。。