RoomClip 開発者ブログ

Amazon RekognitionとMachine Learningで画像判定機を気軽に作る

こんにちは、エンジニアの平山です。
昨今のAIブームのなか、引きの強そうなタイトルで思い切って記事を書いてみます。
ちと長くなるのでざっくりですすめますよ。

まずはじめに、
機械学習系の話題はテーマを絞り込まないとすぐに深い森に迷い込んでしまうので、
一番最初にそこだけはっきりさせましょう。
本日のテーマです。

「ある条件に合致する画像かそうでないか、を判断できる機械を作る」

例えば、
「部屋全体を写している写真なのか、そうでないのか」という判定はRoomClipでは少し重要な気がします。 もちろん、そうでない写真、例えば何かの接写であっても「問題がある」というわけではないのですが、 ときに「部屋全体が写っている写真に絞り込みたい」と思うこともあります。 そういったことを「画像だけで」判定する方法は、 Deep Learning旺盛の昨今たくさーんありそうですが、本気でこれを組もうとするとなかなか厄介です。 なので、できるだけ簡便に、出来合いのものだけで、ちゃちゃちゃっと作ってみましょう。

さて、まずは改めてテーマに戻ります。
余計な部分を省いて眺めると、

「...合致する画像か、そうでないか、を判断...」

とあるので、これはいわゆる「2値分類」とか言われる領域とわかります。
要は「TRUE」か「FALSE」か、仕分けるだけ。
この学習だけなら、Amazon Machine Learningで十分できそうです。

さて残る問題は、「ある条件に合致する画像」という部分。
画像に対して「条件」を作用させなくてはならない。
つまり、画像側が同じ「条件」で表現されていなくてはならない。
これは少し困りました。画像を入力としてある程度正規化された表現を得なくてはいけない。

過去多くの場合はここで止まっていたと思います。
が、現代は画像解析アプリケーションはゴマンとあるわけです。
その一つがAmazon rekognition。これを使えば非常に簡単にAPIをつかって画像解析ができます。 例えば僕の部屋の画像を使うと、、、

こんな風に「この画像に何が写っているのか」を簡易に解析してくれます。
ただ、もちろん万能ではありません。
限られたLabelと、そのLabelが指し示すものが写っているかどうかの確信度、
この程度しか現時点では得られません。(※随時年齢判定とか追加されたみたいです)
例えば、「Foodが80%の確率で写っていて、さらにDogが90%の確率で写ってる」とかそういうレベルです。

しかしそれでも工夫と根性で予測モデルを作ることはできます。
さっそく設計してみましょう。

Amazon Machine Learningの2値分類は、ロジスティック回帰と呼ばれる手法で学習するのですが、 これは一見難しそうに見えてとてもシンプルな方法です。
要は「回帰式」なのだから、
目的変数 = (説明変数×係数)の和
という式に最終的になるのです。
ロジスティック回帰の場合、この目的変数がざっくり「確率」になります。
今回の具体的な例で言うと、
「画像が部屋全体を写したものである確率」を目的変数として、
「Rekognitionによってつけられたラベルごとの確信度」を説明変数とします。
そうすれば、なんとなくRekognitionの結果をつかって、
画像が「部屋全体であるか、否か」を判定できそうです。

もっと具体的な話をすると、
ある画像をRekognitionにかけたとき、「90の確信度でRoom」「80の確信度でDinning」と仮に判定したとします。
この時、この画像はどれくらいの確率で「部屋全体の写真」と言えるのかを判断する式が、

(この画像が部屋全体である確率) = (Roomの確信度×0.005) + (Dinningの確信度×0.004)

で与えられたとしたら、

(この画像が部屋全体である確率) = 90 × 0.005 + 80 × 0.004 = 0.65

なので、65%で部屋全体の画像であることが言えました!

こういうことができれば、見事に目的を達成できます。
障害になりそうなのは、

  • 「どうやって0.005とか0.004とかの係数を計算すればいいの?」
  • 「RoomとDinningとか、どのラベルで判定すればいいの?」

という2つです。
前者も後者も「事前に人が判定した正解データ(=教師データ)」があれば、
あとはAmazon Machine Learningがなんとかしてくれます。
RekognitionとMachine Learningがうまく連鎖してくれそうですね。

じゃぁやってみましょう。

最初は根性フェーズです。
事前に正解のデータを作っておく必要があります。
試しに、3500件ほどの画像について人力で判定してみましょう。(え
ここでは3500件のうち、1800件が「部屋全体の写真」で、1200件が「そうではない写真」だったとします。

続いて、3500件の画像すべてをRekognitionに放り投げ、labelを取得しておきます。
仮にそのlabelの種類が全部で1000個ほどあったとします。
(Dog,Food,Room,Chair...などなど)
これは少し多い気がするので、そのうち「部屋全体」と判定された写真のなかで多く出現するlabelを50個。
逆に「部屋全体ではない」と判定された写真の中で多く出現するlabelを50個もってきましょう。
これで合計100個のラベルが手に入りました。

この時点で、1つの画像ごとに下記のようなデータが手に入っているはずです。

  1. 指定100個のラベルが写っているかどうかのそれぞれの確信度
  2. 部屋全体の写真なのかどうかの判定結果

これが3500件あるわけです。
今、部屋全体の写真なのかどうかの判定結果を、judgeというラベルで管理し、

  • 「judge = 1」なら部屋全体、
  • 「judge = 0」なら部屋全体ではない、

というふうに数値で表現するとしましょう。

すると、画像ごとに合計101個の要素をもったベクトルが手に入ります。
これをカンマ区切りで、3500行のCSVにしてみましょう。
やりました。これこそが「教師データ」となります。

あとはこれをAmazon Machine Learningに放り込みます。
放り込むときは「2値判定をする」「目的変数をjudgeとする」「説明変数を残り100個のラベルの値とする」というような設定をすれば、もう完了です。
本当にこれだけで完了です。
具体的な画面遷移はこんな感じ。

S3にあげた教師データを選択する。1click。


CSVの1行目がラベルであることを教える。2click。


目的変数がjudgeであることを教える。3click。


あとはVerify!を連続して3,4回ほどクリックすれば、
じっと待っているとそのうち計算が終わり、見事に予測できるようになるのです!
こうなれば、全く新しい画像であっても、まずはRekognitionでLabelとその確信度を取得し、 その値をMachine Learningになげつければ、「部屋全体」の写真なのかどうかをjudgeしてくれるわけです。

さっそく本番環境で使えるような具体的な仕組みにしてみましょう。

  1. S3に画像がアップされる
  2. それをトリガーにLambdaが起動
  3. Rekognitionに画像を放り投げLabelとConfidenceを取得
  4. それをMachine LeaningのEndpointに投げて「部屋全体判定」

びっくりすることに、EC2いらず、いわゆるサーバレスと言うやつです。
これにて、今回のテーマである、

「ある条件に合致する画像かそうでないか、を判断できる機械を作る」

ということがなんとか達成できました。
なんとなくブラックボックスが多いですが、出来合いのものだけでもソコソコのシステムが作れるよ、というお話でした。





なんですが、、、
やっぱり色々もっと詳しく知りたいですよね、という方向けに少しMachine Learningの知識周りの整理を。
ここからさきは興味ある人だけどうぞ。






まず、作成した予想モデルの精度評価はAUCという数値で表現されます。
これはROC曲線の積分値になるのですが、その辺周りの概説をします。

2値判定の予想の場合、その予想結果のリスクは大きく2つに別れます。
僕は勝手にそれを「誤解リスク」と「見逃しリスク」と呼んでます。

  1. 本当は0だったものを1といってしまうリスク
    => 無罪の人を有罪と言ってしまう誤解リスク
  2. 本当は1だったものを0といってしまう=リスク
    => 病気の人を健康と言ってしまう見逃しリスク

まず自分がやろうとしている予想が、
どちらのリスクを「最低限にしなければならないか」を決めないといけません。
今回の「部屋全体」判定の場合、
本当は「部屋全体」だったものを見逃すリスクより、
「外の写真」だったものを「部屋全体」としてしまう誤解リスクのほうを最低限にしなければいけません。
とにかく「部屋全体」の写真しか見たくないのですから。

本当は0だったものを1といってしまう誤解リスクは、
本当に0である要素のうち、どれくらいの割合1と予想してしまったのかという誤解割合が重要になります。
つまり、

1と予想したが本当は0だった数 / 実際に0である数

となり、もっと詳しく言うと、

(1と予想したがFalseした) / (1と予想してFalseした + 0と予想してTrueだった)

と表現でき、これを一般に、

FP / (FP + TN)

と書きます。 FPは False Positive といい、Positiveと予想したが間違えた、という表現となります。
TNは True Negative といい、Negativeと予想して、たしかに的中した、という表現です。

このレートのことを、
False Positive Rateといいます。
(MLのAmazon説明はこちら

このレートが小さいほど「1と予想して外すことが少ない = 誤診が少ない」と言えそうです。
実はこれものすごーく簡単に下げることができます。
シンプルに沈黙すればいいのです。
ビビり倒していれば少なくとも誤診することはありません。
しかしそれではなんの意味もありません。

よって今度は、予想結果のリスクではなく効能について考えてみます。
予想結果の効能は、1のものを1といえる力、つまり再現率が高いことです。
つまり1と予想して本当に1だった数が、実際に1だった人の数と近ければよいのです。
これを先程のルールで表現すると、

TP/(TP+FN)

となります。 True Positive Rateといえなくもないですが、一般にこれをRecall(再現率)と呼びます。

しかしよく見るとこれも問題のある指標です。
とにかく皆を1と判定しまくれば、たしかに実際に1だった人を全員1と判定することができます。

かくして、

「沈黙する」 vs 「何でもかんでも1という」

の綱引きが始まるわけですね。

ここで、リスクの話を思い出します。
今回の予想モデルでは誤診が一番の問題なので、これを最小限に抑えて、
再現率は努力目標としたいところです。
なので、とにかくこの誤診率=False Positive Rateを「どこまで妥協できるか」という覚悟を決めなくてはいけません。
仮にこのレートを5%まで、と決めたとしましょう。

すると、Machine Learningはとても便利なのでこのFalse Positive Rateをいじることができます。
Evaluationsの右下にある、このバーです。


まずこのFalse Positive Rateを0まで下げます。
するとRecall(再現率)の値も下がるはずです。これでは効能が得られない。
よって、ゆっくりゆっくりFPRを上昇させていきます。
するとRecallの値も上がっていきます。
そして、FPRが我慢の限界、今回は0.05ですが、この値まで上がってきたところでストップしましょう。
その時のRecallの値がこのモデルの性能と呼べます。

ところで、このFPRを調整しているとき一体何を調整していたのでしょうか。
それはモデルから出力される確率のしきい値=score thresholdです。
このしきい値を超えたscoreが出力されれば1と予想し、そうでなければ0と予想するという風にモデルを調整できるわけです。
極端な話、このしきい値を1としてしまえば、このモデルはすべて0と予想します。
いわゆる「沈黙」です。
逆にしきい値を0としてしまえば、何でもかんでも1と予想します。

さて、少し前に戻って、
FPRをゆっくりあげていったとき、きっとこう思ったはずです。
「FPR(誤診率)の上昇に比して、Recall(再現率)が凄い勢いで上昇すればいいのに」
全くその通りです。
FPRが0.01増えるだけでRecallが0.8くらいまであがってくれれば願ったり叶ったりですね。 乱暴に言うと、この度合いを示しているのが最初のAUCという値になります。
AUCが1に近ければ、この勢いが激しいと言えるケースが多くなります。
(厳密にはFPRとRecallのプロットの積分値。プロットの曲線が上に凸な増加関数であればAUCは高くなる)
よってこのAUCの値がざっくり「このモデルの全体的な性能」を評価していることになるので、まずもってこの値が重要になるんですね。
score thresholdは実際にそのモデルを使うシーンの覚悟によって異なるわけです。

以上で大体のAmazon Machine Learningでの2値問題処理をする時の重要な指標の説明となります。

ちなみに、
弊社で色々試行錯誤を重ねた結果、
300程度の説明変数、6000くらいの教師データ数で、そこそこの誤診率・再現率を叩き出しております。
ロジスティック回帰の係数を計算する方法(SGD:確率的勾配降下法)の調整などもできるようですが、 複雑なことが必要な機械学習であればもう実装したほうが良いと思います。 あくまでざっくり簡単な学習、というときにAmazon MLをうまく使いこなせると良いと思っています。

非常に長くなりましたが、今日はこのへんで。


この記事を書いた人:平山知宏

Tunnel株式会社のエンジニア。 インフラからサーバサイド全般担当。 インダストリアル系の家に住みたいと思うけど、子供もできちゃったし危ないから工具とかほっぽっとくのやめようと思っている1985年生まれ。


[Android] Firebase Dynamic Linksを試してみた!

こんにちは。
TunnelでAndroidアプリの開発を担当しています冨永です。

RoomClipでは昨年、主要なページを対象にAppIndexingの対応を行いました。
これによりWebの検索からシームレスにアプリを開くことができるようになっています。

また検索結果からだけではなく、RoomClipのページ内に「アプリで見る」等のボタンを配置してアプリを開くということもできるようになりました。
しかし、AppIndexingはアプリをインストールしていることが前提となっています。

そこでアプリをインストールしていない方にもWebから快適にアプリを使用していただくために、Firebaseの機能の1つであるDynamic Linksを導入したい!と思い、今回実際にリンクを作成して動作を確認してみました。


Dynamic Linksとは?

Firebaseの機能

Firebaseは、Google I/O 2016で話題となった開発をサポートするモバイルプラットフォームです。Firebaseには、Analyticsを中心に様々な機能があります。

Dynamic Linksはその中でもGROWカテゴリに属する機能で、「さまざまプラットフォームで最適に動作するよう、動的に動作を変更できる便利な URL です」と紹介されています。

Dynamic Linksは、リンクを開いたユーザーの端末にアプリがインストールされていれば、そのリンクに紐づくコンテンツをアプリ上で表示し、インストールされていなければ、指定したGoogle Play ストアのページが開きインストールすることができます。
それだけでなく、インストール後も開いたURLを取得することができ適切にユーザーの求めるコンテンツを提供することができます。


実際に試してみる!

それでは、実際にリンクを作成して動かしてみたいと思います。

Firebaseのコンソールでリンクを作成

Firebaseのプロジェクト作成方法・初期設定は割愛します。

左メニューからDynamicLinksを選択 左メニューからDynamic Linksを選択します。
パッケージ名やIDが公的に入手可能なアセット/関連ファイルに追加されますがいいですか?と聞かれるので内容を確認して「始める」をクリックします。

Androidのアセットに関してはApp Links、iOSの関連ファイルに関してはUniversal Linksを確認してください。

ディープリンクURLを設定 質問に答えていくことでリンクが作成されます。
まず、ディープリンクURLとダイナミックリンク名を設定します。リンク名はコンソール画面での判別用の名前です。ディープリンクURLは、タグページがすでにディープリンクに対応しているのでこちらを使用します。

ちなみに、このURLでは西海岸インテリアのタグがついた実例写真一覧が表示されます。RoomClip Award 2016で見事1位に輝いた2016年主役のインテリアスタイルです。ぜひチェックしてみてください!

Androidでの動きを設定 今回はiOSでの動作確認は割愛します。

Androidではアプリでディープリンクを開き、インストールされていない場合はGoogle Play ストアへ遷移するように設定します。
カスタムURLやアプリのバージョンで動きを変えることもできるのでサービスに合わせて設定してください。

残りの項目では、解析用のパラメータ設定やリンクがシェアされた際のプレビュータイトルなどの指定ができます。必要な場合は設定してください。

一通り設定して「ダイナミックリンクを作成」ボタンをクリックするとURLが作成されます。 URLが作成される

動作を確認

それでは、実際にリンクを開いて動作を確認していきたいと思います。 まず以下の2つの場合を確認しました。使用したアプリは、記事公開時点でGoogle Play ストアに公開されているものでディープリンクに対応しています。

  1. アプリをインストールしていない状態でリンクを開く
  2. アプリをインストールした状態でリンクを開く

1. アプリをインストールしていない状態でリンクを開く

コンソールで設定したRoomClipのGoogle Play ストアのページが開きました。
ストアでインストール完了後、通常では「開く」と表示される部分が「次へ」となっています。
「次へ」をタップするとアプリが起動し、西海岸インテリアの写真一覧が表示されました。 Google Play ストア

2. アプリをインストールした状態でリンクを開く

こちらの場合も適切に西海岸インテリアの写真一覧が表示されました。

IntentFilterおよび受信したIntentとDynamic Linksの関係

アプリ側を変更せずに理想的な動作をしてしまいました。
理想的すぎて不安になったので動作を理解するために、IntentFilterおよび受信したIntentとDynamic Linksの関係を調べてみました。

確認には以下のコードを使用しました。action, data, ResultCallbackで取得できるディープリンクURLを確認しています。
コードは、公式のドキュメントで紹介されているものを使用しています。Firebaseそのものの実装はこちらの公式ドキュメントをご確認ください。

タグページのIntentFilterのみ実装しリンクを開く

受信したIntentからactionandroid.intent.action.VIEWdataは設定したディープリンクURLが取得できました。 また、ResultCallbackでもディープリンクURLを取得できています。

通常のディープリンクによる遷移は、actionがIntent.ACTION_VIEWであること、dataがnullでないことの2点の条件に当てはまるかどうかで判定し、dataに格納されているURLを解析して適切な画面を表示しています。

今回、Dynamic Linksのリンクを開きましたが、アプリ側では通常のディープリンクとして処理されていることがわかりました。

IntentFilterをすべて削除してリンクを開く

IntentFilterをすべて削除してリンクを開いたところ、アプリが起動しトップ画面が表示されました。

actionandroid.intent.action.MAINdataは設定したディープリンクURLが取得できました。 また、ResultCallbackでもディープリンクURLを取得できています。

IntentFilterを設定していない場合は、通常起動と同じように起動されるようです。

Dynamic Linksに対応したIntentFilterを設定する

今回生成したリンクに対応したIntentFilterを設定し、以下の2パターンを確認しました。

  1. Dynamic LinksのIntentFilterのみ設定した場合
    actionandroid.intent.action.MAINdataは設定したディープリンクURLが取得できました。

  2. Dynamic Linksとタグページの両方のIntentFilterを設定した場合
    actionandroid.intent.action.VIEWdataは設定したディープリンクURLが取得できました。

1,2どちらの場合も、ResultCallbackでもディープリンクURLを取得できています。

Dynamic Linksを開いた際の動作まとめ

どうやら以下のような関係となっているようです。

  • ディープリンクURLに対応するIntentFilterが設定されている場合、対応するActivityが開かれ、ブラウザ等でディープリンクを開いた際と同じ動作をする
  • 通常のディープリンクとDynamic Linksを区別するにはResultCallbackで取得できる値を確認する必要があるが、非同期のため処理を工夫する必要がある
  • IntentFilterを設定していない場合、通常起動と同じ動作をする

今回の調査でdataにディープリンクURLが入っていたことが予想外でした。
実装前は、通常のディープリンクの場合はdataにURLが入っていて、Dynamic Linksの場合は、dataは空でResultCallbackでURLが取得できてコンテンツを表示するという動きを期待していました。

まとめ

良いところ

  • コンソールを使用するとURLの生成が簡単
  • Android, iOSの両方に対応したURLが作成できる
  • バージョン指定でアップデートにも対応できる
  • ディープリンクに対応していれば、そのURLを設定することで簡単にDynamic Linksに対応することができる
  • インストール後もすぐにユーザーの求めるコンテンツを表示することができる
  • コンソールでURLを生成した場合は、クリック数を確認することができる

良くないところ

  • アプリをインストール済みでリンクを開いた場合と、Google Play ストアでインストールした場合で表示するコンテンツを変えたい場合、実装が難しい
  • コンソールでは、一度作成したリンクの内容を変更できない

使いどころ

今回URLはコンソールで作成しましたが、プログラムによってダイナミック リンクを作成することもできるようです。プログラムで作ることができると様々なページに対応することができます。
新規インストールしたユーザーを判別すれば、チュートリアルを表示した上で適切なコンテンツを表示することもできるので理想的な動作になり良いなと思いました。

また、広告で使用することも想定されているようですので、広告内容とリンク先内容の組み合わせを適切に設定することでより満足度の高い体験をしてもらえるのではないかと思いました。


以上となります。
今回は、弊社のRoomClipにDynamic Linksをテスト実装してみました。もともとのアプリの作り次第では、良くないところとして上げた点もうまく解消できる可能性があります。

URLの自動生成やiOSの実装に関してはまたの機会に。
ここまでお付き合いいただきありがとうございました!


<参考>
Firebase
Firebase Dynamic Links


この記事を書いた人:Tominaga

Androidエンジニア。 ドロイドくんが好き。