読者です 読者をやめる 読者になる 読者になる

ディープラーニングを活用したマイクロサービスを構築し、画像から商品カテゴリの分類をしてみる

クローラー Python ディープラーニング 機械学習 画像処理 マイクロサービス Chainer

こんにちは、VASILYのバックエンドエンジニアの塩崎です。 iQONの中ではクローラーと検索サーバーを担当しています。

iQONのクローラーには提携ECサイトさんからクロールした商品を商品カテゴリー(Tシャツ、ワンピース、etc.)に自動的に分類する機能があり、商品タイトルや商品説明文などのテキスト情報を元に分類を行っています。 しかし、一部のカテゴリー(セーター・ニット帽)の商品はテキスト情報だけからでは精度の良い分類を行うことができません。 そのため、これらのカテゴリーの商品については画像を用いたカテゴリー分類を導入しました。

これらの機能を実現するために、当社のデータサイエンスチームとも協力を行い、ディープラーニングを用いたカテゴリー判定器を開発しました。 また、この機能は既存のクローラーの機能からの独立性が高いので、クローラーに組み込むときにはマイクロサービス化をして組み込みました。

その結果、カテゴリ判定の精度として、セーターカテゴリーでは99.7%、ニット帽カテゴリーでは99.8%という結果を得ることができました。

背景

iQONのクローラーは提携ECサイトさんから毎日数百万商品のクロールを行っており、毎日数千〜数万商品がiQONに新たに追加されます。 これらの数の商品のカテゴリー分類を人間が行うことは大変な労力を要するため、iQONのクローラーには商品カテゴリーの分類を自動的に行う機能があります。 商品タイトルや商品説明文といったテキスト情報に対して形態素解析を行い、文脈情報を考慮した上で候補となる単語を1つに絞り込み、その単語に対して辞書マッチを行うことでカテゴリー分類を行います。 その結果、精度98%での商品カテゴリー分類を実現しました。

f:id:vasilyjp:20160306161245p:plain

しかし、セーター(iQONでのカテゴリ分類ではトップス・ニットと表記されるが、紛らわしいのでこの記事ではセーターと表記)とニット帽の判定をテキストだけから精度良く行うことは困難です。 これらの商品のタイトル、説明文には「ニット」という表記しかされていない場合が多いからです。 なぜこのような曖昧な表記になってしまっているかというと、人間が商品情報を見るときには画像とテキストを同時に見て判断をするために、このような表記でも通常のECサイトには十分なためだと思います。 この問題を解決するためには、商品画像を用いたカテゴリー判定が必要になります。

画像からカテゴリー判定する手法の検討

近年、画像判定の分野では畳み込みニューラルネットワーク(CNN)が注目されていますが、この問題に対してもCNNが有効であるかを確認するために、以下に列挙された3つの手法との比較を行いました。

  • 多層パーセプトロン (MLP)
  • HOG特徴量 + k近傍法 (HOG + kNN)
  • HOG特徴量 + サポートベクターマシン (HOG + SVM)

これら合計4つの手法を用いて、セーターとニット帽の分類を行った結果を以下の表に示します。 なお、この検証では速度を優先してRGB画像をグレースケール画像に変換して分析を行いました。

手法 accuracy (%)
CNN 97.84
MLP 93.08
HOG + kNN 96.74
HOG + SVM 97.47

この結果からCNNでも他の手法に比肩する精度が出ることがわかりました。 この後、約100のカテゴリー分類するための分類器を作る必要があることや、商品に対して複数のタグ(トップス + ニット)を付与する必要があることなどを考慮し、CNNを採用しました。

ディープラーニングを用いた商品カテゴリーの判定

画像からのカテゴリー判定を行うために、以下のようなCNNのモデルをChainerを用いて作成しました。

import chainer.functions as F
import chainer.links as L
from chainer import Variable, FunctionSet

model = FunctionSet(
    conv1 = F.Convolution2D(  3,  64, 4, stride = 2, pad = 1, wscale = 0.02*math.sqrt(4*4*3)),
    conv2 = F.Convolution2D( 64, 128, 4, stride = 2, pad = 1, wscale = 0.02*math.sqrt(4*4*64)),
    conv3 = F.Convolution2D(128, 256, 4, stride = 2, pad = 1, wscale = 0.02*math.sqrt(4*4*128)),
    conv4 = F.Convolution2D(256, 512, 4, stride = 2, pad = 1, wscale = 0.02*math.sqrt(4*4*256)),
    fl    = L.Linear(6*6*512, 2, wscale = 0.02*math.sqrt(6*6*512)),
    bn1   = F.BatchNormalization(64),
    bn2   = F.BatchNormalization(128),
    bn3   = F.BatchNormalization(256),
    bn4   = F.BatchNormalization(512))

def forward(x_data, train = True):
    x = Variable(x_data, volatile = not train)
    h = F.relu(model.bn1(model.conv1(x)))
    h = F.relu(model.bn2(model.conv2(h)))
    h = F.relu(model.bn3(model.conv3(h)))
    h = F.relu(model.bn4(model.conv4(h)))
    y = model.fl(h)

    return F.softmax(y)

f:id:vasilyjp:20160307104226p:plain

CNNの層数は4層で全ての層が畳み込み層です。 出力層にはソフトマックス関数を用いているために、各カテゴリに属している確率が出力されます。 入力は96 x 96 pixelのカラー画像(RGB)です。 ECサイトから取得する画像はこのサイズではないことがほとんどですので、CNNに入力する前にリサイズ処理を行います。 アスペクト比1:1ではない画像については、正方形のエリアに貼り付けを行います。

プーリング層も組み合わせたモデルも作成し検証を行いましたが、全てが畳み込み層であるようなモデルの精度が最も高かったたために、このような構成にしています。 ECサイトから取得する画像の中にあるアイテムの位置は大体固定されているため、このような結果になったのではないのかと思います。

学習にはセーター・ニット帽の各カテゴリーの画像を1.5万枚ずつ使用しました。 テキストからのカテゴリー判定の結果を補助的に用いながら、すべての画像を目視でチェックしデータクレンジングを行いました。

また、学習の際にはGoogleが2015年に発表したBatch Normalization(http://arxiv.org/abs/1502.03167 )という手法も用いて、学習の高速化を図っています。

マイクロサービス化した理由

この機能をiQONのクローラーに組み込む際にはマイクロサービスとして組み込みを行いました。 使用している言語・ライブラリなどの違いにより同一ホスト上で稼働させるよりも、独立した機能として切り出した方がクローラーシステム全体の構成がシンプルになると判断したためです。 また、画像からカテゴリーを判定するという機能それそのものの独立性が高いことも理由の1つです。

現在クローラーはCentOS 6.5 + rubyで稼働しているのに対して、画像判定処理はUbuntu 14.04 + pythonで稼働しています。

画像判定サーバーの構成

画像判定用のサーバーの構成は以下のようにしました。

f:id:vasilyjp:20160306161336p:plain

現在の負荷状況ではインスタンスタイプc4.largeが一台あれば十分です。 負荷が増えた時にはサーバーのスケールアップ・スケールアウトも検討に入れる必要があります。

VASILYではデプロイツールとしてcapistranoを使用していますが、今回はPythonで製作をしたのでデプロイツールもPython製のfabric(http://www.fabfile.org/ )を使用しました。 通常のWEBアプリケーションのデプロイに要求されるようなアプリケーションコードの変更をするデプロイの他に、モデルのパラメーターを変更した時にモデルファイルをAmazon S3から取得し更新を行うデプロイ方法もサポートしています。 Teslaを搭載した学習用サーバーで生成したモデルファイルをAmazon S3にアップロードした後に、そのファイルをAmazon S3から取得し、サーバーの再起動を行います。

このサーバーに対して以下のようにリクエストを投げることで、画像判定の結果を得ることができます。 サーバーに対するリクエスト・レスポンスのフォーマットはGoogle Cloud Vision APIを参考にしました。

$ curl -X POST -H "Content-Type: application/json" -d '\
{ \
    "requests":[ \
        { \
            "features":[ "CATEGORY_DETECTION"], \
            "image":{ \
                "uri":"http://www.example.com/image.jpg" \
            } \
        } \
    ] \
}' \
"http://image-determination:8080/annotate"

{
    "responses": [
        {
            "categoryAnnotations": [
                {
                    "score": 80.0,
                    "description": "sweater"
                },
                {
                    "score": 20.0,
                    "description": "knit_caps"
                }
            ]
        }
    ]
}

ここで返されるscoreはそのカテゴリーに属する確率を%で表現したものです。

確率からカテゴリーへのマッピング

カテゴリー判定器の出力は、各カテゴリに属している確率ですので、それを元にカテゴリーにマッピングする必要があります。 今回は以下のような関数を用いて各カテゴリーにマッピングを行いました。

def map_to_category:
    threshold = 90
    if score_sweater > threshold
        return SWEATER
    elif score_knit_caps > threshold
        return KNIT_CAP
    else
        return UNKNOWN

単純にscoreの高い方のみを採用するということはせずに、scoreの高い方でありなおかつscore > threshold を満たすものを判定結果としています。 これは、カテゴリ判定ミスのFalse PositiveとFalse Negativeがユーザーに与える影響が大きく異なるための処理です。 簡単に言いますと、判定結果が曖昧な商品を間違ったカテゴリーに表示するよりも、その商品を出さない方がマシという考えです。

結果

このカテゴリー判定器を用いてセーターとニット帽の画像1万枚ずつを分類した結果を以下に示します。

予測

実測

セーター ニット帽 不明
セーター 9839 15 146
ニット帽 32 9636 332

これを元に、セーターカテゴリーの精度(precision)を求めると、9839 / (9839 + 32) = 99.7% という結果が得られました。 同様にニット帽カテゴリーの精度を求めると、99.8%という結果が得られました。

以下のiQONの画面からわかる通り、平置きの商品画像・人間が着用している商品の画像・マネキンが着用している画像など、様々な種類の画像を問題なく分類できています。

セーターの判定結果

f:id:vasilyjp:20160306161401p:plain

ニット帽の判定結果

f:id:vasilyjp:20160306161407p:plain

これからの展望

このカテゴリー判定器をさらに発展させたものとして、iQONで使用している約100のカテゴリに分類を行う判定器も開発しています。 それを実現するためには画像情報だけではなく、テキスト情報やブランド情報などの画像以外の情報も統合的に用いて判定を行います。

VASILYでは、最新の研究論文にアンテナを張りながら、同時にユーザーの課題解決を積極的に行うメンバーを募集しています。 興味のある方はこちらからご応募ください。 https://www.wantedly.com/projects/42989