Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

【RでDeep Learning】R+KerasでCifar10の画像分類に挑戦してみる

本記事は、R Advent Calendar 2017の14日目の記事です。


これまで、R言語でロジスティック回帰やランダムフォレストなどを実践してきました。

Rは統計用のライブラリが豊富、Pythonは機械学習用のライブラリが豊富。というイメージがありますが、Rでも機械学習は可能です。

今回は、Kerasという深層学習を行うのに便利なライブラリを使って、画像分類に挑戦してみます。

車や船など10種類の画像を含むCifar10という超有名なデータセットを用います。
Cifar10について詳しくは、Cifar10をご覧ください。

また、今回は畳み込みニューラルネットワークを使って画像分類を行います。畳み込みニューラルネットワーク自体の説明は本記事ではしておりませんのでご注意ください。

ニューラルネットの基本的な考え方から畳み込みニューラルネットワークについては、以下の書籍で勉強するのが良いと思います。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

また画像分類と畳み込みニューラルネットワークについては、以下がよくまとまっていました。ただし、かなり中身が濃いので読むの時間かかります…。

画像認識 (機械学習プロフェッショナルシリーズ)

画像認識 (機械学習プロフェッショナルシリーズ)

また今回使ったサンプルコードはGithubにも置いておきます。
github.com


Kerasをインストール

まず、KerasをR環境にインストールしましょう。

公式サイトに従ってインストールを進めます。

> devtools::install_github("rstudio/keras")

github上から最新版をインストールします。
エラーが出た場合、devtoolsライブラリが古い可能性があります。もしエラーが出たら、devtoolsライブラリをアップデートしてから再度試してみてください。

インストールが完了したら、下記を実行してKerasを使う準備を整えましょう。

library(keras)
install_keras()

ここのinstall_keras()は結構時間かかります。

ここまでエラーが特に出なければ、準備は完了です。

cifar10の読み込み

cifar10はkerasライブラリから読み込むことができます。

cifar10_data = dataset_cifar10() #結構時間かかります

#訓練データとテストデータ読み込み
cifar10_data_train = cifar10_data$train
cifar10_data_test = cifar10_data$test

#データから特徴量とラベルを取得
cifar10_data_train_x = cifar10_data_train$x
cifar10_data_train_y = cifar10_data_train$y
cifar10_data_test_x = cifar10_data_test$x
cifar10_data_test_y = cifar10_data_test$y

dataset_cifar10()でデータを読み込みましょう。すでに学習データとテストデータに分けてくれているので、それぞれ「$train」と「$test」で取り出します。

またx(画像情報)とy(画像の正解ラベル)も簡単に取り出すことができるので、学習データとテストデータそれぞれで新しい変数として取得しておきましょう。

ここでcifar10_data_train_xとcifar10_data_train_yの次元を確認しておきましょう。

> dim(cifar10_data_train_x)
(50000, 32, 32, 3)
> dim(cifar10_data_train_y)
(50000, 1)
> head(cifar10_data_train_y)
     [,1]
[1,]    6
[2,]    9
[3,]    9
[4,]    4
[5,]    1
[6,]    1

cifar10_data_train_xの方は、最初の50000は画像の枚数を意味しています。次の (32, 32) は画像のピクセル数(32×32)です。そして最後の3はRGB情報(1 ~ 255)です。

つまり、50000枚の各画像の各ピクセル(32×32)にRGB情報がそれぞれ格納されていることが分かります。(32×32)なので相当荒い画像ですね。

それぞれどんな画像かは、各ピクセルに該当するRGBの色でプロットすることで再現することができます。(今回はやりませんが、プロットしてみると面白いと思います。)

また、cifar10_data_train_y は50000枚の画像データの正解ラベルが0~9の値で入っています。

0と1ラベルのみ抽出

10個のラベルすべて使って分類してみてもよいのですが、今回は簡単のため0と1(飛行機と車)だけで分類してみましょう。

ということで50000枚のデータから、抽出しておきます。

#訓練データとテストデータから、ラベル1と2のみ抽出
cifar10_data_train_x_0or1 = cifar10_data_train_x[cifar10_data_train_y %in% c(0,1), , ,]
cifar10_data_train_y_0or1 = cifar10_data_train_y[cifar10_data_train_y %in% c(0,1)] %>% as.matrix()

cifar10_data_test_x_0or1 = cifar10_data_test_x[cifar10_data_test_y %in% c(0,1), , ,]
cifar10_data_test_y_0or1 = cifar10_data_test_y[cifar10_data_test_y %in% c(0,1)] %>% as.matrix()

そしたらRGBは値は1~255で入っているので、0~1に正規化しておきます。

X_train = cifar10_data_train_x_0or1 / 255
X_test = cifar10_data_test_x_0or1 / 255

また、yの値は

> head(cifar10_data_test_y_0or1)
     [,1]
[1,]    0
[2,]    1
[3,]    1
[4,]    0
[5,]    0
[6,]    0

のように一列でラベルが入っていますが、これをone-hot表現(正解が1でそれ以外が0)に変換します。これはKerasを使う上でこのように正解データを与えるようになっているので従いましょう。

Y_train = to_categorical(cifar10_data_train_y_0or1, num_classes = 2)
Y_test = to_categorical(cifar10_data_test_y_0or1, num_classes = 2)

これにて、以下のように正解データがone-hot表現になりました。

> head(Y_train)
     [,1] [,2]
[1,]    0    1
[2,]    0    1
[3,]    1    0
[4,]    1    0
[5,]    0    1
[6,]    1    0

データの事前準備は完了したので、次節でkerasを使って分類してみましょう。

Kerasの使い方

PythonでKerasを使ったことがある方は、書き方は大体一緒なので分かりやすいと思います。

model = keras_model_sequential()

これでmodelを宣言した後、以下のように各層をどのようにするか書いていくだけです。

model %>%
  
  ## 畳み込み層
  layer_conv_2d(
    filter = 32, kernel_size = c(3,3), padding = "same", 
    input_shape = c(32, 32, 3)
  ) %>%
  layer_activation("relu") %>%
  
  ## 畳み込み層
  layer_conv_2d(filter = 32, kernel_size = c(3,3)) %>%
  layer_activation("relu") %>%

各層を「%>%」で結んでいくだけなので、作りたい層構造をそのまま表現することができます。

畳み込みは、layer_conv_2d()にて、活性化関数はlayer_activation()にて、またプーリング層はlayer_max_pooling_2d()で表現します。

また、全結合はlayer_dense()、ドロップアウトはlayer_dropout()で書くことができます。非常に直観的な書き方ができるので、keras最高っす。

今回は、

  • 畳み込み層
  • 畳み込み層
  • プーリング層
  • 畳み込み層
  • 畳み込み層
  • プーリング層
  • 全結合

というスタンダードな層構造で分類してみましょう。

model = keras_model_sequential()

model %>%
  
  ## 畳み込み層
  layer_conv_2d(
    filter = 32, kernel_size = c(3,3), padding = "same", 
    input_shape = c(32, 32, 3)
  ) %>%
  layer_activation("relu") %>%
  
  ## 畳み込み層
  layer_conv_2d(filter = 32, kernel_size = c(3,3)) %>%
  layer_activation("relu") %>%
  
  ## プーリング層 →
  layer_max_pooling_2d(pool_size = c(2,2)) %>%

  ## 畳み込み層
  layer_conv_2d(filter = 64, kernel_size = c(3,3), padding = "same") %>%
  layer_activation("relu") %>%
  
  ## 畳み込み層
  layer_conv_2d(filter = 64, kernel_size = c(3,3)) %>%
  layer_activation("relu") %>%
  
  ## プーリング層
  layer_max_pooling_2d(pool_size = c(2,2)) %>%

  ## 全結合 
  layer_flatten() %>%
  layer_dense(512) %>%
  layer_activation("relu") %>%
  ##dropout
  layer_dropout(0.5) %>%
  
  layer_dense(2) %>%
  layer_activation("softmax")

adam = optimizer_adam(lr = 1e-4)

model %>% compile(
  loss = "categorical_crossentropy",
  optimizer = adam,
  metrics = "accuracy"
)

最適化アルゴリズムには、adamを今回使っています。これにてモデルは完成。以下を実行して学習をスタートさせましょう。

model %>% fit(
  X_train, Y_train,
  batch_size = 32,
  epochs = 10,
  validation_data = list(X_test, Y_test),
  shuffle = TRUE
)

validation_data にテストデータを与えると、学習の各エポックにて正答率を計算してくれます。
実行すると…

> model %>% fit(
+     X_train, Y_train,
+     batch_size = 32,
+     epochs = 10,
+     validation_data = list(X_test, Y_test),
+     shuffle = TRUE
+ )
Train on 10000 samples, validate on 2000 samples
Epoch 1/10
10000/10000 [==============================] - 67s 7ms/step - loss: 0.4472 - acc: 0.7913 - val_loss: 0.3154 - val_acc: 0.8655
Epoch 2/10
10000/10000 [==============================] - 67s 7ms/step - loss: 0.3143 - acc: 0.8666 - val_loss: 0.2521 - val_acc: 0.8955
Epoch 3/10
10000/10000 [==============================] - 65s 7ms/step - loss: 0.2603 - acc: 0.8921 - val_loss: 0.2116 - val_acc: 0.9135
Epoch 4/10
10000/10000 [==============================] - 69s 7ms/step - loss: 0.2237 - acc: 0.9110 - val_loss: 0.1834 - val_acc: 0.9295
Epoch 5/10
10000/10000 [==============================] - 68s 7ms/step - loss: 0.1874 - acc: 0.9269 - val_loss: 0.2198 - val_acc: 0.9100
Epoch 6/10
10000/10000 [==============================] - 71s 7ms/step - loss: 0.1699 - acc: 0.9321 - val_loss: 0.1554 - val_acc: 0.9405
Epoch 7/10
10000/10000 [==============================] - 71s 7ms/step - loss: 0.1566 - acc: 0.9381 - val_loss: 0.1438 - val_acc: 0.9440
Epoch 8/10
10000/10000 [==============================] - 70s 7ms/step - loss: 0.1418 - acc: 0.9449 - val_loss: 0.1406 - val_acc: 0.9480
Epoch 9/10
10000/10000 [==============================] - 70s 7ms/step - loss: 0.1279 - acc: 0.9503 - val_loss: 0.1527 - val_acc: 0.9385
Epoch 10/10
10000/10000 [==============================] - 71s 7ms/step - loss: 0.1178 - acc: 0.9555 - val_loss: 0.1205 - val_acc: 0.9495

という結果になりました。テストデータで正答率95%ほどの分類に成功しました。
うーん、まずまずの結果ですね。

最後に

今回はR+ Kerasの導入編ということで、Cifar10の画像分類(2分類)をしてみました。
この層構造で10分類やると、大体75%ぐらいの精度はでました。

精度をこれから上げるには、エポック数を増やしたりoptimizer_adam()あたりの学習率を変更したり、層構造を深くしたり…などなど色々やり方はあると思います。

お時間ある方は、挑戦してみてください!