본문 바로가기

회사생활/R

party::cforest vs. randomForest::randomForest

랜덤 포레스트를 위한 2가지 R 함수 party::cforest vs. randomForest::randomForest 비교하기




(1) {party} cforest를 사용하여 랜덤 포레스트 구현하기

(2) {randomForest} randomForest를 사용하여 랜덤 포레스트 구현하기

(3) 두 함수의 성능 비교하기




랜덤 포레스트를 구현하기 위한 R 패키지와 함수는 크게 2가지가 있다.

Ⅰ party 패키지의 cforest 함수로 

Ⅱ. randomForest 패키지의 randomForest 함수 (caret 패키지를 이용하는 경우에도 이 함수를 사용하는 것과 같다.)


이번 포스팅에서는 두 함수를 사용하는 방법과 두 함수를 이용해 Variable Importance를 구하는 방법, 그리고 성능을 비교해보고자 한다.

사용할 데이터는 R 기본 제공 데이터인 iris이며, iris의 Sepal.Length, Sepal.Width, Petal.Length, Petal.Width를 이용해 Species 3가지 중 어떤 Species인지 구분하도록 랜덤 포레스트 알고리즘을 구현하는 코드를 작성해 보겠다.




(1)  {party} cforest를 사용하여 랜덤 포레스트 구현하기


1.  먼저 R 기본 제공 데이터인 iris 데이터를 불러온다.


data(iris)



2.  패키지 설치하고 라이브러리 불러온다.


cforest 함수를 사용할 수 있도록 party 패키지를 설치하고 라이브러리를 불러온다.

install.packages("party")
library(party)



3.  cforest 함수를 이용해 랜덤 포레스트 모델을 생성한다.


랜덤 포레스트는 이름 그대로 "Random"성이 있기 때문에 set.seed 함수를 이용해 재현할 수 있도록 구현한다.

옵션으로는 mtry = 2 (하나의 트리에 2개의 변수를 선택해 트리를 구성)와 ntree = 100 (이런 트리를 100개 만들도록) 옵션을 설정했다.

set.seed(1)
cf <- cforest(formula = Species ~ .,
              data = iris,
              controls = cforest_unbiased(mtry = 2, ntree = 100))
cf




4.  Variable Importance를 추출한다.


랜덤 포레스트를 사용하는 경우는 주로 Variable Importance를 추출하기 위한 경우가 많은 것 같다. 랜덤 포레스트 함수들은 반응변수(여기서는 Species)를 분류할 때 가장 영향을 끼치는 변수가 무엇인지 판단할 수 있는 수치를 제공한다.

-
-

cforest 함수에서 Variable Importance를 추출하기 위해서는 varimp 함수를 이용한다.

cf_varimp <- varimp(cf)
cf_varimp



결과를 해석하자면 iris 데이터에서 Species를 분류할 때 가장 중요한 역할을 하는 변수는 Petal.Width > Petal. Length > Sepal.Length > Sepal.Width 순이라는 의미이다.




이번에는 randomForest 패키지의 randomForest 함수를 사용해서 위와 같은 코드를 작성해보자.


(2).  {randomForest} randomForest를 사용하여 랜덤 포레스트 구현하기


1.  먼저 R 기본 제공 데이터인 iris 데이터를 불러온다.


data(iris)



2.  패키지 설치하고 라이브러리 불러온다.


randomForest 함수를 사용할 수 있도록 randomForest 패키지를 설치하고 라이브러리를 불러온다. (대소문자에 주의한다.)

install.packages("randomForest")
library(randomForest)



3.  randomForest 함수를 이용해 랜덤 포레스트 모델을 생성한다.


랜덤 포레스트는 이름 그대로 "Random"성이 있기 때문에 set.seed 함수를 이용해 재현할 수 있도록 구현한다.

위에서 작성한 cforest와 동일하게 옵션으로 mtry = 2 (하나의 트리에 2개의 변수를 선택해 트리를 구성)와 ntree = 100 (이런 트리를 100개 만들도록) 옵션을 설정했다.

set.seed(1)
rf <- randomForest(formula = Species ~ .,
                   data = iris,
                   mtry = 2,
                   ntree = 100,
                   corr.bias = T)
rf


randomForest 함수의 경우 결과 출력시 Confusion Matrix로 Classification Error Rate까지 출력해준다.



4.  Variable Importance를 추출한다.


randomForest 함수에서 Variable Importance를 추출하기 위해서는 importance 함수를 이용한다.

rf_varimp <- importance(rf, type = 2)
rf_varimp



결과를 해석하자면 iris 데이터에서 Species를 분류할 때 가장 중요한 역할을 하는 변수는 Petal.Width > Petal. Length > Sepal.Length > Sepal.Width 순이라는 의미이다. 이 결과는 앞서 cforest를 이용한 Variable Importance 추출 결과와 비교할 때 수치 자체는 다르지만 결과는 변수 중요도 순서는 동일하다.


하지만 변수의 수가 많은 경우, set.seed를 설정하지 않으면 다양한 랜덤 포레스트 결과가 생성되기 때문에 변수 중요도 순서와 수치는 계속 변경될 수 있다는 점을 꼭 명심하자.




이번에는 두 Random Forest 함수를 돌려 Variable Importance를 추출하는데 까지 걸리는 시간을 비교해보려고 한다.


(3)  두 함수의 성능 비교하기


여기서 말하는 성능이란, 처리 결과와 처리 시간을 말한다. 처리 결과와 처리 시간을 비교하는 실험을 설계해보자.


1) 데이터는 iris 데이터를 10배 뻥튀기한 데이터를 사용한다.

2) Species 각 종별 100건, 250건, 500건씩 추출하여 데이터의 row 수는 300, 750, 1500 3가지 단계로 비교한다.

3) set.seed를 설정하지 않고 랜덤으로 데이터 사이즈당 5번씩 반복한다.



1.  먼저 위에 설계한대로 결과 저장을 위한 데이터프레임을 생성한다.


tm_result는 처리시간 테스트 결과를 저장하는 데이터프레임이고, rk_result는 Variable Importance 처리결과의 수치를 저장하는 데이터프레임이다.

tm_result <- data.frame(size = rep(c(300, 750, 1500), each = 5),
                        k = rep(c(1:5), 3),
                        cf_time = NA,
                        cf_unit = NA,
                        rf_time = NA,
                        rf_unit = NA)

rk_result <- data.frame(size = rep(c(300, 750, 1500), each = 10),
                        k = rep(c(1:5), each = 2, 3),
                        func = rep(c("cf", "rf"), 15),
                        Sepal.Length = NA,
                        Sepal.Width = NA,
                        Petal.Length = NA,
                        Petal.Width = NA)



2.  처리시간 테스트를 위한 반복문을 작성한다.


처리시간 테스트에는 부하를 주기 위해 cforest, randomForest 각각 트리를 3000개씩 생성하도록 ntree 옵션을 변경했다.

# 처리시간 테스트
for(n in c(10, 25, 50)){
  data <- iris[c(1:(1+n-1), 51:(51+n-1), 101:(101+n-1)), ]
  dat <- rbind(data, data, data, data, data, data, data, data, data, data)
  
  # 반복문 1~5
  for(k in 1:5){
    
    dtm <- Sys.time()
    cf <- cforest(formula = Species ~ .,
                  data = dat,
                  controls = cforest_unbiased(mtry = 2, ntree = 3000))
    cf_varimp <- varimp(cf)
    time <- Sys.time() - dtm
    
    tm_result[tm_result$size == nrow(dat) & tm_result$k == k, "cf_time"] <- time
    tm_result[tm_result$size == nrow(dat) & tm_result$k == k, "cf_unit"] <- attributes(time)$units
    
    
    dtm <- Sys.time()
    rf <- randomForest(formula = Species ~ .,
                       data = dat,
                       mtry = 2,
                       ntree = 3000,
                       corr.bias = T)
    rf_varimp <- importance(rf, type = 2)
    time <- Sys.time() - dtm
    
    tm_result[tm_result$size == nrow(dat) & tm_result$k == k, "rf_time"] <- time
    tm_result[tm_result$size == nrow(dat) & tm_result$k == k, "rf_unit"] <- attributes(time)$units
  }
}



3.  처리시간 테스트 결과를 해석한다.


먼저 Sys.time 함수의 숫자는 단위에 따라 크기가 다르기 때문에 단위를 sec로 맞춰준다.

tm_result$cf_time <- ifelse(tm_result$cf_units == "mins", tm_result$cf_time * 60, tm_result$cf_time)
tm_result$rf_time <- ifelse(tm_result$rf_units == "mins", tm_result$rf_time * 60, tm_result$rf_time)

처리시간 테스트의 경우 평균을 계산하여 그래프로 시각화해보자. plyr 패키지의 ddply 함수를 이용해 데이터 사이즈별 각 알고리즘별 평균 처리시간을 계산한다.
library(plyr)
tm_summary <- ddply(tm_result, "size", summarise, avg_cf = mean(cf_time), avg_rf = mean(rf_time))


평균 처리시간을 선 그래프로 시각화한다.

library(scales)
plot(formula = avg_cf ~ size,
     data = tm_summary, 
     type = "o",
     col = alpha("black", 0.5),
     xlim = c(200, 1500),
     ylim = c(0, max(tm_summary$avg_cf)),
     xlab = "n rows",
     ylab = "sec",
     main = "cforest vs. randomForest")
lines(formula = avg_rf ~ size,
      data = tm_summary, 
      type = "o",
      col = alpha("red", 0.5))
legend("topleft",
       c("avg_cf", "avg_rf"),
       pch = 1,
       col = alpha(c("black", "red"), 0.5),
       cex = 0.9,
       bty = "n")



위 그래프를 보면 데이터의 건수가 늘어날 수록 randomForest 대비 cforest의 처리속도가 기하급수적으로 늘어나는 것을 확인할 수 있다. 따라서 처리 속도 측면에서 보았을 때는 randomForest 패키지의 randomForest 함수가 더 유리하는 것을 알 수 있다.



4.  처리결과 테스트를 위한 반복문을 작성한다.


4개의 변수에 대한 중요도 점수를 결과 데이터프레임에 입력하도록 반복문을 작성한다.

# 처리결과 테스트
for(n in c(10, 25, 50)){
  data <- iris[c(1:(1+n-1), 51:(51+n-1), 101:(101+n-1)), ]
  dat <- rbind(data, data, data, data, data, data, data, data, data, data)
  
  # 반복문 1~5
  for(k in 1:5){
    
    cf <- cforest(formula = Species ~ .,
                  data = dat,
                  controls = cforest_unbiased(mtry = 2, ntree = 100))
    cf_varimp <- varimp(cf)
    
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "cf", "Sepal.Length"] <- as.numeric(cf_varimp["Sepal.Length"])
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "cf", "Sepal.Width"] <- as.numeric(cf_varimp["Sepal.Width"])
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "cf", "Petal.Length"] <- as.numeric(cf_varimp["Petal.Length"])
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "cf", "Petal.Width"] <- as.numeric(cf_varimp["Petal.Width"])
    
    
    rf <- randomForest(formula = Species ~ .,
                       data = dat,
                       mtry = 2,
                       ntree = 100,
                       corr.bias = T)
    rf_varimp <- importance(rf, type = 2)
    
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "rf", "Sepal.Length"] <- rf_varimp["Sepal.Length", ]
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "rf", "Sepal.Width"] <- rf_varimp["Sepal.Width", ]
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "rf", "Petal.Length"] <- rf_varimp["Petal.Length", ]
    rk_result[rk_result$size == nrow(dat) & rk_result$k == k & rk_result$func == "rf", "Petal.Width"] <- rf_varimp["Petal.Width", ]
    
  }
}

결과 테이블 중 데이터 사이즈가 300일 때를 살펴보자.



cforest의 경우 1회차, 4회차에는 Petal.Length가, 2회차, 3회차, 5회차에는 Petal.Width가 가장 높은 점수를 받은 것을 알 수 있다.

randomForest의 경우 1회차부터 5회차까지 모두 Petal.Width가 높은 점수를 받은 것을 알 수 있다.


이 결과만 봤을 때는 처리결과 측면에서도 randomForest가 더 일관성이 있는 것처럼 보여서 랜덤 포레스트를 R로 구현할 때에는 party 패키지의 cforest보다는 randomForest 패키지의 randomForest 함수를 사용하는 것이 더 좋은 것 같다.