Rのcaretパッケージで機械学習ー回帰問題

# Introduction
Rの機械学習って色々なパッケージに分かれていてややこしい．そんなとき200種類以上の機械学習手法を簡単な記述で扱える便利なパッケージ```caret```を見つけたので基本的な使い方についてまとめる．

# データの準備
今回はボストンデータを使用．
chasとradはカテゴリー変数であるため，factor型に変換しておく

```{r}
library(MASS)
data = Boston
```
# caretパッケージのインストールと呼び出し
```{r}
#install.packages("caret")
library(caret)
```

# 並列化処理
モデルの学習を高速化するため，複数のコアで同時に処理をしてもらうための命令文．```caret```を使用するときはこちらも呪文として唱えておくとよい．

```{r}
#install.packages("doParallel")
library(doParallel)

# 全コア数の半分を使用
cores = detectCores() / 2
cl <- makePSOCKcluster(cores)
registerDoParallel(cl)
```


# データの分割
訓練データとテストデータの分割には```caret```パッケージ内の```createDataPartition```を使用．```p```で分割割合を指定．今回は8 : 2に分割．

```{r}
set.seed(11)

# データ分割用行番号作成
partition = createDataPartition(data[,1], p = 0.8, list = FALSE)

data_train = data[partition,]
data_test = data[-partition,]

X_train = data_train[,colnames(data) != 'medv']
y_train = data_train['medv']

X_test = data_test[,colnames(data) != 'medv']
y_test = data_test['medv']
```

# データの前処理
## 標準化
データの標準化（平均0，標準偏差１）には```preProcess```を使用．

```{r}
# 標準化モデルの構築
scale_model = preProcess(X_train, method = c('center', 'scale'))

# 学習データの標準化
X_train_scale = predict(scale_model, newdata = X_train)
# テストデータの正規化
X_test_scale = predict(scale_model, newdata = X_test)
```

## ダミー変数化
カテゴリー変数のダミー変数化（One-Hot Encoding）には```dummyVars```を使用．

```{r}
# ダミー変数化モデルの構築
dummy_model = dummyVars(~., data = X_train, fullRank = TRUE)

# 学習データのダミー変数化
X_train_scaledummy = data.frame(predict(dummy_model, newdata = X_train_scale))
# テストデータのダミー変数化
X_test_scaledummy = data.frame(predict(dummy_model, newdata = X_test_scale))
```

データの一部
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/931058/09ab974b-f1fd-5da2-0e25-d56a29219b5d.png)

# モデルの構築
caretパッケージ内で使用できるアルゴリズム一覧はこのサイトで確認できる．
[caretパッケージ内アルゴリズム一覧](https://topepo.github.io/caret/available-models.html)
また```modelLookup```でアルゴリズムの詳細が確認できる．
今回はランダムフォレストでモデルを構築してみる．

```{r}
modelLookup('rf')
```

Out:
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/931058/0b07cc15-4697-1e98-fe19-260c5a064cc2.png)

## 学習
学習は```train```で簡単に行える．
最低限以下の3つの引数が必要．

- ```formula```：モデルの構造を記述したオブジェクト
- ```data```：目的変数と説明変数が含まれたデータ
- ```method```：アルゴリズムの種類

上記に加え今回はパラメータのチューニングをクロスバリデーションで行うため，以下の2つの引数も追加．

- ```tuneLength```：各パラメータのチューニング回数
- ```trControl```：パラメータチューニングの方法

引数をあらかじめ設定

```{r}
formula = medv ~ .  # 目的変数medv, 説明変数すべて
method = 'rf'  # ランダムフォレスト
tL = ncol(X_train_scaledummy) - 1  # 今回は説明変数はそれほど多くないため，説明変数の数-1とする 
tC = trainControl(method = 'cv', number = 5)  # 5分割のクロスバリデーション
```
最適なハイパーパラメータを持つモデルが生成される．

```{r}
set.seed(11)
model_rf <- train(formula, data = cbind(y_train, X_train_scaledummy),
                  method = method, tuneLength = tL, trControl = tC)
```

## 学習結果
学習の結果，9個の特徴量を含むモデルのRMSEが最も小さく，最良のモデルといえる．

```{r}
model_rf
```

```
Out:

Random Forest 

406 samples
 20 predictor

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 325, 324, 325, 326, 324 
Resampling results across tuning parameters:

  mtry  RMSE      Rsquared   MAE     
   2    4.288801  0.8206868  2.898890
   3    3.771919  0.8551542  2.530535
   4    3.536236  0.8685549  2.393931
   5    3.407725  0.8756650  2.290953
   6    3.359574  0.8769825  2.256613
   7    3.299778  0.8802924  2.224651
   8    3.311583  0.8787008  2.231915
   9    3.277041  0.8808178  2.220307
  10    3.279955  0.8797193  2.233822
  11    3.340146  0.8748618  2.245376
  12    3.339566  0.8748642  2.267656
  13    3.333614  0.8750673  2.259978
  14    3.312243  0.8765812  2.244778
  15    3.326672  0.8755117  2.249137
  16    3.380582  0.8708817  2.269478
  17    3.368999  0.8716857  2.284721
  18    3.392529  0.8699610  2.282623
  19    3.392954  0.8693971  2.272084
  20    3.397931  0.8691550  2.279761

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was mtry = 9.
```

特徴量の重要度は```varImp```で見ることができる．
今回はrmからageまでを含むモデルが選択されたことになる．

```{r}
plot(varImp(model_rf))
```
Out:
![000004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/931058/a5c9d876-d304-9211-7a2a-af2b75bb8c4a.png)

モデルが格納されたオブジェクトを```plot```に入れれば，パラメータ毎の精度がグラフで確認できる．

```{r}
plot(model_rf)
```

Out:
![000004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/931058/ff9e262c-fdf2-15e3-1d6a-f2917dbaeff3.png)

# テストデータで検証
最後にテストデータの精度を確認する.
```predict```で予測値を算出し，```postResample```で精度を確認．

```{r}
pred = predict(model_rf, X_test_scaledummy)
postResample(y_test, pred)
```
```
Out:
     RMSE  Rsquared       MAE 
3.0827929 0.8790809 2.1232139
```

# Memo
パラメータチューニングの評価指標のデフォルトはRMSEになっているが，別の評価指標を使用したい場合は```train```の引数に```metric```を追加する．
以下のコードは評価指標にMAEを使用した例．

```{r}
set.seed(11)
model_rf = train(formula, data = cbind(y_train, X_train_scaledummy),
                  method = method, tuneLength = tL, trControl = tC,
                  metric = 'MAE')
```

# Conclusion
今回使用した```caret```パッケージ内の関数．

- ```createDataPartition```：訓練データとテストデータの分割
- ```preProcess```：データの標準化
- ```dummyVars```：データのダミー変数化
- ```modelLookup```：機械学習アルゴリズムの詳細を確認
- ```trainControl```：パラメータチューニングの方法
- ```train```：モデルの学習
- ```varImp```：特徴量重要度の表示
- ```postResample```：精度の確認

今回はランダムフォレストを回帰問題に適用してみた．
次回は分類問題への適用や，手法の比較をまとめてみようと思う．

# Code

```{r}
# データ準備
library(MASS)
data = Boston

data[,'chas'] = as.factor(data[,'chas'])
data[,'rad'] = as.factor(data[,'rad'])

# パッケージインストール
#install.packages("caret")
library(caret)

# 並列化処理
#install.packages("doParallel")
library(doParallel)

cores = detectCores() / 2
cl <- makePSOCKcluster(cores)
registerDoParallel(cl)

# データの分割
set.seed(11)
partition = createDataPartition(data[,1], p = 0.8, list = FALSE)

data_train = data[partition,]
data_test = data[-partition,]

X_train = data_train[,colnames(data) != 'medv']
y_train = data_train['medv']

X_test = data_test[,colnames(data) != 'medv']
y_test = data_test['medv']

# データの前処理
## 標準化
scale_model = preProcess(X_train, method = c('center', 'scale'))

X_train_scale = predict(scale_model, newdata = X_train)
X_test_scale = predict(scale_model, newdata = X_test)

## ダミー変数化
dummy_model = dummyVars(~., data = X_train, fullRank = TRUE)

X_train_scaledummy = data.frame(predict(dummy_model, newdata = X_train_scale))
X_test_scaledummy = data.frame(predict(dummy_model, newdata = X_test_scale))

# モデルの構築
## 学習
formula = medv ~ .  # 目的変数medv, 説明変数すべて
method = 'rf'  # ランダムフォレスト
tL = ncol(X_train_scaledummy) - 1  # 今回は説明変数はそれほど多くないため，説明変数の数-1とする 
tC = trainControl(method = 'cv', number = 5)  # 5分割のクロスバリデーション

set.seed(11)
model_rf = train(formula, data = cbind(y_train, X_train_scaledummy),
                  method = method, tuneLength = tL, trControl = tC)

## 学習結果
model_rf

plot(varImp(model_rf))

plot(model_rf)

# テストデータの精度を確認
pred = predict(model_rf, X_test_scaledummy)
postResample(y_test, pred)

```