Learning & Reasoning/R

Supervised Learning with R

이현봉 2014. 8. 2. 23:23

지난 주에 7주에 걸쳐 하던 일반인 대상 예측분석과정을 끝냈다.  10명이 안되는 수강생들과 오붓하게 했다.  이 과정 역시 내가 해보고 싶어 연 것이었다.  올 초에 하려 했는데 들을 사람이 없어 못하다가 내가 3명만 되면 하고 싶다고 해서 열었다.  학원은 재정적으로 별로 좋은 과정이 아니었겠다.  내가 재미있었듯이 수강생들에게도 유익했으면 좋겠다.

작년부터 이런 과정을 하고 싶던 중 책을 찾아보았는데 내 맘에 드는 것이 없었다.  책은 많은데 어떤 것은 수강생들에게 너무 어렵고, 또 어떤 것들은 지나치게 피상적이어서 자칫 헛바람만 주입시킬 것 같다는 생각이 들어 거의 포기하던 중 "An Introduction to Statistical Learning" 의 draft를 접하곤 희망을 품을 수 있었다. 

잘 쓴 책이다.  비전공자들을 위해서도 이해를 돕기 위해 많은 노력을 했다는 것을 알 수 있다.  설명이 정말 좋다.  문제들도 훌륭하고.  학생들을 앞에 놓고 말을 하듯이 진도 나가는데 Authority와 유머도 곳곳에서 보인다.  좋은 학습서는 대부분 이렇다.  분석가를 지향하는 사람들을 위해 기본적인 supervised learning 입문 코스 표준으로 삼으면 좋겠다.  누군가 한글로 번역한다면 유머스런 면도 잘 표현하면 좋겠다. 

마지막 날 한 실습에서 하나의 데이터(german credit)를 갖고 logistic regression, classification tree, random forest 로 classification 해 보았다.  내가 진행한 과을 그대로 보여주었다.  작년 초에 한 것을 다시 보니 새로왔다.  classification이지만 가능한 숫자를 끝까지 갖고 가도록 하고, 그래서 classification도 확률로 간주하고, 확률은 우리가, 모델이 생각하는 믿음이지 그게 어떤 진리가 아니기에 언제라도 새로운 데이터가 나오면 수정할 수 있다는 것을 전해주려고 노력했다.  3가지 모델을 모두 이용해 ensemble/meta learning 까지 나가 볼까도 했는데 하지 못했다.  

다음 과정은 마케터나 세일즈맨, 현업이 간단한 분석을 직접 해 볼 수 있도록 강의를 해 보려한다.  그들의 필요가 무엇인지, 어떻게 접근하면 되겠는지, 소통하면서 아이디어를 계발했으면 좋겠다.  프로그래밍을 하지 않으면서,  

Unsupervised learning, Ensemble Learning, Time series, Text mining 도 해 보고 싶은데 될 지 모르겠다.  Introduction to statistical learning같이 딱 맞는 책 찾기부터 어렵다. 들을 사람은 더욱 글쎄...  

=========

cennade@gmail.com

오늘은 하나의 데이터를 이용해 다양한 방법으로 예측분석을 시도해
봅니다. 데이터는 널리 알려진 "german credit data" 입니다. 우리는 이 데이터를 이용해
고객 신용도를 판단해 보려는 것이지만, 이 모형은 "잠재적 고객 분류", "churn model"
을 포함해서 많은 다른 모형과도 비슷합니다 


# 대출심사

### 1. 비즈니스 이해

데이터 출처 :  http://archive.ics.uci.edu/ml/datasets/Statlog+(German+Credit+Data)

이 데이터는 독일의 한 은행이 고객들의 채무불이행을 추적 조사한 내용으로
1000명의 데이터가 있으며, 20개의 feature/predictors와 1개의 response(마지막 열)로 되어있음
이 분석의 목적은 20개의 feature들을 predictor삼아 response를 학습하여 향후
새로운 고객에 대해 그 고객의 채무불이행 가능성을 판단하기 위함

참고 : german.doc

예측 목표(예) : 고객의 채무불이행에 따른 손해를 현재보다 10% 감소 목표!!

response : 1 = Good, 2 = Bad loan   
It is worse to class a customer as good when they are bad (손해 5), than it is to class
a customer as bad when they are good (손해 1).
채무불이행자를 좋은 고객으로 분류하는 할 때의 손해가 5라면, 채무를 잘 갚을 고객을
채무불이행자로 분류할 (잠재적으로 고객을 놓치는 손해 때문) 때의 손해는 1이다.  
**즉, 잠재적 채무불이행자를 검출하는 것이 더 중요하다.**
  
german.data의 (숨겨진) 속성 : 
이 데이터는 미리 몹시 신경써 만든 것이라는 것을 짐작할 수 있음.  채무이행자:채무불이행자 의
비율이 7:3 인데, 상식적으로 채무불이행자의 비율이 전체 고객 중 30% 정도나 될 수는 없음. 이러면 은행 100% 망함. 그러니 이 데이터는 인위적으로 위 비율을 조정한 것임.  Why?
비율이 50:50에서 너무 벗어나면 learning에 큰 문제가 생김. 이를 "class imbalance" 문제라 함.
따라서 이 문제에 빠지지 않기 위해 채무불이행자의 (자연적) 비율보다 높였음.
 
imbalanced data/class 에 관한 얘기는 본 강의의 영역을 벗어남. 구글 검색을 해 보도록.

아래 3개의 패키지가 필요함
```{r}
source("R_Utility.R")    #  C/Java의 local 영역 include/import 흉내

pkgTestnLoad("lattice")  # 시각화 패키지
pkgTestnLoad("ggplot2")  # 시각화 패키지
pkgTestnLoad("plyr")     # 데이터 조작 패키지
```
  
### 2. 데이터 이해하기
**2.1 데이터 갖고 오기** : a) 웹에서 갖고 오기 or b) pc 파일을 load 하기  

a)와 b) 중 한가지 선택

*a) 웹에서 직접 데이터를 가져와 credit 데이터프레임에 넣기*
```{r}
credit <- read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data", header=F, sep=" ")
```

*b) PC에 있는 데이터를 가져와 credit 데이터프레임에 넣기*

이렇게 하려면 현재 working directory를 이 R script가 위치한 곳으로
바꾸어 주어야 함. Rstudio 메뉴에서 Session -> Set Working Directory ->
To Source File Location 으로 할 수 있음. 또는 setwd() 사용
```{r}
credit <- read.table(paste(getwd(), "/german.data.txt", sep=""))
```

  
**2.2 데이터 특징**
```{r}
str(credit)
head(credit)
```

 
**2.4 데이터 munging**  
별로 할 것은 없지만...
credit 데이터에 변수/predictor 명을 줍니다  
http://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.doc 참고
```{r}
names(credit) <- c("checkingstatus" , "duration" , "history" , "purpose" ,      
                   "amount" , "savings" , "employ.since" , "installment.rate" , "status.sex" ,       
                   "cosigners" , "residence.since", "collateral" , "age" , "otherplans" ,  
                   "housing" , "existing.credits" , "job" , "no.dependents" , "telephone",          
                   "foreign", "default" )

str(credit)   # sanity check
sum(is.na(credit))  # 0이 나옴. 즉 NA가 업다는 말. 데이터에 빈란이 없음

# default 변수를 factor로 변환; 이를 조건삼아 다양하게 분석을 하기 위함
credit$default <- factor(credit$default)
table(credit$default)  # good loan(채무이행)이 700, bad loan(채무불이행)이 300
```

factor 변수들의 level(카테고리값)들이 "A30", "A201" 이런 식으로 되어있어 그 내용이
무엇인지 알기 힘들다.  그래서, 알기쉽게 레벨들을 바꾼다.
```{r}
levels(credit$history) = c("very_good","good","OK","poor","critical_acc")
credit$foreign <- factor(credit$foreign, levels=c("A201","A202"), labels=c("foreign","german"))
credit$housing <- factor(credit$housing, levels=c("A151","A152", "A153"), labels=c("rent", "own", "for_free"))
credit$purpose <- factor(credit$purpose, levels=c("A40","A41","A42","A43","A44","A45","A46","A47","A48","A49","A410"))
levels(credit$purpose) <- c("new_car","used_car","furniture_equip","radio_TV","appliances","repairs",
                            "education","vacation_NA?","retraining", "business", "others")
credit$default <- factor(credit$default, levels=c("1", "2"), labels=c("good_loan", "bad_loan"))

head(credit)
```

  
**2.5 탐색적 데이터 분석**  
1. 과거이력과 default 관계를 보자. 전에 불이행한 사람이 이번에도 할 가능성이 높은가?

```{r}
t_def_wrt_hist = table(credit$default, credit$history)
df <- data.frame(t_def_wrt_hist)    
names(df) <- c("Default", "Credit", "Freq")
df1 <- ddply(df, "Credit", transform, Percentage= (Freq / sum(Freq))*100)
ggplot(df1, aes(x=Credit, y=Percentage, fill=Default)) +
  geom_bar(stat="identity", color="gray1") + scale_fill_brewer(palette="Pastel1") +
  ylab("불이행 Percentage") + xlab("신용도")   # very nice

```

분석 : 이전에 신용이 좋았던 사람이 상대적으로 채무불이행도가 높다.
Why? 이유는, 이 데이터가 이미 대출이 된 사람들을 대상으로 만들어
진 것이기 때문이다. 전에 문제가 있던 고객에게는 아마도 세심하게 심사를
했을 것이고, 이 데이터에 포함된 고객들은 그 심사를 통과한 사람들이다.
 
**2. 대출목적과 default 관계를 보자.**
```{r}
t_def_wrt_purpose = table(credit$default, credit$purpose)
# barplot(t_def_wrt_purpose)
df <- data.frame(t_def_wrt_purpose)
names(df) <- c("Default", "purpose", "Freq")
df1 <- ddply(df, "purpose", transform, Percentage= (Freq / sum(Freq))*100)
ggplot(df1, aes(x=purpose, y=Percentage, fill=Default)) +
  geom_bar(stat="identity", color="gray1") + scale_fill_brewer(palette="Pastel1") +
  ylab("불이행 Percentage") + xlab("대출목적")
# 분석 : 생각외로 교육을 위해 대출받은 고객들이 상대적으로 채무불이행율이 높다

rm(df, df1)   # 이제 필요하지 않은 객체를 제거함
```
  
워낙 factor들이 많아 변수들이 numeric이어야 탐색분석이 가능한 "상관관계" 같은 것을
하기에 제약이 있습니다. 
    
### 3. 모델링
```{r}
str(credit)
summary(credit)
```
 
어떤 predictor들이 중요한가?
```{r}
pkgTestnLoad("rpart")
tree.model <- rpart(default~., data=credit, control=
                           rpart.control(minsplit=10))
plot(tree.model)
text(tree.model)  # root node가 가장 중요. level이 내려갈수록 중요도 감소
tree.model

pkgTestnLoad("randomForest") 
rf.credit = randomForest(default~., data=credit,
                         importance=TRUE)
varImpPlot(rf.credit)
```
      
변수 중요성이 checkingstatus, duration, history, amount, purpose,,, 순

변수 중요성은 정말 다양햔 방법으로 분석할 수 있지만 어떤 것이 절대적으로 효과적인 것인지는
말하기 어려움. 여기서는 randomForest의 varImpPlot를 이용.  randomForest는 non-parametric이기에 데이터의 distribution 패턴 (feature space 에서의 분포 모양) 에 까탈스럽지 않고,
response나 predictor가 numeric이거나 factor이거나 가리지 않고, randomForest 자체를 잘 몰라도
온 인류가 쓸 수 있도록 Random Forest 저자들이 워낙 쉽게 만들어 주었기에(만세!) 안 쓰면 죄. 
따라서 randomForest를 예측분석의 목표 성능치 출발점/baseline으로 생각하기 바람. 

Think? 이 데이터의 경우 regression에서 말하는 variable P value와 randomForest가 가져다 준 결과 중 어느 것에 더 믿음이 가나요?  

*일단 모든 변수를 사용해 logistic regression 모델 생성*
```{r}
set.seed(101)
train = sample(1:nrow(credit), nrow(credit)*0.7)
train.x = credit[train,]
train.y = credit$default[train]
validation.x = credit[-train,]
validation.y = credit$default[-train]

contrasts(credit$default)  # bad_loan이 1, 즉 success 이다
logistic.credit <- glm(default~., family=binomial, data=train.x) # logistic reg
```
 
**평가 : training set에 대하여**
```{r}
glm.probs <- predict(logistic.credit, type="response")   # against training set
glm.preds <- ifelse(glm.probs <= 0.5, "0_good", "1_bad")
conf = table(glm.preds, train.y) ; conf
```
  
**평가 : validation set에 대하여**
```{r}
glm.probs <- predict(logistic.credit, newdata=validation.x, type="response") 
glm.preds <- ifelse(glm.probs <= 0.5, "0_good", "1_bad")
conf = table(glm.preds, validation.y) ; conf
```
 
**1) Logistic Regression, all predictors, cutoff=0.5, againt validation set** 
accuracy = (193+37)/sum(conf) = 0.766 
sensitivity = 37/(52+37) = 0.415 
precision = 37/(37+18) = 0.672 
*sensitivity가 만족스럽지 않다* 

상기 모델은 cutoff threshold를 0.5로 삼았음.  그런데, bad_loan을 식별
못했을 시의 손해가 그 반대보다 5배 크므로, bad_loan을 더 열심히 발견해야
함.  따라서, cutoff threshold를 낮추어 bad_loan에 더 민감히 반응하도록
만들어 본다. cutoff threshold = 1/6
```{r}
glm.preds <- ifelse(glm.probs <= 1/6, "0_good", "1_bad")
conf = table(glm.preds, validation.y) ; conf
```

**2) Logistic Regression, all predictors, cutoff=0.166, against validation set** 
accuracy = (124+76)/300 = 0.66 
sensitivity = 76/(13+76) = 0.853 
precision = 76/(87+76) = 0.466 

*sensitivy는 개선되었지만, accuracy와 precision은 나빠짐*

*이번에는 predictor중 일부만 사용해 logistic regression. No interaction, No high order*
```{r}
logistic.credit <- glm(default~checkingstatus+duration+history + age +
                         amount+age+purpose+savings,, family=binomial,
                       data=train.x) #  basic logistic reg

glm.probs <- predict(logistic.credit, newdata=validation.x, type="response") 
glm.preds <- ifelse(glm.probs <= 0.25, "0_good", "1_bad")
conf = table(glm.preds, validation.y) ; conf
```

**3) Logistic Regression, selected predictors, cutoff=0.25, against validation set** 
accuracy = (143+70)/300 = 0.71 
sensitivity = 70/(19+70) = 0.7865169 
precision = 70/(68+70) = 0.5072464 

**Logistic Regression을 5-fold Cross Validation**
```{r}
pkgTestnLoad("DAAG")
logistic.credit <- glm(default~., family=binomial, data=credit) # logistic reg
out = CVbinary(obj=logistic.credit, nfolds=5)
```

*Cross-validation estimate of accuracy = 0.752*   
  
cross-validation을 통한 Sensitivity, Precision, Specificity는 위 코드를 참고해 loop로 만들어 보도록.
      
      
## Classification Tree ("rpart")를 사용해 예측분석   
 
R에서는 rpart외에 C5.0, tree, ctree 등의 tree 패키지가 있다. 같은 데이터에 대해서 결과가
조금씩 다름.  다 유효한 모델들임.

**우선 tree의 depth를 4로 제한해 보자**
```{r}
pkgTestnLoad("rpart")
pkgTestnLoad("rpart.plot")
str(credit)

rpart.tree <- rpart(default ~ ., data=train.x, method="class",  # classification tree
               control=rpart.control(maxdepth=4) )
rpart.plot(rpart.tree, type=4, extra=101)
plot(rpart.tree)
text(rpart.tree)
```
 
**Compute confusion matrix**
```{r}
result <- data.frame(prediction=predict(rpart.tree, newdata=validation.x, type="class"),
                     True_Class=validation.y )

confusion <- table(result) ; confusion
```
 
**4) Classification Tree, depth=4, default cutoff(0.5), against validation set**
```{r}
accuracy = sum(diag(confusion))/sum(confusion) ; accuracy   # 0.7333333
precision = confusion[2,2]/sum(confusion[2,]) ; precision   # 0.5818182
sensitivity_recall = confusion[2,2]/sum(confusion[,2]) ; sensitivity_recall # 0.3595506
false_alarm_rate = confusion[2,1]/sum(confusion[,1]); false_alarm_rate # 0.1090047
```
 
**앞의 1) logistic regression 경우보다 모든 성능이 떨어진다**  
 
**Classification Tree의 response를 class로 가져오고 pruning을 해 보자**
```{r}
rpart.tree <- rpart(default ~ ., data=train.x, method="class")
              
printcp(rpart.tree) # display the results
plotcp(rpart.tree) # visualize cross-validation results
summary(rpart.tree) # detailed summary of splits

rpart.plot(rpart.tree, type=4, extra=101)  # bushy tree
```

**Prune the tree**  
cp 값을 cptable의 xerror값 중 최소값일 때의 CP 값으로 선택
```{r}
tree.pruned <- prune(rpart.tree,
                     cp=rpart.tree$cptable[which.min(rpart.tree$cptable[,"xerror"]),"CP"])

# plot the pruned tree
plot(tree.pruned, uniform=TRUE,
   main="Pruned rpart Classification Tree")
text(tree.pruned, use.n=TRUE, all=TRUE, cex=.8)
```

**Predict on validation set & compute the confusion matrix**
```{r}
result <- data.frame(prediction=predict(tree.pruned, newdata=validation.x, type="class"),
                     True_Class=validation.y )

confusion_pruned <- table(result) ; confusion_pruned
```
 
**5) Classification Tree, pruned, default cutoff(0.5), against validation set**
```{r}
accuracy_p = sum(diag(confusion_pruned))/sum(confusion_pruned) ; accuracy_p #0.73
precision_p = confusion_pruned[2,2]/sum(confusion_pruned[2,]) ; precision_p # 0.5689655
sensitivity_recall_p = confusion_pruned[2,2]/sum(confusion_pruned[,2]) ; sensitivity_recall_p # 0.3707865
false_alarm_rate_p = confusion_pruned[2,1]/sum(confusion_pruned[,1]); false_alarm_rate_p # 0.1184834
```
*앞의 4)와 비슷하나 sensitivity가 조금 향상*   
 
### Sensitivity를 높여 보자. Prediction의 결과를 확률로 갖고 오자 
```{r}
result <- data.frame(prediction=predict(tree.pruned, newdata=validation.x, type="prob"),
                     True_Class=validation.y )
head(result, 50)
result$Prediction <- ifelse(result$prediction.bad_loan > 0.5, "1_bad_loan", "0_good_loan")
result_ranked=result[order(result$prediction.bad_loan,decreasing=TRUE),]
head(result_ranked, 20)

table(result$True_Class)
CM_Reg = table(prediction=result$Prediction, True_Class=result$True_Class); CM_Reg
confusion_pruned  # 5)의 결과와 동일함. So it checks
```
 
Sensitivity를 더 예민하게 해 보자
```{r}
result$Prediction <- ifelse(result$prediction.bad_loan > 0.25, "1_bad_loan", "0_good_loan")
table(result$Prediction)   # bad_loan으로 실제보다 많이 분류함
# result_ranked=result[order(result$prediction.bad_loan,decreasing=TRUE),]
table(prediction=result$Prediction, True_Class=result$True_Class)
```
 
**6) Classification Tree, pruned, cutoff = 0.25, against validation set** 
accuracy_p = (129+65)/300 = 0.6466667 
precision_p =  65/(65+82) = 0.4421769 
sensitivity_recall_p =  65/(24+65) = 0.7303371 
*유사한 경우인 앞의 3)보다 전반적으로 성능이 처진다*


  
## Random Forest를 사용해 예측분석  

random Forest 알고리즘은 Breiman 교수가 2001년 Machine Learning 에 올림. 고 Breiman 교수는
미국 통계학회 회장도 지낸 저명한 분. Tree를 만들 때 전체 observation을 그대로 쓰지 않고
"resample with replacement" 해서 쓰고, 또 노드에서 split할 때도 전체 feature/predictor를
참고하지 않고 그 중 일부만 무작위로 삼아 split에 쓰고, 그런 tree들을 여러개를 만들어 "집단 판단"에 따르는
것이 좋다는 이런 생각은 통계학자만이 할 수 있는 것 같음. 

randomForest 사용법 중 가장 좋은 내용은 CRAN의  
http://cran.r-project.org/web/packages/randomForest/randomForest.pdf

```{r}
library(randomForest)
# trainSet과 validationSet을 7:3으로 나눔
set.seed(101)
ind <- sample(nrow(credit), 0.7*(nrow(credit)))
trainSet <- credit[ind,]
validationSet <- credit[-ind,]
```
  
  
  
set.seed(101)
train = sample(1:nrow(credit), nrow(credit)*0.7)
train.x = credit[train,]
train.y = credit$default[train]
validation.x = credit[-train,]
validation.y = credit$default[-train]

**Random Forest Model 만들기**
```{r}
Formula <- default ~ .  # Use all predictors
credit_RF <- randomForest(Formula, data=train.x, ntree=700, proximity=T,
                          importance=TRUE)   # can't get easier than this
credit_RF
str(credit_RF)
head(data.frame(trainSet$default, credit_RF$predicted, credit_RF$votes), 20)
```

**plot the error rates with various number of trees.**
```{r}
plot(credit_RF)
legend("topright", colnames(credit_RF$err.rate),col=1:3,cex=0.8,fill=1:3)
```
 
**test randomForest performance against trainSet**
```{r}
table(predict(credit_RF, newdata=train.x), train.x$default)
```

The importance of variables can be obtained with functions  
importance() and varImpPlot()  
**MeanDecreaseGini 가 클수록 중요한 변수**
```{r}
importance(credit_RF)
varImpPlot(credit_RF)
```

Now test the model against the validationSet
```{r}
predict_credit_rf = predict(credit_RF, newdata=validation.x) 
predict_credit_rf 
data.frame(True_Class=validation.x$default, Prediction=predict_credit_rf) 
table(predict_credit_rf, validation.y)
```

 
**7) Random Forest, cutoff = 0.5, against validation set**  
accuracy = (200+32)/300 = 0.7733333  
sensitivity = 32/(57+32) = 0.3595506  
precision = 32/(11+32) = 0.744186  
    
**Return probabilities**
```{r}
predict_credit_rf_reg = predict(credit_RF, newdata=validation.x, type="prob")
head(data.frame(predict_credit_rf_reg, True_Class=validation.x$default, Prediction_0.5=predict_credit_rf), 30)
```
  
  
**credit_RF의 sensitivity를 높이자**
```{r}
sensitive_RF = ifelse(predict_credit_rf_reg[,"bad_loan"] > 0.25, "bad_loan", "good_loan")
credit_RFEx = data.frame(validation.x, predict_credit_rf_reg, Prediction_0.5=predict_credit_rf, Prediction_0.25=sensitive_RF)
table(credit_RFEx$Prediction_0.25, credit_RFEx$default)
```
       
            good_loan bad_loan
  good_loan       126       18
  bad_loan         85       71
  
**8) Random Forest, cutoff = 0.25, against validation set**  
accuracy = (126+71)/300 = 0.6566667  
sensitivity = 71/(18+71) = 0.7977528  
precision = 71 / (85+71) = 0.4551282  

...
...

위 3개의 모델들이 각각의 observation들에 대해 어떤 판단을 내렸는지 보자
여기서는 Default로 분류될 cutoff를 Prob(Default) = 0.25 로 잡은 것을 본다.
  
train.x   # training set
train.y   # default of training set
validation.x  # validation set
validation.y  # default of validation set

이제부터 3개의 모델들이 각각의 training set observation들에 대한 판단을 본다.
1) Logistic Model
 
```{r}
logistic.credit  # logistic model with selected predictors
logistic.probs = predict(logistic.credit, newdata=train.x, type="response")
train.evalModels = data.frame(True_Class=train.y, logistic.probs,
                         logistic.class=ifelse(logistic.probs <= 0.25, "0_good", "1_bad"))
                     
logistic.probs = predict(logistic.credit, newdata=validation.x, type="response")
valid.evalModels = data.frame(True_Class=validation.y, logistic.probs,
                         logistic.class=ifelse(logistic.probs <= 0.25, "0_good", "1_bad"))   
table(valid.evalModels$logistic.class, valid.evalModels$True_Class)  # OK
```

2) tree 모델. 여기서는 앞의 tree.pruned 모델을 쓴다  
```{r}
tree.pruned  
train.evalModels$tree.probs <- predict(tree.pruned, newdata=train.x, type="prob")[,"bad_loan"]  
train.evalModels$tree.class <- ifelse(train.evalModels$tree.probs<= 0.25,  
                                      "0_good", "1_bad")  

valid.evalModels$tree.probs <- predict(tree.pruned, newdata=validation.x, type="prob")[,"bad_loan"] 
valid.evalModels$tree.class <- ifelse(valid.evalModels$tree.probs<= 0.25, 
                                      "0_good", "1_bad")  
table(valid.evalModels$tree.class, valid.evalModels$True_Class)
```

                  
3) random forest 모델.   
```{r}
credit_RF  
train.evalModels$rf.probs <- predict(credit_RF, newdata=train.x, type="prob")[,"bad_loan"] 
train.evalModels$rf.class <- ifelse(train.evalModels$rf.probs<= 0.25,  
                                      "0_good", "1_bad")  
table(train.evalModels$rf.class, train.evalModels$True_Class)  # RF looks likde overfitting
                                       
valid.evalModels$rf.probs <- predict(credit_RF, newdata=validation.x, type="prob")[,"bad_loan"] 
valid.evalModels$rf.class <- ifelse(valid.evalModels$rf.probs<= 0.25,  
                                      "0_good", "1_bad") 
table(valid.evalModels$rf.class, valid.evalModels$True_Class)
```
  
**Some Analysis on evalModels**
```{r}
summary(train.evalModels)  # all three means are similar, but median and quantiles differ
pairs(train.evalModels[train.evalModels$True_Class=="bad_loan", c(2,4,6)])  # all 3 predictors correlate. tree is jumpy
pairs(train.evalModels[train.evalModels$True_Class=="good_loan", c(2,4,6)])
```
  
## ensemble/meta learning  
여러개의 모델을 결합해 더 정확한 예측을 하는 방법. 포괄적 이론적 고찰은 Friedman & Popescu 가 2003년 "Importance Sampled
Learning Ensembles" 에서 발표했지만 실제는 그보다 훨씬 전부터 사용된 기술. 웹에서 논문을 구할 수 있음. 

앞에서 얻은 train.evalModels와 valid.evalModels를 관찰.
- 일단 RF는 위 설정에서 train set에 대해서는 완벽함.  그런데 validation set에 대해서는 logistic regression보다 못함  
- 단순한 tree는 역시 성능이 안 좋음

**What Next**  
1) False Negative, False Positive 에러에 따라 다른 cost를 고려해 각 모델마다 최적의 cutoff를 찾는다.
위와 같이 observation들을 7:3 으로 나누지 말고, K-fold CV 을 쓴다
2) ensemble learning을 써 본다. 위와 같이 모델을 3개 만들어도 되고, 아니면 logistic regression의 식을 달리해
더 많은 수의 base 모델을 만들어도 된다. 개별 base model들의 prediction들을 어떻게 합치면 좋을까?  앞에서 우리
evalModels 에는 base model들의 class prediction 확률도 함께 있다. 가장 단순한 방법은 이들을 이용해 다시
classification 알고리즘을 적용하는 것이다.  하지만, 위 경우 RF가 training set에서는 워낙 잘 맞아 이 녀석이
다른 모델을 압도할 가능성이 많다.  
3) 최소한 1990년 이전에도 ensemble learning은 사용되었다.  2000년대 들어선 이 방법을 쓰지 않고는 supervised
learning 시합에서 여간해 못 이긴다. ensemble learning은 서로 다른 특성의 base model들이 조합되어 variance를
낮추기 때문에 특성이 강한 모델 (boosting, neural net 계열)을 조합하면 좋다.  
4) 많은 특성이 알려졌음에도 실제에서는 ensemble learning은 그래도 많은 부분이 art이다. 
5) R에서 직접 만들어도 되고 또는 Max Kuhn의 패키지를 참고해보도록.    

위 내용은 누구나 사용해도 됨. 인용을 해도, 안해도 됨.
This content is open to anyone. No need to cite this.
Have a great day. 
이현봉
Lee Hyun Bong

=========

germancredit.rmd