データサイエンティストの中村です。VASILYではファッションに特化した画像解析エンジンを開発しています。本記事では、スナップ写真からファッションアイテムを検出するシステムを紹介したいと思います。
概要
このシステムの入力はスナップ写真です。スナップ写真が入力されたとき、システムは以下のタスクを解きます。
- 写真中からファッションアイテムに該当する領域を検出する
- 検出したファッションアイテムのカテゴリを予測する
- 検出したファッションアイテムに似ているアイテムをDBから検索する
各タスクを解く方法は様々ありますが、弊社のシステムでは2種類のネットワークを使ってこれを達成しています。
ファッションアイテムの検出とカテゴリ予測
検出は画像認識の基本的なタスクで盛んに研究されていて様々な手法が提案されていますが、今回はSingle Shot MultiBox Detector (SSD)*1 と呼ばれる手法を使いました。 SSDでは検出、すなわち領域の予測とカテゴリ予測を単一のネットワークで同時に解きます。 シンプルなモデルなので学習や拡張が簡単になるというメリットがあります。また、処理速度も既存の手法に比べてだいぶ改善されています。
検出・カテゴリ予測問題のデファクトスタンダードといえばRegion-based Convolutional Neuralnetwork (R-CNN)*2 があげられると思いますが、R-CNNやFast R-CNN*3 はSelective Search*4 という前処理を必要としていて、この処理の重さが課題になっています。Faster RCNN*5 で速度の問題はある程度改善されていますが、SSDの処理速度はFaster RCNNを凌ぎます。PASCAL VOCを用いた実験ではFaster RCNNが 7 FPS (frames per second) であるのに対し、SSDは 58 FPSを記録したそうです。
モデル
SSDのネットワークは以下です。
前半の層はVGG*6 と同じアーキテクチャを採用しています。VGGの全結合層は不要なので削除し、代わりに畳み込み層を追加しています。 こうして何層にも積まれた畳み込み層ひとつひとつの出力 (feature map) を領域の予測とカテゴリの予測に使います。複数のfeature mapの出力を予測に使う点がSSDの強みだそうです。
学習時の入力は画像とオブジェクトの矩形 (ground truth) 及びカテゴリです。学習開始時にground truthと各feature map上の矩形 (default box) との対応付けを行います。 default boxはセルごとにアスペクト比を変えて複数個定義されていて、それぞれについて矩形のオフセットとカテゴリを予測します。
SSDに関しては著者がコードを公開しています(github)。SSDを試したい場合はこのコードをそのまま使うことをおすすめします。 ファインチューニングの実験はREADMEには書かれていない操作が必要になるかもしれませんが、よくある落とし穴はissueで著者が回答しているので、困ったらissueを探してみてください。
ファッションアイテムの検索
画像検索の基本的なアプローチは画像特徴量の類似度計算です。 画像から特徴抽出して得たベクトルについて何らかの関数で類似度を計算することで類似度を定量化します (コサイン類似度やユークリッド距離がよく使われます) 。定量化した類似度を降順にソートすることで画像検索が可能になります。
ところが今回の画像検索においては画像のドメインに注意する必要があります。
まず入力画像はスナップ写真からトリミングされたアイテムの画像です。トリミングされているとは言え、矩形のスケールによっては背景が写り込んでいますし、向きも正面から撮影されているとは限りません。照明の影響も受けます。すなわち入力画像にはノイズが大量に含まれていると考えられます。一方で検索対象である商品画像は、アイテム単体が目立つように正面から撮影されています。背景も単色であることがほとんどです。 このようにドメインが異なる画像を検索するタスクについては、それぞれの特徴量をコサイン類似度のような単純な手法を使って比較しても精度は期待できません。
そこで今回は、ノイズの大きさに関わらず同じアイテムであれば大きな値を返す関数を学習によって獲得するというアプローチを取りました。
線形な関数で解けそうな問題でもないと思ったので、非線形関数を仮定します。となるとやはりニューラルネットワークを試すのが手っ取り早いです。
類似度学習
ディープ系の類似度学習にはcontrastive lossやtriplet lossを使ったものがあります*7 *8 。今回はtriplet lossを採用しました。
トリミングされたスナップ写真の特徴量を、写真と同じアイテムの特徴量を、写真と異なるアイテムの特徴量をとした時、triplet loss関数にはのtripletを入力します。 理想的な検索システムでは、の任意の組み合わせについてとなっているはずです。triplet lossによる学習はこの状態を目指します。
特徴抽出
特徴抽出にはVGGとSimo-Serra2016*9 のアーキテクチャを参考に以下のネットワークを使いました。
import numpy as np import chainer import chainer.links as L import chainer.functions as F from chainer import Variable class StyleExtractorBN(chainer.Chain): def __init__(self): self.train = False self._layers = {} self._layers['conv1_1'] = L.Convolution2D(3, 64, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*3)) self._layers['conv1_2'] = L.Convolution2D(64, 64, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*64)) self._layers['conv2_1'] = L.Convolution2D(64, 128, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*64)) self._layers['conv2_2'] = L.Convolution2D(128, 128, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*128)) self._layers['conv3_1'] = L.Convolution2D(128, 256, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*128)) self._layers['conv3_2'] = L.Convolution2D(256, 256, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*256)) self._layers['conv4_1'] = L.Convolution2D(256, 128, 3, stride=1, pad=1, wscale=0.02*np.sqrt(3*3*256)) self._layers['fc5'] = L.Linear(3072, 128) self._layers['bn1_1'] = L.BatchNormalization(64) self._layers['bn1_2'] = L.BatchNormalization(64) self._layers['bn2_1'] = L.BatchNormalization(128) self._layers['bn2_2'] = L.BatchNormalization(128) self._layers['bn3_1'] = L.BatchNormalization(256) self._layers['bn3_2'] = L.BatchNormalization(256) super(StyleExtractorBN, self).__init__(**self._layers) def __call__(self, x): h = F.relu(self.bn1_1(self.conv1_1(x), test=not self.train)) h = F.relu(self.bn1_2(self.conv1_2(h), test=not self.train)) h = F.max_pooling_2d(h, 4, stride=4) h = F.dropout(h, train=self.train, ratio=0.25) h = F.relu(self.bn2_1(self.conv2_1(h), test=not self.train)) h = F.relu(self.bn2_2(self.conv2_2(h), test=not self.train)) h = F.max_pooling_2d(h, 4, stride=4) h = F.dropout(h, train=self.train, ratio=0.25) h = F.relu(self.bn3_1(self.conv3_1(h), test=not self.train)) h = F.relu(self.bn3_2(self.conv3_2(h), test=not self.train)) h = F.max_pooling_2d(h, 4, stride=4) h = F.dropout(h, train=self.train, ratio=0.25) h = F.relu(self.conv4_1(h)) h = self.fc5(h) return h
結果
検出・カテゴリ予測・検索の結果を一度に表示します。 定性的な結果は概ね良好だと思います。
学習結果の可視化
学習した特徴抽出器で商品画像から抽出した特徴量をt-SNEで2次元に圧縮してみます。 これを見ると特徴の似たアイテムが近くに分布してることがわかります。
まとめ
スナップ写真からファッションアイテムを検索する仕組みについて紹介しました。検出・判別・検索の3種類のタスクが要求されるシステムを2つのネットワークを組み合わせて実装しました。特に検索についてはチャンピオンモデルのようなものが存在していないので、手探りな状態から始めました。幾つか実験を重ねて今の実装になりましたが、まだまだ高度化の余地はあると感じています。より使いやすいシステムを目指して、各タスクの高精度化や検索対象の拡大に取り組んでいきたいと思います。
最後に
VASILYでは、最新の研究にアンテナを張りながら、同時にユーザーの課題解決を積極的に行うメンバーを募集しています。 興味のある方はこちらからご応募ください。
*1:Liu, W., Anguelov, D., Erhan, D., Szegedy, C., Reed, S.: SSD: Single Shot MultiBox Detector. arXiv (2015)
*2:Girshick, R., Donahue, J., Darrell, T., Malik, J.: Rich feature hierarchies for accurate object detection and semantic segmentation. In: CVPR. (2014)
*3:Girshick, R.: Fast R-CNN. In: ICCV. (2015)
*4:Uijlings, J.R., van de Sande, K.E., Gevers, T., Smeulders, A.W.: Selective search for object recognition. In: IJCV. (2013)
*5:Ren, S., He, K., Girshick, R., Sun, J.: Faster R-CNN: Towards real-time object detection with region proposal networks. In: NIPS. (2015)
*6:Simonyan, K., Zisserman, A.: Very deep convolutional networks for large-scale image recog- nition. In: NIPS. (2015)
*7:Bell, S., Bala, K.: Learning visual similarity for product design with convolutional neural networks. In: SIGGRAPH. (2015)
*8:Hoffer, E., Ailon, N.: Deep metric learning using triplet network. In: ICLR. (2015)
*9:Simo-Serra, E., Ishikawa, H.: Fashion Style in 128 Floats: Joint Ranking and Classification using Weak Data for Feature Extraction. In: CVPR. (2016)