はだだだだ

定食にサラダは不要だと思う。

MENU

RでK-fold Cross Validationを実装してみた

以下の記事を参考にK-fold クロスバリデーションを実装してみました。解く問題はkNN法のハイパーパラメータのkを決定する問題です。

Cross-Validation for Predictive Analytics Using R - MilanoR

作業概要

  • irisデータセット(n = 150)を使用。
  • irisデータセットを80%:20%で訓練データ(n = 120)とテストデータ(n = 30)に分ける。
  • 訓練データを更に80%:20%で訓練データとバリデーションデータに分ける。(5-foldのクロスバリデーション)
  • 訓練データ(n = 120)で最適なkを決定する際には各foldのaccuracyの平均を使用する。
  • 最後に訓練データとテストデータを合わせて(irisデータ全体:n = 150)でもう一度クロスバリデーションを実施し、モデルのaccuracyの推定値を計算する

クロスバリデーションのやり方については、以下の記事を参考にしました。

Chapter 29 Cross validation | Introduction to Data Science

データの分割方法イメージは以下の通りです。


実装コード

今回作成したコードは以下の通りです。

library(tidyverse)
library(caret)

set.seed(2019)

### Choosing best kNN's k
# split iris data into train data and test data
data_size <- nrow(iris)
train_rate <- 0.8
train_i <- sample(1:data_size, size  = data_size * train_rate, replace = FALSE)
train_data <- iris[train_i, ]
test_data <- iris[-train_i, ]

# get index for spliting train data into each fold
fold_i <- sample(rep(1:n_fold, length.out = nrow(train_data)))

# parameters for cross validation
kmax <- 20
k_range <- 1:kmax
n_fold <- 5

# initialise matrix which holds validation result(accuracy)
accuracy <- matrix(NA, nrow = n_fold, ncol = kmax)

### cross validation 
for (i in 1:n_fold) {
  validate_i <- which(fold_i == i)
  train_temp <- train_data[-validate_i, ]
  validate_temp <- train_data[validate_i, ]
  # build model
  models <- apply(t(k_range), 2, function(k) knn3(Species ~ ., train_temp, k = k))
  preds <- mapply(function(model) predict(model, newdata = validate_temp, type = "class"), models)
  accuracy[i, ] <- colMeans(validate_temp$Species == preds)
}

### plot cross validation accuracy
av <- colMeans(accuracy)
df <- data.frame(cbind(k = 1:kmax, av))
ggplot(data=df, aes(x = k)) +
  geom_line(aes(y = av) , color = "red") +
  labs(x = "k-fold's k", y = "validation accuracy") +
  coord_cartesian(ylim = c(0, 1))
  
### choose best k
kbest <- which(av == max(av))
if (length(kbest) != 1) {
  # choose lower k
  kbest <- kbest[1]
}
kbest
max(av)

### estimate test accuracy
# get fold index for full dataset (iris)
fold_i <- sample(rep(1:n_fold, length.out = nrow(iris)))
# initialise accuracy vecotr
accuracy <- rep(NA, length.out = n_fold)

# cross validation of full dataset (iris)
for (i in 1:n_fold) {
  # get index of fold i
  test_i <- which(fold_i == i)
  # split iris data into train data and test data
  train_temp <- iris[-test_i, ]
  test_temp <- iris[test_i, ]
  # build model and predict
  model <- knn3(Species ~ ., train_temp, k = kbest)
  pred <- predict(model, newdata = test_temp, type = "class")
  # calculate accuracy 
  accuracy[i] <- mean(test_temp$Species == pred)
}

# calculate test accuracy (estimate)
test_accuracy <- mean(accuracy)
test_accuracy

実装のポイント

クロスバリデーションのメイン部分は以下です。

### cross validation 
for (i in 1:n_fold) {
  validate_i <- which(fold_i == i)
  train_temp <- train_data[-validate_i, ]
  validate_temp <- train_data[validate_i, ]
  # build model
  models <- apply(t(k_range), 2, function(k) knn3(Species ~ ., train_temp, k = k))
  preds <- mapply(function(model) predict(model, newdata = validate_temp, type = "class"), models)
  accuracy[i, ] <- colMeans(validate_temp$Species == preds)
}

ポイント以下です。

  • 事前に設定したインデックス(fold_i)を使用して、バリデーションデータに重複がないように訓練データとバリデーションデータを作成する。

実行結果概要

クロスバリデーションを行い、accuracyの平均をとった結果が以下のグラフです。

### plot cross validation accuracy
av <- colMeans(accuracy)
df <- data.frame(cbind(k = 1:kmax, av))
ggplot(data=df, aes(x = k)) +
  geom_line(aes(y = av) , color = "red") +
  labs(x = "k-fold's k", y = "validation accuracy") +
  coord_cartesian(ylim = c(0, 1))

f:id:hadadada00:20190531172717p:plain

大きな違いは出ませんでしたが、この中で最もaccuracyが高いkを最適なkとします。accuracyが同じ場合はkが小さいほうを採用します。
なお、最適なkの決定方法としてよく使われる"one-standard error"ルールは簡略化のために今回使用しておりません。

"one-standard error"ルールについては以下を参照ください。
Cross-Validation for Predictive Analytics Using R - MilanoR

### choose best k
kbest <- which(av == max(av))
if (length(kbest) != 1) {
  # choose lower k
  kbest <- kbest[1]
}
kbest
max(av)

> kbest
[1] 6
> max(av)
[1] 0.9583333

計算の結果、最適なkは6となりました。

クロスバリデーションの実装としては以上ですが、追加で、モデルのaccuracyの推定値を計算しました。
訓練データにテストデータをあわせたデータ全体でもう一度クロスバリデーションを行い、その平均accuracyをモデルのaccuracyの推定値とします。

# cross validation of full dataset (iris)
for (i in 1:n_fold) {
  # get index of fold i
  test_i <- which(fold_i == i)
  # split iris data into train data and test data
  train_temp <- iris[-test_i, ]
  test_temp <- iris[test_i, ]
  # build model and predict
  model <- knn3(Species ~ ., train_temp, k = kbest)
  pred <- predict(model, newdata = test_temp, type = "class")
  # calculate accuracy 
  accuracy[i] <- mean(test_temp$Species == pred)
}

# calculate test accuracy (estimate)
test_accuracy <- mean(accuracy)
test_accuracy

> test_accuracy
[1] 0.96

なお、max(av)はモデルaccuracyの推定値としては使用できません。max(av)は最適なkを使用するためのバリデーションデータでの精度です。最終的なモデルaccuracyの推定値は、最初に取っておいたテストデータを含めた全量でクロスバリデーションを行った際のaccuracyを使用します。

以上