Handling Class Imbalance with R


실제 데이터에 머신러닝을 적용하려고 하다보면 한 쪽의 class가 과도하게 큰 문제인 class imbalance 문제에 직면하게 된다.

Method to improve performance on imbalanced data

  • Class weights: impose a heavier cost when errors are made in the minority class

  • Down-sampling: randomly remove instances in the majority class

  • Up-sampling: randomly replicate instances in the minority class

  • Synthetic minority sampling technique (SMOTE): down samples the majority class and synthesizes new minority instances by interpolating between existing ones

SMOTE를 그림으로 설명하면 아래와 같다.

Imbalanced Classification in R

class imbalance 문제를 해결할 수 있는방법은 ROSE DMwR 페키지를 사용하는 것이다.
여기서 사용하는 예제는 Binary Classification문제를 다룬다.

  • ROSE: Random over smapling examples packages 이다. 이것은 artifical data를 샘플링 방법에 기반해서 생성 하게 된다.
install.packages("ROSE")
library(ROSE)

내부적으로 ROSE 페키지는 imbalanced data의 하나인 hacide.train hacide.test를 보유하고 있다.

SMOTE 방법

oversampling 방법은 너무 중복된 값이 많이 생성되고 undersampling은 중요한 데이터를 너무 많이 잃어 버린다.
ROSE는 이러한 문제를 해결 한다.

구현 코드

library(ROSE)

data(hacide) # imbalanced data
str(hacide.train)
str(hacide.test)


# check table
table(hacide.train$cls)
table(hacide.test$cls)

# check classes distribution
prop.table(table(hacide.train$cls))
prop.table(table(hacide.test$cls))


# only 2% of data is positive. 
# it is a severely imbalanced data set.

library(rpart)
treeimb <- rpart(cls ~ ., data = hacide.train)
pred.treeimb <- predict(treeimb, newdata = hacide.test)

# check model accuracy 
accuracy.meas(hacide.test$cls, pred.treeimb[,2])
roc.curve(hacide.test$cls, pred.treeimb[,2], plotit = F)


# over sampling
data_balanced_over <- ovun.sample(cls~., data = hacide.train, method = "over", N=1960)$data
# N refers to number of observations in the resulting balanced set.

table(data_balanced_over$cls)

# under sampling
data_balanced_under <- ovun.sample(cls~., data = hacide.train, method = "under", N=40)$data

# under and over smapling (both)
# the minority class is oversampled with replacement and majority class is undersampled without replacement
data_balanced_both <- ovun.sample(cls ~ ., data = hacide.train, method = "both", p=0.5, N=1000, seed = 1)$data
table(data_balanced_both$cls)

data.rose <- ROSE(cls ~ ., data = hacide.train, seed = 1)$data
table(data.rose$cls)


#build decision tree models
tree.rose <- rpart(cls ~ ., data = data.rose)
tree.over <- rpart(cls ~ ., data = data_balanced_over)
tree.under <- rpart(cls ~ ., data = data_balanced_under)
tree.both <- rpart(cls ~ ., data = data_balanced_both)

#make predictions on unseen data
pred.tree.rose <- predict(tree.rose, newdata = hacide.test)
pred.tree.over <- predict(tree.over, newdata = hacide.test)
pred.tree.under <- predict(tree.under, newdata = hacide.test)
pred.tree.both <- predict(tree.both, newdata = hacide.test)

#AUC ROSE
roc.curve(hacide.test$cls, pred.tree.rose[,2])

#AUC Oversampling
roc.curve(hacide.test$cls, pred.tree.over[,2])

#AUC Undersampling
roc.curve(hacide.test$cls, pred.tree.under[,2])

#AUC Both
roc.curve(hacide.test$cls, pred.tree.both[,2])

실행결과

> roc.curve(hacide.test$cls, pred.tree.rose[,2])
Area under the curve (AUC): 0.989
> roc.curve(hacide.test$cls, pred.tree.over[,2], add.roc=TRUE)
Area under the curve (AUC): 0.798
> roc.curve(hacide.test$cls, pred.tree.under[,2], add.roc=TRUE)
Area under the curve (AUC): 0.876
> roc.curve(hacide.test$cls, pred.tree.both[,2], add.roc=TRUE)
Area under the curve (AUC): 0.798

결론적으로 SMOTE방법을 구현한 ROSE package를 이용한 방법이 가장 정확도가 높게 나온다.

참고문헌

[1] Wicked Good Data
[2] Silicon Valley Data Science blog post

[3] SMOTE Implementation in Python
[4] https://www.analyticsvidhya.com/blog/2016/03/practical-guide-deal-imbalanced-classification-problems/

[5] http://machinelearningmastery.com/tactics-to-combat-imbalanced-classes-in-your-machine-learning-dataset/


'AI > Machine Learning with R' 카테고리의 다른 글

Feature Selection with Caret (Auto)  (0) 2016.11.20
Caret의 이해  (0) 2016.03.06
Cross Validation  (0) 2016.02.26
Ensemble method: Bagging (bootstrap aggregating)  (0) 2015.11.19
Bootstrapping  (0) 2015.11.19

Feature Selection with Caret (Auto)


자동 Feature Selection을 통해서 해당 Featrue가 Accurate Model을 생성하는데 필요한지 안한지를 결정할 수 있다.

기법들은 많이 있지만 인기있는 기술중 하나가Recursive Feature Elimination (RFE)방법 이다. 그리고 이 방법을 Caret R package에서 지원 한다.

Caret에서는 rfeInter와 rfe가 존재 한다.
하지만 재귀적 방법을 사용하는 rfe에 초점을 맞춘다.

rfe 함수의 필요 인자는 아래와 같다.

  • x, a matrix or data frame의 예측에 사용되는 feature들
  • y, 결과 백터값
  • sizes, subset의 크기를 결정하는 integer vector이다. 지정하지 않으면 ncol(x)로 설정 된다.
  • rfeControl, option을 포함 한다.

rfeControl 설정시 중요한것이 모델링에 사용할 function을 정하는 것이다.

control <- rfeControl(functions=nbFuncs, method="cv", number=10)

위경우 cross validation 방식으로 naive Baeys로 모델링 한다는 것이다.
그리고 cv는 10개로 쪼개서 수행 한다. 기본값이 10이다. 따로 설정안해도 number=10의 효과를 얻는다.

repeatedcv를 수행하기 위해서는 아래와 같다.

ctrl <- rfeControl(functions = lmFuncs,
                   method = "repeatedcv",
                   repeats = 5,
                   verbose = FALSE)

그 외에는 아래와 같다.

  • linear regression lmFuncs
  • random forests rfFuncs
  • naive Bayes nbFuncs

- bagged tress treebagFuncs

참고문헌

[1] Recursive Feature Elimination

[2] Feature Slection with the Caret R Package


'AI > Machine Learning with R' 카테고리의 다른 글

클래스 불균형을 다루는 방법 (Class Imbalance)  (1) 2017.07.20
Caret의 이해  (0) 2016.03.06
Cross Validation  (0) 2016.02.26
Ensemble method: Bagging (bootstrap aggregating)  (0) 2015.11.19
Bootstrapping  (0) 2015.11.19

Caret의 이해

Caret pakcage에 대해서 좀 더 알아 본다.

이전 포스
공식 사이트

Ch.11 Improving Model Performance

트레이닝 조건의 셋의 최적화 지점을 체계적으로 찾아서 모델의 성능을 튜닝하는 방법을 어떻게하면 자동으로 하는지를 배운다.

모델을 하나의 그룹으로 연합시키는 방법이다.

결정 트리의 variant

Tuning Stock Models for Better Performance

Machine Learning 알고리즘을 선택하는 만큼이나,
Parameter를 선택하는 것도 중요하고 어려운 문제이다.
Caret pakcage는 이러한 parameter 튜닝을 쉽게 하기위해서 grid search를 제공 한다.

Parameter tuning
This process of adjusting the model options to identify the best fit is called parameter tuning.

각 모델에서 필요한 파라메터를 바꿔 가면서 모델링을 수행 할 수 있다.

지원하는 모든 모델의 리스트는 아래 URL에서 확인 가능하다.
https://topepo.github.io/caret/modelList.html

특정 모델의 지원 파라메터를 잊었을 경우 아래 명령어로 확인 할 수 있다.

> modelLookup("nb")
  model parameter              label forReg forClass probModel
1    nb        fL Laplace Correction  FALSE     TRUE      TRUE
2    nb usekernel  Distribution Type  FALSE     TRUE      TRUE

Searching a set of cndidate models comprasing a matrix, or gride, of paremter combinations is impractical.
So, only a subset of possibilities is used to construc the grid.
By default, caret searches at most three values for each of the p parameters.
This means that at most $3^p$ cnadidate models will be tested.

하지만 이렇게 default tuning matrix을 쓰는것은 그다지 scientific하지 않다.

튜닝 파라메터를 주고 그중에서 최적의 모델을 선택 할수도 있다.

아래와 같은 코드에서 grid 방식으로 $ 4 \times 4$의 경우의 수가 존재하게 된다.
이것들을 하나하나 적용하면서 가장 정확도가 높은 model을 자동으로 선택하게 된다.
모델의 평가 방식은 10-folds cross validation 방식을 따른다.

set.seed(12358)
library(foreach)
library(caret)
ctrl <- trainControl(method="cv", 10)
grid <- data.frame(.fL=c(0,0,1,1), .usekernel=c(FALSE,TRUE,FALSE,TRUE))
sms_model1 <- train(dfList, factorClassList, method="nb",trControl = ctrl, tuneGrid = grid)

Variable Importance

Variable Importance

two class problems
만약 Model에서 제공해주는 importance 측정 방법이 존재 하지 않는다면
각 predictor의 importance는 filter를 적용해서 평가 되어 질 수 있다.

ROC Curve분석을 각각의 predictor에 적용하게 된다.

trapezoidal rule을 이용해서 ROC curve의 면적을 계산 한다. 그리고 이 면적의 값이variable importance가 된다.

Multi-class problems
각각의 클래스를 분리한다.
그렇게해서 class들간의 쌍을 만든다.

class1 vs class2
class2 vs claass3
이런 형식이다.

이러한 각 class들에 대해서 최대 curve 면적 값을 가지는 것을 variable importance로 규정 한다.

regression

t-value의 기울기 절대치를 이용하거나
R^2를 이용 한다.

Example

sacle=FALSE 이면 정규화를 하지 않는다.
scale=TRUE로 설정하면 0~100 사이의 score로만 표현된다.

varImp(sms_model1, scale=FALSE)

                    Importance
df.title                0.7292
df.hours                0.6968
df.app_name             0.5589
df.recentPhoneUsage     0.5360

filterVarImp

the area under the ROC curve를 각각의 predictor에 대해서 구하고 싶을 경우, filterVarImp를 사용하게 된다.

RocImp <- filterVarImp(x = training[, -ncol(training)], y = training$Class)
RocImp
          M         R
V1 0.6273646 0.6273646
V2 0.5765656 0.5765656
V3 0.5999674 0.5999674
V4 0.6614481 0.6614481
V5 0.6520711 0.6520711
V6 0.5923842 0.5923842

위와 같이 각 feature들을 1개씩 적용 했을 때의 각각의 class에 대한 ROC 면적을 알 수 있다.

Bayes Classifier의 경우 scale을 FALSE로 설정할 경우 varImp와 filterVarImp의 결과는 같게 된다.

VI <- varImp(sms_model1, scale=FALSE)
VI["importance"][[1]][[1]]

RocImp <- filterVarImp(x = dfList[[1]], y = factorClassList[[1]])
RocImp

실행 결과

> RocImp
      FALSE.     TRUE.
X1 0.9085812 0.9085812
X2 0.5491523 0.5491523
X3 0.6975939 0.6975939
X4 0.4907589 0.4907589
X5 0.4425584 0.4425584
X6 0.5000964 0.5000964
X7 0.4230355 0.4230355
X8 0.5114213 0.5114213

> VI["importance"][[1]]
      FALSE.     TRUE.
X1 0.9085812 0.9085812
X2 0.5491523 0.5491523
X3 0.6975939 0.6975939
X4 0.4907589 0.4907589
X5 0.4425584 0.4425584
X6 0.5000964 0.5000964
X7 0.4230355 0.4230355
X8 0.5114213 0.5114213

이전 POST: ROC 정의

그래프 그리기

plot(varImp(sms_model1, scale=FALSE))


Cross Validation


Ch.10 Evaluating Model Performance
k-fold cross-validation에 대해서 다룬다.

K는 통상 10을 선택 한다.
After the process of training and evaluating the model has occured for 10 times (with 10 different training/testing combinations), the average performance across all the folds is reported.

k가 너무 크면, leave-one-out method 방법이다. 이 방법은 training에 사용할 데이터가 너무 적을 때 사용하는 방법이다.

createFolds

아래의 함수를 이용해서 k-fold를 수행 할 수 있다.

createFolds(y, k = 10, list = TRUE, returnTrain = FALSE)

createFolds를 사용하기 위해서는 입력 y의 타입이 vector type이어야 한다.
그리고 chronological order야만 한다.

# 10-fold cross validation with implementation
set.seed(12358)
folds <- createFolds(dfList_jemin[[2]]$app_name, k = 10, returnTrain = TRUE)

cv_results <- lapply(folds, function(x) {
    training <- data.frame(df[x, ])
    testing <- data.frame(df[-x, ])

    classTraining <- class[x]
    classtesting <-  class[-x]


    sms_model1 <- train(training, classTraining, method="nb")

    credit_pred <- predict(sms_model1, testing)

    cm1 <- confusionMatrix(credit_pred, classtesting, positive="TRUE")

    return(cm1$overall[[1]])
})

str(cv_results)
mean(unlist(cv_results))

실행 결과

> str(cv_results)
List of 10
 $ Fold01: num 0.886
 $ Fold02: num 0.793
 $ Fold03: num 0.788
 $ Fold04: num 0.676
 $ Fold05: num 0.5
 $ Fold06: num 0.719
 $ Fold07: num 0.688
 $ Fold08: num 0.719
 $ Fold09: num 0.788
 $ Fold10: num 0.788
> mean(unlist(cv_results))
[1] 0.7343925

Caret control option을 이용한 방법: trControl=ctrl

caret package의 control 함수를 이용해서도 위의 cross-validation 방법을 수행 할 수 있다.

# 10-fold cross validation  with caret
ctrl <- trainControl(method="cv", 10, verbose = TRUE)

set.seed(12358)
sms_model1 <- train(df,class, method="nb", trControl=ctrl)
sms_model1

실행 결과

Naive Bayes 

325 samples
  1 predictor
  2 classes: 'FALSE', 'TRUE' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 292, 293, 293, 292, 292, 292, ... 
Resampling results across tuning parameters:

  usekernel  Accuracy   Kappa      Accuracy SD  Kappa SD 
  FALSE      0.7321481  0.4587484  0.07483437   0.1433968
   TRUE      0.7321481  0.4587484  0.07483437   0.1433968

Tuning parameter 'fL' was held constant at a value of 0
Accuracy was used to select the optimal model using  the largest value.
The final values used for the model were fL = 0 and usekernel = FALSE. 


'AI > Machine Learning with R' 카테고리의 다른 글

Feature Selection with Caret (Auto)  (0) 2016.11.20
Caret의 이해  (0) 2016.03.06
Ensemble method: Bagging (bootstrap aggregating)  (0) 2015.11.19
Bootstrapping  (0) 2015.11.19
Principal Component Analysis (PCA)  (0) 2015.11.18

Ensemble method: Bagging (bootstrap aggregating)


일반적인 ensemble 방법의 하나가 bootstrap aggregating인 Bagging 방법이다.

Leo Breiman에 의해서 1994년에 만들어 졌다.


bagging은 오리지널 트레이닝 셋으로 부터 bootstrap 샘플링 방법을 이용해서 여러개의 트레이닝 데이터를 생성하는 방법이다.

이렇게 생성된 데이터 셋은 하나의 모델을 생성하기 위해서 사용 된다.


상대적으로 Ensemble 기법들 중에서는 Bagging은 단순한 방법에 속하지만 꽤 잘 동작 한다.

특히 Decision Tree 알고리즘의 경우 약간의 입력 데이터의 변화에도 급격히 다른 결과값을 이끌어 내므로

Bagging으로 이것을 보완 할 수 있다.



ipred package는 bagged decision tree를 구현한 것이다.


'AI > Machine Learning with R' 카테고리의 다른 글

Caret의 이해  (0) 2016.03.06
Cross Validation  (0) 2016.02.26
Bootstrapping  (0) 2015.11.19
Principal Component Analysis (PCA)  (0) 2015.11.18
Evaluating Model Performance with R  (2) 2015.11.12

Bootstrapping


resubstitution error는 한글로 재치환오류라고 하며 트레이닝 셋에서 훈련된 모델에서의 에러를 말한다.

다른 용어로는 In sample error라고도 한다.

당연히 수치는 긍정적으로 평가된다. 왜냐하면 같은 데이터로 훈련하고 테스트 하기 때문이다.



Bootstrap sampling

큰 데이터 셋을 추정하기 위해서 임의로 여러번 샘플링을 하는것을 말한다.

When this principle is applied to machine learning model performance, it implies the creation of several randomly-selected training and test datasets, which are then used to estimate performance statistics.

The results from the various random datasets are then averaged to obtain a final estimate of future performance.


어떤 점이 그럼 k-fold Cross Validation이랑 다른 것일까?

이 방법은 생성한 각각의 구획들이 단 한번만 나타나게 된다.


하지만 bootstrap의 방법은 복원 추출을 하기 때문에 여러번 나올 수 있다.

증명에 의하면 초기 데이터 셋의 수인 N이 엄청 크다면, bootstrap으로는 63.2%를 커버한다는 증명이 있다.


결국 36.8%는 트레이닝 셋이다.


original data의 size가 N 레코드라고 하자. 이때 bootstrap으로 샘플링을 수행하면 N번 수행하면 당연히 N개의 레코드가 있고

이러한 N개의 레코드는 원래 original data size인 N개 중 63.2%를 포함한다고 한다.


N번의 bootstrap sampling에서 단일 record가 선택 되어질 확률은 아래와 같다.

$$1-(1-\frac{1}{N})^{N}$$

이것은 asymptotically approaches $1-e^{-1}=0.632$에 근접한다.

이렇게 bootstrap sampling으로 포함되지 않은 것들은 test set으로 자동으로 포함된다.


이에반에 10-fold cv를 할경우 90%가 결국 트레이닝이 된다.


N이 충분히 크다면 bootstrap은 더 낮은 representative를 보인다.


당연히 63.2%의 트레이닝 셋을 가지고 테스트 하기 때문에 더 큰 트레이닝셋을 가지고 만든 모델보다 더 낮은 정확도를 가지게 된다.


최종적인 error rate의 계산식은 아래와 같다.

$$ error = 0.632 \times error_{test} + 0.368 \times error_{train}$$


cross validation 보다 더 좋은 bootstap의 장점은 더 작은 dataset에서 잘 동작하는 특성이 있다는 것이다.

또한 bootstrap sampling은 모델 정확도 향상의 효과가 있을 수도 있다




주어진 데이터로 부터 복원 표본을 구하는 작업을 여러 번 반복해 원하는 값을 추정 하는 것이다.


예를 들어 보면,

평균의 신뢰 구간을 구하는 경우를 생각해보자.


평균의 신뢰 구간은 평균이 속할 범위를 95% 신뢰도로 찾는 것이다.

부트스트래핑은 데이터로 부터 표본을 복원 추출로 구하고 이들의 평균을 기록하는 일을 반복한다.


이렇게 구한 평균들을 나열한 뒤 가장 작은 값으로 부터 2.5% 가장 큰 값으로 부터 97.5% 지점의 평균 두 개를 구한다.

그러면 두 평균 사이의 구간이 평균 95% 신뢰 구간이 된다.


실제 코드로 구현해 보자.

평균 30, 표준편차 3인 정규 분포로부터 1,000개의 난수를 만들고 이 난수로부터 역으로 평균을 추정해보자.

x <- rnorm(1000, mean=30, sd=3)
t.test(x)
	One Sample t-test
data:  x
t = 308, df = 999, p-value < 2.2e-16
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 29.72329 30.10447
sample estimates:
mean of x 
 29.91388 

t검정 결과 평균의 신뢰 구간이 (29.78891, 30.16317)로 구해졌다. 

library(foreach)
bootmean <- foreach(i=1:10000, .combine=c) %do% {
    return(mean(x[sample(1:NROW(x), replace=TRUE)]))
}

이제는 부트스트래핑으로 복원 추출을 통해 샘플을 구하고 평균을 구하는 작업을 10,000회 반복해보자.

평균의 신뢰구간은 이 평균들의 하위 2.5%, 상위 97.5%에 속하는 값의 구간이다.

bootmean <- sort(bootmean)
bootmean[c(10000*0.025, 10000*0.975)]
[1] 29.72578 30.10531

t검정과 유사한 값이 부트스트랩핑으로도 구해진것을 알 수 있다.


부트스트래핑을 사용한 모델의 정확도 평가도 마찬가지로 수행된다.

데이터의 일부를 트레이닝 데이터로, 일부를 테스트 데이터로 복원 추출을 통해서 구한다.

그리고 이를 수회 반복하여 정확도를 추정한다. 이것이 trainControl()에서 method="boot"가 하는 일이다.



.







'AI > Machine Learning with R' 카테고리의 다른 글

Cross Validation  (0) 2016.02.26
Ensemble method: Bagging (bootstrap aggregating)  (0) 2015.11.19
Principal Component Analysis (PCA)  (0) 2015.11.18
Evaluating Model Performance with R  (2) 2015.11.12
Naive Bayes with Caret package (R)  (0) 2015.11.12

Principal Component Analysis (PCA)


주성분 분석은 데이터에 많은 변수가 있을 때 변수의 수를 줄이는 차원 감소(Dimensionality Reduction)기법 중 하나다.

PCA는 변수들을 주성분(Principal Component)이라 부르는 선형적인 상관관계가 없는 다른 변수들로 재표현한다.


무슨말인가 하면,


선형적인 관계가 있는 두 변수가 아래와 같이 있다고 가정해보자.

A = 2c+3

B= 3c

이다.

이때 Y = f(A,B)라는 어떤 모델이 있다고 하자.

이럴때 실제로 A,B 둘 사이에는 C라는 변수에 의한 선형적 관계가 있는데 둘 다 이용해서 Y를 예측하는 모델을 생성할 필요가 있을까?

이와 같을 때는 f(A,B)로 Y를 예측하기 보다는 그냥 f(C)로 Y를 예측하는것이 더 좋다.


Principal component들은 원 데이터의 분산(퍼짐 정도)을 최대한 보존하는 방법으로 구한다.

좀 더 자세한 내용은 아래의 이전 포스트를 참조하자.

practical machine learning (johns hopkins)

machine learning (standford)



R을 이용해서 실제로 적용해 보기 [R프로그래밍 실습서]


pincomp() 함수 또는 preComp()를 이용한다.


pincomp: 주성분 분석을 수행 한다.

x, # 행렬 또는 데이터 프레임

cor=FASLE # cor=FALSE면 공분산 행렬, TRUE면 상관 행렬을 사용한 주성분 분석을 한다.


아래와 같이 1:10을 저장한 x

약간의 noise를 추가한 y. 즉 x + noise = y 이다.

마지막으로 z는 x + y 에 noise를 추가한 것이다.

x <- 1:10

y <- x + runif(10, min=-.5, max=.5)

z <- x + y + runif(10, min=-10, max=.10)

data frame으로 내용을 출력해보면 아래와 같다.

> (data <- data.frame(x,y,z))

    x         y          z

1   1  1.413629 -0.5826325

2   2  2.021631 -0.9343342

3   3  3.045810 -3.1059315

4   4  3.857941  6.3310046

5   5  5.418124  0.4202864

6   6  6.306387  6.6968121

7   7  7.488154 13.1231456

8   8  8.148416 12.0075606

9   9  9.260750 10.7979732

10 10 10.086332 16.6841931


# do PCA using data

pr <- princomp(data)

summary(pr)


Importance of components:

                          Comp.1    Comp.2       Comp.3

Standard deviation     7.5667362 1.5335677 0.1356764036

Proportion of Variance 0.9602481 0.0394432 0.0003087272

Cumulative Proportion  0.9602481 0.9996913 1.0000000000


위 데이터들은 주성분들이 원 데이터의 분산 중 얼마만큼을 설명해 주는지를 알 수 있다.

Proportion of Variance 행을 보면 첫 번째 주성분은 데이터의 분산 중 92.86%를 설명해 주며,

두 번째 주성분은 데이터의 분산 중 7.1%를 설명함을 알 수 있다.

세 번째 주성분은 가장작은 0.03%의 분산을 설명 한다.

마지막 행의 Cumulative Proportion은 Proportion of Variance의 누적 값이다.


결국 원 데이터의 분산은 첫 번째와 두 번째 주성분에 의해 99.97%가 포함됨을 알 수 있다.

이들 두 주성분상의 좌표는 scores를 보고 구하면 된다.


결국 x,y,z의 데이터 분포는 그냥 2개의 차원으로 축소가 된것이다. 아래와 같은 좌표값을 가지게 된다.

> pr$scores[,1:2]

           Comp.1      Comp.2

 [1,]   8.9555327  1.91623469

 [2,]   8.6788410  0.76263371

 [3,]   9.8173286 -1.57583908

 [4,]   1.0457826  2.13444485

 [5,]   5.2072178 -2.44130849

 [6,]  -0.8729855 -0.38916205

 [7,]  -7.1881748  1.55801181

 [8,]  -6.8265993 -0.01728680

 [9,]  -6.5475880 -1.91981691

[10,] -12.2693552 -0.02791173



알츠하이머(Alzheimer)를 이용한 예제 [Practical Machine Learning HW-2]


데이터를 불러온다.

set.seed(3433)

library(AppliedPredictiveModeling)

data(AlzheimerDisease)

adData = data.frame(diagnosis, predictors)

inTrain = createDataPartition(adData$diagnosis, p = 3/4)[[1]]

training = adData[inTrain, ]

testing = adData[-inTrain, ]


위 데이터 셋에서 "IL"로 시작하는 트레이닝 셋 variables(변수)들에 대해서만 PCA를 수행한다.

preProcess()를 이용하며, PCA 수행결과 80%의 variance(분산)를 capture할 수 있는 변수들의 갯수를 결정해 보자.


먼저 grep과 정규표현으로 해당 열 데이터를 수집한다.

IL_str <- grep("^IL", colnames(training), value = TRUE)


아래와 같이 caret package의 preProcess()를 이용해서 pca를 수행한다. 임계값은 80%이다.

preProc <- preProcess(training[, IL_str], method = "pca", thresh = 0.8)


실행 결과 총 12개의 IL 변수중 7개만으로 원래 데이터 분포의 80%수준의 분산을 설명 할 수 있음을 알 수 있다.

preProc$rotation

                      PC1           PC2           PC3          PC4         PC5          PC6         PC7

IL_11         -0.06529786  0.5555956867  0.2031317937 -0.050389599  0.73512798 -0.102014559  0.20984151

IL_13          0.27529157  0.3559427297 -0.0399010765  0.265076920 -0.25796332 -0.068927711  0.58942516

IL_16          0.42079000  0.0007224953  0.0832211446 -0.082097273  0.04435883 -0.007094672 -0.06581741

IL_17E        -0.01126118  0.5635958176  0.3744707126  0.302512329 -0.38918707  0.221149380 -0.46462692

IL_1alpha      0.25078195 -0.0687043488 -0.3008366900  0.330945942  0.16992452  0.742391473  0.12787035

IL_3           0.42026485 -0.0703352892 -0.1049647272 -0.065352774  0.02352819 -0.165587911 -0.09006656

IL_4           0.33302031  0.0688495706 -0.1395450144  0.165631691 -0.14268797 -0.297421293  0.19661173

IL_5           0.38706503 -0.0039619980  0.0005616126 -0.224448981  0.08426042  0.153835977 -0.16425757

IL_6           0.05398185 -0.4248425653  0.6090821756  0.417591202 -0.00165066 -0.166089521  0.21895103

IL_6_Receptor  0.21218980  0.1005338329  0.2920341087 -0.659953479 -0.29654048  0.138000448  0.22657846

IL_7           0.32948731  0.0806070090 -0.1966471906  0.165544952  0.11373532 -0.405698338 -0.42065832

IL_8           0.29329723 -0.1883039842  0.4405255221  0.002811187  0.28608600  0.184321013 -0.14833779



이전과 같이 princomp를 수행하면 아래와 같다.

summary(princomp(training[, IL_str]))

                          Comp.1    Comp.2    Comp.3     Comp.4    Comp.5     Comp.6     Comp.7     Comp.8     Comp.9    Comp.10      Comp.11
Standard deviation     1.4947237 1.2328654 1.1784414 0.62482551 0.5437811 0.40042942 0.36239787 0.30216932 0.25401803 0.25223488 0.0286080996
Proportion of Variance 0.3523411 0.2397026 0.2190067 0.06156856 0.0466326 0.02528678 0.02071156 0.01439933 0.01017585 0.01003348 0.0001290683
Cumulative Proportion  0.3523411 0.5920437 0.8110505 0.87261902 0.9192516 0.94453840 0.96524995 0.97964928 0.98982513 0.99985861 0.9999876814
                            Comp.12
Standard deviation     8.838105e-03
Proportion of Variance 1.231856e-05
Cumulative Proportion  1.000000e+00



PCA가 정확도에 미치는 영향을 glm 모델을 만들어서 알아보자 [Practical Machine Learning HW-2]



set.seed(3433)

library(AppliedPredictiveModeling)

data(AlzheimerDisease)

adData = data.frame(diagnosis, predictors)

inTrain = createDataPartition(adData$diagnosis, p = 3/4)[[1]]

training = adData[inTrain, ]

testing = adData[-inTrain, ]


set.seed(3433)

## grep the predictors starting with 'IL'

IL_str <- grep("^IL", colnames(training), value = TRUE)

## make a subset of these predictors

predictors_IL <- predictors[, IL_str]

df <- data.frame(diagnosis, predictors_IL)

inTrain = createDataPartition(df$diagnosis, p = 3/4)[[1]]

training = df[inTrain, ]

testing = df[-inTrain, ]



PCA를 적용하지 않은 glm 모델

## train the data using the first method

modelFit <- train(diagnosis ~ ., method = "glm", data = training)

predictions <- predict(modelFit, newdata = testing)

## get the confustion matrix for the first method

C1 <- confusionMatrix(predictions, testing$diagnosis)

print(C1)


Confusion Matrix and Statistics


          Reference

Prediction Impaired Control

  Impaired        2       9

  Control        20      51

                                         

               Accuracy : 0.6463         

                 95% CI : (0.533, 0.7488)

    No Information Rate : 0.7317         

    P-Value [Acc > NIR] : 0.96637        

                                         

                  Kappa : -0.0702        

 Mcnemar's Test P-Value : 0.06332        

                                         

            Sensitivity : 0.09091        

            Specificity : 0.85000        

         Pos Pred Value : 0.18182        

         Neg Pred Value : 0.71831        

             Prevalence : 0.26829        

         Detection Rate : 0.02439        

   Detection Prevalence : 0.13415        

      Balanced Accuracy : 0.47045        

                                         

       'Positive' Class : Impaired 


PCA를 적용해서 수행한 결과이다.

# prediction with PCA

A1 <- C1$overall[1]


## do similar steps with the caret package

modelFit <- train(training$diagnosis ~ ., method = "glm", preProcess = "pca", 

                  data = training, trControl = trainControl(preProcOptions = list(thresh = 0.8)))

C2 <- confusionMatrix(testing$diagnosis, predict(modelFit, testing))

print(C2)


Confusion Matrix and Statistics


          Reference

Prediction Impaired Control

  Impaired        3      19

  Control         4      56

                                          

               Accuracy : 0.7195          

                 95% CI : (0.6094, 0.8132)

    No Information Rate : 0.9146          

    P-Value [Acc > NIR] : 1.000000        

                                          

                  Kappa : 0.0889          

 Mcnemar's Test P-Value : 0.003509        

                                          

            Sensitivity : 0.42857         

            Specificity : 0.74667         

         Pos Pred Value : 0.13636         

         Neg Pred Value : 0.93333         

             Prevalence : 0.08537         

         Detection Rate : 0.03659         

   Detection Prevalence : 0.26829         

      Balanced Accuracy : 0.58762         

                                          

       'Positive' Class : Impaired      


적용전 0.65

적용후 0.72


결국 dimentionality reduction이 정확도 향상에도 기여한다는 것을 알 수 있다.





Evaluating Model Performance with R


이론적인 부분은 아래의 이전 포스트를 확인한다.




Confusion Matrix


가장 쉽게 생성할 수 있는 방법은 table()을 이용 하는 것이다.

vec1 <- factor(c("ham","ham","spam","ham"))
vec2 <- factor(c("spam","ham","spam","ham"))
> table(vec1,vec2)
      vec2
vec1   ham spam
  ham    2    1
  spam   0    1


좀더 상세한 결과를 원한다면, gmodels package의 CrossTable() function을 사용하자.

default로써 CrossTable() output으로써 proportion값을 포함하고 있다.

> class(sms_test_pred)
[1] "factor"
> class(sms_raw_test$type)
[1] "factor"
> CrossTable(sms_test_pred, sms_raw_test$type)

 
   Cell Contents
|-------------------------|
|                       N |
| Chi-square contribution |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  1390 

 
              | sms_raw_test$type 
sms_test_pred |       ham |      spam | Row Total | 
--------------|-----------|-----------|-----------|
          ham |      1202 |        31 |      1233 | 
              |    16.109 |   106.250 |           | 
              |     0.975 |     0.025 |     0.887 | 
              |     0.996 |     0.169 |           | 
              |     0.865 |     0.022 |           | 
--------------|-----------|-----------|-----------|
         spam |         5 |       152 |       157 | 
              |   126.514 |   834.437 |           | 
              |     0.032 |     0.968 |     0.113 | 
              |     0.004 |     0.831 |           | 
              |     0.004 |     0.109 |           | 
--------------|-----------|-----------|-----------|
 Column Total |      1207 |       183 |      1390 | 
              |     0.868 |     0.132 |           | 
--------------|-----------|-----------|-----------|

위 데이터를 가지고 직접 precision, recall, accuracy, error rate 등을 계산 하면 된다.





Beyond accuracy, other measures of performance


자동 계산을 위해서는 또 다른 package인 caret을 사용 한다.

해당 package는 classificationregression training에 대한 정보를 담고 있으며, Max Kuhn에 의해서 만들어 졌다.


preparing, training, evaluating, and visualizing machine learning models and data 등을 지원 한다.


Journal of statistical software에 "Building predictive models in R using the caret package."을 보면 상세한 내용을 알 수 있다.


아래와 같이 사용하며, 단지 positive label이 무엇인지만 정의해 주면 된다. 

> library(caret)
필요한 패키지를 로딩중입니다: lattice
필요한 패키지를 로딩중입니다: ggplot2
> confusionMatrix(sms_test_pred, sms_raw_test$type, positive = "spam")
Confusion Matrix and Statistics

          Reference
Prediction  ham spam
      ham  1202   31
      spam    5  152
                                          
               Accuracy : 0.9741          
                 95% CI : (0.9643, 0.9818)
    No Information Rate : 0.8683          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8795          
 Mcnemar's Test P-Value : 3.091e-05       
                                          
            Sensitivity : 0.8306          
            Specificity : 0.9959          
         Pos Pred Value : 0.9682          
         Neg Pred Value : 0.9749          
             Prevalence : 0.1317          
         Detection Rate : 0.1094          
   Detection Prevalence : 0.1129          
      Balanced Accuracy : 0.9132          
                                          
       'Positive' Class : spam    


이제 일반적인 statistics를 알아보자.


■ The kappa statistic





■ Leave One Out Cross - Validation


cross validation 방법의 일종으로써


총 N개의 샘플을 생성 한다.

이때 N개는 해당 데이터 셋의 샘플들의 총 수를 말한다

즉, 100개의 데이터 샘플이 있다면 N은 100이다.


이에대해서 이제 N개의 모델을 생성 한다. 그리고 각각의 모델을 생성할때

하나의 샘플씩을 제외한다. 즉 N개의 모델은 N-1개의 샘플 수로 만들어지는 것이다.


그리고 각각의 모델에 대해서 그 제외한 샘플 1개를 이용해서 test를 수행한다.

그렇게 나온 test 결과 N개에대한 평균 값을 구하게 된다.



MSE가 측정 방법이라고 한다면 아래의 식과 같다.

$MSE_{ i }=(y_{ 2 }-\hat { y_{ 2 } } )^{ 2 }$


구하려고 하는 성능의 척도는 결국 아래와 같다.

$CV_{(n)}=\frac{1}{n}\sum_{i=1}^{n}{MSE_{i}}$



LOOCV의 장점은 결국 모든 샘플에 대해서 다 한번씩은 test하는 것이 되므로

어떠한 랜덤성도 존재하지 않게 된다. 따라서 굉장히 stable한 결과를 얻을 수 있다.


단점으로는 N개의 모델을 만들어서 N번 테스트 하게 되므로 computing time이 굉장히 오래 걸리게 된다.

또한 K-fold Cross validation에 비해서 bias의 비중이 높게 된다. 증 다양성을 포함하기 어렵게 된다.


아래는 작업을 수행한 코드이다.

# LOOCV 처리방법
library(ISLR)
glm.fit=glm(mpg~horsepower, data=Auto) #mpg를 horsepower에 대해서 linear regression을 힙니다.
library(boot) #cv.glm을 사용하기 위한 라이브러리입니다.
cv.err=cv.glm(Auto, glm.fit) #cv.glm함수를 통해 LOOCV를 시행합니다.
cv.err$delta #delta는 cross-validation 결과들을 담고 있습니다.

#linear model, polynomial model에 한해서 LOOCV에 간단한 형태의 공식을 사용할 수 있습니다.
loocv=function(fit){
    h=lm.influence(fit)$h
    mean((residuals(fit)/(1-h))^2)
}

loocv(glm.fit)

cv.error=rep(0,5)
degree=1:5 #1차부터 5차함수까지 fitting해 봅시다.
for(d in degree){
    glm.fit=glm(mpg~poly(horsepower,d), data=Auto) #poly함수를 이용해 d차 함수를 fit할 수 있습니다.
    cv.error[d]=loocv(glm.fit) #LOOCV 결과를 각 벡터에 저장합니다.
}
plot(degree,cv.error,type="b") #LOOCV 결과를 plot합니다.



5차원에서 가장 에러가 적은것을 알 수 있다.


자료출처: https://smlee729.github.io/r/machine%20learning/2015/03/19/1-loocv.html



















Naive Bayes with Caret package


이전 포스트에서 e1071 package로 구현 했던 것을 다시 caret package로 구현 한다.
caret package에는 많은 기능들이 포함되어 있으고 더 강력한것 같다.

Preliminary information

data set은 같은 것을 사용함.

  • 추가 package 사용 목록
  • caret package 사용
  • tm package 사용
  • pander package 사용: 출력을 보기좋게 표로 나타내줌.
  • doMC package: 멀티코어 병렬처리 가능. 하지만 내부적으로 Fork()를 이용해서 구현했기 때문에 Linux 환경에서만 동작 한다.
  • 몇가지 도우미 함수들 정의

Import Packages

# libraries needed by caret
library(klaR)
library(MASS)
# for the Naive Bayes modelling
library(caret)
# to process the text into a corpus
library(tm)
# to get nice looking tables
library(pander)
# to simplify selections
library(dplyr)

# doMC package use fork() internally. so, it it not working in Window OS.
#library(doMC)
#registerDoMC(cores=4)

# a utility function for % freq tables
frqtab <- function(x, caption) {
    round(100*prop.table(table(x)), 1)
}

# utility function to summarize model comparison results
sumpred <- function(cm) {
    summ <- list(TN=cm$table[1,1],  # true negatives
                 TP=cm$table[2,2],  # true positives
                 FN=cm$table[1,2],  # false negatives
                 FP=cm$table[2,1],  # false positives
                 acc=cm$overall["Accuracy"],  # accuracy
                 sens=cm$byClass["Sensitivity"],  # sensitivity
                 spec=cm$byClass["Specificity"])  # specificity
    lapply(summ, FUN=round, 2)
}

Reading and Preparing the data

다운로드 받아서 수행하는 것은 잘 안된다.

이전 POST의 e1071에 업로드한 csv 파일을 다운받아서 
데이터를 R object로 생성하는것은 똑같이 진행 한다.

# read the sms data into the sms data frame
sms_raw <- read.csv("sms_spam.csv", stringsAsFactors = FALSE)

sms_raw[1072,"text"] <- c("All done, all handed in. Don't know if mega shop in asda counts as celebration but thats what i'm doing!")
sms_raw[1072,"type"] <- c("ham")

# examine the structure of the sms data
str(sms_raw)

# convert spam/ham to factor.
sms_raw$type <- factor(sms_raw$type)

# examine the type variable more carefully
str(sms_raw$type)
table(sms_raw$type)

# build a corpus using the text mining (tm) package
library(tm)

# try to encode again to UTF-8
sms_raw$text <- iconv(enc2utf8(sms_raw$text),sub="byte")

sms_corpus <- Corpus(VectorSource(sms_raw$text))

# examine the sms corpus
print(sms_corpus)
inspect(sms_corpus[1:3])

Output

<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 3

[[1]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 49

[[2]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 23

[[3]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 43

Preparing the data

이전에 수행한 방식과 마찬가지로 data cleanup 및 transformation을 수행한다.
이때 dplyr 문법을 사용해서 작업을 수행 한다.
아래의 명령어를 수행하면 상용법을 알수 있다.

browseVignettes(package = "dplyr")
# ------------------------------------
# ------------------------------------
# We will proceed in similar fashion as described in the book, but make use of "dplyr"
# syntax to execute the text cleanup / transformation operations
sms_corpus <- Corpus(VectorSource(sms_raw$text))
sms_corpus_clean <- tm_map(sms_corpus, content_transformer(tolower))
sms_corpus_clean <- tm_map(sms_corpus_clean, removeNumbers)
sms_corpus_clean <- tm_map(sms_corpus_clean, removeWords, stopwords())
sms_corpus_clean <- tm_map(sms_corpus_clean, removePunctuation)
sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace)

sms_dtm <- DocumentTermMatrix(sms_corpus_clean)

Creating a Classification model with Naive Bayes

1. Generating the training and testing datasets

데이터를 분할하고
분할한 training set과 test set 모두에서 spam의 비율을 똑같이 설정 한다.
이전과 다른것은 Data 분할을 createDataPartition()을 이용해서 분할 한다는 것이다.

# -----------------------------------
# Creating a classificiation model with Naive Bayes
# Generating the training and testing datasets
train_index <- createDataPartition(sms_raw$type, p=0.75, list=FALSE)
sms_raw_train <- sms_raw[train_index,]
sms_raw_test <- sms_raw[-train_index,]
sms_corpus_clean_train <- sms_corpus_clean[train_index]
sms_corpus_clean_test <- sms_corpus_clean[-train_index]
sms_dtm_train <- sms_dtm[train_index,]
sms_dtm_test <- sms_dtm[-train_index,]

# check proportions in the testing and training sets 
ft_orig <- frqtab(sms_raw$type)
ft_train <- frqtab(sms_raw_train$type)
ft_test <- frqtab(sms_raw_test$type)
ft_df <- as.data.frame(cbind(ft_orig, ft_train, ft_test))
colnames(ft_df) <- c("Original", "Training set", "Test set")
pander(ft_df, style="rmarkdown",
       caption=paste0("Comparison of SMS type frequencies among datasets"))

|       |  Original  |  Training set  |  Test set  |
|:----------:|:----------:|:--------------:|:----------:|
|  **ham**   |    86.6    |      86.5      |    86.6    |
|  **spam**  |    13.4    |      13.5      |    13.4    |

Table: Comparison of SMS type frequencies among datasets

일정 이상 빈도인 것들만 추려내며
Absent 와 Present 이진데이터로 변경 한다.

# we will pick terms that appear at least 5 times in the training doucment term matrix.
sms_dict <- findFreqTerms(sms_dtm_train, lowfreq=5)
sms_train <- DocumentTermMatrix(sms_corpus_clean_train, list(dictionary=sms_dict))
sms_test <- DocumentTermMatrix(sms_corpus_clean_test, list(dictionary=sms_dict))
# modified sligtly fron the code in the book
convert_counts <- function(x) {
    x <- ifelse(x > 0, 1, 0)
    x <- factor(x, levels = c(0, 1), labels = c("Absent", "Present"))
}
sms_train <- sms_train %>% apply(MARGIN=2, FUN=convert_counts)
sms_test <- sms_test %>% apply(MARGIN=2, FUN=convert_counts)

2. Training the two prediction models

Naive Bayes 을 이용해서 기계학습을 수행 한다.
생성은 10-fold cross validation을 사용해서 한다.

그리고 두개의 모델을 각각 나눠서 수행 한다.
각각의 모델은 라플라스 추정자와 kernel density를 각각 적용한 것과 그렇지 않은것으로 구분 한다.

우선 Caret pakcage에서 Naive Bayes 모델에 대해서 지원하는 Control parameter의 종류는 아래와 같다.

> modelLookup("nb")
  model parameter              label forReg forClass probModel
1    nb        fL Laplace Correction  FALSE     TRUE      TRUE
2    nb usekernel  Distribution Type  FALSE     TRUE      TRU

위와 같이 modelLookup()명령어를 통해서 필요한 Machine Learning모델의 파라메터 지원의 모든 종류를 확인 할 수 있다.

아래는 라플라스 조정자를 적용하지 않은것이다. default 값을 의미한다.

ctrl <- trainControl(method="cv", 10)
set.seed(12358)
sms_model1 <- train(sms_train, sms_raw_train$type, method="nb", trControl=ctrl)
sms_model1

Naive Bayes 

4170 samples
1234 predictors
   2 classes: 'ham', 'spam' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 3753, 3753, 3754, 3753, 3753, 3753, ... 
Resampling results across tuning parameters:

  usekernel  Accuracy   Kappa      Accuracy SD  Kappa SD  
  FALSE      0.9798567  0.9098827  0.003606845  0.01680031
   TRUE      0.9798567  0.9098827  0.003606845  0.01680031

Tuning parameter 'fL' was held constant at a value of 0
Accuracy was used to select the optimal model using  the largest value.
The final values used for the model were fL = 0 and usekernel = FALSE.

아래는 라플라스 조정자를 1로 설정하고 kernel density는 적용하지 않았다.

set.seed(12358)
sms_model2 <- train(sms_train, sms_raw_train$type, method="nb", 
                    tuneGrid=data.frame(.fL=1, .usekernel=FALSE),
                    trControl=ctrl)

> sms_model2
Naive Bayes 

4170 samples
1234 predictors
   2 classes: 'ham', 'spam' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 3753, 3753, 3754, 3753, 3753, 3753, ... 
Resampling results

  Accuracy   Kappa      Accuracy SD  Kappa SD  
  0.9793765  0.9071502  0.004822776  0.02320774

Tuning parameter 'fL' was held constant at a value of 1
Tuning parameter 'usekernel' was held constant at a value
 of FALSE

Testing the predictions

두개의 모델을 confusionMatrix()을 이용해서 평가한다.
positive result의 목적은 message가 SPAM으로 prediction 됬을 때를 의미한다.

confusionMatrix()의 사용법은 positive label만 설정해주고 두개의 비교할 라벨 데이터만 넣어주면된다.

# Testing the predictions
sms_predict1 <- predict(sms_model1, sms_test)
cm1 <- confusionMatrix(sms_predict1, sms_raw_test$type, positive="spam")
cm1
Confusion Matrix and Statistics

          Reference
Prediction  ham spam
      ham  1199   25
      spam    4  161

               Accuracy : 0.9791         
                 95% CI : (0.9702, 0.986)
    No Information Rate : 0.8661         
    P-Value [Acc > NIR] : < 2.2e-16      

                  Kappa : 0.9055         
 Mcnemar's Test P-Value : 0.0002041      

            Sensitivity : 0.8656         
            Specificity : 0.9967         
         Pos Pred Value : 0.9758         
         Neg Pred Value : 0.9796         
             Prevalence : 0.1339         
         Detection Rate : 0.1159         
   Detection Prevalence : 0.1188         
      Balanced Accuracy : 0.9311         

       'Positive' Class : spam 

Laplace estimator 설정 kernel density를 적용하지 않은 모델의 경우

> sms_predict2 <- predict(sms_model2, sms_test)
There were 50 or more warnings (use warnings() to see the first 50)
> cm2 <- confusionMatrix(sms_predict2, sms_raw_test$type, positive="spam")
> cm2
Confusion Matrix and Statistics

          Reference
Prediction  ham spam
      ham  1200   27
      spam    3  159

               Accuracy : 0.9784          
                 95% CI : (0.9693, 0.9854)
    No Information Rate : 0.8661          
    P-Value [Acc > NIR] : < 2.2e-16       

                  Kappa : 0.9015          
 Mcnemar's Test P-Value : 2.679e-05       

            Sensitivity : 0.8548          
            Specificity : 0.9975          
         Pos Pred Value : 0.9815          
         Neg Pred Value : 0.9780          
             Prevalence : 0.1339          
         Detection Rate : 0.1145          
   Detection Prevalence : 0.1166          
      Balanced Accuracy : 0.9262          

       'Positive' Class : spam            

라플라스 추정자를 설정하지 않을경우 0 확률이 존재해서 데이터가 작다면 매우 부정확해 질 수 있다. 즉 어느 하나가 0%이기 때문에 다른것에 상관없이 무조건 0 probablity가 나와 버린다.

caret based Naive Bayes와 e1071 based Naive Bayes의 비교

TP, TN, FP, FN
accuracy
sensitivity (also known as recall or true positive rate)
specificity (also known as true negative rate)

> # from the table on page 115 of the book
> tn=1203
> tp=151
> fn=32
> fp=4
> book_example1 <- list(
+     TN=tn,
+     TP=tp,
+     FN=fn,
+     FP=fp,
+     acc=(tp + tn)/(tp + tn + fp + fn),
+     sens=tp/(tp + fn),
+     spec=tn/(tn + fp))
> 
> # from the table on page 116 of the book
> tn=1204
> tp=152
> fn=31
> fp=3
> book_example2 <- list(
+     TN=tn,
+     TP=tp,
+     FN=fn,
+     FP=fp,
+     acc=(tp + tn)/(tp + tn + fp + fn),
+     sens=tp/(tp + fn),
+     spec=tn/(tn + fp))
> 
> b1 <- lapply(book_example1, FUN=round, 2)
> b2 <- lapply(book_example2, FUN=round, 2)
> m1 <- sumpred(cm1)
> m2 <- sumpred(cm2)
> model_comp <- as.data.frame(rbind(b1, b2, m1, m2))
> rownames(model_comp) <- c("Book model 1", "Book model 2", "Caret model 1", "Caret model 2")
> pander(model_comp, style="rmarkdown", split.tables=Inf, keep.trailing.zeros=TRUE,
+        caption="Model results when comparing predictions and test set")


|                |  TN  |  TP  |  FN  |  FP  |  acc  |  sens  |  spec  |
|:-------------------:|:----:|:----:|:----:|:----:|:-----:|:------:|:------:|
|  **Book model 1**   | 1203 | 151  |  32  |  4   | 0.97  |  0.83  |   1    |
|  **Book model 2**   | 1204 | 152  |  31  |  3   | 0.98  |  0.83  |   1    |
|  **Caret model 1**  | 1199 | 161  |  25  |  4   | 0.98  |  0.87  |   1    |
|  **Caret model 2**  | 1200 | 159  |  27  |  3   | 0.98  |  0.85  |   1    |

Table: Model results when comparing predictions and test set

비교결과 accuracy에 대해서는 e1071와 caret 두 package는 별다른 차이가 없다.
하지만, sensitivity 측면에서는 차이가 있는 것을 알 수 있다.

추가내용 참조


Naive Bayes Classification with R


Bayesian rule에 대해서는 이전 theorem post를 참조 한다.

Bayes algorithm을 이용해서 mobile phone spam을 filtering하는 방법

Short Message Service (SMS)를 구분 하는 작업을 수행 한다.



Step 1 - collecting data


naive bayes classifer를 개발하기 위해서 SMS Spamcollection으로 부터 알고리즘을 적용 한다.

http://www.dt.fee.unicamp.br/~tiago/smsspamcollection/


이 스팸 데이터의 포맷은 아래와 같다.



spam 라벨은 junk message를 의미하고, ham 라벨은 legitimate message를 의미한다.


ham massage의 예:

  • Better. Made up for Friday and stuffed myself like a pig yesterday. Now I feel bleh. But at least its not writhing pain kind of bleh.
  • If he started searching he will get job in few days. He have great potential and talent.
  • I got another job! The one at the hospital doing data analysis or something, starts on monday! Not sure when my thesis will got finished


spam message의 예:

  • Congratulations ur awarded 500 of CD vouchers or 125gift guaranteed & Free entry 2 100 wkly draw txt MUSIC to 87066
  • December only! Had your mobile 11mths+? You are entitled to update to the latest colour camera mobile for Free! Call The Mobile Update Co FREE on 08002986906
  • Valentines Day Special! Win over £1000 in our quiz and take your partner on thetrip of a lifetime! Send GO to 83600 now. 150p/msg rcvd.
ham과 spam 메시지와의 차이는 우선 위의 메시지만을 고려해 볼 때,
세개 중 두개의 spam은 "free"라는 메시지를 사용 했다. 

이와 다르게, ham message의 경우 2개의 메시지들은 각각 상세한 날짜를 언급 하고 있다는 점이 다르다.

이러한 데이터에 naive bayes classifier를 적용할 경우, 위에서 언급한 단어의 발생 빈도를 이용해서 SMS message가 spam 인지 ham인지를 구분하게 된다.

단순히 free가 들어간 메시지를 spam으로 처리하는 것은 문제가 될 수 있다. 
왜냐하면, 같은 free라도
"are you free on Sunday?" 는 ham 이고
"free ringtones."는 spam 이다.
따라서 제안된 classifier는 확률을 이용해서 전체 메시지의 단어 빈도를 분석해서 spam 인지 아닌지를 결정 해야 한다.


Step 2 - exploring and preparing the data


text 파일을 처리가 어려움으로 csv 파일로 변경된 것을 사용 한다.

sms_spam.csv

#데이터 읽기
sms_raw <- read.csv("sms_spam.csv",stringsAsFactors =  FALSE)
#구조 분석 파악
> str(sms_raw)
'data.frame':	5559 obs. of  2 variables:
 $ type: chr  "ham" "ham" "ham" "spam" ...
 $ text: chr  "Hope you are having a good week. Just checking in" "K..give back my thanks." "Am also doing in cbe only. But have to pay." "complimentary 4 STAR Ibiza Holiday or 짙10,000 cash needs your URGENT collection. 09066364349 NOW from Landline not to lose out"| __truncated__ ...
#데이터 타입 분석
> class(sms_raw)
[1] "data.frame"

feature는 type과 text 두개로 구성되었다.
data frame의 구조를 가지며, 5559개의 열로 구성 되었다.

그리고 text feature에 SMS message의 raw 데이터가 기록 되어 있다.


현재 type은 stringAsFactors =  FALSE 로 설정 했기 때문에 factor가 아니라, character vector로 구성 되어 있다.
하지만, 이 type은 categorical variable 이기 때문에 factor로 변환 하는것이 좋다.
※ 처음 부터 factor로 만들지 않은 것은 text는 factor 타입보다는 character vector에 더 적합하기 때문이다.
#변경을 수행 한다.
> sms_raw$type <- factor(sms_raw$type)
#변경 결과를 확인하면 정상적으로 Factor가 된것을 알 수 있다.
> str(sms_raw)
'data.frame':	5559 obs. of  2 variables:
 $ type: Factor w/ 3 levels "All done, all handed in. Don't know if mega shop in asda counts as celebration but thats what i'm doing!",..: 2 2 2 3 3 2 2 2 3 2 ...
 $ text: chr  "Hope you are having a good week. Just checking in" "K..give back my thanks." "Am also doing in cbe only. But have to pay." "complimentary 4 STAR Ibiza Holiday or 짙10,000 cash needs your URGENT collection. 09066364349 NOW from Landline not to lose out"| __truncated__ ...
이상하게도 기본적으로 제공되는 csv 파일을 가지고 작업을 수행할 경우 문제가 발생한다.
따라서 아래의 코드 작업을 수행 해야 한다.
# 부족한 데이터를 삽입한다.
sms_raw[1072,"type"] <- factor("ham")
sms_raw[1072,"text"] <- c("All done, all handed in. Don't know if mega shop in asda counts as celebration but thats what i'm doing!")


부족한 데이터 삽입전에는 아래와 같이 요인이 3개가 된다. 하나가 요인 값이랑 text 값이 잘못 되어 있기 때문이다.

> table(sms_raw$type)
All done, all handed in. Don't know if mega shop in asda counts as celebration but thats what i'm doing! 
                                                                                                       1 
                                                                                                     ham 
                                                                                                    4811 
                                                                                                    spam 
                                                                                                     747

잘못된 데이터를 수정 하면 아래와 같이 바뀐다.

> table(sms_raw$type)
 ham spam 
4812  747 


■ Data preparation - processing text data for analysis


Document Classification은 주어진 문서를 하나 이상의 분류로 구분하는 문제이다.

지금 다루는 스팸 인지 아닌지를 구분하는 것이 문서 분류의 가장 흔한 예라고 할 수 있다.

또 다른 예로는 제품 리뷰 글을 보고 해당 리뷰가 제품에 대한 긍정적인 리뷰인지 부정적인 리뷰인지를 구분하는 

감성 분석 (Sentiment Analysis)이 있다.


text 처리를 위해서 특화된 package인 Text Mining (tm)의 사용에 대해서 다루 겠다.

TM은 Ingo Feinerer에 의해서 Vienna University에서 박사학위 논문의 일환으로 만들어진 Package 이다.

전공은 Economics and Business이다. 해당 package에 대해서 좀 더 깊이 있는 학습을 원할경우 아래의 사이트를 참조 하자.

http://tm.r-forge.r-project.org/


install.packages("tm")

library(tm)


우선 corpus function을 이용해서 text 데이터를 오브젝트로 생성 한다.


포멧팅 오류를 막기 위해서, corpus 실행전에 UTF-8 en-coding을 수행 하자.

그다음 corpus 함수를 수행 한다.

아래와 같이 corpus 함수는 text를 정의하고 있는 파라메터를 입력 받는다. 이를 위해서 text를 일련의 vector로 만들었었다. 이것을 넘겨줄때 VectorSource()를 이용해서 vector임을 corpus에게 알려주게 된다.

최종적으로 corpus는 sms_corpus라고 이름 지어진 Object를 반환하게 된다. 이 Object를 이용해서 이제 Text Data 처리를 쉽게 할 수 있다.

# try to encode again to UTF-8
sms_raw$text <- iconv(enc2utf8(sms_raw$text),sub="byte")
sms_corpus <- Corpus(VectorSource(sms_raw$text))
# examine the sms corpus
print(sms_corpus)
inspect(sms_corpus[1:3])
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 3
[[1]]
Metadata:  7
Content:  chars: 49
[[2]]
Metadata:  7
Content:  chars: 23
[[3]]
Metadata:  7
Content:  chars: 43


Corpus는 정말로 유연한 문서 읽기 라이브러리이다. 

PDF나 Microsoft Word 문서 같은 것들을 읽을 수 있다. 좀 더 조사하고 싶다면, Data Input section에 대해서

tm package의 vignette를 이용해서 관련 문서를 확인하다.

command: print(vignette("tm"))


본격적으로 text를 분할해서 단어를 분석하기 전에 기본적으로 

데이터를 클린하는 것이 필요하다.

즉, punctuation과 의미 없는 단어들을 말한다.


각각의 단계는 아래와 같다.

# clean up the corpus using tm_map()
# 소문자로 모두 변경 한다.
corpus_clean <- tm_map(sms_corpus, content_transformer(tolower))
# 숫자를 모두 제거 한다.
corpus_clean <- tm_map(corpus_clean, removeNumbers)
# stop words로 알려진, "to", "and", "but", "or"를 모두 제거 한다.
# 이것을 위해서 stop word를 새로 정의하지 않고, 알려진 리스트인 stopword()를 이용 한다.
corpus_clean <- tm_map(corpus_clean, removeWords, stopwords())
# punctuation을 제거 한다.
corpus_clean <- tm_map(corpus_clean, removePunctuation)
# single space로 모두 만든다. 즉, whitespace(여백)을 모두 제거 한다.
corpus_clean <- tm_map(corpus_clean, stripWhitespace)


각각 변경된 내부 구조를 살펴 보면 아래와 같다.

> lapply(sms_corpus[1:3],as.character)
$`1`
[1] "Hope you are having a good week. Just checking in"
$`2`
[1] "K..give back my thanks."
$`3`
[1] "Am also doing in cbe only. But have to pay."
> lapply(corpus_clean[1:3],as.character)
$`1`
[1] "hope good week just checking "
$`2`
[1] "kgive back thanks"
$`3`
[1] " also cbe pay"


data pre-processing의 마지막 단계는 tokenization 단계이다.

해당 단계에서는 각각의 word를 하나의 컴포넌트로 구분하게 된다.


DocumentTermMatrix() 함수를 이용해서 sparse matrix를 구성 한다.

기본적으로 그냥 생성하면 SMS message를 나타내는 것은 5,559개 이며, term을 나타내는 것은 7,000개 이다.

> sms_dtm <- DocumentTermMatrix(corpus_clean)
> sms_dtm
<>
Non-/sparse entries: 42635/44407129
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)

이제 sparse matrix을 생성 했으므로 word frequency를 수행 할 수 있다.



■ Data preparation - creating training and test datasets 

트레이닝과 테스트로 데이터를 나눠서 나중에 평가를 위해서도 사용 할 수 있도록 한다.


75 vs 25로 분할한다. 그리고 이미 임의로 섞여 있기 때문에 그냥 나누면 된다.


raw data / document-term matrix / corpus 이렇게 세가지 종류의 데이터 셋을 모두 분할 한다.

# creating training and test datasets
sms_raw_train <- sms_raw[1:4169, ]
sms_raw_test  <- sms_raw[4170:5559, ]

sms_dtm_train <- sms_dtm[1:4169, ]
sms_dtm_test  <- sms_dtm[4170:5559, ]

sms_corpus_train <- corpus_clean[1:4169]
sms_corpus_test  <- corpus_clean[4170:5559]

비율이 정상적으로 분할 되었는지는 아래의 명령어를 통해서 간단히 알 수 있다.

> # check that the proportion of spam is similar
> prop.table(table(sms_raw_train$type))
      ham      spam 
0.8647158 0.1352842 
> prop.table(table(sms_raw_test$type))
      ham      spam 
0.8683453 0.1316547 


■ Visualizing text data - word clouds

word cloud는 각 단어의 빈도를 text data로 묘사하게 된다.

여기서 보여지는 단어들의 배치는 랜덤이며 그 글자 크기가 빈도를 나타낸다.

이것을 통해서 경향을 파악할 수 있기 때문에 word cloud는 많이 사용 된다.


spam과 ham을 구분하는 예제에서는 word cloud를 그려 봄으로서 생성하려는 classifier가 성공적으로 동작 할지 안할지를

가늠 할 수 있게 된다.

word cloud는 Ian Fellows가 전문 통계학자로 UCLA에 있을때 만들어 진것이다.

http://cran.r-project.org/web/packages/wordcloud/index.html 에서 더 자세한 정보를 확인 할 수 있다.


install.packages("wordcloud")
library(wordcloud)
wordcloud(sms_corpus_train, min.freq = 30, random.order = FALSE)



random.order=FALSE 면, 높은 빈도일 수록 가운데에 집중 되는 경향을 보인다.

min.freq의 의미는 최소 빈도를 의미한다. 이것을 넘어야 cloud에서 보여지게 된다.

만약 word를 figure로 보지 못한다는 메시지를 본다면, min.freq를 조정 하면 된다.


이것을 spam과 ham에 대해서 각각 나눠서 수행 해 보자.

# subset the training data into spam and ham groups
spam <- subset(sms_raw_train, type == "spam")
ham  <- subset(sms_raw_train, type == "ham")
wordcloud(spam$text, max.words = 40, scale = c(3, 0.5))
wordcloud(ham$text, max.words = 40, scale = c(3, 0.5))



  


오른쪽이 스팸이고, 왼쪽이 Ham이다.

Spam의 경우 urgent, free, mobile, call, claim, stop 같은 것들이 있는것을 알 수 있다.

Ham의 경우 can, sorry, need, time이 나오는 것을 알 수 있다.



■ Data preparation - creating indicator features for frequent words

데이터 가공의 마지막 단계는 이러한 sparse matrix을 naive Bayes classifier가 처리할 수있는 데이터 타입으로 변형해 주는 것이다.

sparse matrix은 7000개의 feature들을 가지고 있다. 하지만 모든 feature들이 유용하지 않다는 것은 당연한 사실 이다.

따라서 이러한 feature들을 줄이는 작업을 수행 한다.


tm package에 있는 findFreqTerms() 함수를 이용 한다.

findFreqTerms(sms_dtm_train, 5)

위 command의 뜻은 최소 matrix으로 부터 최소 5번 이상 발생한 word로 구성된 character vector를 만들라는 명령어 이다.


이제 이러한 frequent word로만 구성 시켜서 matrix을 만들기 위해서 다시 DocumentTermMatrix을 수행 한다.

# indicator features for frequent words
sms_dict<- findFreqTerms(sms_dtm_train, 5)
sms_train <- DocumentTermMatrix(sms_corpus_train, list(dictionary = sms_dict))
sms_test  <- DocumentTermMatrix(sms_corpus_test, list(dictionary = sms_dict))

이렇게 수행 하면 최종적으로 7000개의 feature들은 1200개 수준으로 줄어 들게 된다.


naive Bayes classifier는 기본적으로 categorical feature들에 대해서만 학습을 수행 한다.

따라서 지금의 구조는 셀이 빈도를 나타내는 numerical type으로 표현 되므로 이것을 factor variable로 변경 해야 한다.

여기서는 "Yes", "No"와 같은 간단한 두 종류의 factor로 구분한다.

# convert counts to a factor
convert_counts <- function(x) {
  x <- ifelse(x > 0, 1, 0)
  x <- factor(x, levels = c(0, 1), labels = c("No", "Yes"))
}
# apply() convert_counts() to columns of train/test data
sms_train <- apply(sms_train, MARGIN = 2, convert_counts)
sms_test  <- apply(sms_test, MARGIN = 2, convert_counts)

apply를 이용해서 처리한다. margin=2의 의미는 column을 의미한다.

더 자세한 사항은 이전 포스트를 참조하자.




Step 3 - training a model on the data


이제 naive Bayes algorithm을 적용할 때가 왔다.

Machine Learning의 대부분의 시간은 지금과 같이 data 가공에 소모된다. 데이터만 가공된다면 적용하는것은 일도 아니다.

Naive Bayes algorithm의 원리는 이전 포스트의 "Probabilistic Learning - Classification using Naive Bayes

"를 참조 하자.


naive Bayes를 최적으로 구현하는 것은 쉬운 일이 아니므로, 이미 구현된 library를 사용하자.

"e1071" package에는 다양한 Machine Learning algorithm들이 구현되어 있다.

Vienna University of Technology (TU Wien)의 Statistics 학과에서 개발한 package이다.


install.packages("e1071")

library(e1071)


설치가 실패할 경우, 해당 package는 바이너리로 제공이 안되기 때문이다. 수작업으로 설치해 준다.

.libPaths() # 라이브러리 설치 경로 확인

https://cran.r-project.org/web/packages/e1071/index.html에서

Windows binaries항목의 zip파일을 다운 받은 다음 라이브러리가 저장된 폴더에 복사 한다.


사실 다른 package들도 많이 있다.

klaR 또한 naiveBayes를 지원 한다. 어느것을 쓰던 상관없으니 마음대로 찾아서 쓰면된다. 


Naive Bayes는 Learning과 Prediction을 구분해서 수행되는 알고리즘이다.

즉, eager learning 방식이다. 특별한 Model을 생성하고 prediction 하게 된다. 별도의 트레이닝 실행 시간을 필요로 하지만

prediction에 걸리는 시간은 트레이닝을 통해서 생성된 model에 기반하므로 Lazy Learning 방식 (KNN)에 비해서 빠르게 된다.


Building the classifier

m <- naiveBayes (train, class, laplace = 0)

  • train은 data frame 또는 matrix가 가능하다. training data를 포함하고 있다.
  • class는 factor vector 값이다. 각 행에서 class 값을 가지고 있는 것이다.
  • laplace 는 Laplace Estimator 사용을 위한 최소 값을 의미한다. Default 는 0이다.

해당 함수는 prediction을 위한 bayes model을 반환 한다.


Making Prediction

p <- predict (m, test, type = "class")

  • m은 model이다. naiveBayes()에 의해서 학습 되어진
  • test는 data frame 또는 matrix으로 test data set을 포함하고 있다. 당연히 training과 같은 형태와 수의 feature를 가지고 있어야 한다.
  • type은 "class" 또는 "raw"로 설정 할 수 있다. class는 가장 그럴것 같은 class로 예측해 주는것이고, raw는 확률을 반환해 주게 된다.

해당 함수는 type argument에 의해서 예측되어진 class 또는 확률의 vector를 반환 한다.

## Step 3: Training a model on the data ----
library(e1071)
sms_classifier <- naiveBayes(sms_train, sms_raw_train$type)
sms_classifier
$tables$yesterday
                  yesterday
sms_raw_train$type          No         Yes
              ham  0.995284327 0.004715673
              spam 0.996453901 0.003546099


데이터를 출력해서 보면 각각의 feature들에 대한 확률이 모두 계산되어진 것을 알 수 있다.

위와 같이 각 feature에 대한 조건 확률 들을 모두 계산해 두면 추후에 어떤 message에 대한 spam과 ham의 확률을 쉽게 구할 수 있다.

이것을 통해서 prediction을 즉각적으로 수행 하게 된다.




Step 4 - evaluating model performance 


테스팅을 위해서는 training에 사용하지 않는 message를 가지고 예측을 수행 하면 된다.


평가를 위해서는 이것저것 이전에 생성한 데이터 구조들을 사용 하게 된다.


sms_test 에는 보지 않은 데이터들의 feature들이 Yes와 No로써 저장되어 있다.

sms_raw_test에는 각 message가 "ham"인지 "spam"인지에 대한 참(true) 값이 저장 되어 있다.


학습을 통해서 생성한 classifier는 sms_classifier 이다. 그런 다음 생성한 prediction을 이용해서 true value와 비교하는 작업을 수행 한다.


prediction을 위해서 predict() 함수가 사용 되어 진다. 그리고 이러한 결과는 sms_test_pred vector에 저장 된다.

## Step 4: Evaluating model performance ----
sms_test_pred <- predict(sms_classifier, sms_test)

검증은 confusion matrix을 만들어서 수행 한다.

이를 위해서 CrossTable() 함수를 사용 한다.

> library(gmodels)
> CrossTable(sms_test_pred, sms_raw_test$type,
+            prop.chisq = FALSE, prop.t = FALSE, prop.r = FALSE,
+            dnn = c('predicted', 'actual'))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1390 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1202 |        31 |      1233 | 
             |     0.996 |     0.169 |           | 
-------------|-----------|-----------|-----------|
        spam |         5 |       152 |       157 | 
             |     0.004 |     0.831 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|


위 confusion matrix을 통해서 괭장히 잘 prediction된 것을 알 수 있다.

오직 5개의 message만이 incorrectly 분류되었다 spam으로 이것은 전체 비중에서 0.4% 수준인 것이다.

31개 정도만이 그리고 spam인데 ham으로 prediction 되었다.

project에 투자한 노력에 비해서 상당히 우수한 prediction 실험 결과를 얻어낸 것을 알 수 있다.


하지만 문제는, 단 5개의 message만이 원래 ham인데 spam으로 분류 되었다 할지라도 이것은 심각한 문제를 야기할 수 있다.

즉 spam인데 ham으로 분류되는것은 단순히 불편함을 주지만, 중요한 메시지를 spam으로 분류해서 문제를 발생 시킨다면

그것이 99%의 정확도를 가질지라도 당장 해당 제품은 abandon 되어진다.

즉, False Negative 보다는 절대적으로 False Positive를 줄어야 하는 것이다.



Step 5 - improving model performance


오버피팅 문제를 야기할 수도 있다. 지금까지 생성한 모델은

왜냐하면 모델을 생성하기 위해서 사용한 트레이닝 데이터 셋에 "ringtone"이라는 feature가 오직 spam에서만 존재한다면

해당 경우에는 100% 확률로 spam으로 예측 된다.

의심할 여지 없이 문제가 있는 모델이다. 해당 트레이닝 데이터에서 최고의 정확도를 자랑하지만, 트레이닝 데이터가 현실의 모든 데이터를 반영하지 못하기 때문에 그것이 문제가 있다는 것은 직관적으로 알 수 있다.


laplace = 1로 설정해서 수행해 보자.

## Step 5: Improving model performance ----
sms_classifier2 <- naiveBayes(sms_train, sms_raw_train$type, laplace = 1)
sms_test_pred2 <- predict(sms_classifier2, sms_test)
CrossTable(sms_test_pred2, sms_raw_test$type,
           prop.chisq = FALSE, prop.t = FALSE, prop.r = FALSE,
           dnn = c('predicted', 'actual'))
   Cell Contents
|-------------------------|
|                       N |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1390 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1203 |        31 |      1234 | 
             |     0.997 |     0.169 |           | 
-------------|-----------|-----------|-----------|
        spam |         4 |       152 |       156 | 
             |     0.003 |     0.831 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|


실험 결과 false positive가 5개에서 4개로 1개 줄어든 것을 알 수 있다.

비록 적은 향상이지만 이러한 false positive는 spam filtering product가 use 될지 abandon될지를 결정할 중요한 factor이므로 무시할 수는 없다.

그리고 여전히 false positive로 분류된 4개의 message에 대해서도 분석을 해봐야 할 것이다.








'AI > Machine Learning with R' 카테고리의 다른 글

Ensemble method: Bagging (bootstrap aggregating)  (0) 2015.11.19
Bootstrapping  (0) 2015.11.19
Principal Component Analysis (PCA)  (0) 2015.11.18
Evaluating Model Performance with R  (2) 2015.11.12
Naive Bayes with Caret package (R)  (0) 2015.11.12

+ Recent posts