nnet 패키지를 이용해서 한 가지 더 예제를 시행해 보겠습니다. moonBook 패키지는 국내에서 R 관련 커뮤니티 확산에 큰 기여를 한 문건웅 선생님이 개발한 것으로 여기엔 857명의 협심증, 심근 경색 환자의 데이터인 acs 데이터가 기본 데이터로 포함되어 있습니다. 여기서는 결과값이 3개인 경우 - 불안정 협심증 (unstable angina), 비 ST 상승 심근경색 (NSTEMI), ST 상승 심근경색 (STEMI) - 분류하는 인공 신경망을 만들어 보겠습니다. nnet 패키지는 분류 범주가 여러 개라도 크게 문제 없이 분석이 가능합니다. 하지만 신경망에 넣기 전에 전처리가 필요합니다.
require(moonBook)
str(acs)
summary(acs)
> str(acs)
'data.frame': 857 obs. of 17 variables:
$ age : int 62 78 76 89 56 73 58 62 59 71 ...
$ sex : chr "Male" "Female" "Female" "Female" ...
$ cardiogenicShock: chr "No" "No" "Yes" "No" ...
$ entry : chr "Femoral" "Femoral" "Femoral" "Femoral" ...
$ Dx : chr "STEMI" "STEMI" "STEMI" "STEMI" ...
$ EF : num 18 18.4 20 21.8 21.8 22 24.7 26.6 28.5 31.1 ...
$ height : num 168 148 NA 165 162 153 167 160 152 168 ...
$ weight : num 72 48 NA 50 64 59 78 50 67 60 ...
$ BMI : num 25.5 21.9 NA 18.4 24.4 ...
$ obesity : chr "Yes" "No" "No" "No" ...
$ TC : num 215 NA NA 121 195 184 161 136 239 169 ...
$ LDLC : int 154 NA NA 73 151 112 91 88 161 88 ...
$ HDLC : int 35 NA NA 20 36 38 34 33 34 54 ...
$ TG : int 155 166 NA 89 63 137 196 30 118 141 ...
$ DM : chr "Yes" "No" "No" "No" ...
$ HBP : chr "No" "Yes" "Yes" "No" ...
$ smoking : chr "Smoker" "Never" "Never" "Never" ...
범주형 데이터는 0,1,2... 식으로 연속 변수는 scale 함수로 정규화 시키고 결측값은 모두 제거하겠습니다. 참고로 acs 데이터에 대해서 알고 싶다면 아래 문서가 도움이 될 것입니다.
acs$sex1[acs$sex=="Male"]=1
acs$sex1[acs$sex=="Female"]=0
acs$Dx1[acs$Dx=="Unstable Angina"]=0
acs$Dx1[acs$Dx=="NSTEMI"]=1
acs$Dx1[acs$Dx=="STEMI"]=2
acs$DM1[acs$DM=="Yes"]=1
acs$DM1[acs$DM=="No"]=0
acs$HBP1[acs$HBP=="Yes"]=1
acs$HBP1[acs$HBP=="No"]=0
acs$smoking1[acs$smoking=="Smoker"]=0
acs$smoking1[acs$smoking=="Ex-smoker"]=1
acs$smoking1[acs$smoking=="Never"]=2
acs$cardiogenicShock1[acs$cardiogenicShock=="Yes"]=1
acs$cardiogenicShock1[acs$cardiogenicShock=="No"]=0
data<-acs age="" c="" sex1="" smoking1="" span="" x1="">-acs>
data<-na .omit="" data="" span="">-na>
data$age<-scale age="" data="" span="">-scale>
data$BMI<-scale data="" span="">-scale>
data$LDLC<-scale data="" span="">-scale>
data$Dx1<-as .factor="" data="" span="" x1="">-as>
마지막에 Dx1은 범주형이라는 사실을 알려줘야 nnet 패키지에서 인식할 수 있습니다. 100명을 테스트 데이터로 나머지는 학습 데이터로 나누고 노드를 8개로 만들어 신경망을 돌려보겠습니다.
set.seed(1234)
n = nrow(data)
train <- 651="" n="" sample="" span="">->
test <- data="" span="" train="">->
train <- data="" span="" train="">->
nn <- data="train," nnet="" size="8) </span" x1="">->
nn
data3<-predict nn="" span="" test="" type="class">-predict>
test=cbind(test,data3)
test
test$pred<-ifelse test="" x1="=test$data3,1,0)</span">-ifelse>
table(test$pred)
sum(test$pred)/100
> test$pred<-ifelse test="" x1="=test$data3,1,0)</span">-ifelse>
> table(test$pred)
0 1
47 53
> sum(test$pred)/100
[1] 0.53
이 신경망에서는 맞게 분류한 경우가 53% 정도였습니다. 그냥 아무렇게나 찍는 것보다는 확률이 높지만, 아주 만족스러운 성능은 아니라고 하겠습니다. 아마도 협심증이나 심근경색이 있는 경우 그 상태가 비슷한 것이 원인일 것 같지만, 학습 데이터에 하나 더 추가해서 신경망의 학습 성적을 높일 수 있을지 모릅니다. 여기서는 심장의 수축 기능을 평가하는 지표인 EF를 하나 더 넣어 보겠습니다. 이 경우 결측값 때문에 전체 관측치의 숫자가 줄어들기 때문에 train의 숫자를 577개로 조정하겠습니다.
data<-acs age="" c="" sex1="" smoking1="" span="" x1="">-acs>
data<-na .omit="" data="" span="">-na>
data$age<-scale age="" data="" span="">-scale>
data$BMI<-scale data="" span="">-scale>
data$LDLC<-scale data="" span="">-scale>
data$EF<-scale data="" span="">-scale>
data$Dx1<-as .factor="" data="" span="" x1="">-as>
set.seed(1234)
n = nrow(data)
train <- 577="" n="" sample="" span="">->
test <- data="" span="" train="">->
train <- data="" span="" train="">->
nn <- data="train," nnet="" size="8) </span" x1="">->
nn
data3<-predict nn="" span="" test="" type="class">-predict>
test=cbind(test,data3)
test
test$pred<-ifelse test="" x1="=test$data3,1,0)</span">-ifelse>
table(test$pred)
sum(test$pred)/100
> table(test$pred)
0 1
54 46
> sum(test$pred)/100
[1] 0.46
되려 정확히 분류할 가능성이 더 낮아졌습니다. 반드시 학습 데이터가 많다고 좋지는 않다는 점을 보여주는 사례입니다. 가장 최적의 신경망 모델을 찾는 일은 그렇게 간단하지는 않습니다.
이런 식으로 예제를 바꿔가면서 방법을 익혀나가다 보면 여러 가지 에러를 만나게 되고 이를 해결하는 과정에서 데이터 전처리를 어떻게 하고 신경망을 어떻게 개선해 나가는지를 이해하게 될 것입니다. 기왕 하는 김에 신경망에 대한 이야기를 조금 더 해보겠습니다.
댓글
댓글 쓰기