2007~2008년의 세계 금융 위기는 은행 업무의 투명성과 엄격함의 중요성을 부각시켰다.
신용의 유효성이 제한되면서 은행은 대출 시스템을 강화하고 위험 대출을 더 정확하게 찾아내기 위해 머신 러닝으로 전환했다.
의사 결정 트리는 쉽게 말하면 높은 정확성과 통계 모델을 표현하는 능력 때문에 은행 업계에서 널리 사용되고 있다.
많은 나라의 정부기관은 대출 업무를 면밀히 감시하고 있기 때문에 경영진은 다른 사람들은 대출이 승인되고 한 신청자만
대출이 거절됐다면 그 사유를 설명할 수 있어야만 한다. 이런 정보는 신용 평가가 만족스럽지 않은 이유를 확인하려는 고객들에게도 유용하다.
자동화된 신용 평가 모델을 이용해 전화나 웹에서 대출 신청을 즉시 승인할 수도 있다.
C5.0 의사결정 트리를 이용해 간단한 대출 승인 모델을 개발한다. 또한 기관의 재정적 손실을 야기하는 오류를 최소화하기 위해 모델의 성능을 개선할 것이다.
독일 신용 데이터를 기준으로 통계 예측 모형을 개발한다.
신용 모델 이면의 아이디어는 채무불이행될 위험이 높은지를 예측할 수 있는 요소를 식별하는 것이다. 따라서 과거 은행 대출에 대한 대량 데이터,
대출의 채무불이행 여부, 신청자에 대한 정보를 입수해야 한다.
이런 특성을 갖는 데이터는 [UCI 머신러닝 데이터 저장소](https://archive.ics.uci.edu/ml/datasets/statlog+(german+credit+data) 에서 얻을 수 있다. 이 데이터셋에는 독일의 한 신용기관에서 얻은 대출 정보가 들어있다.
1,000개의 대출 데이터셋에는 대출과 대출 신청자의 특성을 나타내는 일련의 수치 특징과 명목 특징을 포함하고 있다. 클래스 변수는 대출이 채무불이행으로 갔는지 여부를 나타낸다. 즉, 20개의 설명 변수와 1개의 종속 변수(class)가 있다.
| 변수명 | 속성 | 변수 설명 |
|---|---|---|
| check | 범주형 | 자유예금형태 (Status of existing checking account) |
| cduration | 수치형 | 기간 (Duration in month) |
| history | 범주형 | 과거신용정보 (Credit history) |
| purpose | 범주형 | 목적 (Purpose) |
| credit | 수치형 | 신용대출금액 (Credit amount) |
| savings | 범주형 | 저축예금/채권 (Savings account/bonds) |
| employment | 범주형 | 현직장 재직기간 (Present employment since) |
| installment | 수치형 | 가처분소득 대비 적금비율 (Installment rate in percentage of disposable income) |
| personal | 범주형 | 결혼상황 및 성별 (Personal status and sex) |
| debtors | 범주형 | 여타 채무/채권 (Other debtors / guarantors) |
| residence | 수치형 | 현 거주기간 (Present residence since) |
| property | 범주형 | 재산 (Property) |
| age | 수치형 | 나이(Age in years) |
| others | 범주형 | 여타적금 (Other installment plans) |
| housing | 범주형 | 주거형태 (Housing) |
| numcredits | 수치형 | 해당 은행 신용계좌 수 (Number of existing credits at this bank) |
| job | 범주형 | 직업 (Job) |
| residpeople | 수치형 | 부양가족수 (Number of people being liable to provide maintenance for) |
| telephone | 범주형 | 전화소유 (Telephone) |
| foreign | 범주형 | 외국인 노동자 여부 (foreign worker) |
| y | 범주형 | 신용등급 양호 또는 불량 (credit:Good or Bad) |
suppressMessages(library(dplyr)) #edit
suppressMessages(library(readxl)) #excel load
suppressMessages(library(doBy))
suppressMessages(library(fmsb)) # radar chart
suppressMessages(library(ggplot2)) #visualization
suppressMessages(library(corrplot)) #correlation
suppressMessages(library(VIM)) #missing data detection
suppressMessages(library(DMwR)) #outlier detection
suppressMessages(library(corrplot)) #correlation plot
suppressMessages(library(PerformanceAnalytics)) #correlation chart
suppressMessages(library(rpart)) #decision tree
suppressMessages(library(C50)) # decision tree
suppressMessages(library(rattle)) #decision tree fancy tree
suppressMessages(library(rpart.plot)) #decision tree fancy tree
suppressMessages(library(RColorBrewer)) #decision tree fancy tree
suppressMessages(library(ipred)) # bagging
suppressMessages(library(randomForest)) # random forest
suppressMessages(library(adabag)) # adaptive boosting
suppressMessages(library(lme4)) #dummy function
suppressMessages(library(caret)) #standard
suppressMessages(library(class)) #KNN
suppressMessages(library(VennDiagram)) #VennDiagram
suppressMessages(library(neuralnet)) #ann
suppressMessages(library(e1071)) #SVM
suppressMessages(library(ROCR)) #ROC
suppressMessages(library(mctest))
suppressMessages(library(dummies))
suppressMessages(library(Information))
suppressMessages(library(pROC))
credit <- read.csv('input/credit.csv')
str(credit)
## 'data.frame': 1000 obs. of 17 variables:
## $ checking_balance : Factor w/ 4 levels "< 0 DM","> 200 DM",..: 1 3 4 1 1 4 4 3 4 3 ...
## $ months_loan_duration: int 6 48 12 42 24 36 24 36 12 30 ...
## $ credit_history : Factor w/ 5 levels "critical","good",..: 1 2 1 2 4 2 2 2 2 1 ...
## $ purpose : Factor w/ 6 levels "business","car",..: 5 5 4 5 2 4 5 2 5 2 ...
## $ amount : int 1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
## $ savings_balance : Factor w/ 5 levels "< 100 DM","> 1000 DM",..: 5 1 1 1 1 5 4 1 2 1 ...
## $ employment_duration : Factor w/ 5 levels "< 1 year","> 7 years",..: 2 3 4 4 3 3 2 3 4 5 ...
## $ percent_of_income : int 4 2 2 2 3 2 3 2 2 4 ...
## $ years_at_residence : int 4 2 3 4 4 4 4 2 4 2 ...
## $ age : int 67 22 49 45 53 35 53 35 61 28 ...
## $ other_credit : Factor w/ 3 levels "bank","none",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ housing : Factor w/ 3 levels "other","own",..: 2 2 2 1 1 1 2 3 2 2 ...
## $ existing_loans_count: int 2 1 1 1 2 1 1 1 1 2 ...
## $ job : Factor w/ 4 levels "management","skilled",..: 2 2 4 2 2 4 2 1 4 1 ...
## $ dependents : int 1 1 2 2 2 2 1 1 1 1 ...
## $ phone : Factor w/ 2 levels "no","yes": 2 1 1 1 1 2 1 2 1 1 ...
## $ default : Factor w/ 2 levels "no","yes": 1 2 1 1 2 1 1 1 1 2 ...
table(credit$checking_balance)
##
## < 0 DM > 200 DM 1 - 200 DM unknown
## 274 63 269 394
table(credit$savings_balance)
##
## < 100 DM > 1000 DM 100 - 500 DM 500 - 1000 DM unknown
## 603 48 103 63 183
summary(credit$months_loan_duration)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 4.0 12.0 18.0 20.9 24.0 72.0
summary(credit$amount)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 250 1366 2320 3271 3972 18424
- 대출금은 4개월에서 72개월의 기한에 걸쳐 250DM에서 18,424DM까지의 범위에 있고,
- 기간의 중앙값은 18개월, 대출금의 중앙값은 2,320DM이다(100마르크 = 6~7만원).
수표 계좌 잔고와 저축 계좌 잔고는 대출 채무 불이행 상태의 중요한 예측 변수가 될 수 있다.
- 신청자의 수표 계좌 잔고와 저축 계좌 잔고가 범주형 변수로 저장되어 있다.
- 대출 신청시 예금 계좌와 적금 계좌를 확인해서 예금액이 많을 수록 대출이 안전하다고 가정할 수 있다.
- 대출 데이터가 독일에서 입수됐기 때문에 통화의 단위가 독일 마르크(DM)으로 기록
# look at the class variable
table(credit$default)
##
## no yes
## 700 300
- default 벡터는 대출 신청자가 합의된 지불 조건을 맞출 수 없어서 채무 불이행을 했는지를 나타낸다.
- 대출의 30%는 채무 불이행
부도율이 높으면 은행이 투자를 완전히 회수하지 못할 가능성이 있다는 의미기 때문에 은행에게 바람직하지 않다.
모델 개발에 성공한다면 이 모델이 채무불이행의 위험이 높은 신청자를 찾아내 은행이 대출 신청을 거절하게 만들어줄 것이다.
# create a random sample for training and test data
# use set.seed to use the same random number sequence as the tutorial
set.seed(123)
train_sample <- sample(1000, 900)
str(train_sample)
## int [1:900] 288 788 409 881 937 46 525 887 548 453 ...
# split the data frames
data_train <- credit[train_sample, ]
data_test <- credit[-train_sample, ]
# check the proportion of class variable
prop.table(table(data_train$default))
##
## no yes
## 0.7033333 0.2966667
prop.table(table(data_test$default))
##
## no yes
## 0.67 0.33
dim(data_train)
## [1] 900 17
prop.table(table(data_train$default))
##
## no yes
## 0.7033333 0.2966667
dim(data_test)
## [1] 100 17
prop.table(table(data_test$default))
##
## no yes
## 0.67 0.33
# build the simplest decision tree
data_model <- C5.0(data_train[-17], data_train$default)
# display simple facts about the tree
data_model
##
## Call:
## C5.0.default(x = data_train[-17], y = data_train$default)
##
## Classification Tree
## Number of samples: 900
## Number of predictors: 16
##
## Tree size: 57
##
## Non-standard options: attempt to group attributes
# display detailed information about the tree
#summary(data_model)
checking_balance in {> 200 DM,unknown}: no (412/50)
checking_balance in {1 - 200 DM,< 0 DM}:
:...credit_history in {perfect,very good}: yes (59/18)
가끔 트리는 논리적으로 이해가 안 되는 결정을 만든다. 예를 들어, “왜 대출 이력이 매우 좋은 신청자가 채무불이행의 가능성이 있고, 수표 계좌 잔고를 모르는 신청자가 왜 채무불이행의 가능성이 없는가?” 이처럼 모순된 규칙이 가끔씩 발견된다. 그런 규칙들은 데이터에 실제 패턴을 반영하기도 하고, 통계적 이상치일 수도 있다. 두 경우 모두, 트리의 논리가 비즈니스 용도로 합당한지 확인하기 위해 이런 이상한 결정을 조사해보는 것이 중요하다.
Evaluation on training data (900 cases):
Decision Tree
----------------
Size Errors
56 133(14.8%) <<
(a) (b) <-classified as
---- ----
598 35 (a): class no
98 169 (b): class yes
Errors 출력은 모델이 14.8%의 오류율로 900개의 훈련 인스턴스 중 113개를 제외하고 전부 정확하게 분류했음을 말하고 있다.
총35개의 실제 no값은 yes로 부정확하게 분류된 반면(거짓 긍정), 98개의 yes 값은 no로 오분류 됐다(거짓 부정).
의사결정 트리는 모델을 훈련 데이터에 과적합 시키는 경향이 있는 것으로 알려져 있다. 이런 이유로 훈련 데이터에 대해 보고된 오류율은 매우 낙관적일 수 있으며, 테스트 데이터셋에 대해 의사결정 트리를 평가하는 것은 중요하다.
## Evaluating model performance
# create a factor vector of predictions on test data
data_pred <- predict(data_model, data_test)
# cross tabulation of predicted versus actual classes
library(gmodels)
CrossTable(data_test$default, data_pred,
prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
dnn = c('actual default', 'predicted default'))
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 100
##
##
## | predicted default
## actual default | no | yes | Row Total |
## ---------------|-----------|-----------|-----------|
## no | 59 | 8 | 67 |
## | 0.590 | 0.080 | |
## ---------------|-----------|-----------|-----------|
## yes | 19 | 14 | 33 |
## | 0.190 | 0.140 | |
## ---------------|-----------|-----------|-----------|
## Column Total | 78 | 22 | 100 |
## ---------------|-----------|-----------|-----------|
##
##
100개 테스트 대출 신청 레코드 중에서 59개는 채무불이행되지 않았고,
14개는 채무불이행됐음을 모델이 정확하게 예측해서 정확도는 73%이고, 오류율은 27%가 됐다.
처음 보는 데이터에 대해 모델 성능이 더 나빠진다는 점을 감안하면 이 결과가 훈련 데이터에 대한 성능보다 다소 나쁘기는 하지만 전혀 예상 밖인 것은 아니다. 또한 모델이 테스트 데이터에서 실제 33개의 대출 채무불이행 중 단지 14개(42%)만 정확하게 예측했다는 점을 주목하자.
유감스럽게도 이런 오류 유형은 각각의 채무 불이행에 대해 은행이 손해를 보기 때문에 잠재적으로 비용이 매우 많이 드는 실수다.
더 노력해서 결과를 개선할 수 있는지 살펴보겠다.
실시간 신용 평가 애플리케이션에 모델을 배포하기에는 이 모델의 오류율이 높다. 실제 모델이 모든 테스트 케이스에 대해 ’채무불이행이 아님’으로 예측했다면, 67%는 정확했을 것이다(이 모델보다는 성능이 많이 나쁘진 않지만, 훨씬 적은 노력이 드는 결과다). 900개의 예시에서 대출의 채무불이행을 예측한다는 것은 어려운 문제 같아 보인다.
설상가상으로 자신의 대출을 채무불이행하는 신청자를 찾을 때 이 모델은 특히 저조한 성능을 보인다. 다행히 전반적인 실수 유형이나 더 비용이 많이 드는 실수 유형에 대해 모델의 성능을 향상시키는 몇 가지 간단한 C5.0 알고리즘 조정 방법이 있다.
C5.0 알고리즘이 C4.5 알고리즘을 개선한 방법 중 하나는 적응형 부스팅(AdaBoost)을 추가한 것이다. 이 방법은 여러 개의 의사결정 트리를 만들어서 각 예시에 대해 최고 클래스를 투표하게 만드는 과정이다.
부스팅은 성능이 약한 여러 학습자를 결헙함으로써 어느 한 학습자 혼자보다 훨씬 강한 팀을 만들 수 있다는 생각에 뿌리를 두고 있다. 각 모델은 각자의 유일한 강점과 약점을 찾으며, 특정 문제를 풀 때 더 좋거나 더 나쁠 수 있다. 그러므로 상호 보완적인 장점과 단점을 갖는 여러 학습자를 조합해 분류기의 정확도를 극적으로 향상시킬 수 있다.
부스팅 팀에 사용할 독립적인 의사결정 트리의 개수를 나타내는 trials 파라미터를 간단히 추가한다. trials 파라미터는 상한선을 설정한다. 추가 시행이 정확도를 향상시키지 못할 것으로 보이면 알고리즘은 더 이상 트리를 추가하지 않는다.
10회 시행으로 시작할 것인데, 연구에 따르면 10회 시행 시 테스트 데이터에 대한 오류율이 약 25% 정도 줄기때문에 이 숫자는 사실상 표준이 된 상태다.
## Improving model performance
## Boosting the accuracy of decision trees
# boosted decision tree with 10 trials
data_boost10 <- C5.0(data_train[-17], data_train$default,
trials = 10)
data_boost10
##
## Call:
## C5.0.default(x = data_train[-17], y = data_train$default, trials = 10)
##
## Classification Tree
## Number of samples: 900
## Number of predictors: 16
##
## Number of boosting iterations: 10
## Average tree size: 47.5
##
## Non-standard options: attempt to group attributes
#summary(data_boost10)
10회의 반복을 통해 트리의 크기가 줄어들었다.
Evaluation on training data (900 cases):
Trial Decision Tree
----- ----------------
Size Errors
0 56 133(14.8%)
1 34 211(23.4%)
2 39 201(22.3%)
3 47 179(19.9%)
4 46 174(19.3%)
5 50 197(21.9%)
6 55 187(20.8%)
7 50 190(21.1%)
8 51 192(21.3%)
9 47 169(18.8%)
boost 34( 3.8%) <<
(a) (b) <-classified as
---- ----
629 4 (a): class no
30 237 (b): class yes
이 분류기는 오류율이 3.8%로 900개의 훈련 예시에 대해 34개의 실수를 했다.
부스팅을 추가하기 전의 훈련 오류율 13.9%에 비하면 엄청나게 개선된 것이다.
하지만 테스트 데이터에 대해서도 비슷한 개선을 확인할 수 있는지는 더 지켜봐야 한다.
data_boost_pred10 <- predict(data_boost10, data_test)
CrossTable(data_test$default, data_boost_pred10,
prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
dnn = c('actual default', 'predicted default'))
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 100
##
##
## | predicted default
## actual default | no | yes | Row Total |
## ---------------|-----------|-----------|-----------|
## no | 62 | 5 | 67 |
## | 0.620 | 0.050 | |
## ---------------|-----------|-----------|-----------|
## yes | 13 | 20 | 33 |
## | 0.130 | 0.200 | |
## ---------------|-----------|-----------|-----------|
## Column Total | 75 | 25 | 100 |
## ---------------|-----------|-----------|-----------|
##
##
- 이 결과를 보면 전체 오류율이 부스팅 이전 27%에서 부스팅 모델의 18%로 줄었다.
- 이득이 큰 것처럼 보이지 않을 수도 있겠지만, 예상했던 25%의 감소보다 실제 더 크게 감소 됐다.
- 한편으로 모델은 여전히 채무 불이행을 잘 예측하지 못하고, 20/33 = 61%만 정확히 예측하고 있다.
- 더 크게 개선되지 않는 이유는 상대적으로 작은 훈련 데이터셋과 함수 관계에 있거나, 이 문제가 원래 해결하기 매우 어려운 문제이기 때문일 수도 있다.
- "부스팅이 이렇게 쉽게 추가될 수 있다면 왜 모든 의사결정 트리에 디폴트로 적용하지 않는가?"
- 두 가지 이유가 있다.
- 첫째, 의사결정 트리를 한 번 만드는데 계산 시간이 많이 걸린다면 여러 개의 트리를 만드는 것은 계산적으로 비현실적일 수 있다.
- 둘째, 훈련 데이터에 잡음이 많으면 부스팅으로 전혀 개선되지 않을 수 있다.
- 여전히 더 높은 정확도가 필요하다면 시도해볼 가치는 있다.
- 채무불이행 가능성이 있는 신청자에게 대출을 해주는 것은 비용이 많이 드는 실수가 될 수 있다.
- 거짓 부정 개수를 줄일 수 있는 방안 중 하나는,
- 은행이 위험한 대출로부터 돈을 전혀 상환받지 못할 때 일어날 대규모의 손실이 벌게 될 이자를 능가한다는 가정 하에
- 애매한 신청자를 좀 더 많이 거절하는 것이다.
- C5.0 알고리즘은 트리가 좀 더 비용이 많이 드는 실수를 하지 못하게 여러 오류 유형에 패널티를 줄 수 있다.
- 패널티는 비용 행렬(Cost Matrix)에 지정되며, 각 오류가 다른 예측에 비해 얼마나 비용이 많이 드는 지를 명시한다.
- 비용 행렬을 구성하기 위해 먼저 차원을 지정한다. 예측된 값과 실제 값 모두 yes 또는 no 두 값을 갖기 때문에
이 두 값으로 이뤄진 두 벡터의 리스트를 이용해 2x2 행렬을 기술한다. 이와 함께 이후의 혼란을 피하기 위해 행렬의 차원에 이름을 지정할 것이다.
## Making some mistakes more costly than others
# create dimensions for a cost matrix
matrix_dimensions <- list(c("no", "yes"), c("no", "yes"))
names(matrix_dimensions) <- c("predicted", "actual")
matrix_dimensions
## $predicted
## [1] "no" "yes"
##
## $actual
## [1] "no" "yes"
다음은 다양한 오류 유형에 패널티를 주기 위해 네 개의 값을 제공해 행렬을 채운다.
R은 행렬을 채울 때 열 단위로 위에서 아래 방향으로 채우기 때문에 특정 순서대로 값을 제공해야 한다.
은행은 대출의 채무불이행으로 놓친 기회의 4배만큼 비용이 드는 것으로 생각한다고 가정해보자.
그러면 패널티 값은 아래와 같이 정의될 수 있다.
# build the matrix
error_cost <- matrix(c(0, 1, 4, 0), nrow = 2, dimnames = matrix_dimensions)
error_cost
## actual
## predicted no yes
## no 0 4
## yes 1 0
이 행렬에 정의된 것처럼 알고리즘 분류기가 no 또는 yes를 정확히 분류할 때는 비용이 할당되지 않지만, 거짓 부정은 거짓 긍정의 비용 1에 대해 비용 4를 갖는다. 비용 행렬이 분류에 어떻게 영향을 주는지 확인하기 위해 C5.0()함수의 costs 파라미터를 이용해서 의사결정 트리에 적용해보자. 그렇지 않으면 이전에 했던 것과 같은 단계를 적용할 것이다.
# apply the cost matrix to the tree
data_cost <- C5.0(data_train[-17], data_train$default,
costs = error_cost)
data_cost_pred <- predict(data_cost, data_test)
CrossTable(data_test$default, data_cost_pred,
prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
dnn = c('actual default', 'predicted default'))
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 100
##
##
## | predicted default
## actual default | no | yes | Row Total |
## ---------------|-----------|-----------|-----------|
## no | 37 | 30 | 67 |
## | 0.370 | 0.300 | |
## ---------------|-----------|-----------|-----------|
## yes | 7 | 26 | 33 |
## | 0.070 | 0.260 | |
## ---------------|-----------|-----------|-----------|
## Column Total | 44 | 56 | 100 |
## ---------------|-----------|-----------|-----------|
##
##
부스팅 모델과 비교하면 비용 행렬 37% vs. 부스팅 18% 오류로 이 버전이 전체적으로 좀 더 많은 실수를 한다. 하지만 실수의 유형은 매우 다르다. 이전 모델이 채무불이행을 단지 39% 부정확하게 분류했고, 61%를 정확하게 분류했다면, 이 모델의 경우 실제 채무불이행의 79%가 채무 불이행한 것으로 예측됐다. 거짓 긍정을 증가시킨 대가로 거짓 부정을 줄인 이 거래는 비용추정이 정확하다면 수용 가능하다.
## Training
dt <- rpart(as.factor(default)~., data = data_train, cp = 0.1^20) # 모든 변수 사용, Full tree 생성
xerror_min_which <- which.min(dt$cptable[, "xerror"])
xerror_min <- min(dt$cptable[, "xerror"])
printcp(dt) # cptable 출력
##
## Classification tree:
## rpart(formula = as.factor(default) ~ ., data = data_train, cp = 0.1^20)
##
## Variables actually used in tree construction:
## [1] age amount checking_balance
## [4] credit_history employment_duration housing
## [7] job months_loan_duration other_credit
## [10] percent_of_income purpose savings_balance
## [13] years_at_residence
##
## Root node error: 267/900 = 0.29667
##
## n= 900
##
## CP nsplit rel error xerror xstd
## 1 4.1199e-02 0 1.00000 1.00000 0.051325
## 2 1.8727e-02 4 0.80899 0.88390 0.049421
## 3 1.4981e-02 5 0.79026 0.92884 0.050202
## 4 1.3733e-02 6 0.77528 0.93633 0.050326
## 5 7.4906e-03 16 0.62172 0.91760 0.050012
## 6 6.2422e-03 17 0.61423 0.94007 0.050388
## 7 5.6180e-03 20 0.59551 0.95506 0.050631
## 8 3.7453e-03 22 0.58427 0.98127 0.051042
## 9 1.0000e-20 33 0.53184 1.03745 0.051862
plotcp(dt) # cpplot 출력
abline(v = xerror_min_which, lty = 2, col = "red")
text(xerror_min_which, xerror_min, labels = round(xerror_min_which, 2), pos = 3, col = "red")
# pruning
dt_prune <- prune(dt, cp = dt$cptable[which.min(dt$cptable[, "xerror"]), "CP"])
# training accuracy
pred_tr_dt <- predict(dt_prune, type = "class") # class(범주형)으로 예측
t_tr_dt <- table(pred_tr_dt, data_train$default) # confusion matrix
t_tr_dt
##
## pred_tr_dt no yes
## no 551 134
## yes 82 133
print("Accuracy")
## [1] "Accuracy"
acc_tr_dt <- sum(diag(t_tr_dt)) / sum(t_tr_dt) # accuracy
acc_tr_dt
## [1] 0.76
CrossTable(x = data_train$default, y = pred_tr_dt, prop.chisq = FALSE)
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Row Total |
## | N / Col Total |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 900
##
##
## | pred_tr_dt
## data_train$default | no | yes | Row Total |
## -------------------|-----------|-----------|-----------|
## no | 551 | 82 | 633 |
## | 0.870 | 0.130 | 0.703 |
## | 0.804 | 0.381 | |
## | 0.612 | 0.091 | |
## -------------------|-----------|-----------|-----------|
## yes | 134 | 133 | 267 |
## | 0.502 | 0.498 | 0.297 |
## | 0.196 | 0.619 | |
## | 0.149 | 0.148 | |
## -------------------|-----------|-----------|-----------|
## Column Total | 685 | 215 | 900 |
## | 0.761 | 0.239 | |
## -------------------|-----------|-----------|-----------|
##
##
# test accuracy
pred_te_dt <- predict(dt_prune, data_test, type = "class")
t_te_dt <- table(pred_te_dt, data_test$default)
t_te_dt
##
## pred_te_dt no yes
## no 62 20
## yes 5 13
acc_te_dt <- sum(diag(t_te_dt)) / sum(t_te_dt)
acc_te_dt
## [1] 0.75
CrossTable(x = data_test$default, y = pred_te_dt, prop.chisq = FALSE)
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Row Total |
## | N / Col Total |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 100
##
##
## | pred_te_dt
## data_test$default | no | yes | Row Total |
## ------------------|-----------|-----------|-----------|
## no | 62 | 5 | 67 |
## | 0.925 | 0.075 | 0.670 |
## | 0.756 | 0.278 | |
## | 0.620 | 0.050 | |
## ------------------|-----------|-----------|-----------|
## yes | 20 | 13 | 33 |
## | 0.606 | 0.394 | 0.330 |
## | 0.244 | 0.722 | |
## | 0.200 | 0.130 | |
## ------------------|-----------|-----------|-----------|
## Column Total | 82 | 18 | 100 |
## | 0.820 | 0.180 | |
## ------------------|-----------|-----------|-----------|
##
##
confusionMatrix(data_test$default, pred_te_dt)
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 62 5
## yes 20 13
##
## Accuracy : 0.75
## 95% CI : (0.6534, 0.8312)
## No Information Rate : 0.82
## P-Value [Acc > NIR] : 0.97051
##
## Kappa : 0.3609
## Mcnemar's Test P-Value : 0.00511
##
## Sensitivity : 0.7561
## Specificity : 0.7222
## Pos Pred Value : 0.9254
## Neg Pred Value : 0.3939
## Prevalence : 0.8200
## Detection Rate : 0.6200
## Detection Prevalence : 0.6700
## Balanced Accuracy : 0.7392
##
## 'Positive' Class : no
##
confusionMatrix(data_test$default, pred_te_dt, positive = "yes")
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 62 5
## yes 20 13
##
## Accuracy : 0.75
## 95% CI : (0.6534, 0.8312)
## No Information Rate : 0.82
## P-Value [Acc > NIR] : 0.97051
##
## Kappa : 0.3609
## Mcnemar's Test P-Value : 0.00511
##
## Sensitivity : 0.7222
## Specificity : 0.7561
## Pos Pred Value : 0.3939
## Neg Pred Value : 0.9254
## Prevalence : 0.1800
## Detection Rate : 0.1300
## Detection Prevalence : 0.3300
## Balanced Accuracy : 0.7392
##
## 'Positive' Class : yes
##
# plotting
plot(dt_prune, margin = 0.1)
text(dt_prune, use.n = T)
fancyRpartPlot(dt_prune, cex = 1) #fancy tree
dt_prune$variable.importance
## checking_balance credit_history savings_balance
## 46.7036272 14.9289045 13.5494871
## months_loan_duration amount age
## 10.7319256 4.3876137 1.7003748
## purpose housing phone
## 1.0868401 0.9660801 0.9258267
## employment_duration
## 0.9068665
barplot(dt_prune$variable.importance, ylim = c(0, 55))
modelLookup("C5.0")
## model parameter label forReg forClass probModel
## 1 C5.0 trials # Boosting Iterations FALSE TRUE TRUE
## 2 C5.0 model Model Type FALSE TRUE TRUE
## 3 C5.0 winnow Winnow FALSE TRUE TRUE
# automated parameter tuning of C5.0 decision tree
set.seed(300) # 시뮬레이션할 때, 동일한 결과를 반복하기 위해 난수 고정
m <- train(default ~ ., data = credit, method = "C5.0")
# summary of tuning results
m
## C5.0
##
## 1000 samples
## 16 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Bootstrapped (25 reps)
## Summary of sample sizes: 1000, 1000, 1000, 1000, 1000, 1000, ...
## Resampling results across tuning parameters:
##
## model winnow trials Accuracy Kappa
## rules FALSE 1 0.6960037 0.2750983
## rules FALSE 10 0.7147884 0.3181988
## rules FALSE 20 0.7233793 0.3342634
## rules TRUE 1 0.6849914 0.2513442
## rules TRUE 10 0.7126357 0.3156326
## rules TRUE 20 0.7225179 0.3342797
## tree FALSE 1 0.6888248 0.2487963
## tree FALSE 10 0.7310421 0.3148572
## tree FALSE 20 0.7362375 0.3271043
## tree TRUE 1 0.6814831 0.2317101
## tree TRUE 10 0.7285510 0.3093354
## tree TRUE 20 0.7324992 0.3200752
##
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were trials = 20, model = tree
## and winnow = FALSE.
최고의 모델을 식별한 후에 train()함수는 튜닝 파라미터를 사용해서 전체 입력 데이터셋에 대해 모델을 구축한다. 이 모델은 m 리스트 객체에 m$finalModel로 저장된다. 대부분의 경우 직접 작업할 필요가 없다. m객체와 함께 predict() 함수를 다음과 같이 사용한다.
# apply the best C5.0 candidate model to make predictions
p <- predict(m, credit)
table(p, credit$default)
##
## p no yes
## no 700 2
## yes 0 298
최종 모델을 훈련하기 위해 사용된 1,000개의 예시 중 2개만 잘못 분류됐다. 하지만 모델이 훈련 데이터와 테스트 데이터에 대해 만들어졌기 때문에 정확도가 낙관적이다. 따라서 처음 보는 데이터에 대한 성능 지표로 보지 않도록 주의해야 한다. (요약 출력에 나타난) 73%의 부트스트랩 추정이 좀 더 실질적인 미래의 성능 추정이다.
# obtain predicted classes (default: type = "raw", probabilities: type = "prob")
head(predict(m, credit))
## [1] no yes no no yes no
## Levels: no yes
디폴트 설정을 사용하면 최적화된 모델을 쉽게 만들 수 있다. 하지만 디폴트 설정을 학습 작업에 좀 더 특화된 설정으로 변경하게 되면 성능을 높은 수준으로 올리는 데 도움이 된다. 파라미터를 최적화하기 위해 10-fold 교차검증(10-Fold C.V)를 이용해서 카파 통계량을 추정하고 반영할 것이다.
trainControl() 함수는 제어 객체로 알려진 설정 옵션 집합을 생성하기 위해 사용된다. 제어 객체는 train()함수를 이끈다. 이 옵션들을 통해 모델의 평가 기준을 관리할 수 있으며, 리샘플링 전략과 최고 모델을 선택하는 데 사용되는 척도 같은 것들이 해당된다. 이 함수는 튜닝 실험의 거의 모든 측면을 수정하는 데 사용할 수 있지만, 지금은 주요 파라미터인 method와 selectionFunction에 집중할 것이다.
trainControl() 함수의 경우 method parameter를 홀드아웃 샘플링 또는 k-fold 교차 검증과 같은 리샘플링 방법을 설정하는 데 사용할 수 있다.
selectionFunction 파라미터는 다양한 후보 중에 최적의 모델을 선택하는 함수를 지정하는데 사용한다. 그런 함수는 세 개가 있다. best 함수는 단순히 명시된 성능 척도에 대해 최고 값을 갖는 후보를 선택한다. 이 함수가 디폴트로 사용된다. 다른 두 함수는 최고 모델의 특정한 성능 임계치 내에 있는 가장 인색하거나 가장 단순한 모델을 선택하기 위해 사용된다. oneSE 함수는 최고 성능의 1표준 오차 내에 가장 단순한 후보를 선택하며, tolerance는 사용자 지정 비율 내에 가장 단순한 후보를 사용한다.
# use trainControl() to alter resampling strategy
# 10-fold Cross Validation과 oneSE 선택 함수를 사용해 ctrl이란 이름의 제어 객체를 생성
ctrl <- trainControl(method = "cv", number = 10,
selectionFunction = "oneSE")
최적화할 파라미터의 그리드 생성.
그리드의 열은 희망하는 모델의 파라미터 이름으로 구성된다. 그리드의 행은 원하는 파라미터 값의 조합으로 구성된다.
지금은 C5.0 의사결정 트리를 사용하기 때문에 .model, .trials, .winnow라는 이름으로 열이 만들어진다.
# use expand.grid() to create grid of tuning parameters
# 상수 model = "tree"와 winnow = "FALSE"를 유지하면서 8 종류의 trials를 검색
grid <- expand.grid(.model = "tree",
.trials = c(1, 5, 10, 15, 20, 25, 30, 35),
.winnow = FALSE)
# look at the result of expand.grid()
grid
## .model .trials .winnow
## 1 tree 1 FALSE
## 2 tree 5 FALSE
## 3 tree 10 FALSE
## 4 tree 15 FALSE
## 5 tree 20 FALSE
## 6 tree 25 FALSE
## 7 tree 30 FALSE
## 8 tree 35 FALSE
train() 함수는 각 행의 모델 파라미터 조합을 이용해서 평가를 위한 후보 모델을 구축할 것이다.
직전에 생성된 검색 그리드와 제어 리스트가 있으므로 train() 실험을 철저히 맞춤화해서 시행할 준비가 됐다.
이전에 했던 것처럼 반복 가능한 결과를 보장하기 위해 시드를 임의의 값 300으로 설정한다.
하지만 이번에는 파라미터 metric = “Kappa”를 추가하면서 제어 객체와 튜닝 그리드를 전달할 것이다.
이 경우 모델 평가 함수(이 경우 “oneSE”)가 카파 통계량을 사용한다는 것을 말한다.
# customize train() with the control list and grid of parameters
set.seed(300)
m <- train(default ~ ., data = credit, method = "C5.0",
metric = "Kappa",
trControl = ctrl,
tuneGrid = grid)
m
## C5.0
##
## 1000 samples
## 16 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 900, 900, 900, 900, 900, 900, ...
## Resampling results across tuning parameters:
##
## trials Accuracy Kappa
## 1 0.735 0.3243679
## 5 0.722 0.2941429
## 10 0.725 0.2954364
## 15 0.731 0.3141866
## 20 0.737 0.3245897
## 25 0.726 0.2972530
## 30 0.735 0.3233492
## 35 0.736 0.3193931
##
## Tuning parameter 'model' was held constant at a value of tree
##
## Tuning parameter 'winnow' was held constant at a value of FALSE
## Kappa was used to select the optimal model using the one SE rule.
## The final values used for the model were trials = 1, model = tree
## and winnow = FALSE.
단일 모델의 성능을 향상시키는 방법에 대한 대안으로 몇 개의 모델을 결합해서 강력한 팀을 만들 수 있다. 최고의 스포츠 팀이 중복되는 기술이 아닌 상호 보완적인 기술을 갖는 선수로 이뤄지는 것처럼, 일부 최고의 머신 러닝 학습 알고리즘은 상호 보완적인 모델로 이뤄진 팀을 활용한다. 모델은 학습 작업에 고유한 편향을 가져오기 때문에 예시의 어떤 부분은 순조롭게 학습하지만, 다른 부분에서는 어려움이 있을 수 있다. 따라서 여러 다양한 팀 구성원의 재능을 현명하게 사용해서 여러 약한 학습자(weak learner)로 이뤄진 강한 팀을 만들 수 있다.
여러 모델의 예측을 결합하고 관리하는 이 기법은 광범위한 메타 학습(meta-learning) 방법에 속하며 학습 방법을 학습하는 기법을 정의한다. 이 방법은 반복적으로 설계를 결정하면서 점진적으로 성능을 향상시키는 단순한 알고리즘(자동 파라미터 튜닝)부터 학습 작업에 맞춰 스스로 수정하거나 적응하기 위해 진화 생물학과 유전학에서 빌려온 개념을 이용하는 매우 복잡한 알고리즘까지 모든 것을 포함한다.
텔레비전 퀴즈 쇼 참가자라고 생각해보자. 100만 달러 상금이 걸린 마지막 질문에 대한 답변을 도와줄 다섯 사람의 패널을 선택할 수 있다고 가정하자. 대부분의 사람들은 다양한 주제 관련 전문가로 패널을 채우려고 할 것이다. 대중문화 전문가와 함께 문학, 과학, 역사, 예술 교수를 포함하는 패널은 틀림없이 균형 잡힌 그룹이 될 것이다. 그들의 폭넓은 지식을 고려하면 그룹을 난처하게 할 질문은 없을 것이다.
다양한 전문가 팀을 만드는 것과 유사한 원리를 활용하는 메타 학습 방법을 앙상블(ensemble)이라고 한다. 모든 앙상블 방법은 약한 학습자 여러 개를 결합하면 강한 학습자가 만들어진다는 아이디어를 기반으로 한다. 다음 두 가지 질문에 대한 답변으로 다양한 앙상블 방법을 대부분 구분할 수 있다.
- 약한 학습 모델이 어떻게 선택되고 구성되는가?
- 하나의 최종 예측을 만들기 위해 약한 학습자의 예측이 어떻게 결합되는가?
이 질문에 대한 답을 할 때 앙상블을 다음과 같은 프로세스 다이어그램의 관점에서 상상하는 것이 도움이 될 것이다.
training data는 여러 모델을 구성하는데 사용된다. 할당 함수(allocation function)는 각 모델이 받을 훈련 데이터 양을 규정한다. 이상적인 앙상블은 다양한 모델을 포함하지만, 생성된 학습자가 같은 유형이더라도 할당 함수는 학습자를 편향시키기 위해 입력 데이터를 인위로적으로 변화시켜 다양성을 높인다. 예를 들어 각 모델에 고유한 훈련 데이터셋을 구축하거나 각 모델에 특징이나 예시의 다른 부분집합을 넘기기 위해 부트스트랩 샘플링을 사용한다. 한편 앙상블이 이미 (신경망, 의사결정 나무, k-NN 분류기와 같은) 다양한 알고리즘을 포함하고 있다면 할당 함수는 상대적으로 변경 없이 데이터를 각 알고리즘에 전달할 것이다.
모델이 구성되고 나면 예측을 생성하는데 사용되며, 예측은 어떤 방식으로든 관리돼야 한다. 결합 함수(combination function)는 예측 간에 불일치를 어떻게 조율할지를 관리한다. 예를 들어 앙상블은 최종 예측을 결정하기 위해 과반수 의결을 사용하거나 각 모델의 표(vote)에 과거 성능 기반으로 가중치를 부여하는 것과 같은 좀 더 복잡한 전략을 사용할 수 있다.
어떤 앙상블은 다양한 예측 조합에서 결합 함수를 학습하기 위해 다른 모델을 이용하기도 한다. 예를 들어 M1, M2가 모두 예(yes)로 투표할 때, 실제 클래스 값은 항상 아니오(no)라고 가정해보자. 이 경우, 앙상블은 M1, M2가 일치할 때 투표를 무시하게 학습할 수 있다. 최종 결정권자를 훈련하기 위해 여러 모델의 예측을 사용하는 과정을 __스태킹(stacking)__이라고 한다.
앙상블을 사용하는 이점 중 하나는 단일의 최고 모델을 추구하는 데 소요되는 시간을 단축할 수 있다는 점이다. 대신 적당히 강한 여러 후보를 훈련하고 결합할 수 있다. 하지만 편의성만이 앙상블 기반의 방법이 머신 러닝 대회에서 승리를 계속 거두는 유일한 이유는 아니다. 앙상블은 단일 모델에 비해 많은 성능적 혜택도 제공한다.
- 미래 문제에 대한 더 나은 일반화 가능성
- 여러 학습자의 의견이 하나의 최종 예측으로 통합되므로 하나의 편향이 우세할 수 없다. 따라서 학습 작업에 과적합될 가능성이 줄어든다.
- 대용량 또는 극소량의 데이터 셋에 대해 향상된 성능
- 극도로 많은 특징이나 예시가 사용될 때, 모델이 대부분 메모리나 복잡도의 한계에 도달하므로, 하나의 완전한 모델보다 여러 소형 모델을 훈련하는 것이 좀 더 효율적이다. 역으로 앙상블은 작은 데이터셋에 대해서도 잘 수행되는데, 부트스트래핑과 같은 리샘플링 방법이 기본적으로 대부분의 앙상블 설계의 일부이기 때문이다. 가장 중요한 것은 분산 컴퓨팅 방법을 이용해서 앙상블을 병렬로 훈련할 수 있다는 것이다.
- 여러 도메인 데이터를 합성하는 능력
- 모든 분야에 적용되는 학습 알고리즘은 없기 때문에 복잡한 현상은 다양한 분야에서 추출된 데이터에 의존하므로 여러 유형의 학습자로부터 얻은 증거를 통합하는 앙상블의 능력은 점점 더 중요해지고 있다.
- 여러운 학습 작업에 대한 좀 더 섬세한 이해
- 실제 현상은 보통 상호작용을 하는 여러 복잡한 요소들로 극도로 복잡하다. 작업을 작은 부분으로 분할하는 모델은 하나의 전역적인 모델이 놓치는 미세한 패턴을 좀 더 정확히 포착할 수 있다.
가장 대중적인 앙상블 방법 몇 가지를 이용해서 지금까지 작업했던 신용모델의 성능을 개선할 것이다.
또 다른 일반적인 앙상블 기반의 방법은 부스팅(boosting)이라고 하는데, 약한 학습자의 성능을 올려서(boost) 강한 학습자의 성능을 얻기 때문이다. 배깅과 유사하게 부스팅은 리샘플링된 데이터에 대해 훈련된 모델의 앙상블과 최종 예측을 결정하기 위해 투표를 사용한다. 두 가지 주요 차이점이 있다. 첫 번째로 부스팅에서 __리샘플링된 데이터셋은 상호 보완적인 학습자를 생성하게 특별히 구성__돼야 한다. 두 번째로 부스팅은 각 학습자에게 __동일한 표를 주는 대신 표에 과거 성능을 기반으로 하는 가중치를 부여__한다. 따라서 성능이 좋은 모델은 앙상블의 최종 예측에 더 큰 영향을 미친다.
부스팅은 앙상블의 최고 모델보다 보통 매우 좋거나 분명히 더 나쁘지 않은 성능을 보인다. 앙상블의 모델은 상호 보완적으로 구축되기 때문에 각 분류기가 임의의 우연(random choice)보다 잘 수행한다는 가정하에 단순히 그룹에 분류기를 추가함으로써 앙상블의 성능을 임의의 임계치까지 올릴 수 있다.
부스팅은 임의의 낮은 오류율을 만족하는 모델을 생성할 수 있지만, 이렇게 하는 것이 늘 합리적이지 않다. 첫째, 학습자가 추가될 때마다 성능 획득이 점점 줄어드므로 어떤 임계치의 경우 실제 실현이 불가능하다. 둘째, 순수한 정확도를 추구하게 되면 훈련 데이터에 과적합되는 모델이 만들어질 수 있으며, 처음 보는 데이터 대해 일반화되기 어렵다.
에이다부스트(AdaBoost) 또는 __적응형 부스팅(adaptive boosting)__으로 불리는 부스팅 알고리즘은 자주 오분류 되는 예시에 좀 더 집중함으로써(즉, 가중치를 더 주어) 분류하기 힘든 예시의 대부분을 반복적으로 학습하는 약한 학습자를 만든다는 아이디어를 기반으로 한다.
가중치가 없는 데이셋에서 시작해서 최초의 분류기는 결과를 모델링하려고 시도한다. 분류기가 정확하게 예측한 예시는 다음 분류기를 위한 훈련 데이터셋에 나타날 가능성이 적으며, 역으로 분류가 힘든 예시는 좀 더 자주 나타날 것이다. 약한 학습자의 라운드가 추가되면 연속해서 더 어려운 예시를 갖는 데이터에 대해 훈련된다. 원하는 전체 오류율에 도달하거나 성능이 더 이상 향상되지 않을 때까지 과정은 계속된다. 그 시점에서 각 분류기를 구축한 훈련 데이터의 정확도에 따라 분류기의 표(vote)에 가중치가 부여된다.
부스팅의 원리는 어떤 종류의 모데렝도 거의 적용될 수 있지만, 가장 일반적으로 의사결정 나무와 같이 사용된다. 부스팅을 C5.0 의사결정 나무의 성능을 개선하는 방법은 위에서 이미 사용했다.
에이다부스트.M1(AdaBoost.M1) 알고리즘은 분류를 위한 에이다부스트 트리 기빈의 다른 구현을 제공한다. 에이다부스트.M1 알고리즘은 adabag 패키지에서 찾을 수 있다. 신용 데이터에 대해 AdaBoost.M1 분류기를 생성해보자. 알고리즘에 대한 일반적인 구문은 다른 모델링 기법과 비슷하다.
## Using C5.0 Decision Tree (not shown in book)
#library(C50)
m_c50_bst <- C5.0(default ~ ., data = credit, trials = 100)
평소와 같이 만들어진 객체에 predict() 함수를 적용해 예측을 한다.
## Using AdaBoost.M1
library(adabag)
# create a Adaboost.M1 model
set.seed(300)
m_adaboost <- boosting(default ~ ., data = credit)
p_adaboost <- predict(m_adaboost, credit)
이 명령은 관행을 벗어나서 예측 벡터를 반환하는 대신, 모델 정보가 포함된 객체를 반환한다.
예측은 class라고 불리는 서브객체에 저장된다. 혼동 행렬은 confusion 서브객체에서 발견된다.
head(p_adaboost$class)
## [1] "no" "yes" "no" "no" "yes" "no"
p_adaboost$confusion
## Observed Class
## Predicted Class no yes
## no 700 0
## yes 0 300
에이다부스트 모델이 실수가 전혀 없다. 이 혼동 행렬은 훈련 데이터에 대한 모델 성능을 기반으로 한다는 것을 기억하자. 부스팅은 오류율이 임의의 수준으로 낮아지는 것을 허용하기 때문에 학습자는 단순히 더 이상 오류가 없을 때까지 계속 진행됐다. 이렇게 되면 훈련 데이터 셋에 대해 과적합될 가능성이 있다.
처음 보는 데이터에 대한 좀 더 정확한 성능 평가를 하려면 다른 평가 방법을 사용할 필요가 있다. adabag 패키지는 10-폴드 교차 검증을 사용하는 간단한 함수를 제공한다.
# create and evaluate an Adaboost.M1 model using 10-fold-CV
set.seed(300)
adaboost_cv <- boosting.cv(default ~ ., data = credit)
## i: 1 Wed Oct 10 22:20:13 2018
## i: 2 Wed Oct 10 22:20:44 2018
## i: 3 Wed Oct 10 22:22:40 2018
## i: 4 Wed Oct 10 22:23:47 2018
## i: 5 Wed Oct 10 22:24:22 2018
## i: 6 Wed Oct 10 22:25:28 2018
## i: 7 Wed Oct 10 22:26:00 2018
## i: 8 Wed Oct 10 22:26:30 2018
## i: 9 Wed Oct 10 22:27:01 2018
## i: 10 Wed Oct 10 22:27:35 2018
adaboost_cv$confusion
## Observed Class
## Predicted Class no yes
## no 594 151
## yes 106 149
컴퓨터의 용량에 따라 실행하는 데 약간의 시간이 필요할 수 있으며, 반복할 때마다 화면에 로그를 남긴다. 완료된 후에 좀 더 합리적인 혼동 행렬을 볼 수 있다.
vcd 패키지를 사용해서 카파 통계량을 확인할 수 있다.
# calculate kappa
library(vcd)
Kappa(adaboost_cv$confusion)
## value ASE z Pr(>|z|)
## Unweighted 0.3607 0.0323 11.17 5.914e-29
## Weighted 0.3607 0.0323 11.17 5.914e-29
약 0.36의 카파를 가지므로 이 모델이 지금까지 만들 모델 중 최고의 성능을 갖는 신용 평가 모델이다. 앙상블 방법과 비교해보자.
랜덤 포레스트(random forests, 또는 의사결정 트리 포레스트 decision tree forests)라고 하는 다른 앙상블 기반의 방법은 의사결정 트리의 앙상블에만 집중한다. 이 방법은 의사결정 트리 모델에 다양성을 추가하기 위해 배깅의 기본 원리와 임의의 특징 선택을 결합한다. 트리 앙상블이 생선된 후 모델은 트리의 예측 을 결합하기 위해 투표를 사용한다.
랜덤 포레스트는 융통성과 힘을 하나의 머신 러닝 방법으로 결합한다. 앙상블은 전체 특징 집합의 작고 임의의 부분만을 사용하기 때문에 랜덤 포레스트는 소위 ’차원의 저주(curse of dimensionality)’로 다른 모델이 실패하곤 하는 극도로 큰 데이터 셋을 다룰 수 있다. 동시에 대부분의 학습 작업에 대한 오류율은 거의 다른 모든 방법과 동등하다.
다른 앙상블 기반의 방법들과 비교해서 랜덤 포레스트는 매우 경쟁력이 있고 상대적으로 대회에 주요 이점을 제공한다는 점은 주목할 가치가 있다. 예를 들어 랜덤 포레스트는 사용이 쉽고 쉽게 과적합되지 않는다. 다음 표는 랜덤 포레스트 모델의 일반적인 장단점을 나열한 것이다.
| - | 장점 | 단점 |
|---|---|---|
| 1 | 모든 문제에 대해 잘 수행되는 다목적 모델이다. | 의사결정 트리 같지 않게 모델 해석이 쉽지 않다. |
| 2 | 범주형 또는 연속 특징 뿐 아니라 잡음이 있는 데이터나 누락 데이터를 다룰 수 있다. | 모델을 데이터에 맞춰 튜닝하려면 약간의 작업이 필요할 수 있다. |
| 3 | 가장 중요한 특징만을 선택한다. | - |
| 4 | 극도로 큰 개수의 특징이나 예시가 있는 데이터에 사용될 수 있다. | - |
힘, 융통성, 쉬운 사용 덕분에 랜덤 포레스트는 빠른 속도로 가장 대중적인 머신 러닝 기법 중 하나가 되었다. 후반부에는 부스팅 C5.0트리와 랜덤 포레스트 모델을 비교할 것이다.
이 함수는 예측을 위해 사용되는 랜덤 포레스트 객체를 반환한다.
이 함수는 type 파라미터의 값에 따라 예측을 반환한다.
디폴트로 randomForest() 함수는 각 분할 시점에 sqrt(p)개의 랜덤 특징을 고려하는 500개 트리의 앙상블을 생성한다. 이때 p는 훈련 데이터셋의 특징 개수고, sqrt()는 r의 제곱근 함수다. 이 디폴트 파라미터가 적절한지는 학습 작업과 훈련 데이터의 본질에 따라 다르다. 일반적으로 학습 문제가 복잡해지고 (특징이 많거나 예시가 많아서) 데이터 셋이 커질수록 트리에 더 잘 작동하지만, 많은 트리를 훈련하는 계산 비용과 균형을 맞춰야 할 필요가 있다.
대량의 트리를 이용하는 목표는 각각의 특징이 여러 모델에 나타날 기회를 갖게 충분히 훈련하는 것이다. 이 목표가 mtry 파라미터의 디폴트 값이 sqrt(p)인 근거가 된다. 이 값을 이용하면 특징이 충분히 제약되므로 트리 간에 상당히 불규칙한 변동이 발생한다. 예를 들어 신용 데이터는 16개의 특징을 갖기 때문에 각 트리는 어떤 시점이든 네 개의 특징으로 분할되도록 제약된다.
randomForest()의 디폴트 파라미터가 신용 데이터에 어떻게 작동죄는지 살펴보자. 다른 학습자가 했던 것처럼 이 모델을 훈련할 것이다. 다시 set.seed() 함수가 동일한 결과를 반복될 수 있게 보장한다.
## Random Forests ----
# random forest with default settings
library(randomForest)
set.seed(300)
rf <- randomForest(default ~ ., data = credit)
모델 성능의 요약을 보려면 간단히 만들어진 객체의 이름을 입력하면 된다.
rf
##
## Call:
## randomForest(formula = default ~ ., data = credit)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 4
##
## OOB estimate of error rate: 23.3%
## Confusion matrix:
## no yes class.error
## no 638 62 0.08857143
## yes 171 129 0.57000000
예상처럼 출력은 랜덤 포레스트가 500개의 트리를 포함하고 각 분할 시점에 네 개의 변수를 시도했다는 것을 보여준다. 언뜻 보기에 혼동 행렬에 따르면 외관상 나쁜 성능에 놀랐을지도 모른다(오류율(error rate) 23.3%는 지금까지 어떤 다른 앙상블 방법의 재치환 오류(resubstitution error)보다 훨씬 나쁘다). 하지만 이 혼동 행렬은 재치환 오류를 보여주지 않는다. 대신 (출력에 OOB estimate of error rate로 나열돼 있는) OOB 오류율(out-of-bag error rate)을 나타내는데, 이 값은 재치환 오류와는 달리 편향되지 않은 테스트 집합의 오류 추정치다. 이것은 미래 성능의 매우 합리적인 추정치라는 것을 의미한다.
OOB 추정치는 랜덤 포레스트를 구축하는 동안 계산된다. 기본적으로 단일 트리의 부트스트랩 샘플로 선택되지 않은 모든 예시는 처음 보는 데이터에 대해 모델의 성능을 테스트하는 데 사용될 수 있다. 포레스트 구축의 마지막에 각 예시에 대한 에측을 집계하고, 투표를 하여 최종 예측을 결정한다. 그런 예측의 전체 오류율은 OOB 오류율이 된다.
이전에 언급했듯이 randomForest() 함수는 caret에서 지원되며, OOB 오류율을 초과하는 성능 척도를 계산하면서 동시에 모델을 최적화한다. 상황을 흥미롭게 만들기 위해 자동 튜닝 랜덤 포레스트와 앞에서 개발했던 자동 튜닝 부스팅 C5.0 최고 모델과 비교해보자. 이 실험을 마치 머신 러닝 대회에 제출하려는 후보 모델을 식별하려는 것으로 간주할 것이다. 먼저 caret을 로드하고 훈련 제어 옵션을 설정해야 한다. 가장 정확하게 모델 성능을 비교하기 위해 반복 10-폴드 교차 검증(또는 10회 반복하는 10-폴드 교차 검증)을 이용할 것이다. 이 방법은 모델을 구축하는데 시간이 많이 걸리고 평가를 하기 위해 계산적으로 강도가 좀 더 높지만, 최종 비교이기 때문에 옳은 선택이라고 확신해야 한다. 이 결전의 승자는 머신 러닝 대회로 가는 유일한 관문이 될 것이다.
# library(caret)
ctrl <- trainControl(method = "repeatedcv",
number = 10, repeats = 10)
다음은 랜덤 포레스트를 위한 튜닝 그리드를 설정할 것이다. 이 모델의 유일한 튜닝 파라미터는 mtry로, 각 분할 시점에 임의로 선택되는 특징의 개수를 정의한다. 디폴트로 랜덤 포레스트가 트리별로 sqrt(16)또는 4개의 특징을 사용할 것을 알고 있다. 철저히 실험하기 위해 전체 16개 특징뿐 아니라 그것의 반과 두 배 값을 테스트할 것이다. 따라서 다음과 같이 2, 4, 6, 8, 16으로 된 그리드를 생성해야 한다.
# auto-tune a random forest
grid_rf <- expand.grid(.mtry = c(2, 4, 8, 16))
만들어진 그리드를 ctrl 객체와 함께 train 함수에 다음과 같이 제공할 수 있다. 최고 모델을 선택하기 위해 카파 척도를 사용할 것이다.
set.seed(300)
m_rf <- train(default ~ ., data = credit, method = "rf",
metric = "Kappa", trControl = ctrl,
tuneGrid = grid_rf)
이 명령은 해야 할 일이 많기 때문에 완료되는 데 어느 정도 시간이 소요된다. 완료되면 10, 20, 30, 40 반복을 이용하는 부스팅 트리와 비교할 것이다.
# auto-tune a boosted C5.0 decision tree
grid_c50 <- expand.grid(.model = "tree",
.trials = c(10, 20, 30, 40),
.winnow = "FALSE")
set.seed(300)
m_c50 <- train(default ~ ., data = credit, method = "C5.0",
metric = "Kappa", trControl = ctrl,
tuneGrid = grid_c50)
C5.0 의사결정 트리가 최종적으로 완료되면 두 방법을 나란히 비교할 수 있다. 랜덤 포레스트 모델에 대한 결과는 다음과 같다.
m_rf
## Random Forest
##
## 1000 samples
## 16 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 10 times)
## Summary of sample sizes: 900, 900, 900, 900, 900, 900, ...
## Resampling results across tuning parameters:
##
## mtry Accuracy Kappa
## 2 0.7223 0.1157648
## 4 0.7469 0.2830979
## 8 0.7504 0.3287980
## 16 0.7568 0.3638677
##
## Kappa was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 16.
부스팅 C5.0 모델에 대한 결과는 다음과 같다.
m_c50
## C5.0
##
## 1000 samples
## 16 predictor
## 2 classes: 'no', 'yes'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold, repeated 10 times)
## Summary of sample sizes: 900, 900, 900, 900, 900, 900, ...
## Resampling results across tuning parameters:
##
## trials Accuracy Kappa
## 10 0.7293 0.3083621
## 20 0.7362 0.3285118
## 30 0.7349 0.3229257
## 40 0.7368 0.3251888
##
## Tuning parameter 'model' was held constant at a value of tree
##
## Tuning parameter 'winnow' was held constant at a value of FALSE
## Kappa was used to select the optimal model using the largest value.
## The final values used for the model were trials = 20, model = tree
## and winnow = FALSE.
mtry = 16인 랜덤 포레스트 모델이 카파가 약 0.361이므로 8개 모델 중 승자다. 카파가 약 0.334인 최고 C5.0 의사결정 트리보다 높으며 카파가 약 0.360인 에이다 부스트.M1 모델보다 약간 높다. 이 결과를 기반으로 랜덤 포레스트를 최종 모델로 제출할 것이다. 실제 대회 데이터에 대해 모델을 평가하지 않고는 우승을 할지 확실히 알 방법은 없지만 성능 추정치가 있으므로 안전한 내기가 된다. 운이 조금만 따르면 상을 받게 될 것이다.
# Random Forest
library(randomForest)
library(e1071)
set.seed(123)
numrow = nrow(credit)
trnind = sample(1:numrow,size = as.integer(0.7*numrow))
train_data = credit[trnind,]
test_data = credit[-trnind,]
rf_fit = randomForest(default~.,data = train_data ,mtry = 4, maxnodes = 2000, ntree = 1000, nodesize = 2)
rf_pred = predict(rf_fit,data = train_data,type = "response")
rf_predt = predict(rf_fit,newdata = test_data,type = "response")
tble = table(train_data$default,rf_pred)
tblet = table(test_data$default,rf_predt)
acc = (tble[1,1]+tble[2,2])/sum(tble)
acct = (tblet[1,1]+tblet[2,2])/sum(tblet)
print(paste("Train acc",round(acc,4),"Test acc",round(acct,4)))
## [1] "Train acc 0.7486 Test acc 0.7567"
# Grid Search
rf_grid = tune(randomForest,default~., data = train_data, ranges = list(
mtry = c(4,5),
maxnodes = c(700,1000),
ntree = c(1000,2000,3000),
nodesize = c(1,2)
),
tunecontrol = tune.control(cross = 5)
)
summary(rf_grid)
##
## Parameter tuning of 'randomForest':
##
## - sampling method: 5-fold cross validation
##
## - best parameters:
## mtry maxnodes ntree nodesize
## 5 700 1000 1
##
## - best performance: 0.2485714
##
## - Detailed performance results:
## mtry maxnodes ntree nodesize error dispersion
## 1 4 700 1000 1 0.2514286 0.010594569
## 2 5 700 1000 1 0.2485714 0.005976143
## 3 4 1000 1000 1 0.2557143 0.018488827
## 4 5 1000 1000 1 0.2571429 0.015971914
## 5 4 700 2000 1 0.2600000 0.008144110
## 6 5 700 2000 1 0.2600000 0.009583148
## 7 4 1000 2000 1 0.2571429 0.011293849
## 8 5 1000 2000 1 0.2614286 0.022360680
## 9 4 700 3000 1 0.2585714 0.010594569
## 10 5 700 3000 1 0.2542857 0.014811744
## 11 4 1000 3000 1 0.2571429 0.005050763
## 12 5 1000 3000 1 0.2485714 0.018488827
## 13 4 700 1000 2 0.2585714 0.013739560
## 14 5 700 1000 2 0.2528571 0.013923992
## 15 4 1000 1000 2 0.2571429 0.015152288
## 16 5 1000 1000 2 0.2571429 0.011293849
## 17 4 700 2000 2 0.2585714 0.012777531
## 18 5 700 2000 2 0.2528571 0.010832679
## 19 4 1000 2000 2 0.2557143 0.011736912
## 20 5 1000 2000 2 0.2571429 0.012371791
## 21 4 700 3000 2 0.2571429 0.011293849
## 22 5 700 3000 2 0.2528571 0.010832679
## 23 4 1000 3000 2 0.2585714 0.013739560
## 24 5 1000 3000 2 0.2585714 0.013739560
best_model = rf_grid$best.model
summary(best_model)
## Length Class Mode
## call 6 -none- call
## type 1 -none- character
## predicted 700 factor numeric
## err.rate 3000 -none- numeric
## confusion 6 -none- numeric
## votes 1400 matrix numeric
## oob.times 700 -none- numeric
## classes 2 -none- character
## importance 16 -none- numeric
## importanceSD 0 -none- NULL
## localImportance 0 -none- NULL
## proximity 0 -none- NULL
## ntree 1 -none- numeric
## mtry 1 -none- numeric
## forest 14 -none- list
## y 700 factor numeric
## test 0 -none- NULL
## inbag 0 -none- NULL
## terms 3 terms call
y_pred_train = predict(best_model, data = train_data)
train_conf_mat = table(train_data$default, y_pred_train)
print(paste("Train Confusion Matrix - Grid Search:"))
## [1] "Train Confusion Matrix - Grid Search:"
print(train_conf_mat)
## y_pred_train
## no yes
## no 445 46
## yes 130 79
train_acc = (train_conf_mat[1,1] + train_conf_mat[2,2])/sum(train_conf_mat)
print(paste("Train_accuracy-Grid Search:", round(train_acc,4)))
## [1] "Train_accuracy-Grid Search: 0.7486"
y_pred_test = predict(best_model, newdata = test_data)
test_conf_mat = table(test_data$default, y_pred_test)
print(paste("Test Confusion Matrix - Grid Search:"))
## [1] "Test Confusion Matrix - Grid Search:"
print(test_conf_mat)
## y_pred_test
## no yes
## no 195 14
## yes 55 36
test_acc = (test_conf_mat[1,1] + test_conf_mat[2,2])/sum(test_conf_mat)
print(paste("Test_accuracy-Grid Search:", round(test_acc,4)))
## [1] "Test_accuracy-Grid Search: 0.77"
confusionMatrix(test_data$default, y_pred_test)
## Confusion Matrix and Statistics
##
## Reference
## Prediction no yes
## no 195 14
## yes 55 36
##
## Accuracy : 0.77
## 95% CI : (0.7182, 0.8164)
## No Information Rate : 0.8333
## P-Value [Acc > NIR] : 0.9982
##
## Kappa : 0.3765
## Mcnemar's Test P-Value : 1.469e-06
##
## Sensitivity : 0.7800
## Specificity : 0.7200
## Pos Pred Value : 0.9330
## Neg Pred Value : 0.3956
## Prevalence : 0.8333
## Detection Rate : 0.6500
## Detection Prevalence : 0.6967
## Balanced Accuracy : 0.7500
##
## 'Positive' Class : no
##
a <- confusionMatrix(test_data$default, y_pred_test)
a$overall
## Accuracy Kappa AccuracyLower AccuracyUpper AccuracyNull
## 7.700000e-01 3.765060e-01 7.181680e-01 8.164118e-01 8.333333e-01
## AccuracyPValue McnemarPValue
## 9.981510e-01 1.468802e-06
a$byClass
## Sensitivity Specificity Pos Pred Value
## 0.7800000 0.7200000 0.9330144
## Neg Pred Value Precision Recall
## 0.3956044 0.9330144 0.7800000
## F1 Prevalence Detection Rate
## 0.8496732 0.8333333 0.6500000
## Detection Prevalence Balanced Accuracy
## 0.6966667 0.7500000
a$byClass[1]
## Sensitivity
## 0.78
# Variable Importance
vari = varImpPlot(best_model)
print(paste("Variable Importance - Table"))
## [1] "Variable Importance - Table"
print(vari)
## MeanDecreaseGini
## checking_balance 36.509882
## months_loan_duration 29.348506
## credit_history 19.861459
## purpose 19.377138
## amount 44.586679
## savings_balance 16.932539
## employment_duration 20.020070
## percent_of_income 14.344997
## years_at_residence 13.101301
## age 33.781667
## other_credit 8.077092
## housing 9.296663
## existing_loans_count 6.441806
## job 10.662125
## dependents 3.896758
## phone 5.290919