R Notebook

Lab: Decision Trees

8.3.1 Fitting Classification Trees

분류 / 회귀 Tree 모델은 tree library을 통해 사용할 수 있다.

library(tree)

Carseats에서 sales가 높은지, 낮은지 분류하는 bianry classifiaction을 시행하기 전에, sales 변수가 수치형이므로 이를 바꿔준다.

library(ISLR)
attach(Carseats)
High = ifelse(Sales>8,"yes","no")
Carseats = data.frame(Carseats,High)

이제 tree() func을 사용하여 분류를 시행해보자. 구문은 lm() func와 비슷하다.

fit.tree = tree(High~.-Sales,data=Carseats)
summary(fit.tree)
## 
## Classification tree:
## tree(formula = High ~ . - Sales, data = Carseats)
## Variables actually used in tree construction:
## [1] "ShelveLoc"   "Price"       "Income"      "CompPrice"   "Population" 
## [6] "Advertising" "Age"         "US"         
## Number of terminal nodes:  27 
## Residual mean deviance:  0.4575 = 170.7 / 373 
## Misclassification error rate: 0.09 = 36 / 400

training error rate가 9%인 것을 확인할 수 있다. 분류 tree에서는 엔트로피와 유사한 식(책 325쪽 참조)으로 training error가 계산된다.

tree 모델의 가장 좋은 장점 중 하나는 그래프로 나타낼 수 있다는 것이다. pretty=0은 범주형 자료에 대해 각 범주를 포함하라는 옵션이다.

plot(fit.tree)
text(fit.tree,pretty=0)

 #fit.tree
#을 입력하면 나무의 가지에 해당하는 결과(분류)를 보여준다.

이제 training data와 test data로 나눠서 tree 모델을 적용해보자.

set.seed(2)
train=sample(1:nrow(Carseats),200)
Carseats.test = Carseats[-train,]
High.test = High[-train]
fit.tree = tree(High~.-Sales , data=Carseats, subset=train)
tree.pred = predict(fit.tree,newdata=Carseats.test, type="class")
table(tree.pred, High.test)
##          High.test
## tree.pred no yes
##       no  86  27
##       yes 30  57
(88+56)/200
## [1] 0.72

이제 tree model을 prune하는 것이 향상된 결과를 가져오는지 확인한다. cv.tree() func은 최적의 tree 복잡도를 결정하기 위해 CV를 실시한다. FUN=prune.misclass는 classification error를 원할 때 쓴다.

set.seed(3)
cv.carseats = cv.tree(fit.tree,FUN=prune.misclass)
names(cv.carseats)
## [1] "size"   "dev"    "k"      "method"
cv.carseats
## $size
## [1] 19 17 14 13  9  7  3  2  1
## 
## $dev
## [1] 55 55 53 52 50 56 69 65 80
## 
## $k
## [1]       -Inf  0.0000000  0.6666667  1.0000000  1.7500000  2.0000000
## [7]  4.2500000  5.0000000 23.0000000
## 
## $method
## [1] "misclass"
## 
## attr(,"class")
## [1] "prune"         "tree.sequence"

여기서 size는 terminal nodes의 수(size), 그에 따른 cv-error(dev), cost-complexity parameter의 값(여기서는 k인데 앞서서는 알파)을 출력한다. 결과를 살펴보면 9개의 terminal nodes가 있을 때, cv-error가 가장 낮음을 알 수 있다.

par(mfrow=c(1,2))
plot(cv.carseats$size,cv.carseats$dev,type="b")
plot(cv.carseats$k,cv.carseats$dev,type="b")

이제 어디까지 prune을 해야하는지 cv를 통해서 알아봤으므로 직접 prune.misclass을 통해 prune을 해보자.

prune.carseats = prune.misclass(fit.tree,best=9)
plot(prune.carseats)
text(prune.carseats, pretty=0)

prune이 끝난 tree model으로 test data에 대해서 예측을 해보자.

prune.pred = predict(prune.carseats,newdata=Carseats.test,type="class")
table(prune.pred,High.test)
##           High.test
## prune.pred no yes
##        no  94  24
##        yes 22  60
(94+60)/200
## [1] 0.77

prune이 된 tree model이 해석하기도 더 편할 뿐만 아니라 정확도도 더 올라갔다!

8.3.2 Fitting Regression Trees

Boston data에 대해서 regression tree을 적합시켜보자. 먼저 training data와 test data로 나눈다.

library(MASS)
attach(Boston)
set.seed(1)
train = sample(1:nrow(Boston),nrow(Boston)/2)
Boston.test = Boston[-train,]
medv.test = medv[-train]
fit.tree = tree(medv~.,data=Boston,subset=train)
summary(fit.tree)
## 
## Regression tree:
## tree(formula = medv ~ ., data = Boston, subset = train)
## Variables actually used in tree construction:
## [1] "lstat" "rm"    "dis"  
## Number of terminal nodes:  8 
## Residual mean deviance:  12.65 = 3099 / 245 
## Distribution of residuals:
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## -14.10000  -2.04200  -0.05357   0.00000   1.96000  12.60000

regression tree에서는 deviance가 tree에 대한 sum of squared errors라고 보면 된다.

plot(fit.tree)
text(fit.tree,pretty=0)

이제 prune을 통해 결과를 더 향상시킬 수 있을지 살펴보자.

cv.boston = cv.tree(fit.tree)
cv.boston
## $size
## [1] 8 7 6 5 4 3 2 1
## 
## $dev
## [1]  5226.322  5228.360  6462.626  6692.615  6397.438  7529.846 11958.691
## [8] 21118.139
## 
## $k
## [1]      -Inf  255.6581  451.9272  768.5087  818.8885 1559.1264 4276.5803
## [8] 9665.3582
## 
## $method
## [1] "deviance"
## 
## attr(,"class")
## [1] "prune"         "tree.sequence"
plot(cv.boston$size,cv.boston$dev,type="b")

prune을 해보자

prune.boston = prune.tree(fit.tree,best=7)
plot(prune.boston)
text(prune.boston,pretty=0)

prune을 할 때, classification tree에서는 prune.misclass()이었지만 regression tree에서는 prune.tree()을 사용한다.

prune을 거친 tree model을 통해 test mse을 구해보자.

prune.pred = predict(prune.boston,newdata=Boston.test)
mean((medv.test-prune.pred)^2)
## [1] 25.72341

prune을 하기 전, tree model을 통해 test mse을 구해보자.

unprune.pred = predict(fit.tree,newdata=Boston.test)
mean((medv.test-unprune.pred)^2)
## [1] 25.04559

prune을 한 tree model이 test mse가 살짝 더 높은 것을 알 있다.

8.3.3 Bagging and Random Forests

bagging과 random forests는 randomForest package을 이용하여 실행할 수 있다.

library(randomForest)
## randomForest 4.6-14

## Type rfNews() to see new features/changes/bug fixes.

bagging은 randomforest의 특별한 경우다. 즉, bagging은 m=k인 경우다.따라서 randomForest()로 bagging, randomforest 모두를 시행할 수 있다.

먼저 bagging을 해보자.

set.seed(1)
fit.bag = randomForest(medv~.,data=Boston,subset=train,mytry=ncol(Boston)-1,importance=TRUE)
fit.bag
## 
## Call:
##  randomForest(formula = medv ~ ., data = Boston, mytry = ncol(Boston) -      1, importance = TRUE, subset = train) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 4
## 
##           Mean of squared residuals: 12.48435
##                     % Var explained: 84.88
names(fit.bag)
##  [1] "call"            "type"            "predicted"      
##  [4] "mse"             "rsq"             "oob.times"      
##  [7] "importance"      "importanceSD"    "localImportance"
## [10] "proximity"       "ntree"           "mtry"           
## [13] "forest"          "coefs"           "y"              
## [16] "test"            "inbag"           "terms"
fit.bag$importance # 중요도 살펴보기.
##            %IncMSE IncNodePurity
## crim     7.2905442    1247.87271
## zn       0.2570270      84.97987
## indus    5.3449029    1308.70340
## chas     0.2279444      98.53459
## nox      6.6837255    1168.44941
## rm      30.6389694    5636.39110
## age      3.7418214     660.34542
## dis      6.0070861    1453.81733
## rad      1.5398313     161.38834
## tax      3.6917809     736.79049
## ptratio  5.1991608    1191.12896
## black    1.6716001     418.42770
## lstat   60.8139498    6179.11950

이 bagging model에 대한 test mse를 구해보자.

bag.pred = predict(fit.bag,newdata=Boston.test)
mean((medv.test-bag.pred)^2)
## [1] 11.6076

decision tree의 test mse보다 훨씬 줄어들었다. 또한 tree의 갯수를 조절할 수도 있다.

fit.bag = randomForest(medv~. , data=Boston, subset=train, mytry=ncol(Boston)-1, ntree=25)
bag.pred = predict(fit.bag, newdata=Boston.test)
mean((medv.test - bag.pred)^2)
## [1] 11.65373

randomforest을 시행하는 것은 mytry을 바꾸는 것 이외에 모두 동일하다. 기본값으로, randomForest() func은 분류 문제에 대해서 root(p)개의 변수를, regression 문제에 대해서는 p/3개의 변수를 사용한다.

set.seed(1)
fit.rf = randomForest(medv~. , data=Boston, subset=train, mytry=6, importance=TRUE)
rf.pred = predict(fit.rf, newdata=Boston.test)
mean((medv.test-rf.pred)^2)
## [1] 11.6076

각 변수중 중요도를 살펴보자.

importance(fit.rf)
##           %IncMSE IncNodePurity
## crim    12.712371    1247.87271
## zn       3.340046      84.97987
## indus   10.611878    1308.70340
## chas     1.733390      98.53459
## nox     13.843947    1168.44941
## rm      29.465247    5636.39110
## age      6.565646     660.34542
## dis     12.830180    1453.81733
## rad      4.679142     161.38834
## tax      9.195031     736.79049
## ptratio 11.521584    1191.12896
## black    8.385275     418.42770
## lstat   26.313510    6179.11950

왼쪽은 해당 변수가 빠졌을 때, 예측변수의 정확도 하락의 평균이다. 즉 이 수치가 크다면 이 변수가 빠질 시에 정확도가 크게 내려간다는 뜻이고 그만큼 중요하다는 뜻이다. 오른쪽은 그 변수에서 split했을 때 node impurity 총 하락을 측정것 것이다. 결국 얘가 크다는 것은 그만큼 중요하다는 뜻이다. varImpPlot()을 통해서 plot으로 그려보자.

varImpPlot(fit.rf)

rm과 lstat 변수가 가장 중요함을 알 수 있다!

8.3.4 Boosting

boosting model을 적용하기 위해 gbm package의 gbm() func을 이용한다.

library(gbm)
## Loading required package: survival

## Loading required package: lattice

## Loading required package: splines

## Loading required package: parallel

## Loaded gbm 2.1.3
set.seed(1)
fit.boost = gbm(medv~. , data=Boston[train,], distribution = "gaussian", n.trees=5000, interaction.depth=4) #gbm은 subset을 쓰면 error가 뜸.
names(fit.boost)
##  [1] "initF"             "fit"               "train.error"      
##  [4] "valid.error"       "oobag.improve"     "trees"            
##  [7] "c.splits"          "bag.fraction"      "distribution"     
## [10] "interaction.depth" "n.minobsinnode"    "num.classes"      
## [13] "n.trees"           "nTrain"            "train.fraction"   
## [16] "response.name"     "shrinkage"         "var.levels"       
## [19] "var.monotone"      "var.names"         "var.type"         
## [22] "verbose"           "data"              "Terms"            
## [25] "cv.folds"          "call"              "m"
summary(fit.boost)

##             var    rel.inf
## lstat     lstat 45.9627334
## rm           rm 31.2238187
## dis         dis  6.8087398
## crim       crim  4.0743784
## nox         nox  2.5605001
## ptratio ptratio  2.2748652
## black     black  1.7971159
## age         age  1.6488532
## tax         tax  1.3595005
## indus     indus  1.2705924
## chas       chas  0.8014323
## rad         rad  0.2026619
## zn           zn  0.0148083

rm과 lstat가 가장 중요한 변수임을 알 수 있다.

또한 이 두 개의 변수에 대한 partial dependence plots을 그릴 수도 있다. 이 plot은 다른 변수를 integrate out한 후에, 반응변수에 대한 선택된 변수의 marginal effect을 보여준다.

par(mfrow=c(1,2))
plot(fit.boost,i="rm")
plot(fit.boost,i="lstat")

이제 boost model의 test mse를 구해보자.

boost.pred = predict(fit.boost, newdata=Boston.test, n.trees=5000)
mean((medv.test-boost.pred)^2)
## [1] 11.84434

만약에 원한다면, 다른 shrinkage parameter lambda로 boosting을 할 수도 있다. 기본 값은 0.001이며 여기서는 0.2로 해보았다.

fit.boost = gbm(medv~., data=Boston[train,], distribution="gaussian",n.trees=5000,interaction.depth=4,shrinkage=0.2)
boost.pred = predict(fit.boost, newdata=Boston.test, n.trees=5000)
mean((medv.test-boost.pred)^2)
## [1] 11.51109