富士フイルムコンペ: 写真の撮影年推定 - 0.06%足りなかった人の解法

 

はじめに

2019年12月頭から2020年1月半ばまでの約1か月間、富士フイルム Brain(s)さん主催のデータサイエンスチャレンジ*1が開催されており、私も参加していました。(次回もあるそうなので奮ってご参加ください)

私は残念ながら4位フィニッシュだったため、授賞式で解法を話せなかったのでここにまとめようと思います。

 

TL;DR

解法の主要素は以下の6点です。

  • Pretrain済みEfficientNet-b5による42クラス分類
  • Softmax Cross Entropy + Center Loss を最小化
  • Data Augmentation(DA) に MixUp を利用
  • 入力データはRGB表色系からCYM(K)表色系へ変換
  • クラスラベルは撮影年±1年を正解としたマルチクラス
  • 学習済みEfficientNet-b5から1792次元の特徴量を取り出してBagging(300回)

この中でも特に有効だと感じたものは以下の3つです。

  1. MixUp
  2. Center Loss
  3. マルチクラス化

これだけで、EfficientNet-b5単体で約0.76まで出ます。

 

 コンペの概要

今回の課題は、スキャナーによりスキャンされたアナログ写真が撮影された年を推定するというものでした。

この課題は、富士フイルムさんが開発している PhotoBank というサービスを意識した内容の様でした。このサービスはスマホ等で撮影した写真をオンラインのライブラリに登録し、デジタルなアルバムを作成するものです。

アナログ写真をスキャンして登録することも可能ですが、この時に問題となるのが写真がいつ撮影されたのか?という情報が画像のみでは簡単にはわからない点です。アルバムは撮影年月日をもとにソートされているのが一般的ですが、これがわからなければソートのしようがないのは想像に易いと思います。

前回は写真上に印字された撮影年月日を読み取る(いわゆるOCR)問題でしたが、今回は写真に撮影年月日が印字されてない場合を想定した内容でした。

f:id:onimanP:20200206055736p:plain

今回の画像例: 撮影年月日の印字がない

f:id:onimanP:20200206055944p:plain

前回の画像例: 撮影年月日が印字されている

 

配布データセット

配布されたデータセットは以下のような特徴があります。

  • 1枚の画像(写真)から部分的にクロップされている(つまり全体が写っていない)
  • 撮影年は1979 - 2018年の40年(数に偏りがかなりある)
  • 画像サイズは縦横256px
  • trainデータ: 6686枚 / testデータ: 1671枚
  • testデータに public / private分割はない

評価方法等

  • 実際に撮影された年の前後1年は正解として扱われる
    (例: 2000年に撮影された画像は、1999, 2000, 2001のいずれかの予測で正解となる)
  • スコアの計算は "(正解数) / (テスト画像枚数)" とシンプル
  • Submissionは1日に何度でもでき、回数も無制限
  • 配布データ以外を利用可能 (Pretrain済みモデルの利用が可能)

 

解法

全体として、単一の分類モデル(EfficientNet-b4)を学習し、その後モデルから得られる特徴ベクトルに対しBaggingによるアンサンブル学習を行うことで精度を若干高めています。

1. EfficientNet-b4による分類モデル

入力データの変換

入力画像はRGB表色系からCMYK表色系に変換し、K(Key plate)は落としたCMYの3 channelの画像を入力としています。劣化した写真は全体的に黄色がかっていることが多いため、黄色(Y)を直接含むCMYK表色系のほうが信号値に差が出るのでは無いかと仮定したため採用しています。正直、あまり効果は無かったようには思います。

Kを落としたのは、Pretrain済みモデルが3 channelを想定しているのが一番大きな要因で、Kは劣化に大した影響を及ばさないだろうと仮定したためです。(図示するまでもないですが...)簡単に図示すると以下のような感じ。

                   f:id:onimanP:20200207064034p:plain

MixUpによるDA

MixUp*2は簡単に言うと2つの画像とラベルを、ある割合 \lambda( \lambda\sim\beta(0.2, 0.2))で合成してしまおうというものものです。イメージは以下のような感じ。また、ラベルは実際の撮影年を1.0、前後1年を0.5として与えています。前後1年を考慮する関係で、全体のクラス数は42クラス(1978-2019年)となります。

          f:id:onimanP:20200207073838p:plain

この、MixUpとマルチラベル化 は分類性能に大きく効いている様な印象を受けました。実際に、上位の方もMixUpはイイゾと言っていました(言ってましたっけ)。

 

 EfficientNet-b4の学習

EfficientNet-b4はPretrain済みの重みを利用する。モデルの学習を図示すると以下のような感じになる。

        f:id:onimanP:20200207061959p:plain

入力からCNNによるEncodingによって1792次元の特徴ベクトルを取得し、得られた特徴ベクトルに対して、Embedding空間(特徴空間)における各クラスの重心間距離を損失とする(Center Loss*3 )。

Center Lossに加えて分類で一般的なSoftmax Cross Entropyを計算し、全体として "Center Loss + Softmax Cross Entropy" を最小化する様にモデルを学習しています。

Center Lossはいわゆる距離学習の1つで、式が簡潔かつ実装が簡単な割に効果は大きかったです(4%程度は上がるはず)。

 

 

学習を回し続けても若干精度が向上することがわかっていたので、学習エポック数は1200エポックで設定していました。また、通常はTrain/Validationでデータセットを切るところ、Validationには一切データを割かず、すべてのデータをTrainにまわしていました。
この欠点は、学習をどこで打ち切れば良いのかがわからない点ですが、今回に限ってはオーバーフィットしても性能が下がらない(Train/Testデータが同じ画像からクロップされているため?)傾向にあったので、全部のデータをTrainに回した方が効果的でした。

ただ、これをやるとモデルを得るのに1日以上かかります...

 

上記の手順でEfficientNet-b4単独でScore: 0.7594までは達成出来ました。

2. Baggingによるアンサンブル学習

手順は至って単純です。

  1. まず、すべての画像(Augmentation無し)からEfficientNet-b4によって1792次元の特徴ベクトルを取り出す。
  2. 特徴ベクトルの長さが512次元になるようにランダム(重複無し)に取り出し、新たな特徴ベクトルを得る。取り出した512次元の特徴ベクトルを用いて3層のニューラルネットワークを学習し、予測クラス(予測年)を得る。
  3. 上記の手順を300回繰り返し、300個の推定年の中央値を最終的な予測として得る。

図示すると以下のような感じです。これによって、0.1%程度の改善がみられました。

f:id:onimanP:20200207082539p:plain

これで、最終的なScoreは0.7606でコンペを終えました。

リーダーボード非公開時は2位(1位のWatermarkRemoverさんは入賞対象外なので実質1位)だったので、これはイケると思ったのですが結局1週間で3名の方に抜かれてしまいました...(悔しい)

 

気づいた点

今回の場合ではData Augmentationはあまり行わない方が良いように感じました。最初の方で複数のDAを試してみましたが、性能が上がるどころか悪くなることが多かったです。

この理由は、アナログ画像の経年劣化は画像全体に影響を及ぼすため、下手に変換を加えると性質が全く変わってしまうためかなと直感的には思いました。特に色の分布を正規化等で変えてしまうと、劣化に関する情報が消えてしまうのではないかと思います。

 

興味深い点

特徴ベクトルを取り出していた関係で、時々t-SNEで2次元に落としてプロットをしてみていました。EfficientNet-b4とResNeXt50で学習した後のプロットが以下の様になります。見方としては、青から赤にかけて撮影年が新しい(1979->2018)画像のプロットです。

f:id:onimanP:20200207083824p:plain

細かい条件が少々変わっているので一概には言えませんが、EfficientNet-b4が各クラスを完璧に分類する(クラスごとにかたまっている)特徴ベクトルを生成しているのに対し、ResNeXt50は年代が近い画像は似通った(連続している?)特徴ベクトルが得られている様に見えます。

直感的には今回の課題は回帰問題として解くのが良さそうで、ResNeXt50の様に年代で連続している様な特徴ベクトルが得られれば、これを用いて回帰を行っても上手くいったかもしれません。

 

他にやりたかったこと

上位の解法を見ていると、やはりモデルのアンサンブルを行うのが効果的の様でした。私自身もモデルのアンサンブルを行おうとは思ったのですが、学習エポック数を1200と巨大にしていたため、1回の学習に時間が掛かるために後回しにしていました。

エポック数を少なくして複数のモデルを組んでみるべきだったと後悔しています。

 

さいごに

今回が初めてのコンペで、色々とわからない点も多かったですが、非常に楽しく取り組め、何とか入賞も出来て良かったです。ただ、慢心からか最後に3名の方に追い抜かれてしまった点が後悔が残ると同時に、反省点かなと思います。

次回もあるようなので、是非とも参加しようと思います。