MATHGRAM

主に数学とプログラミング、時々趣味について。

chainerでYahoo!の占有率推定を実装してみた

こんにちは. 今回は僕も参加したYahoo! JAPAN データ&サイエンス カンファレンス 2016で取り上げられていた占有率推定を実装してみました.

前書き

Yahoo! Japanは働き方改革のロールモデルとして, 頻繁にメディアに取り上げられていますね. 特に週休三日制度のインパクトは大きく, 各所で話題になっていることだろうと思います. 業務効率を落とさずにこの制度を現実なものとするためには単純作業をAIに行わせるということが不可欠となって来ます.

そんな中, つい先日こんな記事を見かけました. itpro.nikkeibp.co.jp

この技術は僕がカンファレンスに参加した時に一番興味を持った(画像系を主としている僕にはちゃんとわかるものがこれくらいしかなかった)ものでした. しかしその時はトリミングで使われるなどの説明は恐らくしていなかったように思えます. 僕の記憶が正しければ, 主に検索の最適化で使われると説明していました. 何はともわれ, この記事のおかげでカンファレンスのことやこの論文を実装しようとしていたことなどを思い出したので, 今回実装してみた次第です.

論文の概要

実装した論文はこちらにあります.

論 文 - Yahoo! JAPAN研究所 - ヤフー株式会社

モチベーション

  • 画像中に特定の物体が占める割合(占有率) を高速に求めたい.

既存手法との差異, 本手法の利点

  • 既存手法ではsegmentationを行なった後に占有率を算出していた.
  • 本手法ではsegmentationをせず, 占有率を直接算出できる.
  • 既存のImageNetの学習済みパラメータを利用できる.

アルゴリズム

  • 出力層をクラス数のunitからなる全結合にする.
  • 出力層の活性化関数をsoftmaxにし出力を [0, \,1]とする.
  • 占有率を以下のように定め教師データにすることで回帰問題に落とし込める.
 \displaystyle
t_n( \boldsymbol{x}) =  \frac{\sum_p \delta_{n, \,j_p}}{ \sum_i \sum_p \delta_{i, \,j_p}}

ここで,  n n番目の画像データ,  pピクセル,  iはクラス,  \deltaクロネッカーのデルタ,  j_pピクセル pのラベルである.

実験

環境

  • chainer 1.20.1
  • AWS: Bitfusion Boost Ubuntu 14 Torch 7

方法

  • 以前実装したFCNによるSegmentationと速度比較する. FCNについてはこちら.

www.mathgram.xyz

  • モデルはVGG16を使用する.
  • データセットPASCALVOC 2012のSegmentationClassを用いる.
  • 画像の大きさは224*224に固定. (全結合を用いるので固定せざるを得ない)
  • FCNは学習済みの重みを使用していないので今回も使っていない.
  • 論文にはLossに関する記述がないが回帰問題なのでMSEを用いた.
  • 最適化手法はAdamで学習率は 1.0*10^{-5}.

コードの説明

部分的にコードの説明をしておきます.

  • 占有率について
def calc_occupancy(x, n_class=21):
    v = xp.empty((n_class))
    h, w = x.shape

    for i in range(n_class):
        v[i] = np.sum(x == i)
    v /= h*w
    if v.sum != 1.0:
        diff = 1.0 - v.sum()
        v[0] += diff
    return v

教師データにはピクセルごとにクラスが記録されているので, それぞれのクラスごとに和をとり, 画像の大きさで割るだけです. また境界値は背景と同じクラス0でマスクしました.

しかし教師データによっては合計が1にならなかったりしたので, その差分は全て背景クラスに加算しています. 恐らく境界データの部分がうまくマスクできていないと思われます. 以下の部分がちょうどその演算にあたる部分です.

    if v.sum != 1.0:
        diff = 1.0 - v.sum()
        v[0] += diff
  • VGGモデル

PASCAL VOCのSegmentationClassは背景を含めて21のクラスで構成されていまので出力層のfull connectを21unitにしました.

class VGG(chainer.Chain):
    def __init__(self, n_class=21):
        super(VGG, self).__init__(
            conv1_1=L.Convolution2D(3, 64, 3, stride=1, pad=1),
            conv1_2=L.Convolution2D(64, 64, 3, stride=1, pad=1),

            conv2_1=L.Convolution2D(64, 128, 3, stride=1, pad=1),
            conv2_2=L.Convolution2D(128, 128, 3, stride=1, pad=1),

            conv3_1=L.Convolution2D(128, 256, 3, stride=1, pad=1),
            conv3_2=L.Convolution2D(256, 256, 3, stride=1, pad=1),
            conv3_3=L.Convolution2D(256, 256, 3, stride=1, pad=1),

            conv4_1=L.Convolution2D(256, 512, 3, stride=1, pad=1),
            conv4_2=L.Convolution2D(512, 512, 3, stride=1, pad=1),
            conv4_3=L.Convolution2D(512, 512, 3, stride=1, pad=1),

            conv5_1=L.Convolution2D(512, 512, 3, stride=1, pad=1),
            conv5_2=L.Convolution2D(512, 512, 3, stride=1, pad=1),
            conv5_3=L.Convolution2D(512, 512, 3, stride=1, pad=1),

            fc6=L.Linear(25088, 4096),
            fc7=L.Linear(4096, 4096),
            fc8=L.Linear(4096, n_class)
        )
        self.train = False

以上のようにほとんどVGGと変化はありません.

結果

申し訳ないのですが, サーバー側で色々実験をするのが面倒だった(画像を移動させる等)ので予測の部分はCPUの演算のみになっています. 速度比較という点では問題はないと思うので許してください.

環境は以下です.

また一番注意しなければならない点なのですが, FCNも含めて学習が不十分なため"精度“の点にはあまり注目しないで欲しいです. (論文では精度も本手法の方が良くなったという記述がある.) とにかく速度に注目して比較してみよう.

1枚目

まずはこの画像から.
占有率は以下.

label: occupancy
background: 38.18%
person: 61.82%

邪魔なのでほぼ0%のクラスは出力してません. 足しても100%にならないかもです.

FCNによる推定

label: occupancy
background: 40.68%
person: 59.22%

time: 1.88s

本手法による推定

background: 27.52%
cat: 1.85%
chair: 1.24%
person: 65.3%

time: 1.19s

2枚目

次はこの画像.
占有率は以下.

label: occupancy
background: 73.42%
bus: 26.58%

FCNによる推定

label: occupancy
background: 80.55%
bus: 19.45%

time: 1.81s

本手法による推定

label: occupancy
background: 37.96%
aeroplane: 4.65%
boat: 7.58%
bottle: 1.12%
bus: 24.83%
car: 6.87%
diningtable: 1.32%
motorbike: 4.79%
person: 1.23%
sofa: 2.14%
train: 1.72%
tv/monitor: 1.08%

time: 1.29s

ちょっとノイズが多いですね. でもバスはちゃんと24%です.

3枚目


占有率は以下.

label: occupancy
background: 87.16%
dog: 12.84%

FCNによる推定

label: occupancy
background: 90.6%
bird: 2.05%
dog: 5.58%
person: 1.32%

time: 1.89s

本手法による推定

background: 87.83%
aeroplane: 3.45%
dog: 8.41%

time: 1.30s

ラスト


占有率は以下.

label: occupancy
background: 20.0%
car: 80.0%

ぴったり?まじかw

FCNによる推定

label: occupancy
background: 25.92%
aeroplane: 2.89%
car: 70.61%

time: 1.99s

本手法による推定

label: occupancy
background: 61.32%
aeroplane: 3.54%
bird: 1.95%
boat: 12.49%
bus: 4.01%
car: 1.4%
motorbike: 2.11%
person: 4.12%
train: 2.68%

time: 1.26s

うーん, これは全然ダメだ.

考察

まず間違いなく学習が足りてませんね. もっと厳密に実験したいのですが, 今月のAWSの請求2万くらい行きそうなんでもう無理っすw

速度だけで見ると本手法の圧勝です. 精度に関しても学習が進めばしっかりと占有率を出してくれそうですね.

一番個人的に気になっているのが, トリミングの実用的にはどうしているのかって部分です. このモデルはFCNと違い, 全結合を用いているので任意の大きさに対応できません. つまり固定の大きさの画像しか占有率を計算できないということになります.

今のところ, 僕が運用として思いつくのは, 様々な比に対応したモデルを複数用意するくらいです. ここはYahoo!ニュースなどがどのようなルールでトリミングを行なっているかによりますが, トリミングの比がある程度定まっているのであれば, そこまで大変なことではないと思います. AWSを借りてるような僕にはできませんがw

まぁとりあえず僕的には満足の行く実験&結果でした.

既存モデルのたった1層に工夫をするだけで, 会社に大きな貢献をすることができる.

とっても夢のある話じゃないでしょうか.

まとめ

  • CNNによる占有率推定を実装しました
  • 速度向上は測れたが画像によっては精度が悪い
  • 学習が不十分なのがとても心残り

一応レポジトリあげときますが, 出力の部分とか手元でざっとスクリプト書いたのでここに含まれてないです. まぁほんの参考程度に.

GitHub - k3nt0w/occupancy_estimate

以上です.