# パッケージのインストール
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")
次のコマンドを実行すると,
初回はデータをウェブサイトからダウンロードしてくる。
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
# 描画自作関数
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)
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')) # 評価指標:精度
# コールバック設定
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)
上手くモデリングし,計算環境も良ければ90%超える精度を出すことができる。
(リンク先の中段にある,「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
自分で作成した画像を認識させるテスト。 以下の画像は竹田が作成した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%"
次の手書き数値画像の文字認識を行い精度を求めよ。
データの詳細は次を参照のこと。
【AI・機械学習のデータセット辞典】MNIST:手書き数字の画像データセット
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 ...