gimmesilver's blog

Agbird.egloos.com

포토로그



Kaggle bike sharing demand 문제 풀이 #2 - 예측 모델링 데이터분석


  R에서는 대부분의 모델링 알고리즘에 대해서 데이터를 학습하여 예측하는 방법이 거의 비슷하게 정형화되어 있다. 그래서 우선 학습 및 예측을 어떻게 하는지 감을 잡기 위해 원래 갖고 있는 데이터를 최대한 그대로 사용해서 1차 예측을 해보겠다. 앞서 언급했듯이 예측에는 여러 가지 알고리즘을 사용할 수 있는데 여기서 사용할 알고리즘은 random forest 이다. 
  random forest는 tree 기반의 알고리즘인데 학습에 사용된 정답 집합을 random sampling을 통해 여러 집합으로 쪼개서 각 조각별로 의사 결정 트리를 만들어 모델을 생성한다. 그리고 예측할 때는 각 트리 별로 따로 예측을 한 후에 예측 대상이 명목형 변수이면 다수결을 통해서, 연속형 변수이면 보통 평균을 이용해서 결과를 통합한다. random forest 는 이렇게 random sampling을 통해 여러 개의 tree를 생성하기 때문에 붙여진 이름이다. random forest는 범용적으로 사용하기 쉬우면서도 안정적인 성능을 갖고 있기 때문에 가장 널리 사용되는 예측 알고리즘 중 하나이다. 이를테면 random forest는 예측 모델링 세계에서의 quick sort 같은 존재라 할 수 있다. 어쨌든 random forest를 이용한 가장 단순한 코드는 아래와 같다. 

# install.package('randomForest') 맨 처음에 실행할 때는 이 코드를 이용해서 randomForest 패키지를 설치해야 함
library(randomForest) 

# datetime 필드를 년, 월, 일, 시 로 나눠서 필드 생성
feature.processing<-function(data, log.processing=T) {
  data$year<-as.integer(substr(data$datetime, 1, 4))
  data$month<-as.integer(substr(data$datetime, 6, 7))
  data$weekday<-as.integer(format(as.POSIXct(data$datetime, format='%Y-%m-%d %H:%M'), format='%u'))
  data$hour<-as.integer(substr(data$datetime, 12, 13))
  
  factor.feature<-c('season', 'weather', 'workingday', 'year', 'month', 'weekday', 'hour')
  data[, factor.feature]<-lapply(data[, factor.feature], factor)
  
  data
}

# csv 데이터를 읽어서 학습에 필요한 전처리 수행
train<-read.csv('train.csv', header=TRUE)
train.processed<-feature.processing(train)

# 학습에 사용할 feature 및 예측 대상 변수를 formula 형식으로 표현
formula<-count~season+holiday+workingday+weather+temp+atemp+humidity+windspeed+year+month+weekday+hour

# random forest를 이용하여 학습 모델 생성
model<-randomForest(formula, data=train.processed)

# 예측에 사용할 테스트 데이터를 읽어서 예측에 필요한 전처리 수행
test<-read.csv('test.csv', header=TRUE)
test.processed<-feature.processing(test)

# 학습한 모델을 이용하여 대여 횟수 예측
test.processed$count<-predict(model, test.processed)

# kaggle 에 결과를 제출하기 위해 예측 결과를 csv 파일로 저장
write.csv(test.processed[, c('datetime', 'count')], file='result.csv', row.names=FALSE)

  하지만 아마 위 결과를 kaggle 에 제출하면 썩 좋은 결과를 볼 수는 없을 것이다(내가 이 글을 쓰기 위해 테스트해 본 바에 의하면 0.63632가 나왔는데 약 2500등에 해당하는 성적이다). 비록 random forest 가 좋은 알고리즘이기는 하나 이렇게 간단하게 문제를 해결할 수 있었다면 요즘처럼 data scientist가 비싼 몸값을 자랑하지는 않을 것이다. 따라서 이제 좀 더 좋은 예측을 위해 우리는 앞으로 설명할 여러 가지 작업을 수행해야 한다. 

  세부적인 모델 튜닝 단계에 들어가기에 앞서 과정을 단순화하기 위해 우선 기존 탐사 분석을 통해 알고 있는 사실과 kaggle 사이트에 있는 정보를 토대로 아래와 같은 두 가지 사전 튜닝을 진행하겠다. 
  • casual과 registered 에 대해 별도로 모델링 수행: 탐사 분석에서 알 수 있었듯이 회원과 비회원의 자전거 대여 패턴은 상당히 다르다. 따라서 count에 대해 하나의 예측 모델을 만드는 것보다 회원/비회원 별 예측 모델을 따로 만든 후에 이 두 값을 합해서 count 예측값을 생성하는 것이 더 좋다.
  • 예측 대상 변수에 log를 씌운다. 어차피 자전거 대여 횟수는 0보다 작을 수 없고 kaggle에서 이 문제에 대한 성능을 측정할 때 예측 변수의 log에 대한 RMSE를 측정하기 때문에 비슷한 방식으로 변수를 가공하여 사용하는 것이 더 좋다. 실제 테스트를 해보면 동일한 모델에 대해서 예측 대상 변수를 그냥 이용할 때보다 log를 취한 값을 이용할 때 예측 성능이 더 좋다.
  위 두 방법을 적용하여 다시 예측을 수행한 코드는 아래와 같다.

# casual 변수와 registered 변수에 각각 1을 더한 후 자연 로그를 취한다. (1을 더하는 이유는 0값은 로그를 취할 수 없기 때문임)
train.processed$casual.log<-log(train.processed$casual+1)
train.processed$registered.log<-log(train.processed$registered+1)

# casual과 registered 에 대해 각각 학습 수행
casual.formula<-casual.log~season+holiday+workingday+weather+temp+atemp+humidity+windspeed+year+month+weekday+hour
casual.model<-randomForest(casual.formula, train.processed)

registered.formula<-registered.log~season+holiday+workingday+weather+temp+atemp+humidity+windspeed+year+month+weekday+hour
registered.model<-randomForest(registered.formula, train.processed)

# 테스트 데이터에 대해서 각 모델별 예측을 수행한 후 count 값 계산
test.processed$casual.log<-predict(casual.model, test.processed)
test.processed$registered.log<-predict(registered.model, test.processed)
test.processed$count<-exp(test.processed$casual.log)+exp(test.processed$registered.log)-2

# kaggle 에 결과를 제출하기 위해 예측 결과를 csv 파일로 저장
write.csv(test.processed[, c('datetime', 'count')], file='result.csv', row.names=FALSE)

  사실 위 작업만으로도 기존보다 훨씬 좋은 점수를 얻을 수 있다(참고로 내가 위 코드를 실행한 결과를 제출했을 때는 0.43294 가 나왔고 순위는 약 600등 정도가 된다). 아마 좀 더 경험이 풍부한 분석가라면 여기서 몇 가지 파라미터 튜닝만으로도 더 좋은 성능을 내는 모델을 만들 수 있을 것이다. 하지만 여기서는 좀 더 정석적인 방법으로 모델링 작업을 수행해 보겠다.
  
  예측 모델링 단계는 크게 세 가지 작업으로 나눌 수 있다(아래 작업들이 반드시 순차적이거나 독립적으로 진행되는 것은 아니다).
  • feature selection: 앞서 전처리단계에서는 예측에 도움이 될만한 모든 변수를 추출했지만 실제로는 예측에 도움이 되는 변수도 있고 아닌 변수도 있다. 그런데 실제 예측을 하기 전까지는 정확히 어떤 변수가 도움이 되는지 알 수 없다. 따라서 도움이 되는 변수를 선별하기 위한 작업이 필요하다.
  • model algorithm selection: 다양한 예측 알고리즘이 있는데 각 알고리즘마다 장/단점이 있기 때문에 주어진 문제에 가장 적합한 알고리즘을 선정하는 것이 필요하다. 혹은 여러 알고리즘에서 나온 결과를 조합하는 경우도 있다. 
  • hyper-parameter tuning: 각 알고리즘마다 학습 및 예측 과정에서 알고리즘 자체가 사용하는 몇 가지 변수가 있다. 예를 들어 내부적으로 iteration 을 하는 알고리즘이라면 최대 iteration 횟수나 종료 조건을 지정해야 하며, 앞서 소개한 random forest의 경우 몇 개의 트리를 사용할 것인지나 트리의 깊이 등을 지정해야 한다. 물론 우리는 앞에서 이런 조건을 지정하지 않았는데 그 이유는 기본적으로 해당 패키지에서 제공하는 기본값이 있기 때문이다. 하지만 좀 더 예측력을 높이기 위해선 이런 변수의 값을 잘 지정하는 것이 필요하다.
  위 작업을 하기 위한 다양한 기법이 있긴 하지만 공통적으로 테스트를 통해 가장 예측력이 좋은 값을 찾는 일종의 최적화 작업이라는 특징이 있다. 여기서 가장 중요한 문제는 '어떻게하면 예측력이 좋은지를 판단할 것인가?' 이다. 물론 이 문제의 경우 kaggle에 결과를 제출하면 친절하게 성능을 측정해 주지만 일반적으로는 학습을 위한 데이터만 주어질 뿐 모델 튜닝을 위한 성능 평가 방법이 별도로 주어지지 않는다. 설령 kaggle 문제를 푼다 하더라도 매번 튜닝을 위해 모델을 수정할 때마다 kaggle에 접속해 결과를 제출하여 성능을 측정하는 것은 무척 번거로운 작업이다. 따라서 모델링 단계에서는 성능을 좀 더 쉽게 측정하는 방법이 필요하다.

  이를 위한 여러 가지 측정 방법이 있는데, 가장 널리 사용되는 방법은 'cross validation(https://en.wikipedia.org/wiki/Cross-validation_(statistics))' 이다. cross validation 을 간략하게 설명하자면, 현재 갖고 있는 정답 집합을 둘로 쪼개서 하나는 모델을 학습하는데 사용하고 그렇게 만든 모델을 나머지 하나로 테스트하는 것이다. 그런데 이렇게 하면 정답 집합을 어떻게 쪼개느냐에 따라 성능이 달라질 수 있기 때문에 좀 더 잘 검증하기 위해 데이터를 여러 조각으로 나눠서 학습 집합과 테스트 집합을 다르게 조합하여 성능을 측정한 후 그 평균을 취하는 방법을 더 많이 사용한다. 그리고 이렇게 여러 조합으로 cross validation을 하는 방법 중 대표적인 것이 'k-fold cross validation' 이다. 

  그래서 위에 설명한 feature selection이나 알고리즘 선정, 하이퍼 파라미터 튜닝 등을 할 때는 조금씩 값을 바꿔가며 k-fold cross validation 과 같은 검증 기법을 이용해 성능을 측정한다. 글로만 보면 뭔가 대단한 작업 같지만 실상 대부분 지극히 소모적이고 단순 반복적인 작업이다. 따라서 모델링 단계에서 가장 먼저 해야 할 것은 이런 성능 측정을 최대한 자동화할 수 있도록 검증 로직을 만드는 것이다.
  아래 R 코드는 k-fold cross validation 을 위한 코드이다. 

# measuring Root Mean Square Error (RMSE) 
rmse<-function(data, predict, ground.truth) {
  N<-nrow(data)
  err<-(data[, predict] - data[, ground.truth])^2
  sqrt(sum(err)) / N
}

# data permutation
permutate<-function(data) {
  size<-nrow(data)
  shuffling<-sample(1:size, size)
  data[shuffling, ]
}

# cross-validation
cv<-function(formula, data, FUN, k=5) {
  temp<-permutate(data)  
  N<-nrow(data)
  unit<-N/k
  scores<-vector(length=k)
  for (i in 1:k) {
    from<-unit*(i-1)
    to<-from+unit
    train<-data[-(from:to), ]
    test<-data[from:to, ]
    model<-FUN(formula, train)
    test$pre<-predict(model, test)
    scores[i]<-rmse(test, 'pre', as.character(formula)[2])
  }
  scores
}

  앞으로 모델링 단계에서 여러 가지 테스트를 수행할 때 위 코드를 이용해서 예측 성능을 측정한 후 그 값이 최소가 되도록 변수 선택이나 알고리즘 튜닝을 진행하겠다. 참고로 앞서 보인 random forest 모델에 대해 cv 함수를 사용한 예는 아래와 같다. 

# k-fold cross validation 수행한 결과를 result 변수에 저장. result는 5개의 성능 측정 결과가 담긴 벡터이다.
result<-cv(formula, train.processed, randomForest, k=5)

# 5개 결과의 평균 계산
mean(result)

  이제 위에 있는 cross-validation을 이용해서 모델링 결과를 향상시키는 방법들을 하나씩 소개하겠다. 

덧글

댓글 입력 영역