1 모형 아키텍쳐(Model Architecture)

시간과 비용의 제약으로 인해서 모든 예측 모형에 적합시켜보는 것은 불가능하다. 따라서, 서로 다른 패러다임을 갖는 대표 모형을 선정하여 적절한 모형 아키텍쳐를 선정하는 전략을 취한다. 고객이탈 예측모형을 위해서 두가지 모형 패러다임 즉, 일반화 선형회귀모형, 의사결정나무 모형을 적합시킨다. 의사결정나무도 GBM과 Random Forest를 적합시킨다.

SVM, 신경망, 유전자 알고리즘 등은 시간과 지면이 한정되어 제외한다.

  • 일반화 선형회귀모형
  • 의사결정나무 모형
    • GBM / xgBoost
    • Random Forest
  • SVM
  • 신경망

의사결정나무의 경우 caret 팩키지를 활용하여 교차 타당도 및 반복이 많아 병렬컴퓨팅 기법을 적용시켜 모형 적합을 빨리 수행시킨다.

2 모형설계행렬

목표변수(이탈, Churn)을 예측하고 설명하는데 사용하는 모형설계행렬(Model Desgin Matrix) 혹은 Feature Matrix를 recipes 팩키지를 활용하여 간결하게 데이터 정제작업을 수행한다.

# 0. 환경설정 -----
## 0.1. 팩키지 준비
# library(tidyverse)
# library(caret)
# library(randomForest)
# library(extrafont)
# loadfonts()
# library(doParallel)
# library(recipes)
# library(yardstick)

## 0.2. 병렬처리환경설정
cl <- makeCluster(detectCores())
registerDoParallel(cl)

# 1. 데이터 ------
## 1.1. 데이터 가져오기
churn_df <- read_rds("data/churn_df.rds")

## 1.2. 모형설계행렬(Feature Matrix) ------
### 1.2.1. 새로운 데이터 조리법: 고객이탈 
churn_rec <- recipe(churn_df, Churn ~ .) %>% 
  add_role(gender,           SeniorCitizen,    Partner,          Dependents,       PhoneService,    
           MultipleLines,    InternetService,  OnlineSecurity,   OnlineBackup,     DeviceProtection,
           TechSupport,      StreamingTV,      StreamingMovies,  Contract,         PaperlessBilling,
           PaymentMethod,    MonthlyCharges,   TotalCharges,     tenure_grp, 
           new_role = "predictor") %>%
  add_role(Churn, new_role = "outcome")
Warning: Changing role(s) for gender, SeniorCitizen, Partner, Dependents,
PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup,
DeviceProtection, TechSupport, StreamingTV, StreamingMovies, Contract,
PaperlessBilling, PaymentMethod, MonthlyCharges, TotalCharges, tenure_grp
Warning: Changing role(s) for Churn
### 1.2.2. 고객이탈 요리법 절차
churn_rec_with_steps <- churn_rec %>% 
  step_center(all_numeric()) %>%
  step_scale(all_numeric()) %>% 
  step_dummy(all_nominal(), -Churn)

### 1.2.3. 고객이탈 요리 준비
churn_rec_prepped <- churn_rec_with_steps %>% 
  prep()

### 1.2.4. 고객이탈 데이터 요리 시작
churn_dm <- bake(churn_rec_prepped, newdata = churn_df)

DT::datatable(churn_dm)
Warning in instance$preRenderHook(instance): It seems your data is too
big for client-side DataTables. You may consider server-side processing:
https://rstudio.github.io/DT/server.html

2.1 훈련시험 데이터

caret 팩키지 createDataPartition() 함수를 사용해서 훈련데이터(training_df)와 시험데이터(testing_df)로 구분한다.

## 1.3. 훈련/검증 데이터
train_idx <- createDataPartition(churn_dm$Churn, p=0.7, list=FALSE)

training_df <- churn_df[train_idx,]
testing_df  <- churn_df[-train_idx,]

3 모형 선정

3.1 모형 적합

로지스틱 회귀 모형, RF 모형, GBM 모형, SVM 모형, xgBoost 모형을 순차적으로 학습시키고 결과값을 각 객체에 저장시킨다.

# 2. 모형 아키텍처 선정  ------------------------------------------------------------

## 2.1. 모형 공식 준비 
x_var <- setdiff(colnames(training_df), list('Churn'))
churn_formula <- as.formula(paste('Churn', paste(x_var, collapse=' + '), sep=' ~ '))

## 2.2. 모형 패러미터 튜팅
ml_control <- trainControl(
  method = "repeatedcv", number = 3, ## 10-fold CV
  repeats = 3, ## 10번 반복
  classProbs = TRUE,
  summaryFunction = twoClassSummary
)

## 2.3. 모형 아키텍처
### 로지스틱 회귀 모형
churn_glm <- train(churn_formula, data = training_df,
                        method = "glm", 
                        family=binomial(link='logit'),
                        trControl = ml_control,
                        metric = "ROC")

### RF 모형
churn_rf <- train(churn_formula, data = training_df, 
                 method = "rf", 
                 importance = TRUE,
                 trControl = ml_control, 
                 verbose = FALSE, 
                 metric = "ROC")

### GBM 모형
churn_gbm <- train(churn_formula, data = training_df, 
                   method = "gbm", 
                   trControl = ml_control, 
                   verbose = FALSE, 
                   metric = "ROC")

### SVM 모형
churn_svm <- train(churn_formula, data = training_df, 
                   method = "svmLinear",
                   trControl=ml_control,
                   metric = "ROC")

### xgBoost
churn_xgboost <- train(churn_formula, data = training_df,
                             method = "xgbLinear",
                             trControl = ml_control,
                             verbose = FALSE, 
                             metric = "ROC")
stopCluster(cl)

3.2 모형 평가

상기 5가지 모형에 대해 분류가 잘 되었는지 성능을 yardstick 팩키지를 활용하여 평가한다.

# 4. 모형 아키텍쳐 평가 --------------------------
## 4.1. 모형 아키텍처 시각화
churn_caret_comp <- resamples(list( 
   GLM = churn_glm,
   RF = churn_rf,
   GBM = churn_gbm,
   SVM = churn_svm,
   XgBoost = churn_xgboost))

bwplot(churn_caret_comp, layout = c(3, 1), scales = list(relation = "free"))

## 4.2. 모형 아키텍처 
model_arch <- testing_df %>%
  mutate(GLM = predict(churn_glm, testing_df),
         SVM = predict(churn_svm, testing_df),
         RF = predict(churn_rf, testing_df),
         GBM = predict(churn_gbm, testing_df),
         XgBoost = predict(churn_xgboost, testing_df))

glm_metric_df <- metrics(model_arch, truth = Churn, estimate = GLM) %>% 
  mutate(model = 'GLM')
rf_metric_df <- metrics(model_arch, truth = Churn, estimate = RF) %>% 
  mutate(model = 'RF')
gbm_metric_df <- metrics(model_arch, truth = Churn, estimate = GBM) %>% 
  mutate(model = 'GBM')
svm_metric_df <- metrics(model_arch, truth = Churn, estimate = SVM) %>% 
  mutate(model = 'SVM')
xgboost_metric_df <- metrics(model_arch, truth = Churn, estimate = XgBoost) %>% 
  mutate(model = 'XgBoost')

bind_rows(glm_metric_df, rf_metric_df) %>% 
  bind_rows(gbm_metric_df) %>%
  bind_rows(svm_metric_df) %>% 
  bind_rows(xgboost_metric_df) %>% 
  DT::datatable() %>% 
  DT::formatRound(c("accuracy"), digits=2)

4 모형선정과 적용

다양한 모형 아키텍쳐를 접합시키고 나서 일반화 선형모형을 최종 모형 아키텍처로 선정한다. 예측모형 양산을 위해서 과적합 방지 및 고성능 예측을 위해서 일반화 선형모형에 대한 튜닝작업을 진행한다.

변수선택을 step 함수를 통해 수행한다. 그리고 나서, 선택된 변수를 대상으로 양산 예측모형을 적합시킨다.

4.1 모형선정

MASS 팩키지 stepAIC() 함수를 사용해서 모형 아키텍처에서 선정한 일반화선형모형(GLM)을 기본으로 변수를 선정하여 강건한 모형을 구축한다.

# 3. 로지스틱 모형 변수 선택  ------------------------------------------------------------
## 3.1. 변수 선택
churn_glm <- glm(churn_formula, family="binomial", data=training_df)
churn_var_glm <- MASS::stepAIC(churn_glm, direction = "both", trace = 0)

churn_parsi_fmla <- as.formula(summary(churn_var_glm)$call)

## 3.2. 모형적합
churn_fit_glm <- glm(churn_parsi_fmla, family="binomial", data=training_df)

4.2 최적 컷오프 설정

이탈 확률에 대해 특정 확률값(컷오프, cutoff)을 적용하게 되면 이탈고객과 잔존 고객으로 나눠지게 되는데 이에 대해서 다음과 같이 행렬을 구성할 수 있다.

실제 이탈 (Reference)
참(True): Event 거짓(False): No Event
모형 예측 양성(Positive): Event TP(True Positive): A FP(False Positive): B
(Predicted) 음성(Negative): No Event FN(False Negative): C TN(True Negative): D

하지만, 상기 지표는 이탈고객과 잔존고객의 이익과 손실이 모두 1로 가정한 것이다. 고객이 그대로 남게 되면 25만큼의 이익이 생긴다고 가정하고, 이탈하게 되면 30만큼 손해가 생긴다고 가정해서 최적의 컷오프를 지정해 보자.

실제 이탈 (Reference)
참(True): Event 거짓(False): No Event
모형 예측 양성(Positive): Event 0 0
(Predicted) 음성(Negative): No Event -30 25
# 4. 예측모형 성능  ------------------------------------------------------------
## 4.1. 최적 컷오프 설정 
testing_df$pred <- predict(churn_fit_glm, newdata = testing_df, type = "response", na.action = na.exclude)

payoff_df <- data_frame(
  cutoff = seq(from = 0.1, to = 1.0, by = 0.1),
  payoff = NA
)

for(i in 1:nrow(payoff_df)) {
  conf_matrix <- SDMTools::confusion.matrix(as.integer(testing_df$Churn)-1,
                                 testing_df$pred, 
                                 threshold = payoff_df$cutoff[i])
  payoff_df$payoff[i] <- conf_matrix[1,1] * 25 + conf_matrix[1,2] * (-30)
}

## 4.2. 최적 컷오프 시각화 
payoff_max_df <- payoff_df %>% 
  filter(payoff == max(payoff))

payoff_df %>% 
  ggplot(aes(x=cutoff, y=payoff)) +
    geom_line() +
    geom_point() +
    theme_minimal(base_family = "NanumGothic") +
    labs(x="컷오프(cutoff)", y="수익") +
    scale_y_continuous(labels = scales::comma) +
    geom_vline(xintercept = payoff_max_df$cutoff, color="green") +
    geom_hline(yintercept = payoff_max_df$payoff, color="green")

## 4.3. 최적금액 산출 
# payoff = 25 * true negative - 30 * false negative
payoff_matrix <- SDMTools::confusion.matrix(as.integer(testing_df$Churn)-1,
                           testing_df$pred, 
                           threshold = 0.5)

25 * payoff_matrix[1,1] - 30 * payoff_matrix[1,2]
[1] 27325

4.3 예측모형 성능

양산 예측모형이 준비되면 예측모형에 대한 성능을 파악한다. 그리고, 일반화 선형모형이 예측모형을 활용될 경우 오즈비를 통해 변수별로 고객이탈에 대한 비이탈고객대비 이탈고객에 대한 정보는 덤으로 획득가능하다.

# 4. 모형 성능  ------------------------------------------------------------
## 4.1. 모형 성능

churn_glm_df <- broom::augment(churn_fit_glm, newdata=testing_df, type.predict = "response")

churn_glm_df <- churn_glm_df %>% 
  mutate(Churn_pred = ifelse(.fitted >= 0.5, "Yes", "No"))

churn_glm_tbl <- table(churn_glm_df$Churn, churn_glm_df$Churn_pred)

confusionMatrix(churn_glm_tbl, positive = "Yes")
Confusion Matrix and Statistics

     
        No  Yes
  No  1399  149
  Yes  255  305
                                         
               Accuracy : 0.8083         
                 95% CI : (0.7909, 0.825)
    No Information Rate : 0.7846         
    P-Value [Acc > NIR] : 0.003977       
                                         
                  Kappa : 0.4772         
 Mcnemar's Test P-Value : 1.752e-07      
                                         
            Sensitivity : 0.6718         
            Specificity : 0.8458         
         Pos Pred Value : 0.5446         
         Neg Pred Value : 0.9037         
             Prevalence : 0.2154         
         Detection Rate : 0.1447         
   Detection Prevalence : 0.2657         
      Balanced Accuracy : 0.7588         
                                         
       'Positive' Class : Yes            
                                         
## 4.2. 로지스틱 모형 - 오즈비
churn_glm_odds <- exp(cbind(OR=coef(churn_glm), confint(churn_glm)))
Waiting for profiling to be done...
DT::datatable(churn_glm_odds) %>% 
  DT::formatRound(c(1:3), digits=2)

4.4 예측모형 양산

고객이탈 양산에 대한 자세한 내용은 TV 시청시간 예측서비스 - 데이터는 openCPU, 구현은 plumber, R 양산환경(plumber) - 타이타닉을 참조하여 RESTful API형태로 서비스를 제공한다.