ディープニューラルネットワークと近似最近傍探索で類似画像検索する

この記事は[ピクシブ株式会社 AdventCalendar 2017](https://qiita.com/advent-calendar/2017/pixiv) 7日目の記事です。

[@tamanobi](https://twitter.com/tamanobi)と申します。ピクシブ内ではPHPやRuby on Railsを使って開発をしています。

今回は、ディープニューラルネットワークと近似最近傍探索で画像検索を行ってみました。実装にあたって詳しい理論部分まで調べきれていないため、補足や指摘は歓迎します。

# はじめに
「あのとき見た画像がどうしても検索できない」「画像はあるけど誰の作品かわからない」というときはないでしょうか？ [Google](https://www.google.co.jp)や[TinEye Reverse Image Search](https://www.tineye.com/)には、テキストから画像を探すのではなく、画像を入力（クエリ)として画像を検索できる機能が存在します。

画像をクエリにすることができると、手元にある冊子を撮影して検索することができたり、似たような画像を探すことができます。画像がクエリであれば、テキストをクエリにした場合と異なり、言語圏をまたいだ検索が容易です。

類似画像検索の手法は、画像をダウンスケールしてハッシュを求める[ImageHash](https://pypi.python.org/pypi/ImageHash)や、カラーヒストグラムから似たような色合いの画像を見つける方法などさまざまなタイプがあります。今回は、ディープニューラルネットワークの中間層を用いた特徴量による類似画像検索を実装して試してみました。

# 類似画像
ひとことで類似画像と言っても、さまざまな解釈があります。少し考えてみただけでも以下の類似が例として挙げられます。

* 差分が近しいから類似している(ImageHash、単純な差分計算など)
* 写っているものは違えど、構図や色合いが似ている(カラーヒストグラムやGISTなど)
* 写っているものが同じだから似ている(ディープニューラルネットワークやSIFT/SURFなど)

ImageHashやカラーヒストグラムや過去に個人的に試したことがあるので、今回の対象は「写っているものが同じだから似ている」とします。当然写っているものが何か識別することが重要になってきます。

画像中に写っているものを識別したり、写っているものの領域を区別したり（セグメンテーション）する技術は、ディープニューラルネットワークの成長によって、ここ数年で格段に進歩しました。ライブラリも充実しており、手軽に試せるようになっているのでそれらのライブラリを利用して画像の特徴を取り出します。

# 類似画像検索に必要なもの
今回は、画像の実際の差分をみて似ているかどうか判別するわけではありません。画像から特徴量を抽出し、ベクトルデータとして効率よく検索できるように保存します。特徴抽出にはディープニューラルネットの中間層を、検索には特徴量などのベクトルデータの高速検索が可能な近似最近傍探索ライブラリを使います。

* ディープニューラルネットワークの中間層: 入力画像から画像特徴量を抽出する
* 近似最近傍探索ライブラリ: 特徴量を保存し、効率よく似ている画像特徴量を検索する

# 今回使用する画像
類似画像検索を試すために、https://www.pexels.com にて [Creative Commons Zero (CC0) license](https://creativecommons.org/publicdomain/zero/1.0/)で提供されている画像を利用します。

![今回特徴量を抽出した画像の一部。これらの画像はhttps://www.pexels.comでCC0で提供されている。](https://qiita-image-store.s3.amazonaws.com/0/38967/9d0aa885-37d8-fca7-ba80-e37758ec600d.png)

画像URLのリスト(120枚): https://gist.github.com/tamanobi/62e7096506aebdf6ee31a120623a8d00

# ディープニューラルネットワークの中間層を特徴量抽出のために利用する
ディープニューラルネットワークを利用するためのライブラリは、[Chainer](https://chainer.org/)や[PyTorch](http://pytorch.org/)、[TensorFlow](https://www.tensorflow.org/)などたくさんありますが、一番手軽に使えそうな[Keras](https://keras.io/ja/)を使うことにします。

今回は、簡単のために[ImageNet](http://www.image-net.org/)を学習した重みを利用します。多量の学習データが手元にある場合は、自分で学習させてもいいと思います。

[KerasのApplicationsページ](https://keras.io/ja/applications/)にはコード付きでモデルがいくつか紹介されています。今回は、学習済みの重みがApache2ライセンスの[InceptionV3](https://keras.io/ja/applications/#inceptionv3)を選びました。画像から特徴量を抜き出すコード例も載っています。

Kerasのページで紹介されているモデル以外にも、ディープニューラルネットワークはさまざまな改善が行われており、驚くほどたくさんのアーキテクチャが考案されています。

# 近似最近傍探索ライブラリ
画像から抽出された特徴量は、高次元のベクトルデータとなります。今回InceptionV3の中間層を特徴ベクトルとすると2048次元です。画像の局所特徴量として有名なSIFTは128次元です。かなり次元数に違いがあります。

数件では問題になりませんが、ベクトルデータが100万件のオーダーにもなると全探索は現実的ではありません。そのため、大量のベクトルデータから類似度の高いベクトルを高速に検索する近似近傍探索ライブラリがいくつか存在します。

* https://github.com/facebookresearch/pysparnn
* https://github.com/facebookresearch/faiss
* https://github.com/yahoojapan/NGT
* https://github.com/spotify/annoy
* https://github.com/monochromegane/gannoy

上に列挙した中で、僕が使ったことがあるのはNGTだけでした。NGTはC++で書かれており、JSONの取扱が若干厄介なため、選択肢から外しました。ほかのライブラリの中から、インタフェースやビルド方法などが簡便で使い勝手がよさそうなgannoyを使用します。

# アプリケーションの構成
ウェブサーバーと近傍探索サーバーに分かれています。ウェブサーバーの役割は、ユーザーからリクエストを受け付けて特徴量抽出を近傍探索サーバーに保存したり、問い合わせた結果をユーザーに返したりします。

* ウェブサーバー(Tornado+InceptionV3)
* 近傍探索サーバー(gannoy)

# 実際に類似画像検索を試してみる
## ネコ
### クエリ画像
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/e2f10f48-62ab-38fb-009e-f3a5df2232fa.png)
### 結果画像
左から順にスコアが高い順です。120枚の画像中、ネコ画像は3枚のみだったので、すべてのネコが検索にヒットしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/c7a89429-caec-7aa6-16a9-7e0a3ff20f84.png)

## 桜
桜の画像です。少しごちゃごちゃしているのであまり良い結果は期待できないかもしれません

### クエリ画像
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/7bcb03c1-493f-4a83-560f-23c1b06669f2.png)

### 結果画像
桜を入力したら、花と識別されたのか植物画像が結果としてでてきました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/3d1e27a8-5134-913c-c3ea-e98d2b0df82f.png)

花がすべて出てくればよかったのですが、わりとわかりやすいだろう花(下図)が検索結果にでていないのは少し不思議です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/29d6b654-007c-6a42-f904-29e12ea13306.png)

## ガジェット類
物が沢山写っている画像だとどうでしょう

### クエリ画像
![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/b34cfb43-e2bf-be31-8693-eb853abaa2b7.png)

### 結果画像
最左にはスマートフォンが写っています。ガジェットというくくりでは正解でしょうか。ほかの画像は、平面に何かものが置いてあるように見えます。机が写っているとみなされた可能性があります。クエリ画像が机の上にものが置いてあるように見えなくもないですね。ぱっと見では、うまくいっているように見えませんね。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38967/3a563d1d-f3f7-2d5e-00da-8f4e69abee83.png)

# まとめと感想
「あのとき見た画像がどうしても検索できない」「画像はあるけど誰の作品かわからない」という気持ちを解消できるのでは？という出発点から、類似画像検索をライブラリを組み合わせて試してみました。
ライブラリが充実していたため、システムの構築自体は数時間でできましたが、両手をあげて喜べる結果にはなりませんでした。まだまだ検証不足で良いとも悪いとも言えない結果ですが、データ数を増やして継続して評価していく予定です。

NGTと比べて、gannoyは使いやすかったので常用するかもしれません。

実験が終了してから気づいたのですが、[monochromegane/gannoy](https://github.com/monochromegane/gannoy)の元となった[spotify/annoy](https://github.com/spotify/annoy)のドキュメントに「ベクトルの次元数が100くらいならうまく動く」と書いてありました。2048次元は多すぎるかもしれないです。

# 告知
明日の[ピクシブ株式会社 AdventCalendar 2017](https://qiita.com/advent-calendar/2017/pixiv) 8日目の担当者は、@printf_moriken です。お楽しみに。