Aidemy Tech Blog

機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる、株式会社アイデミーの技術ブログです。

データを水増しする際の注意点!

 

機械学習がしたい...でもデータがない!

機械学習の勉強をするうえでほしいデータは、web上で機械学習用のデータとして見つけることができます。ただもし自分で実装するときは独自でデータセットの収集を行う必要になりそうです。自分でデータセット見つけるのは大変そう、めんどくさい。そこで今回は少ないデータセットでもよりいい精度を出すためにデータの水増しを実装してみました!データセットにはcifar-10のデータ数を少なくして使います。そこでデータの水増しの概要と気を付ける点についてまとめてみました!

データの水増しとは

データの水増しとはデータに様々な線形変換を加えることによってデータの数を増やすことを言います。線形変換とは、左右反転したり、コントラストを変えたり、ズームしたり、ずらしてみたりといったようなことです。今回は左右反転、ずらし、ノイズ付加の3つを実装してみました。ほかにも様々な変換がありますので詳しくは次のサイトを参考にしてみてください。

qiita.com

このように少ないデータを水増しする技術をデータオーギュメンテーションというそうです。





cifar-10とは?

cifar-10というデータセットを知っていますか?cifar-10とはairplane, automobile, bird, cat, deer, dog, frog, horse, ship, truckの10クラスの写真が格納されたデータベースです。合計60000枚あり、サイズは32×32ピクセルでRBGの3チャネルがあります。80 million tiny images というもののサブセットで物体が何かを認識する一般物体認識という分野で有名なデータセットです!ちなみにデータはnumpy.ndarrayの形で入っているのですぐにpythonで扱うことができます。

 cifar-10の詳細に関してはこちら

aidiary.hatenablog.com

では、このcifar-10をCNNで学習しましょう!

 




水増しcifar-10をCNNで推定してみる。

まずkarasから出しているdatasetsを利用してcifar-10を入れる

random_state = 0
(cifar_X_1, cifar_y_1), (cifar_X_2, cifar_y_2) = cifar10.load_data()
cifar_X = np.concatenate((cifar_X_1,cifar_X_2),axis = 0)
cifar_y = np.concatenate((cifar_y_1,cifar_y_2),axis = 0)
#cifar_Xを0~1に、cifar_yをone_hot形式にする
cifar_X = cifar_X / 255.
train_X, test_X, train_y, test_y = train_test_split(cifar_X, cifar_y, test_size=10000, 
random_state=random_state)

 

これでそれぞれにデータが入りました。
表示してみると

fig = plt.figure(figsize=(9, 15))
fig.subplots_adjust(left=0, right=1, bottom=0, top=0.5, hspace=0.05,
                    wspace=0.05)

for i in range(36):
    ax = fig.add_subplot(6,6, i + 1, xticks=[], yticks=[])
    ax.imshow(train_X[i])

f:id:bkenken1234:20170920002615p:plain
cifer-10.plot

このように写真を見ることができます。
写真粗い 笑



次にCNNの実装をしたいところなのですが、その前にデータの数をそれぞれ100個づつに減らしていきます。

y_num = np.argmax(train_y,1)
train_X_10 = np.zeros([100,32,32,3])
for i in range(10):
    train_X_10 = np.concatenate((train_X_10,train_X[np.where(y_num == i)][:100]),axis = 0)
train_y_100 = np.repeat(range(10),100)
train_X_100 = train_X_10[100:]

 

これで各クラス10個の計1000のデータとそのラベルが得られました!

次にこのデータに3つの加工を加えて4倍に数を増やします。

#processing
train_X_pro = train_X_100
train_y_pro = train_y_100
#flapping 左右反転
train_X_flip = train_X_100[:, :, ::-1, : ]
#cropped 移動
padded = np.pad(train_X_100, ((0, 0), (4, 4), (4, 4), (0, 0)), mode='constant')
crops = rng.randint(8, size=(len(train_X_100), 2))
cropped_train_X = [padded[i, c[0]:(c[0]+32), c[1]:(c[1]+32), :] for i, c in enumerate(crops)]
train_X_cropped = np.array(cropped_train_X)

#gain noise ノイズ付加
train_X_noise = train_X_100 * rng.binomial(size=(train_X_100.shape), n=1, p=0.8)


train_X_processing = [train_X_flip,train_X_cropped,train_X_noise]
for train_processing in train_X_processing:
    train_X_pro = np.concatenate((train_X_pro,train_processing),axis = 0)
    train_y_pro = np.concatenate((train_y_pro,train_y_100),axis = 0)



ここで一回整理
train_X_100 , train_y_100 が加工する前のデータで、
train_X_pro , train_y_pro が加工後のデータとなっています。




この後CNNを実装します。CNNの実装に関しては次のページを参照してみてください。
CNNの構造に関しては今回のメインではないので割愛させていただきます。詳しい実装は以下のサイトなど参考にしてみてください。

qiita.com



それではCNNを使ってそれぞれepoch数を5にしてF値を計算してみました。



水増しcifar-10結果

EPOCHは試行回数
cost は誤差関数の値、今回はクロスエントロピーというものを使用。
test F1はCNNより得られたF値です。testには画像1万枚を使用しました。

水増し無し
EPOCH:: 1, cost: 2.457, test F1: 0.102
EPOCH:: 2, cost: 2.431, test F1: 0.116
EPOCH:: 3, cost: 2.418, test F1: 0.124
EPOCH:: 4, cost: 2.414, test F1: 0.131 
EPOCH:: 5, cost: 2.417, test F1: 0.138

水増しあり
EPOCH:: 1, cost: 2.395, test F1: 0.112
EPOCH:: 2, cost: 2.379, test F1: 0.118
EPOCH:: 3, cost: 2.370, test F1: 0.121
EPOCH:: 4, cost: 2.362, test F1: 0.123
EPOCH:: 5, cost: 2.354, test F1: 0.127

...?むしろさがってる!?
最初は水増しデータのほうが成績が良かったんですがすぐに逆転されてしまいました。
どうしてF値が下がってしまったのでしょうか




考えられる理由

  • 少ない画像を加工して何回も使うのでオーバーフィッティングした

オーバーフィッティングとは
datahotel.io


今回の場合、同じようなデータを入れすぎたため汎用性がなくなってしまったのかなと思います。


  • 与えたデータが難しすぎた

たとえばノイズなど実際のテストデータには存在しない水増しデータも追加していたのでその影響で目的としてないノイズが入った画像でも分類できるモデル!を作ろうとしてしまい目的であった分類が難しくなってしまった。与える水増しデータは実際に想定されるものでないとおかしな汎用性を生んでしまいF値が低下してしまう。


例として
たとえばデータセットをMNISTにして、水増しデータとして左右逆転のものを入れると


#水増し無し
EPOCH:: 1, cost: 2.265, test F1: 0.183
EPOCH:: 2, cost: 2.240, test F1: 0.247
EPOCH:: 3, cost: 2.213, test F1: 0.316
EPOCH:: 4, cost: 2.182, test F1: 0.401
EPOCH:: 5, cost: 2.146, test F1: 0.484
EPOCH:: 6, cost: 2.102, test F1: 0.539
EPOCH:: 7, cost: 2.049, test F1: 0.585
EPOCH:: 8, cost: 1.983, test F1: 0.612
EPOCH:: 9, cost: 1.902, test F1: 0.627
EPOCH:: 10, cost: 1.806, test F1: 0.636

#水増しあり
EPOCH:: 1, cost: 2.270, test F1: 0.168
EPOCH:: 2, cost: 2.251, test F1: 0.188
EPOCH:: 3, cost: 2.232, test F1: 0.195
EPOCH:: 4, cost: 2.211, test F1: 0.193
EPOCH:: 5, cost: 2.187, test F1: 0.187
EPOCH:: 6, cost: 2.159, test F1: 0.188
EPOCH:: 7, cost: 2.127, test F1: 0.192
EPOCH:: 8, cost: 2.087, test F1: 0.209
EPOCH:: 9, cost: 2.042, test F1: 0.241
EPOCH:: 10, cost: 1.988, test F1: 0.285

結果はかなり悪くなりました。ほんとうに悪い
データは加工無しが100枚でCNNの構造はcifar-10と同じです。

MNISTに関してはtestデータもだいたい中心にそろっているわけだから移動させたデータもよくなかったのかもしれません。

この話はデータセットの話だけでなく実際にもあって
ほかにも顔検出の場合にたは、顔の部分の隠れが多すぎる画像を加えると、顔検出の性能を悪くすることが起こったりするらしいです。


改善案

  • データの数を多くしてオーバーフィッティングを防ぐ。
  • クロスバリデーションなどオーバーフィッティングの対策アルゴリズムを実装する。
  • 線形変換によりありえそうなものを考えて実装する。


今回はデータの少なすぎることが原因でオーバーフィッティングしてそうなので
実際にデータ数を各クラス1000にしてオーバーフィッティングを防いでみました。

#水増し無し
EPOCH:: 1, cost: 2.004, test F1: 0.267
EPOCH:: 2, cost: 1.829, test F1: 0.339
EPOCH:: 3, cost: 1.712, test F1: 0.385
EPOCH:: 4, cost: 1.634, test F1: 0.420
EPOCH:: 5, cost: 1.585, test F1: 0.439

#水増しあり
EPOCH:: 1, cost: 1.685, test F1: 0.384
EPOCH:: 2, cost: 1.454, test F1: 0.489
EPOCH:: 3, cost: 1.341, test F1: 0.532
EPOCH:: 4, cost: 1.266, test F1: 0.563
EPOCH:: 5, cost: 1.211, test F1: 0.583

計算するのになかなか時間がかかりました。
しかしちゃんと水増しの効果が得られたことがみえます!
やっぱりデータ数が各クラス100ではデータが少なすぎてオーバーフィッティングしてたっぽいですね!

総括

今回はデータの水増しについて勉強しました。はじめ思いついたときにデータ少なくてもできるじゃん!!と思ったんですが簡単にはうまくいかないものですね。水増しについて調べながらオーバーフィッティングや画像変換の方法など学ぶことができました。参考になったサイトを下に貼っておきます。


blog.takuya-andou.com

qiita.com