井出草平の研究ノート

モンテカルロ交差検証を用いたLasso[R]

モンテカルロ交差検証(Monte Carlo cross-validation, MCCV)、もしくは反復ランダムサブサンプリング検証(Repeated random sub-sampling validation)と呼ばれる方法は、訓練データと検証データをランダムに分割し、ホールドアウト法と同じく訓練データに基づいて検証データモデル評価を行うという手順を複数回(例えば100回)行う。ランダム分割の数が無限大に近づくにつれて、p-個抜き交差検証の結果に近づくとされている。反復交差検証とよく似た手順で行われるが、反復交差検証はk-fold交差検証を繰り返すのに比べ、モンテカルロ交差検証はホールドアウト法を繰り返すところに違いがある。

モンテカルロ交差検証の実施

# 必要なライブラリの読み込み
library(haven)
library(tidyr)
library(caret)
library(glmnet)
library(dplyr)

# データの準備
set.seed(123)
auto_data <- haven::read_dta("http://www.stata-press.com/data/r9/auto.dta")
auto_data <- auto_data %>% drop_na()  # 全カラムに対してNAがない行を抽出
auto_data$foreign <- as.integer(auto_data$foreign)

price_data <- auto_data$price
X_data <- auto_data[, c("mpg", "rep78", "headroom", "trunk", "weight", "length",
                        "turn", "displacement", "gear_ratio", "foreign")]
X_data <- as.matrix(X_data)

# MCCVの設定
train_control <- trainControl(method = "LGOCV", 
                              p = 0.8,  # トレーニングセットの割合
                              number = 100,  # 繰り返し回数
                              savePredictions = "final")

# Lasso回帰モデルのトレーニング
lasso_model <- train(x = X_data, y = price_data,
                     method = "glmnet",
                     trControl = train_control,
                     tuneLength = 10)  # チューニングするλの数

# 結果の表示
print(lasso_model)

# 最良のλを取得
best_lambda <- lasso_model$bestTune$lambda

# 最終モデルの係数を取得
final_coefs <- coef(lasso_model$finalModel, s = best_lambda)
print(final_coefs)

結果。

RMSE was used to select the optimal model using the smallest value.
The final values used for the model were alpha = 0.1 and lambda = 111.2479.

11 x 1 sparse Matrix of class "dgCMatrix"
                      s1
(Intercept)  5491.703265
mpg           -30.009245
rep78         219.079762
headroom     -592.963110
trunk          31.700221
weight          2.529418
length        -24.302025
turn          -93.892082
displacement   14.249337
gear_ratio   -401.315437
foreign      2989.835976

変数選択と推定量の安定性のシミュレーション

異なる乱数シードを設定し、50回反復計算をして、1) 独立変数側で選ばれた変数の回数、2)選ばれた時の推定値の平均とその標準偏差を計算した。

# 必要なライブラリの読み込み
library(haven)
library(tidyr)
library(caret)
library(glmnet)
library(dplyr)
library(doParallel)

# データの準備
set.seed(123)
auto_data <- haven::read_dta("http://www.stata-press.com/data/r9/auto.dta")
auto_data <- auto_data %>% drop_na()  # 全カラムに対してNAがない行を抽出
auto_data$foreign <- as.integer(auto_data$foreign)

price_data <- auto_data$price
X_data <- auto_data[, c("mpg", "rep78", "headroom", "trunk", "weight", "length",
                        "turn", "displacement", "gear_ratio", "foreign")]
X_data <- as.matrix(X_data)

# 結果を保存するリストを初期化
coef_list <- list()
selected_vars_list <- list()
n_repeats <- 50

# 並列処理
num_cores <- detectCores() - 2
cl <- makeCluster(num_cores)
registerDoParallel(cl)

# 50回の反復計算
for (i in 1:n_repeats) {
  set.seed(i + 1000)  # 異なる乱数シードを設定
  
  # MCCVの設定
  train_control <- trainControl(method = "LGOCV", 
                                p = 0.8,  # トレーニングセットの割合
                                number = 1,  # 繰り返し回数
                                savePredictions = "final",
                                allowParallel = TRUE)

  # Lasso回帰モデルのトレーニング
  lasso_model <- train(x = X_data, y = price_data,
                       method = "glmnet",
                       trControl = train_control,
                       tuneLength = 10)  # チューニングするλの数
  
  # 最良のλを取得
  best_lambda <- lasso_model$bestTune$lambda
  
  # 最終モデルの係数を取得
  final_coefs <- coef(lasso_model$finalModel, s = best_lambda)
  coef_list[[i]] <- as.vector(final_coefs)
  names(coef_list[[i]]) <- rownames(final_coefs)  # 係数に名前をつける
  
  # 選ばれた変数を保存
  final_coefs_matrix <- as.matrix(coef(lasso_model$finalModel, s = best_lambda))
  selected_vars <- rownames(final_coefs_matrix)[final_coefs_matrix != 0]
  selected_vars_list[[i]] <- selected_vars
}

# 並列処理の停止
stopCluster(cl)

# 変数名を取得
variables <- rownames(final_coefs_matrix)

# 結果1: 各変数が選ばれた回数の集計
selected_counts <- sapply(variables, function(var) {
  sum(sapply(coef_list, function(coefs) !is.na(coefs[var]) && coefs[var] != 0))
})

# 結果2: 各変数の推定値の平均と標準偏差の計算
coef_stats <- sapply(variables, function(var) {
  coefs <- unlist(lapply(coef_list, function(coefs) if (!is.na(coefs[var]) && coefs[var] != 0) coefs[var] else NA))
  coefs <- coefs[!is.na(coefs)]  # NAを除外
  c(mean = mean(coefs), sd = sd(coefs))
})

# Jaccard係数の計算
jaccard_index <- function(set1, set2) {
  intersect_len <- length(intersect(set1, set2))
  union_len <- length(union(set1, set2))
  if (union_len == 0) return(NA)
  return(intersect_len / union_len)
}

jaccard_indices <- combn(1:length(selected_vars_list), 2, function(index) {
  set1 <- selected_vars_list[[index[1]]]
  set2 <- selected_vars_list[[index[2]]]
  jaccard_index(set1, set2)
})

mean_jaccard_index <- mean(jaccard_indices, na.rm = TRUE)

# データフレームにまとめる
selected_counts_df <- data.frame(Variable = variables, SelectedCount = selected_counts)
coef_stats_df <- data.frame(Mean = coef_stats["mean",], SD = coef_stats["sd",])

# 結果の表示
print(selected_counts_df)
print(coef_stats_df)
cat("平均Jaccard係数: ", mean_jaccard_index, "\n")

選ばれた変数の回数

                 Variable SelectedCount
(Intercept)   (Intercept)            50
mpg                   mpg            36
rep78               rep78            50
headroom         headroom            50
trunk               trunk            32
weight             weight            50
length             length            28
turn                 turn            39
displacement displacement            50
gear_ratio     gear_ratio            40
foreign           foreign            50

Jaccard係数

平均Jaccard係数: 0.8055363

推定値の平均と標準偏差

                    Mean          SD
(Intercept)  4080.293882 4276.907768
mpg           -24.196628   13.124276
rep78         172.274918   57.859475
headroom     -472.151445  161.348569
trunk          42.094461   28.146215
weight          2.718052    1.326874
length        -51.858017   27.296527
turn          -78.406699   40.667062
displacement   12.104590    1.320900
gear_ratio   -292.793462  102.845914
foreign      2818.097140  535.273449