개요(Abstraction)
UCI 저장소에 있는 반도체 생산 공정 데이터( Data from a semi-conductor manufacturing process)를 이용한다. 이 데이터는 자유롭게 사용 가능하다. 데이터의 상세한 설명은 저장소 누리집에세 참조 가능하다. 이 데이터의 특징은 노이즈가 포함된 많은 변수를 가지고 있고, 결측 데이터(NA)를 포함한다. 이 데이터를 통해 높은 예측력을 가지는 모델을 구축하고, 각 모델에서 제공하는 도구를 이용하여 변수의 중요도를 파악한다.
1 RPART(Recursive PARTitioning)
결측값을 처리할 수 있으며, 이 특징은 데이터 전처리 작업 없이 원본 그대로 학습을 시킬 수 있다. 이 작업은 대체 변수(Surrogate variables)를 통해 이루어 진다, 즉 다른 독립 변수를 사용하여 결측된 데이터를 추정하는 것이다.
데이터 적재
UCI 저장소 저장소에서 데이터를 가져오고 데이터 특징을 단순하게 탐색한다. 모든 변수는 숫자형 데이터이며, 또한 모두 결측값(Missing Value)를 가지고 있다.
# 데이터 가져오기 # Data sc.data <- read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/secom/secom.data", header = F) # 표기 sc.label <- read.table("http://archive.ics.uci.edu/ml/machine-learning-databases/secom/secom_labels.data", header = F, stringsAsFactors = F) dim(sc.data) # [1] 1567 590 dim(sc.label) # [1] 1567 2 # 데이터 요약 정보 보기 # 변수가 많은 경우 summary를 실행하면 데이터 특징을 보기가 힘들다 summary(sc.data[, 1:5]) # V1 V2 V3 V4 V5 # Min. :2743 Min. :2159 Min. :2061 Min. : 0 Min. : 0.6815 # 1st Qu.:2966 1st Qu.:2452 1st Qu.:2181 1st Qu.:1082 1st Qu.: 1.0177 # Median :3011 Median :2499 Median :2201 Median :1285 Median : 1.3168 # Mean :3014 Mean :2496 Mean :2201 Mean :1396 Mean : 4.1970 # 3rd Qu.:3057 3rd Qu.:2539 3rd Qu.:2218 3rd Qu.:1591 3rd Qu.: 1.5257 # Max. :3356 Max. :2846 Max. :2315 Max. :3715 Max. :1114.5366 # NA's :6 NA's :7 NA's :14 NA's :14 NA's :14 # summary 결과를 Transpose 시킴 head(do.call(rbind, unclass(sapply(sc.data, summary)))) # Min. 1st Qu. Median Mean 3rd Qu. Max. NA's # V1 2743.0000 2966.000 3011.000 3014.000 3057.000 3356 6 # V2 2159.0000 2452.000 2499.000 2496.000 2539.000 2846 7 # V3 2061.0000 2181.000 2201.000 2201.000 2218.000 2315 14 # V4 0.0000 1082.000 1285.000 1396.000 1591.000 3715 14 # V5 0.6815 1.018 1.317 4.197 1.526 1115 14 # V6 100.0000 100.000 100.000 100.000 100.000 100 14 # ......
모델 생성
먼저 기본 설정으로 모델을 훈련시켜 적합을 개략적으로 살펴 보아야 한다.
# 우선 표본 생성 작업을 수행 sc.sample <- cbind(sc.data, LABELS = sc.label$V1) # 펙터로 변환 sc.sample$LABELS <- factor(sc.smaple$LABELS) # -이면 성공, 1이면 실패 공정을 나타냄 table(sc.sample$LABELS) # -1 1 # 1463 104 library(caret) # 훈련 데이터를 생성한다. # 5개의 훈련 데이털를 생성하고, 전체 75%를 훈련 데이터로 사용한다 set.seed(8000) sc.sample.train <- createDataPartition(sc.sample$LABELS, times = 5, p = 0.75) # 첫번째 표본을 가지고 훈련 집합과 검증 집함 생성 fit.data.1.train <- sc.sample[sc.sample.train$Resample1, ] fit.data.1.test <- sc.sample[-sc.sample.train$Resample1, ] fit.rpart <- rpart(LABELS ~ ., data = fit.data.1.train) # 모델의 적합을 확인, 상세한 내용은 비네트 문서 참조 summary(fit.rpart) # 예측력 확인 set.seed(8000) pred.data.1 <- predict(fit.rpart, newdata = fit.data.1.test, type = "class") # 오류율을 확인한다 table(OBSERV = fit.data.1.test$LABELS, PRED = pred.data.1) # PRED # OBSERV -1 1 # -1 362 3 # 1 26 0 # 오류율 0.0741688를 가진다
모델의 변수(파라메터) 초적화 작업을 수행하지 않아도 상당히 정확한 변수가 출력된다. 과적합 등 적합 특성을 파악하려면 비네트 문서를 참조해서 세심하게 보아야 한다.
모델 변수 최적화
rpart의 control 파라메터를 사용하여 최적화 작업을 수행한다. 일반적으로 rpart.control 함수의 결과. 최적 값을 찾는 특별한 규칙은 없다. 우선 기본값으로 모델을 훈련하고, 다음은 기반값의 1/2, 그다음 기본값의 2배로 실행하면서, 모델 적합 결과를 보면서 조정한다. rpart는 기본값만으로도 데이터에 상당히 잘 적합한다. 이 경우에 control 값을 변경하면서 최적화 작업을 수행하면 더 나빠지지 좋아지는 경우는 거의 없었다. 아래는 최적화 작업을 수행하기 위한 단순한 예제이다, rpart.control 변수 설명은 비네트 문서를 참조해야 한다:
ctrl <- rpart.control(minsplit = 20, minbucket = 8, cp = 0.01, maxsurrogate = 5, xval = 10) fit.rpart.2 <- rpart(LABELS ~ ., data = fit.data.1.train, control = ctrl) pred.data.2 <- predict(fit.rpart.2, newdata = fit.data.1.test, type = "class") table(OBSERV = fit.data.1.test$LABELS, PRED = pred.data.2)
upSample
구분이 균일하지 않으면 모델이 편향이 발생한다. caret 패키지를 통해 구분(class)를 균일하게 하고, 모델을 학습시킨다. 그러면 실패(labels = 1, 오류) 예측 정확도가 향싱됨을 알 수 있다. 우리가 공정에서 관심있는 사항은 성공을 올바르게 예측하는 것 보다는 실패에 대한 예측에 더 많은 관심을 가진다.
library(caret) sc.sample.up <- upSample(x = sc.sample[, -ncol(sc.sample)], y = sc.sample$LABELS) table(sc.sample.up$Class) # -1 1 # 1463 1463 # 구분 크기를 같게 만든다. set.seed(8000) sc.sample.up.train <- createDataPartition(sc.sample.up$Class, times = 5, p = 0.5) fit.data.up.1.train <- sc.sample.up[sc.sample.up.train$Resample1, ] fit.data.up.1.test <- sc.sample.up[-sc.sample.up.train$Resample1, ] fit.rpart.up <- rpart(Class ~ ., data = fit.data.up.1.train) pred.data.up.1 <- predict(fit.rpart.up, newdata = fit.data.up.1.test, type = "class") # 오류율을 확인한다 table(OBSERV = fit.data.up.1.test$Class, PRED = pred.data.up.1) # PRED # OBSERV -1 1 # -1 558 173 # 1 37 694 # 이전에 구분이 불균형한 검증 집합에서 실행해 본다. pred.data.1 <- predict(fit.rpart.up, newdata = fit.data.1.test, type = "class") table(OBSERV = fit.data.1.test$LABELS, PRED = pred.data.1) # PRED # OBSERV -1 1 # -1 295 70 # 1 3 23
최적화 작업을 수행한다. 트리간 경쟁을 줄이기 위해 minsplit와 minbucket을 낮추었고, 복잡도 변수(cp)를 0.1 배 더 낮추어 실행했다. 과적합 문제를 제외하면 가장 정확도가 높은 모델이 생성된다.
ctrl <- rpart.control(minsplit = 3, minbucket = 1, cp = 0.001, xval = 10) fit.rpart.up.2 <- rpart(Class ~ ., data = fit.data.up.1.train, control = ctrl) pred.data.2 <- predict(fit.rpart.up.2, newdata = fit.data.up.1.test, type = "class") table(OBSERV = fit.data.up.1.test$Class, PRED = pred.data.2) # PRED # OBSERV -1 1 # -1 642 89 # 1 0 731
아래는 가공하지 않은 원시 데이터에서 생성된 검증 집합을 가지고 예측력이 얼마나 변했는지 확인해 본다
pred.data.1 <- predict(fit.rpart.up.2, newdata = fit.data.1.test, type = "class") (tb <- table(OBSERV = fit.data.1.test$LABELS, PRED = pred.data.1)) # PRED # OBSERV -1 1 # -1 346 19 # 1 0 26 # 오류율 1-(sum(diag(tb))/sum(tb)) # 0.04859335
결과적으로 모델 예측의 정확도를 높이려면 구분이 균일하게 전처리 작업을 수행한 후 모델을 생성하고 그리고 약간의 초적화 작업이 필요하다.
2 adabag
예측 정확도를 더 향상시키기 위해 R에서 대표적인 앙상블(ensemble)학습 알고리즘인 부스팅(Boosting)과 배깅(bagging)을 사용하는 패키지이다. 예측의 정확도가 얼마나 향상되는지 보고, adabag 패키지에서 제공하는 기본 기능에 대해 알아본다. 부스팅(Boosting)과 배깅(bagging)의 목적은 무작위 추측 보다 약간 더 나은 단일 분류기들을 결합하여 분류기의 정밀도를 향상시키는 것이다. 여기서는 정확도가 약간 더 높은 부스팅을 사용한다.
모델 생성
데이터는 앞에서 생성한 업-표본을 사용한다.
# 기본 조건으로 수행 library("adabag") fit.adaboost.1 <- boosting(Class ~ ., data = fit.data.up.1.train) pred.adaboost.1 <- predict(fit.adaboost.1, newdata = fit.data.up.1.test, type = "class") pred.adaboost.1$confusion # Observed Class # Predicted Class -1 1 # -1 730 5 # 1 1 726 # 예측 오류 pred.adaboost.1$error # 0.004103967
최적화 작업을 수행해 본다.
# 노드간 경쟁은 기본 설정, 트리 갯수는 증가, 복잡도 파라메터 낮춤 ctrl <- rpart.control(minsplit = 10, minbucket = 3, cp = 0.001, xval = 10) fit.adaboost.2 <- boosting(Class ~ ., data = fit.data.up.1.train, control = ctrl, mfinal = 500) pred.adaboost.2 <- predict(fit.adaboost.2, newdata = fit.data.up.1.test, type = "class") pred.adaboost.2$confusion # Observed Class # Predicted Class -1 1 # -1 731 5 # 1 0 726 pred.adaboost.2$error # [1] 0.003419973 # 노드간 경쟁은 기본 설정, 트리 갯수는 기본, 복잡도 파라메터 낮춤 ctrl <- rpart.control(minsplit = 10, minbucket = 3, cp = 0.001, xval = 10) fit.adaboost.2 <- boosting(Class ~ ., data = fit.data.up.1.train, control = ctrl, mfinal = 100) pred.adaboost.2 <- predict(fit.adaboost.2, newdata = fit.data.up.1.test, type = "class") pred.adaboost.2$confusion # Observed Class # Predicted Class -1 1 # -1 729 5 # 1 2 726 pred.adaboost.2$error # [1] 0.004787962 # 노드간 경쟁을 심화시키고, 트리 갯수는 기본, 복잡도 파라메터 기본 ctrl <- rpart.control(minsplit = 40, minbucket = 10, cp = 0.01, xval = 10) fit.adaboost.2 <- boosting(Class ~ ., data = fit.data.up.1.train, control = ctrl, mfinal = 100) pred.adaboost.2 <- predict(fit.adaboost.2, newdata = fit.data.up.1.test, type = "class") pred.adaboost.2$confusion # Observed Class # Predicted Class -1 1 # -1 726 5 # 1 5 726 pred.adaboost.2$error # [1] 0.006839945 # 노드간 경쟁을 심화시키고, 트리 갯수는 증가, 복잡도 파라메터 기본 ctrl <- rpart.control(minsplit = 40, minbucket = 10, cp = 0.01, xval = 10) fit.adaboost.2 <- boosting(Class ~ ., data = fit.data.up.1.train, control = ctrl, mfinal = 500) pred.adaboost.2 <- predict(fit.adaboost.2, newdata = fit.data.up.1.test, type = "class") pred.adaboost.2$confusion # Observed Class # Predicted Class -1 1 # -1 729 5 # 1 2 726 pred.adaboost.2$error #[1] 0.004787962 # 최적 조건 실행 ctrl <- rpart.control(minsplit = 40, minbucket = 13, cp = 0.001, xval = 10) fit.adaboost.2 <- boosting(Class ~ ., data = fit.data.up.1.train, control = ctrl, mfinal = 500) pred.adaboost.2 <- predict(fit.adaboost.2, newdata = fit.data.up.1.test, type = "class") pred.adaboost.2$confusion # Observed Class #Predicted Class -1 1 # -1 730 5 # 1 1 726 pred.adaboost.2$error #[1] 0.004103967
위 실험에서, 노드간 경쟁이 심할수록, 복잡도 파라메터가(cp) 가 낮을수록, 그리고 트리 갯수가 증가할수록 모델 정확도가 향상됨을 보인다. 이 데이터에서는 기본 조건이면 충분하다는 결론이 나온다.
변수 중요도(Variable importance)
트리에서, boosting 함수의 경우에서, 이 트리의 가중치, 변수에서 주어진 Gini 계수를 얻기 위해 사용하는 예측자 변수의 연관 중요도의 측정을 위해, caret 패키지(Kuhn 2008, 2012)의 varImp 함수가 각 트리에서 변수의 지니 계수(Gini index)를 얻기기 위해 사용되었다.
# 변수 중요도 fit.adaboost.1.imp <- fit.adaboost.1$imp[order(fit.adaboost.1$imp, decreasing = TRUE)] # 변수 중요도를 그림 barplot(fit.adaboost.1.imp, ylim = c(0, 3), main = "Variables Relative Importance", col = "lightblue") length(fit.adaboost.1.imp[which(fit.adaboost.1.imp > 0, arr.ind = T)]) # [1] 413 # dim(fit.data.up.1.train) # [1] 1464 591 # 590 개의 예측자 중에서 413개의 예측자만 사용되었음을 알 수 있다 # 상위 10개의 예측자를 확인(정보 이득) head(fit.adaboost.1.imp[which(fit.adaboost.1.imp > 0, arr.ind = T)], 10) # V60 V34 V248 V22 V118 V104 V1 V11 V131 V3 # 2.8440880 1.4973837 1.2531450 1.0720579 0.9960293 0.9903873 0.9372817 0.8860977 0.8480454 0.7374941
Figure 1: 반도체 생산 공정 데이터의 변수 중요도 |
마진(Margine)
마진(margin)의 개념은 (Schapire, Freund, Bartlett, and Lee 1998) 중요하다. 객체의 마진은 분류의 확실성에 직관적으로 연관이 있고 그리고 올바른 구분(class) 지지도 및 잘못 된구분의 최대 지지도 사이의 차이(difference)로 계산 된다. 잘못 분류된 모든 표본은 그러므로 음수의 마진을 가질 것이고 그리고 올바르게 분류된 하나는 양수의 마진을 가질 것이다. 높은 신뢰도를 가지는 올바르게 분류된 관측은 1에 가깝게 될 것이다. 다른 한편으로는, 불확실한 분류를 가지는 표본은 작은 마진을 가질 것이다, 즉 말하자면, 마진은 0 에 가깝게 된다.
# 마진 margins.test <- margins(fit.adaboost.1, fit.data.up.1.test)[[1]] margins.train <- margins(pred.adaboost.1, fit.data.up.1.train)[[1]] plot(sort(margins.train), (1:length(margins.train)) / length(margins.train), type = "l", xlim = c(-1,1), main = "Margin cumulative distribution graph", xlab = "m", ylab = "% observations", col = "blue3", lwd = 2) abline(v = 0, col = "red", lty = 2, lwd = 2) lines(sort(margins.test), (1:length(margins.test)) / length(margins.test), type = "l", cex = 0.5, col = "green", lwd = 2) legend("topleft", c("test","train"), col = c("green", "blue"), lty = 1, lwd = 2)
Figure 2: 반도체 생산 공정 데이터의 Margine |
에러 전개(error evolution)
에러 전개는 단지 하나의 벡터를 가지는 목록을 출력한다. 이 정보는 bagging 그리고 boosting이 앙상블 에러가 얼마나 빨리 감소하는지 보기 위해 유용할 수 있다. 게다가, 그것은 과적합의 존재를 찾을 수 있고 그리고, 이 경우에, 연관되는 predict 함수를 사용하는 앙상블의 가지치기에서 편리하다.
# 에러 진화 evol.test <- errorevol(fit.adaboost.1, fit.data.up.1.test) evol.train <- errorevol(fit.adaboost.1, fit.data.up.1.train) plot(evol.test$error, type = "l", ylim = c(0, 0.2), main = "Boosting error versus number of trees", xlab = "Iterations", ylab = "Error", col = "red", lwd = 2) lines(evol.train$error, cex = .5, col = "blue", lty = 2, lwd = 2) legend("topleft", c("test", "train"), col = c("red", "blue"), lty = 1:2, lwd = 2)
Figure 3: 반도체 생산 공정 데이터의 에러 전개 |
이 플롯으로 확인할 수 있는 점은, 반복은 약 60회 정도이면 충분하며, 과적합이 발생되지 않았음을 알 수 있다. 과적합이 박생한 경우 안정적인 일직선이 보이는 전개 이후에 다시 증가하는 부분이 보인다.
3 요약 및 논평
우리가 사용한 반도체 데이터의 예는, 현실 세계의 공정에서 장비의 센서 데이터를 수집한 후 표본 추출한 데이터로 볼 수 있다. 데이터의 특징을 빠르게 파악하기 위해서 원시 데이터 그대로 학습을 시킬 수 있는 rpart 그리고 adabag을 사용했다. adabag의 개별 트리 생성은 rpart 패키지를 이용해서 트리를 구축한다. 단일 분류기를 사용한 rpart인 경우 약 0.5의 에러률를 보이고, adabag인 경우 0.05의 에러률을 보임을 확인할 수 있다. adabag인 경우 반복 학습이 존재하기 때문에 많은 계산 시간이 필요하고, 또한 예측 시간에도 많은 시간이 필요함이 나타났다.
안녕하세요 작성하신 글 정말 잘 보았습니다. 저 또한 R로 상기 데이터를 분석해보려고 하는데 , 데이터의 각 열이 무엇을 상징하는지, 각 숫자가 어떤 의미인지 파악되지않아 진행에 어려움을 겪고 있습니다.. 도움의 말씀 주시면 정말 감사하겠습니다
답글삭제