普通の画像データセットに飽きたら、Food-101はいかが？

ディープラーニングで遊んでいるとMNISTやFashion-MNIST、CIFARといった普通の画像データセットでは飽きることがあります。今回はFood-101というデータセットを紹介します。

## MNIST, CIFARの問題点
MNISTやCIFARは手軽に訓練でき、ニューラルネットワークのベンチマークとして使える素晴らしいデータセットです。しかし、これらのデータをだけで、CNNのアルゴリズムの評価をするのは少し問題があるのではないかと思います。

### 実際問題、手書き数字の分類がしたいわけではない
実際の画像問題を考えると、手書き数字の分類ができればいいかというとそうでもありません。手書き数字の分類は、実際の画像問題の中でのほんの一部でしかありません。この点は[Fashion-MNISTの理念](https://github.com/zalandoresearch/fashion-mnist/blob/master/README.ja.md)にも書かれており、

> MNIST はモダンな CV タスクを表現できません。 [“François Cholle: Ideas on MNIST do not transfer to real CV.”](https://twitter.com/fchollet/status/852594987527045120) を確かめてください。

ちなみにこの引用元はKerasの作者です。

近年のコンピュータービジョンのアルゴリズムの進化はめざましく、リアルの問題（モダンなCVタスク）に対してどんどん的確なアプローチをできるようになっています。しかし、そのアイディアを手書き数字だけに適用したのでは必ずしも意図した評価はできません。逆に手書き数字にチューニングすれば、リアルな問題に対して良い結果を出せるかというと必ずしもそうではありません。

### 画像サイズが小さすぎる
これはメリットでもあり、デメリットでもあります。確かに画像が小さければファイルサイズが少なくてもいいし、訓練も早いのでメリットだらけです。**CIFARなんか時間的にちょうどいい**と思います。

ただ、最近の特にData Augmentationアルゴリズムがだいぶ複雑になっていて、[最近出たRICAP](https://qiita.com/4Ui_iUrz1/items/e35e5f9210bbef693d05)なんか4枚の画像をつなぎ合わせて水増ししています。これは実際CIFAR-10でSOTAを達成しましたが、32x32の画像を分割してつなぎ合わせても、出てきた画像が何のかコンピューターにはわかっても、人間にはよくわからないという問題がおきます。

実際の問題ではよほど計算コストを抑えたくなければ32x32という低解像度で分類するということは少ないと思います。実際高解像度でも軽量にやるアプローチはありますし[^1]、もうちょっと解像度があったほうが人間にもわかりやすいのではないかなと思うことはあります。

[^1]: QiitaでもラズパイレベルでSemantic Segmentationを頑張っている方はいます

### 学習率のスケジューリングや、いかに訓練精度を100%に速く近づけるかが支配的になってしまい、個々のアルゴリズム自体を評価するのが難しい
これが自分が一番困っていることなのですが、CIFARは例えばテスト精度の90%の壁を超えるのに、学習率を落とすとガクッとテスト精度が上がる現象があります。これは一般的なデータセットで見られる現象かというとそうでもありません。
また、訓練精度が100%近くなると、同様に大きくテスト精度が上がります。これは訓練データ側ではラベルの分類はマスターしていて、あとは個々の確率の調整になり、これが結果的に学習率を減らすことと同じような効果を持っているのではないかと思います。

ディープラーニングの精度向上の大半が学習率と言われることもあるように、学習率や訓練精度の向上のスピード次第で、個々のアルゴリズムの差がひっくり返ってしまうということが稀によくあります。少なくともCIFARレベルだと、個々のアルゴリズムの差が1～2%、さらには1%もなくて効いているかどうか微妙というケースがあっても、別の問題に持っていったときにすごくよく効くということがあります[^2]。

[^2]: 例えばPCA Color Augmentationなんか自分がCIFARで試したときは大したことなかったのに、YOLOで1枚だけの画像で学習させたらものすごい効いたそうです　https://blog.shikoan.com/pca-color-augmentation/#CIFAR-10　https://qiita.com/shinmura0/items/f818bcbb92d5fff5279d

自分がCIFARやMNISTをディスりたいとかそういう気持ちはなくて、実際の問題を解きたかったら、もう少し実践的なデータセットで確かめないとよくわからないよねということが言いたかっただけです。

## 代替のデータセットにほしいこと
では実際に使えそうな代替データセットを探してみました。ただし、以下のような条件で絞りました。

### ファイルサイズが極端に大きくない
ぶっちゃけモダンなCVに対してのベストプラクティスは論文でもよく出てくる[ImageNet](http://www.image-net.org/)です。ただ著作権の関係で、URLデータのみ提供されていて、自分でダウンロードしないといけなかったり、[Kaggleにもデータセットあります](https://www.kaggle.com/c/imagenet-object-localization-challenge)が全体で155GBもあったり全然お手軽じゃないんですよね。

自分の中での基準としては、**少なくとも1～10GB、できれば数GBであってほしい**というのが実感です。

### ある程度難しさがほしい
例えば「[Cats vs Dogs](https://www.kaggle.com/c/dogs-vs-cats)」という猫か犬かを分類するデータセットがあります。これは1GBなく、とても優秀なデータセットなのです。しかし、5年前ならコンペとして成立したのですが、この5年間にCNNが発達しすぎたせいで、現在ではCNNの入門的な位置づけで、ちょっと頑張れば95%は出ます。問題がちょっと簡単すぎてアルゴリズム間の差がわからないのです。

逆に言えば、**問題としてある程度難しければ、アルゴリズムの差が大事になってくるので、効果がわかりやすく出てくるはず**です。

### 1から訓練させてもいけるような（転移学習に限定しないような）サンプル数がほしい
一番安直に難しくする方法は**クラスを増やして、クラスあたりのサンプル数を減らす**ことです。例えば1クラス100枚、100クラスなんてすれば簡単に難しくできます。しかしそうすると、転移学習専用になってしまい、1から訓練させることがほとんど不可能になってしまいます。1から訓練させてもうまくいくように、**1クラス500枚～1000枚ぐらいは欲しい**かなと思います。

### クラスあたりのサンプル数が歪んでいない（不均衡データではない）
最近のKaggleのコンペで見られるのですが、あるクラスは1万枚画像があるのに、別のクラスでは100枚しか画像がないというケースがあります。これを「不均衡データ」と呼ぶことにします。

不均衡データでは、そのまま交差エントロピーで計算しても、大きいほうのクラスに合わせてしまえば精度はかなり高くなってしまうので、精度が高くてもF1スコアがえらく低い（つまりちゃんと訓練できていない）というケースがよく起こります。

ただ実際問題不均衡データが悪いというわけではなく、現実世界の問題を考えると不均衡データというのもあってしかるべきだと思いますが、あくまでアルゴリズムを評価する上だと、不均衡データだと不均衡対策に主眼が置かれてちょっと本質的な比較ができないかなと思います。なので、**CIFARやMNISTの代替として使いたいのなら、不均衡データはできれば避けたい**のです。

### できればやっていて楽しいデータセットが欲しい
あとこれは個人的な話ですが、やっててモチベが上がるデータセットだと嬉しいですね。手書き数字の分類なんかしても「ふーん」ぐらいしか思いませんが、逆に[アニメキャラの顔の分類](https://qiita.com/koshian2/items/4f91ac5cceb6125fd57e)とかだとテンション上がりますよね。

## Food-101データセット
「お前ずいぶん欲張りだな。そんなデータセットあるのかよ？」とか言われそうですが、ありました。**Food-101データセット**です。

**Food-101 – Mining Discriminative Components with Random Forests**
Lukas Bossard, Matthieu Guillaumin, Luc Van Gool
https://www.vision.ee.ethz.ch/datasets_extra/food-101/

これは**101種類の料理を分類する問題**で、もともとはランダムフォレストの研究だったみたいですね。クラスを見ると、

```
Apple pie
Baby back ribs
Baklava
Beef carpaccio
Beef tartare
Beet salad
Beignets
Bibimbap
: : : 
Ramen
Ravioli
Red velvet cake
Risotto
Samosa
Sashimi
Scallops
Seaweed salad
Shrimp and grits
Spaghetti bolognese
Spaghetti carbonara
Spring rolls
Steak
Strawberry shortcake
Sushi
Tacos
Takoyaki
Tiramisu
Tuna tartare
Waffles
```

**ラーメンや寿司の画像**なんてもありますよ。楽しそうでお腹が空いてきそうなデータセットですね。例えば1番目の「アップルパイ」のフォルダを見ると、

![food_03.png](https://qiita-image-store.s3.amazonaws.com/0/232088/32178b4d-ca8e-aa4d-d35a-6ecd9e1b94dc.png)

素晴らしい。**機械学習で飯テロ**してくるデータなんてこれだけではないかと思います。

画像見て気づいた方もいらっしゃるかもしれませんが、実はこのデータセット、**ラベルが間違った（mislabeled）なデータ**も入っています。アップルパイと見せかけてキーボードの画像が入っていたこともありました。ただ、データが間違っているというのは現実の問題でもよくあるので、それを含めて考えると実践的なデータではないかと思います。別に面倒だったらデータクレンジングしなくてもいいと思いますよ[^3]。

[^3]: データクレンジングが必須というわけではなくて、とりあえず訓練してみて、分類ミスを調べ上げ、ラベルが間違ったデータが支配的ならクレンジングすればいいだけだと思います。どうせクレンジングしても、データの間違いが少なければ大して精度は上がりません（例えばデータの間違いが1%なら、そこを頑張って直しても最大で1%しか精度上がらないよということです）。

101クラスの画像を1枚ずつ列挙してみました。

![food_01.png](https://qiita-image-store.s3.amazonaws.com/0/232088/642a271a-347a-a108-6b5d-cf89957b1877.png)
![food_02.png](https://qiita-image-store.s3.amazonaws.com/0/232088/6079a7df-e739-ccee-d05e-c4e8f993dfb2.png)

枝豆、餃子、寿司、刺し身だの、ずいぶん日本食多いですね。

実はこのデータセット、**不均衡データは一切なくて全101クラス全て1000枚ずつ用意されています**。つまり、精度を使って直球で評価することができます。オリジナルが4.65GBで、256×256にリサイズして圧縮したら1.2～1.3GB（これは圧縮フォーマットによる）に縮んだので、**容量面でも均衡性でもかなり優秀なデータセット**だと思います。

訓練：テストの分割はmetaのフォルダーに入っています。**各クラス250枚をテスト、750枚を訓練**としています。

最後に1個だけ注意点ですが、このデータセットはライセンスが独自ライセンスです。以下のライセンスです（license_agreement.txt）。個人の研究用で使うとかだったら特に問題ないと思うのですが、商用利用とかだったら注意してください。

```
 LICENSE AGREEMENT
=================
 - The Food-101 data set consists of images from Foodspotting [1] which are not
   property of the Federal Institute of Technology Zurich (ETHZ). Any use beyond
   scientific fair use must be negociated with the respective picture owners
   according to the Foodspotting terms of use [2].

[1] http://www.foodspotting.com/
[2] http://www.foodspotting.com/terms/
```

ちなみに元サイトは現在OpenTableという別のサイトに置き換わっています。FoodspottingがOpenTableに買収されたようですね[^4]。

[^4]: https://japan.cnet.com/article/35027549/

## Inception-V3による転移学習
とりあえず訓練データに水平反転のData Augmentationだけ入れて転移学習させてみました。

```python
import tensorflow as tf
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.models import Model
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense
from tensorflow.keras.callbacks import History
from tensorflow.contrib.tpu.python.tpu import keras_support
import tensorflow.keras.backend as K

import pickle, os

def create_model():
    model = InceptionV3(include_top=False, weights="imagenet", input_shape=(160,160,3))
    x = GlobalAveragePooling2D()(model.layers[-1].output)
    x = Dense(101, activation="softmax")(x)

    # mixed4(132)から先を訓練する
    for i in range(133):
        model.layers[i].trainable = False

    return Model(model.inputs, x)

def train():
    model = create_model()
    model.compile(tf.train.RMSPropOptimizer(1e-4), "categorical_crossentropy", ["acc"])

    tpu_grpc_url = "grpc://"+os.environ["COLAB_TPU_ADDR"]
    tpu_cluster_resolver = tf.contrib.cluster_resolver.TPUClusterResolver(tpu_grpc_url)
    strategy = keras_support.TPUDistributionStrategy(tpu_cluster_resolver)
    model = tf.contrib.tpu.keras_to_tpu_model(model, strategy=strategy)

    batch_size = 2048
    train_generator = ImageDataGenerator(rescale=1/255.0, horizontal_flip=True).flow_from_directory(
        "food101/train", (160,160), batch_size=batch_size, save_format="jpg")
    test_generator = ImageDataGenerator(rescale=1/255.0).flow_from_directory(
        "food101/test", (160,160), batch_size=batch_size, save_format="jpg")

    hist = History()
    model.fit_generator(train_generator, steps_per_epoch=75750//batch_size,
                        validation_data=test_generator, validation_steps=25250//batch_size,
                        callbacks=[hist], epochs=100)
    history = hist.history

    with open("food_history.dat", "wb") as fp:
        pickle.dump(history, fp)

if __name__ == "__main__":
    K.clear_session()
    train()
```

Google Driveにアップロードする容量を軽くするために256×256でリサイズかけています。それを160×160の入力サイズに入れています。2回リサイズかけていますが、ここ気になるようでしたら元の画像でやったり、アスペクト比を加味しながらAugmentationしてみてください。

TPUで計算させて1エポック5分程度でした。10万枚でそこそこ解像度が高いので、程度時間がかかってしまうのは致し方ないです。

結果は、**訓練精度が100%近くなったのに、テスト精度は60.24%でした**。101クラス分類で全部料理の画像だからこんなものではないでしょうか。

![food_04.png](https://qiita-image-store.s3.amazonaws.com/0/232088/fb128c91-9558-52a9-fc14-ecffb6785a74.png)

ちなみにこのデータ・セット、SOTAは90.27%だそうです。

> WISeR: 90.27% Top-1 Accuracy with 10-crops. Martinel, Niki, Gian Luca Foresti, and Christian Micheloni. ["Wide-Slice Residual Networks for Food Recognition." arXiv preprint arXiv:1612.06543 (2016).](https://arxiv.org/pdf/1612.06543.pdf)

以下のリポジトリには、InceptionV3で86.97%出した実装と記録のまとめがあります。いくつかarXivにあるんですね。

https://github.com/stratospark/food-101-keras

しかし、ただの水平反転させた転移学習ではテスト精度60%なのに、そこから30%も伸びるんですね。これはやりがいがあるのではないでしょうか。

以上です。**Food-101**、なかなか面白くて美味しいデータセットなのでチャレンジしてみてはいかがでしょうか。画像分類ある程度慣れた方なら楽しめると思いますよ。