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

機械学習がしたい…でもデータがない! 機械学習の勉強をするうえでほしいデータは、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:]
#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値が下がってしまったのでしょうか

考えられる理由

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

オーバーフィッティングとは NHN TECHORUS Tech Blog 今回の場合、同じようなデータを入れすぎたため汎用性がなくなってしまったのかなと思います。

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

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

結果はかなり悪くなりました。ほんとうに悪い データは加工無しが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

プログラミング未経験からでもAIスキルが身につくAidemy Premium




PythonやAIプログラミングを学ぶなら、オンライン制スクールのAidemy Premiumがおすすめです。
「機械学習・ディープラーニングに興味がある」
「AIをどのように活用するのだろう?」
「文系の私でもプログラミング学習を続けられるだろうか?」
少しでも気になることがございましたら、ぜひお気軽にAidemy Premiumの【オンライン無料相談会】にご参加いただき、お悩みをお聞かせください!