제가 수행하는 연구 중 하나는 동물이 최적의 생산성과 건강을 유지할 수 있는 영양학적 모델을 만드는 작업입니다. 기존에 사용되던 bloken-line regression 기법(Robins et al., 2006)의 경우 intercept와 각 변수의 coefficient를 사람이 볼 수 있는 숫자로 표시해 책이나 프로그램으로 쉽게 배포할 수 있는데 반해, SVM이라던지 ANN과 같은 black-box 알고리즘을 이용해 만든 모델은 배포가 비교적 어렵습니다.
이를 해결하기 위해 ANN으로 만든 모델을 내장데이터 형태로 R패키지를 만들어 Github를 통해 배포하는 방법으로 해결해본 경험을 이번 포스트에서 나눠보고자합니다.

1. 인공신경망 모델

반추동물인 염소는 호흡하는 과정 가운데 메탄을 배출하는데, 소/염소/사슴과 같이 반추동물이 배출하는 메탄은 주요 온실가스로 지구온난화를 가속할 수 있습니다하지만 동물들은 죄가 없습니다. 이를 위해 영양학적 변수(섭취량과 소화율 등)를 기반으로 염소가 메탄을 얼마나 발생시킬지 인공신경망을 이용해 예측모델을 만들었습니다.

모델은 neuralnet 패키지를 사용해 만들습니다. 모델은 만드는 과정은 본 포스팅과 연관성이 적으므로 만들어진 모델을 어떻게 패키지에 올릴 것인지에 초점을 맞추도록 하겠습니다. 만들어진 모델은 다음과 같습니다. raw data의 공개는 여러가지 내부 사정으로 model$data <- "sorry"와 같이 처리하였습니다.

library(neuralnet)
str(model1)
## List of 13
##  $ call               : language neuralnet(formula = CH4d ~ DMI + OMI + CPI + NDFI + DDMI + DOMI +      DCPI + DNDFI, data = df_norm, hidden = 5)
##  $ response           : num [1:150, 1] 0.45 0.489 0.452 0.468 0.537 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:150] "1" "2" "3" "4" ...
##   .. ..$ : chr "CH4d"
##  $ covariate          : num [1:150, 1:8] 0.806 0.565 0.504 0.625 0.953 ...
##  $ model.list         :List of 2
##   ..$ response : chr "CH4d"
##   ..$ variables: chr [1:8] "DMI" "OMI" "CPI" "NDFI" ...
##  $ err.fct            :function (x, y)
##   ..- attr(*, "type")= chr "sse"
##  $ act.fct            :function (x)
##   ..- attr(*, "type")= chr "logistic"
##  $ linear.output      : logi TRUE
##  $ data               : chr "sorry"
##  $ net.result         :List of 1
##   ..$ : num [1:150, 1] 0.469 0.481 0.469 0.478 0.542 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:150] "1" "2" "3" "4" ...
##   .. .. ..$ : NULL
##  $ weights            :List of 1
##   ..$ :List of 2
##   .. ..$ : num [1:9, 1:5] -1.034 -0.472 5.271 2.597 -3.288 ...
##   .. ..$ : num [1:6, 1] -0.0942 0.7173 -1.3375 -0.6344 0.5375 ...
##  $ startweights       :List of 1
##   ..$ :List of 2
##   .. ..$ : num [1:9, 1:5] -1.163 0.213 0.107 -1.138 -0.692 ...
##   .. ..$ : num [1:6, 1] -0.199 0.199 -0.545 -0.365 0.116 ...
##  $ generalized.weights:List of 1
##   ..$ : num [1:150, 1:8] 0.0152 -0.0691 -0.0741 -0.0498 -0.1795 ...
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr [1:150] "1" "2" "3" "4" ...
##   .. .. ..$ : NULL
##  $ result.matrix      : num [1:54, 1] 3.41e-01 6.99e-03 3.21e+03 -1.03 -4.72e-01 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:54] "error" "reached.threshold" "steps" "Intercept.to.1layhid1" ...
##   .. ..$ : chr "1"
##  - attr(*, "class")= chr "nn"

2. 패키지 제작

패키지 제작을 위해 devtools와 roxygen2를 로딩하고 프레임을 만들어줍니다.

library(devtools)
library(roxygen2)

create("CH4goat")

DESCRIPTION 파일을 열어 필요한 사항들을 지정해줍니다. 저는 다음과 같이 지정하였습니다. devtools에는 아래 내용을 쉽게 작성할 수 있는 함수들도 있습니다. Hadley의 자료를 참고하시기 바랍니다.

Package: CH4goat
Title: Package
Version: 1.0
Authors@R: person("Youngjun", "Na", email = "ruminoreticulum@gmail.com", role = c("aut", "cre"))
Description: Deep learning models for predict the methane emission from goats.
Depends: R (>= 3.4.0), neuralnet
License: GLP-2
Encoding: UTF-8
LazyData: true
RoxygenNote: 6.0.1
Imports: neuralnet

모델을 rda형태로 패키지에 넣기

neuralnet을 이용해 만든 모델(model1 & model2)을 패키지내에 넣어보도록 하겠습니다. 들어갈 모델은 기본적으로 Global environment에 있어야 합니다.

setwd("/Users/CH4goat")

use_data(model1,overwrite = TRUE)
use_data(model2,overwrite = TRUE)

위와같은 명령어를 실행하면 CH4goat/data폴더 내에 model1.rdamodel2.rda 파일이 생성되게 되며, 이는 차후 패키지를 로딩할 때 내장 데이터셋으로 함께 로딩되게 됩니다.

필요한 R 함수들 추가

일반적으로 ANN 분석을 수행하기 전에 지역 최적 상태에 빠질 가능성을 줄이기 위해 input data를 normalization을 수행하는데, 생성된 모델들의 normalization의 기준과 이를 원래대로 되돌리기 위한 denormalization에 관련된 함수를 패키지에 추가하였습니다. 저는 CH4goat/R폴더 내에 다음과 같은 roxygen2 setting과 함수를 추가하였습니다.

먼저 normalization을 위한 raw data의 최대 및 최소값을 지정한 rdata를 패키지에 넣습니다. 데이터는 다음과같이 생겼습니다.

minmax
##        DMI      OMI       CPI     NDFI     DDMI     DOMI      DCPI
## 1 407.4004 351.0602  35.21633 185.1230 224.5433 215.1463  15.40251
## 2 829.5032 841.5274 351.76240 619.3901 653.4493 715.5847 762.32588
##      DNDFI   CH4
## 1 101.1407  7.02
## 2 688.4980 60.26
use_data(minmax,overwrite = TRUE)

다음 함수를 normalization.R로 저장하였습니다. 원래는 for문을 사용하지 않고 apply계열을 이용해보려 했으나 실패..

#' normalization
#'
#' A function normalization.
#' @keywords deep learning goat methane
#' @export
#' @examples
#' normalization(data=example1)

normalization <- function(data){
  normalized <- data
  for (n in names(data)){
    normalized[,n] <-
      (data[,n] - min(minmax[,n])) /  (max(minmax[,n]) -  min(minmax[,n]))
  }
  return(normalized)
}

denormalization.R을 저장하였습니다.

#' denormalization
#'
#' A function for back_normalization.
#' @keywords deep learning goat methane
#' @export
#' @examples
#' neuralnet::compute(model1,example1)
#' denormalization(data=example1$net.result)

denormalization <- function(data){
  denormalized <- data*(max(minmax$CH4)-min(minmax$CH4)) + min(minmax$CH4)
  return(denormalized)
}

최종적으로 폴더는 다음과같은 계층을 갖습니다.

man를 생성해주고 패키지 설치를 진행합니다.

document()

# CH4goat의 상위폴더를 지정
setwd("/Users")
install("CH4goat")
library(CH4goat)

위 패키지를 Github에 올리고 배포하는 과정은 다음 포스팅에서 다루도록 하겠습니다.

참고문헌

  • Robbins, K.R., Saxton, A.M., Southern, L.L., 2006. Estimation of nutrient requirements using broken-line regression analysis. Journal of animal science 84, E155–E165.