【ディープラーニング】CIFAR-10のデータセットを使って画像認識を実装してみた




機械学習/ディープラーニングで画像認識を行う際のサンプルデータセットはMNISTが有名ですが、飽きてしまったので別のデータセットで画像認識を実装していることにしました。

今回使うデータセットは、CIFAR-10と呼ばれる有名なデータセットです。

CIFAR-10について

データ概要

CIFAR-10データセットは、10種類(飛行機・自動車・鳥・ネコ・鹿・イヌ・カエル・馬・船・トラック)の画像が60000枚(トレーニングデータ50000枚、テストデータ10000枚)入ったデータセットです。種類は完全に排他的なので、自動車かつトラックのような曖昧な画像は含まれていません。トラックは大型トラックのみが含まれています。

32ピクセル×32ピクセル×3枚(RGB)で画像1枚分という扱いになっています。つまり、画像1枚に対して3072ピクセル分のデータが存在します。

公開されている解析結果について

Data Augmentation(データのかさ増し)をした場合は正解率89%で、しなかった場合は誤答率82%という結果が出ています。ハイパーパラメーターについてはベイズ最適化によって調整をしていったとのことです。

その他さまざまなテクニックを駆使して得られた最高結果は96.53%とのこと。こちらのページで、論文と一緒に結果を見ることが出来ます。

実際にディープラーニングしてみた

今回使ったソースコードはGitHubのページにアップロードしています。

ダウンロード方法

Pythonを利用する場合は、CIFAR-10(公式サイト)内のCIFAR-10 python versionをダウンロードしてください。今回はこちらを使いました。他にMatlab用とC言語用バイナリバージョンが用意されています。

ダウンロードしたファイルを解凍するとさまざまなファイルが入っていますが、実際に使うファイルはdata_batch_1〜data_batch_5(トレーニングデータ)とtest_batch(テストデータ)だけです。

必要なライブラリをインポート

import numpy as np
import pandas as pd
import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.convolutional import MaxPooling2D
from keras.layers import Activation, Conv2D, Flatten, Dense, Dropout
from sklearn.model_selection import train_test_split
from keras.optimizers import SGD, Adadelta, Adagrad, Adam, Adamax, RMSprop, Nadam
from PIL import Image
import numpy as np
import glob
import matplotlib.pyplot as plt
import os

お決まりのライブラリインポートです。足りないものがあったらconda installかpip installでインストールしてください。

ファイルを読み込む関数を準備

def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        data_dict = pickle.load(fo, encoding='bytes')
        fo.close()
    return data_dict

ダウンロードしたファイルを辞書型で読み込むための関数を定義しておきます。

ファイルを読み込む

data_names = [
    '../../data/cnn_cifar_data/data_batch_1',
    '../../data/cnn_cifar_data/data_batch_2',
    '../../data/cnn_cifar_data/data_batch_3',
    '../../data/cnn_cifar_data/data_batch_4',
    '../../data/cnn_cifar_data/data_batch_5'
]

labels = []
dataset = []
for data_name in data_names:
    read_result = unpickle(data_name)
    labels.append(read_result[b'labels'])
    dataset.append(read_result[b'data'])

y = np.concatenate(labels)
x = np.concatenate(dataset)
y_ = unpickle('../../data/cnn_cifar_data/test_batch')[b'labels']
x_ = unpickle('../../data/cnn_cifar_data/test_batch')[b'data']

実際にファイルを読みこんでいきます。data_namesにはトレーニングデータを保存した場所のパスを書いているので、自分の環境に合わせて書き直してください。

トレーニング用データと、テスト用データ(アンダースコア_付き)を用意します。

read_result.keys()

#結果
#dict_keys([b'batch_label', b'labels', b'data', b'filenames'])

ちなみに、読み込んだ辞書型のデータには4つのkeyが含まれています。

  • batch_label:このバッチファイルの名前
  • labels:正解データ(0〜9の自然数)
  • data:画像元データ
  • filenames:画像データのファイル名一覧

データの整形

#次元変換
train_x_list = []
for index in np.arange(len(x)):
    train_x_list.append(np.reshape(x[index], [3, 32, 32]))
train_x = np.array(train_x_list)

test_x_list = []
for index in np.arange(len(x_)):
    test_x_list.append(np.reshape(x_[index], [3, 32, 32]))
test_x = np.array(test_x_list)

#CIFAR-10の正規化
from keras.utils import to_categorical
 
#特徴量の正規化
train_x = train_x /255.
test_x = test_x /255.
 
# クラスラベルの1-hotベクトル化
train_y = to_categorical(y, 10)
test_y = to_categorical(y_, 10)

トレーニング用データ(x, y)とテスト用データ(x_, y_)に分けましたが、このままではxとx_が正規化されていないし、yとy_はone-hot表現になっていません。そこで上記の処理を行います。

xとx_は一次元のリストになっているので、reshapeする必要があるのです。

モデルを定義

from keras import backend as K
K.set_image_dim_ordering('th')

#今回は10種類
num_classes = 10

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=train_x.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

それではモデルを定義していきます。今回は画像認識を行うので畳み込みニューラルネットワークを使用します。

ここで一点注意です。KerasとTensorFlowでは、画像データの次元の順番の扱い方が異なります。つまり、今回のデータはRGB×縦ピクセル×横ピクセルの順番になっているのですが、何もしないと縦ピクセル×横ピクセル×RGBとして認識されてしまうのでエラーが出てしまいます。

そこで

from keras import backend as K
K.set_image_dim縦ピクセル×横ピクセル×RGB_ordering('th')

を使うことで修正しています。逆に縦ピクセル×横ピクセル×RGBを扱う場合は

from keras import backend as K
K.set_image_dim_ordering('tf')

と設定してください。

学習を開始

optimizers ="Adadelta"
results = {}
epochs = 1
model.compile(loss='categorical_crossentropy', optimizer=optimizers, metrics=['accuracy'])
results= model.fit(train_x, train_y, validation_split=0.2, epochs=epochs)

"""
model_json_str = model.to_json()
open('model.json', 'w').write(model_json_str)
model.save_weights('weights.h5');
"""

ここまでで準備は整いました。学習を開始します!コメントアウトを取ると、モデルを保存できるようになります。

学習過程を見てみる

hist = pd.DataFrame(results.history)
hist['epoch'] = results.epoch

plt.figure(figsize=(8, 3), dpi=150)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.plot(hist['epoch'], hist['loss'],
         label='loss')
plt.plot(hist['epoch'], hist['val_loss'],
         label='val loss')
plt.legend()

plt.figure(figsize=(8, 3), dpi=150)
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.plot(hist['epoch'], hist['acc'],
         label='acc')
plt.plot(hist['epoch'], hist['val_acc'],
         label='val acc')
plt.legend()

実際に学習過程を確認してみます。過学習や未学習が起きていないかを確認します。

テストデータで検証

model.evaluate(test_x, test_y)

最後に、モデルデータをmodel.evaluateに渡して精度を計算します。ここまでで一通り終わりになります。畳み込みニューラルネットワークは、通常のニューラルネットワークに比べてハイパーパラメータの数が多く大変で計算量も増えがちです。GPUが無いと計算時間がなかなかつらいと思いました。

おわりに

今回はCIFAR-10を利用してディープラーニングを行っていきました。個人でディープラーニングをする場合はデータセットの用意が一番大変なので予め用意されたものがあるのはとても嬉しいことですね。