이번에는 앞서 다뤄본 예제 데이터 가운데 하나인 보스턴 집값 데이터를 이용해서 neuralnet 패키지를 다뤄보겠습니다. 이 예제 역시 흔한 예제 데이터 가운데 하나로 구글에서 검색해보면 쉽게 코드를 검색할 수 있을 것입니다. 하지만 예제 코드만 돌려봐서는 무슨 내용인지 알기가 쉽지 않습니다. 여기서는 구체적으로 집값을 어떻게 예측하는지를 알아보겠습니다. 이 부분은 구글링한 것이 아니라 제가 스스로 생각해서 적어보는 것입니다.
우선 학습을 시킨 후 새로운 데이터를 바탕으로 집값을 예측하게 만들기 위해 데이터 셋을 train, test 두개로 나눕니다. 이렇게 데이터를 나누는 방식은 머신 러닝에서 매우 흔한 방법론 가운데 하나입니다. 물론 이는 예측 모델을 만드는 데 기본이기도 합니다.
library(neuralnet)
library(MASS)
data("Boston", package = "MASS")
data<-boston span="">-boston>
set.seed(1234)
n = nrow(data)
train <- 400="" n="" sample="" span="">->
test <- data="" span="" train="">->
train <- data="" span="" train="">->
이 코드 내용은 이해하기 어렵지 않을 것입니다. 400개는 train 데이터로 106개는 테스트 데이터로 나눠서 학습시킨 신경망으로 집값을 예측하게 하는 것이죠. 일단 앞서 선형 모델에서 본 모델을 넣어 보겠습니다. 신경망은 간단하게 3,3,3 으로 기본 옵션에 맞춰 진행해 보겠습니다.
f=medv ~ lstat + rm + ptratio + dis + nox + chas + black + zn + crim + tax
fit<-neuralnet f="" span="">-neuralnet>
data=train,
hidden=c(3,3,3),
algorithm = "rprop+",
err.fct = "sse",
act.fct = "logistic",
threshold = 0.1,
stepmax=1e6,
linear.output = TRUE)
pred<-compute black="" c="" chas="" crim="" dis="" fit="" lstat="" nox="" ptratio="" rm="" span="" tax="" test="" zn="">-compute>
result<-cbind net="" pred="" span="" test="">-cbind>
result
이렇게 진행하면 예상하지 않은 결과가 나올 것입니다.
실제 집값과 관계없이 모두 22.51... 로 예측이 되버립니다. 신경망을 10,10,10으로 하면 더 예상하지 않은 결과가 나옵니다.
왜 이런 결과가 나왔을까요. 여러 가지 이유가 있겠지만, 사실 한 가지 과정이 빠졌기 때문입니다. 바로 데이터의 정규화 과정입니다. 변수를 정규화해주면 분류가 훨씬 쉬워져서 신경망의 성능이 올라가고 엉뚱한 결론을 내릴 가능성이 줄어듭니다. (x - mean(x)) / sd(x)의 방법으로 정규분포로 바꿔줄 수 있지만, R에서는 scale 함수로 한 번에 변환이 가능합니다.
library(neuralnet)
library(MASS)
data("Boston", package = "MASS")
data<-boston span="">-boston>
data<-scale data="" span="">-scale>
set.seed(1234)
n = nrow(data)
train <- 400="" n="" sample="" span="">->
test <- data="" span="" train="">->
train <- data="" span="" train="">->
이렇게 다시 데이터를 준비한 후 같은 코드를 실행해 봅니다.
fit<-neuralnet f="" span="">-neuralnet>
data=train,
hidden=c(10,10,10),
algorithm = "rprop+",
err.fct = "sse",
act.fct = "logistic",
threshold = 0.1,
stepmax=1e6,
linear.output = TRUE)
pred<-compute black="" c="" chas="" crim="" dis="" fit="" lstat="" nox="" ptratio="" rm="" span="" tax="" test="" zn="">-compute>
result<-cbind net="" pred="" span="" test="">-cbind>
result
이번에도 곤란한 문제가 생깁니다. 정규화를 한 덕에 결과가 빨리 그리고 보다 정확하게 나왔겠지만, 대신 본래 값이 무엇인지 알 수 없게됐습니다. 앞서 다이아몬드 예제에서도 정규화를 할 수 있었는데, 그렇게 하지 않았기 때문에 본래 값이 무엇인지 쉽게 추정이 가능했습니다. 이 예제에서 우리가 알고자 하는 것은 DNN을 이용해서 집값을 예측하는 것입니다. 그러면 앞서 공식에서 x=y x sd(x) + mean(x)를 유도할 수 있을 것입니다. 정규분포에서 추정한 값을 이용해 본래 값을 얻어보겠습니다.
data("Boston", package = "MASS")
data<-boston span="">-boston>
set.seed(1234)
n = nrow(data)
train <- 400="" n="" sample="" span="">->
test <- data="" span="" train="">->
train <- data="" span="" train="">->
test$medv2=pred$net*sd(train$medv)+mean(train$medv)
test
우리는 train 값만 알고 있다고 가정하기 때문에 train 데이터의 집값의 평균과 표준편차를 사용했습니다.
결과를 다시 보니 얼추 비슷하게 집값을 구했다는 사실을 확인할 수 있습니다. 다만 일부 값이 본래 값과 다르게 추정한 경우도 있었습니다.
보스턴 예제는 흔하게 인터넷에서 볼 수 있지만, 구체적으로 어떻게 DNN을 통해서 집값이 예측하는지 보여주는 대신 전문적인 분석 이야기로 넘어가기 때문에 사실 예제를 돌려봐도 무슨 내용인지 이해가 쉽지 않습니다. 구글링해서 쉽게 구할 수 있는 예제인데도 여기서 풀어 쓴 이유입니다. scale을 왜 하는지, 그리고 이 방법으로 어떻게 집값을 예측 혹은 분류하는 신경망을 만들 수 있는지에 대한 설명은 좀처럼 보기 어려워서 아쉬운 부분이 있었습니다.
다음에는 신경망 모델을 평가하고 개선하는 방법에 대해서 알아보겠습니다.
댓글
댓글 쓰기