# パッケージのインストール
if (!require(keras3))
{
  # 【事前準備】
  # Pythonパッケージtensorfow(テンサーフロー/テンソルフロー)をインストールする。
  # Terminalで「pip install tensorflow」を入力しインストール
  # tensorflowは,Python 3.9–3.12(2024-11-05現在)にのみ対応
  
  install.packages("keras3")
  library(keras3)
  install_keras() # r-kerasという仮想環境にKerasがインストールされる(初回だけ実行)。
  # install_keras(tensorflow = "gpu") # GPU演算用(NVIDIA GPUとcuDNNが必要)
}

if (!require(plotly)) install.packages("plotly")

1 データ

次のコマンドを実行すると, 初回はデータをウェブサイトからダウンロードしてくる。
Rオブジェクトの構造はstrコマンドで確認できる。

d <- dataset_fashion_mnist()
str(d)
## List of 2
##  $ train:List of 2
##   ..$ x: int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:60000(1d)] 9 0 0 3 0 2 7 2 5 5 ...
##  $ test :List of 2
##   ..$ x: int [1:10000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:10000(1d)] 9 2 1 1 6 1 4 6 5 7 ...

データは階層構造になっており,訓練用データはtrainに,試験用データはtestにリストオブジェクトとして格納されている。 両データセットにはクラスラベル0~9がyに,28x28の画素データがxに格納されている。訓練データは60,000画像,テストデータは10,000画像ある。

【クラスラベル対応表】

番号 衣類名
1 Tシャツ
2 ズボン
3 プルオーバ
4 ドレス
5 コート
6 サンダル
7 シャツ
8 スニーカー
9
10 ブーツ
d <- dataset_fashion_mnist()
str(d)
## List of 2
##  $ train:List of 2
##   ..$ x: int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:60000(1d)] 9 0 0 3 0 2 7 2 5 5 ...
##  $ test :List of 2
##   ..$ x: int [1:10000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:10000(1d)] 9 2 1 1 6 1 4 6 5 7 ...
# クラスラベル
LAB.CLASS <- c('Tシャツ', 'ズボン', 'プルオーバ',
               'ドレス',  'コート', 'サンダル',
               'シャツ', 'スニーカー', '鞄', 'ブーツ')

(nclass <- length(LAB.CLASS))
## [1] 10
# 訓練データ
d.tr <- d$train
d.tr$fig <- d.tr$x / 255 # 規格化(0~255階調を0~1に変換)
d.tr$lab <- LAB.CLASS[d.tr$y + 1]
n.tr <- length(d.tr$lab)

# テストデータ
d.te <- d$test
d.te$fig <- d.te$x / 255 # 規格化(0~255階調を0~1に変換)
d.te$lab <- LAB.CLASS[d.te$y + 1]
n.te <- length(d.te$lab)

# 画像サイズ 28x28 ピクセル
(dx <- dim(d.tr$x)[2])
## [1] 28
(dy <- dim(d.tr$x)[3])
## [1] 28

2 描画

# 描画自作関数
draw.images <- function(d, i.fr, i.to,
                        labhat = NA, p = NA, is.pred = F)
{
  par(mfrow = c(3, 6), # 行優先に3x6マスでプロット
      mar = c(4.5, 0, 1, 0) + 0.1, # 図周りのマージン設定
      cex.main = 0.9)

  for (i in i.fr:i.to)
  {
    plot(NA, xlim = c(0, dx), ylim = c(0, dy), axes = F,
        type = 'n', xlab = '', ylab = '', 
        main = paste('Fig.', i))

    rasterImage(d$fig[i, , ], 0, 0, dx, dy) 

    mtext(d$lab[i], side = 1, line = 0.2, adj = 0.5)
    
    # 予測時(is.pred == TRUE)は予測ラベルを貼り付ける。
    if (is.pred)
    {
      mtext(labhat[i], side = 1, line = 1.4, adj = 0.5, col = 4)
      mtext(sprintf('%3d%%', as.integer(p[i])), 
            col = 4, side = 1, line = 2.8, adj = 0.5)
    }
  }
}

# 描画
draw.images(d.tr, i.fr = 1, i.to = 18)

3 分類型ニューラルネットワークのモデル作成

3.1 ニューラルネットワークの階層構造作成

clear_session() # 古いモデルを削除

model <- keras_model_sequential(input_shape = c(dx, dy)) |> # 入力層
  layer_flatten() |> # 画像を1列のピクセルに変換
  layer_dense(units = 128,    activation = 'relu') |> # 中間層(ReLU)
  layer_dense(units =  64,    activation = 'relu') |> # 中間層(ReLU)
  layer_dense(units = nclass, activation = 'softmax') # 出力層(ソフトマックス)

# モデル概要
summary(model)
## Model: "sequential"
## ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
## ┃ Layer (type)                      ┃ Output Shape             ┃       Param # ┃
## ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
## │ flatten (Flatten)                 │ (None, 784)              │             0 │
## ├───────────────────────────────────┼──────────────────────────┼───────────────┤
## │ dense (Dense)                     │ (None, 128)              │       100,480 │
## ├───────────────────────────────────┼──────────────────────────┼───────────────┤
## │ dense_1 (Dense)                   │ (None, 64)               │         8,256 │
## ├───────────────────────────────────┼──────────────────────────┼───────────────┤
## │ dense_2 (Dense)                   │ (None, 10)               │           650 │
## └───────────────────────────────────┴──────────────────────────┴───────────────┘
##  Total params: 109,386 (427.29 KB)
##  Trainable params: 109,386 (427.29 KB)
##  Non-trainable params: 0 (0.00 B)
# 高速演算のためのコンパイル(PCが素早く理解できる機械語に翻訳)
compile(model,
        loss      = 'sparse_categorical_crossentropy',     # 交差エントロピー関数
        optimizer = optimizer_adam(learning_rate = 0.001), # 最適化アルゴリズム 
        metrics   = c('accuracy'))                         # 評価指標:精度

3.2 深層学習モデルのフィッティングと予測

# コールバック設定
callbacks <- list(
  # 早期停止(検証データでの損失値の改善が20エポック以上なかったら停止)
  callback_early_stopping(patience = 20, monitor = "val_loss"),
  
  # 検証データでの損失が改善されない限りモデルを上書きしない設定
  # (early_stoppingとセットで使用する)
  callback_model_checkpoint(filepath = "bestmodel.keras",
                            monitor = "val_loss", save_best_only = T),
  
  # 検証データでの損失が改善せず停滞した時(判定:5エポック)
  # に局所解を抜け出すため学習率を0.1倍に下げる設定。 
  callback_reduce_lr_on_plateau(monitor = "val_loss", 
                                factor = 0.1, patience = 5)
)

# フィッティング
fit(model,                  # モデル
    d.tr$x,                 # 入力(28x28画素データx60000)
    d.tr$y,                 # 目的変数
    verbose    = 0,         # 1:出力表示(低速),0:出力表示抑制
    batch_size = 2^5,       # バッチサイズ(要調整)
    epochs     = 100,       # エポック数
    validation_split = 0.2, # 検証用データ割合(訓練には不使用)
    callbacks  = callbacks) # コールバック設定

pred <- predict(model, d.te$x) # 予測結果(確率)
## 313/313 - 1s - 2ms/step
yhat <- max.col(pred) - 1      # 予測結果(エンコーディング値)
labhat <- LAB.CLASS[yhat + 1]  # 予測結果(クラスラベル)

p <- rep(NA, n.te)
for (i in 1:n.te) p[i] <- pred[i, yhat[i] + 1] * 100 # 確率 [%]

draw.images(d.te, i.fr = 1, i.to = 36, labhat, p, is.pred = T)

3.2.1 精度評価

上手くモデリングし,計算環境も良ければ90%超える精度を出すことができる。

世界トップレベルの精度:testデータで91〜92%

(リンク先の中段にある,「Building our model」セクションを見るとモデリングの様子が分かる)

options(digits = 2)
evaluate(model, d.te$x, d.te$y)
## 313/313 - 1s - 2ms/step - accuracy: 0.8707 - loss: 0.4458
## $accuracy
## [1] 0.87
## 
## $loss
## [1] 0.45
# 混同行列
library(caret)
cm <- confusionMatrix(data = as.factor(yhat),
                      ref  = as.factor(d.te$y))
cm
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1   2   3   4   5   6   7   8   9
##          0 786   2  11  21   0   0 118   0   3   0
##          1   2 961   1   7   1   0   0   0   0   0
##          2  15   7 759  10 102   0  89   0   7   0
##          3  28  26  12 879  29   1  30   0   6   0
##          4   9   1 146  56 807   0  87   0   4   0
##          5   0   0   0   0   0 959   0  15   3   5
##          6 151   3  70  21  55   1 662   0   6   1
##          7   0   0   0   0   0  25   0 966   2  35
##          8   9   0   1   6   6   2  14   0 969   0
##          9   0   0   0   0   0  12   0  19   0 959
## 
## Overall Statistics
##                                         
##                Accuracy : 0.871         
##                  95% CI : (0.864, 0.877)
##     No Information Rate : 0.1           
##     P-Value [Acc > NIR] : <2e-16        
##                                         
##                   Kappa : 0.856         
##                                         
##  Mcnemar's Test P-Value : NA            
## 
## Statistics by Class:
## 
##                      Class: 0 Class: 1 Class: 2 Class: 3 Class: 4 Class: 5
## Sensitivity            0.7860   0.9610   0.7590   0.8790   0.8070   0.9590
## Specificity            0.9828   0.9988   0.9744   0.9853   0.9663   0.9974
## Pos Pred Value         0.8353   0.9887   0.7674   0.8694   0.7270   0.9766
## Neg Pred Value         0.9764   0.9957   0.9733   0.9865   0.9783   0.9955
## Prevalence             0.1000   0.1000   0.1000   0.1000   0.1000   0.1000
## Detection Rate         0.0786   0.0961   0.0759   0.0879   0.0807   0.0959
## Detection Prevalence   0.0941   0.0972   0.0989   0.1011   0.1110   0.0982
## Balanced Accuracy      0.8844   0.9799   0.8667   0.9322   0.8867   0.9782
##                      Class: 6 Class: 7 Class: 8 Class: 9
## Sensitivity            0.6620   0.9660   0.9690   0.9590
## Specificity            0.9658   0.9931   0.9958   0.9966
## Pos Pred Value         0.6825   0.9397   0.9623   0.9687
## Neg Pred Value         0.9626   0.9962   0.9966   0.9954
## Prevalence             0.1000   0.1000   0.1000   0.1000
## Detection Rate         0.0662   0.0966   0.0969   0.0959
## Detection Prevalence   0.0970   0.1028   0.1007   0.0990
## Balanced Accuracy      0.8139   0.9796   0.9824   0.9778

4 自作画像での予測

自分で作成した画像を認識させるテスト。 以下の画像は竹田が作成したTIUロゴ入りシャツ

library(png)
img <- readPNG('image_my_creation.png')
grayimg <- (img[, , 1] + img[, , 2] + img[, , 3]) / 3

plot(NA, xlim = c(0, dx), ylim = c(0, dy), axes = F,
     type = 'n', xlab = '', ylab = '', 
     main = '自作画像')
rasterImage(grayimg, 0, 0, dx, dy) 

x <- array(NA, dim = c(1, 28, 28))
x[1, ,] <- (grayimg / max(grayimg)) * 255

pred <- predict(model, x)       # 予測結果(確率)
## 1/1 - 0s - 44ms/step
(yhat <- max.col(pred) - 1)     # 予測結果(エンコーディング値)
## [1] 6
(labhat <- LAB.CLASS[yhat + 1]) # 予測結果(クラスラベル)
## [1] "シャツ"
# 結果表示
sprintf('%sの確率:%3.1f%%', labhat, pred[1, yhat[1] + 1] * 100)
## [1] "シャツの確率:69.7%"

5 演習課題

次の手書き数値画像の文字認識を行い精度を求めよ。 データの詳細は次を参照のこと。
【AI・機械学習のデータセット辞典】MNIST:手書き数字の画像データセット

5.1 データ

d <- dataset_mnist()
str(d)
## List of 2
##  $ train:List of 2
##   ..$ x: int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:60000(1d)] 5 0 4 1 9 2 1 3 1 4 ...
##  $ test :List of 2
##   ..$ x: int [1:10000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ y: int [1:10000(1d)] 7 2 1 0 4 1 4 9 5 9 ...