-
Notifications
You must be signed in to change notification settings - Fork 7
/
model-feature-engineering.Rmd
432 lines (324 loc) · 19.1 KB
/
model-feature-engineering.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
---
layout: page
title: "Tidyverse 모형 - `tidymodels`"
subtitle: "피처 공학(Feature Engineering)"
author:
- name: "이광춘"
affiliation: "[Tidyverse Korea](https://www.facebook.com/groups/tidyverse/)"
date: "`r Sys.Date()`"
tags: ["데이터 과학", "Data Science", "데이터 사이언스", "tidymodels", "parsnip", "로지스틱 회귀모형", "tidyverse", "팽귄", "penguin", "피처 공학", "Feature Engineering"]
output:
html_document:
include:
after_body: footer.html
before_body: header.html
theme: default
toc: yes
toc_depth: 2
toc_float: true
highlight: tango
code_folding: show
number_section: true
self_contained: true
bibliography: bibliography_model.bib
csl: biomed-central.csl
urlcolor: blue
linkcolor: bluee
editor_options:
chunk_output_type: console
---
```{r, include=FALSE}
source("tools/chunk-options.R")
knitr::opts_chunk$set(echo = TRUE, message=FALSE, warning=FALSE,
comment="", digits = 3, tidy = FALSE, prompt = TRUE, fig.align = 'center')
```
# 예측모형을 위한 데이터 전처리 [^all-about-fe] [^automated-fe-ml] {#data-preprocesing-for-predictive-model}
[^all-about-fe]: [HJvanVeen, Feature Engineering](https://www.slideshare.net/HJvanVeen/feature-engineering-72376750)
[^automated-fe-ml]: [Why Automated Feature Engineering Will Change the Way You Do Machine Learning](https://towardsdatascience.com/why-automated-feature-engineering-will-change-the-way-you-do-machine-learning-5c15bf188b96)
예측모형 개발 과정에 빠지지 않고 등장하는 것이 데이터 전처리(Data Preprocessing) 과정이다.
`readr` 등을 통해 데이터를 R이나 모형개발 환경을 가져오게 되면 `tidy` 깔끔화과정을 거치게 되는데,
데이터를 가져온 다음 혹은 깔끔화 과정이 완료된 후에 **데이터 전처리(Data Preprocessing)** 과정을 수행한다.
흔히 데이터 전처리 과정은 **데이터 정제(data cleansing)**와 예측모형 개발을 위한 **피쳐공학(feature engineering)**과 동일시하는 경향이 있지만,
엄밀한 의미에서 보면 최종 목적이 다르기 때문에 각 목적에 맞는 데이터 전처리 과정에 적합한 용어를 취사선별하여 사용하는 것이 권장된다.
<style>
div.blue { background-color:#e6f0ff; border-radius: 5px; padding: 10px;}
</style>
<div class = "blue">
"More data beats clever algorithms, but better data beats more data." -- Peter Norvig
</div>
각 변수별 전처리 과정을 살펴보면, 결측값 대체를 통해 빠진 결측값을 채워넣는 과정과 중심화와 척도조정을 통해
통계모형에 예측력을 향상시키기 위한 과정이 필요하다.
또한, 분산이 없거나 매우 낮은 분산을 갖는 변수를 제거하고 변수간 상관관계가 높은 변수를 추출하는 과정도 변수간 전처리 과정에 포함된다.
- 결측값 대체: 중위수 대체법, knn 대체
- 피쳐 척도조정(Feature Scaling): 중심화(Centering), 척도조정(Scaling)
- 분산이 없거나, 매우 낮은 분산을 갖는 변수 제거
- 분산이 낮거나 상관변수를 추출: PCA
- 피처 생성: 시간/날짜 데이터, 텍스트/이미지/소리 등 데이터에서 피처를 추출
<img src="fig/ml-preprocessing-overview.png" alt="데이터 전처리 과정" width="97%" />
# 왜 피처 공학인가? [^featuretools-python] [^automated-feature-engineering] [^automated-manual-feature] {#feature-engineering-tools}
[^featuretools-python]: [A Hands-On Guide to Automated Feature Engineering using Featuretools in Python](https://www.analyticsvidhya.com/blog/2018/08/guide-automated-feature-engineering-featuretools-python/)
[^automated-feature-engineering]: [Automated Feature Engineering in Python - How to automatically create machine learning features](https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219)
[^automated-manual-feature]: [KDnuggets - Deep Feature Synthesis: How Automated Feature Engineering Works ](https://www.kdnuggets.com/2018/02/deep-feature-synthesis-automated-feature-engineering.html)
파이썬에서 `Featuretools`는 자동화된 피처공학(Automated Feature Engineering) 실현을 모토로 활발히 영역을 높여가고 있고, R에서는 `recipes`를 바탕으로 역시 `caret`의 다음 버전의 모형행렬 자료구조가 되도록 속도를 높여가나고 있다.
[Featuretools](https://docs.featuretools.com/)를 통해서 자동화된 피처공학(Automated Feature Engineering) 접근법을 제시하고 있고, 그 이전 `caret`에서 데이터 전처리과정에 강조되었던 기능은 `recipes` 팩키지로 넘어가 피처에 대한 공학작업을 명확히 명세하고 이를 바탕으로 모형행렬(Design Matrix)을 예측모형 알고리즘에 넣을 수 있도록 진화하고 있다.
<img src="fig/manual-auto-fe.png" alt="수작업과 자동 피처 공학" width="100%" />
<style>
div.blue { background-color:#e6f0ff; border-radius: 5px; padding: 10px;}
</style>
<div class = "blue">
"One of the holy grails of machine learning is to automate more and more of the feature engineering process." -- Pedro Domingos, A Few Useful Things to Know about Machine Learning
</div>
# 피처 공학 기법: `caret` {#data-preprocesing-reason-for-predictive-model-methods}
예측모형 대부분은 숫자만을 입력값으로 받아야 하는데, 결측값이 입력값으로 전달되는 경우 이를 처리할 수 없다.
이런 문제를 해결하기 위해 결측값을 제거하지 않는 경우 데이터에 편향이 발생하여 모형신뢰성이 떨어진다.
## 결측값 대체 {#feature-engineering-missing}
데이터에 결측값이 존재하는 경우 결측값이 **임의결측(Missing at Random, MAR)**인 경우 중위수 대체법(Median Imputation)을 사용하고,
그렇지 않은 경우, 근처 값을 결측점에 채워넣는 knn 대체법(knn Imputation)을 사용한다.
### 결측값 대체 사례 [^Boston] {#feature-engineering-missing-imputation}
[^Boston]: Harrison, D. and Rubinfeld, D.L. (1978) Hedonic prices and the demand for clean air. J. Environ. Economics and Management 5, 81–102.
회귀분석 사례로 많이 사용되는 보스터 집값 사례를 살펴보자. 데이터를 불러와서 `glimpse`, `summary` 함수로 일별한다.
``` {r boston-housing-price-setting}
##===============================================
## 00. 환경설정
##===============================================
# Classification and Regression Training 팩키지
# install.packages("caret")
library(tidyverse)
library(tidymodels)
##===============================================
## 01. 데이터 가져오기
##===============================================
# 보스톤 주택가격
# install.packages("mlbench")
library(mlbench)
data("BostonHousing")
# 데이터 살펴보기
glimpse(BostonHousing)
summary(BostonHousing)
```
#### 임의 결측값 대체 전략 - 중위수 대체 {#feature-engineering-missing-imputation-median}
`sample` 함수를 사용해서 임의 결측값을 생성하여 `crim` 변수에 10개 넣는다. `caret` 팩키지
`train` 함수를 사용해서 randomForest 모형을 적합시킨다. 하지만 결측값이 학습시킬 데이터에 포함되어 더이상
학습이 되지 않고 오류가 나오게 된다.
`preProcess = "medianImpute"` 인자를 `train` 함수에 넣어 중위수 대체를 하게 되면 결측값에 따른 문제가 해소된다.
``` {r boston-housing-price-median, eval=FALSE}
##===============================================
## 02. 데이터 전처리
##===============================================
#------------------------------------------------
# 02.01. 중위수 대체
#------------------------------------------------
# 임의 결측값 채워넣기
library(randomForest)
set.seed(777)
BostonHousing[sample(1:nrow(BostonHousing), 10), "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, 1:5]
# caret 예측모형 적합
model <- train(x = X, y = Y, method="rf")
# Something is wrong; all the RMSE metric values are missing:
# RMSE Rsquared
# Min. : NA Min. : NA
# 1st Qu.: NA 1st Qu.: NA
# Median : NA Median : NA
# Mean :NaN Mean :NaN
# 3rd Qu.: NA 3rd Qu.: NA
# Max. : NA Max. : NA
# NA's :3 NA's :3
# Error in train.default(x = X, y = Y, method = "rf") : Stopping
# In addition: There were 50 or more warnings (use warnings() to see the first 50)
# 해결책 : 중위수 대체
model <- caret::train(x = X, y = Y, method="rf", preProcess = "medianImpute")
model
```
#### 임의 랜덤이 아닌 결측값 - knn 대체 {#feature-engineering-missing-imputation-knn}
데이터에 결측값이 랜덤으로 임의적으로 만들어진 것이 아닌 경우, 예를 들어 법죄가 0.5 이상인 경우 모드 결측값이 된 경우가 존재한다.
이런 경우 `preProcess = "knnImpute"` 인자는 다른 설명변수를 이용하여 결측값을 추정하여 채워넣게 된다. RMSE 값을 비교하면 더 향상된 것(RMSE 오차가 축소)이 확인된다.
``` {r boston-housing-price-knn}
#------------------------------------------------
# 02.02. knn 대체 : 결측값이 임의가 아님
#------------------------------------------------
# 범죄가 0.5 이상 결측값 채워넣기
set.seed(777)
data("BostonHousing")
BostonHousing[BostonHousing$crim > 0.5, "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, c(1:3,5)]
model_median <- caret::train(x = X, y = Y, method = "glm", preProcess = "medianImpute")
print(min(model_median$results$RMSE))
# install.packages("RANN")
model_knn <- caret::train(x = X, y = Y, method = "glm", preProcess = "knnImpute")
print(min(model_knn$results$RMSE))
```
## 변수 전처리 파이프라인 {#feature-engineering-pipeline}
각 변수별로 결측값이 존재하는 경우 중위수 대체와 knn 대체 방법을 통해 가능하면 많은 변수를 모형에 활용할 수 있다.
결측값 처리 외에도 중심화, 척도조정 등 일련의 전처리 과정을 통해 예측모형 성능을 개선시켜 나간다.
이와 같은 결측값 처리, 중심화, 척도조정 작업이 `caret` 팩키지 `preProcess` 인자를 순차적으로 연결하여 자동화한다.
이런 경우 작업 순서가 매우 중요하다.
1. 분산이 없거나, 매우 낮은 분산을 갖는 변수 제거 → `zv`, `nzv`
1. 결측값 대체, 중위수 대체법, knn 대체 → `medianImpute`, `knnImpute`
1. 중심화(Centering) → `center`
1. 척도조정(Scaling) → `scale`
1. 분산이 낮거나 상관변수를 추출, PCA → `pca`, `spatialSign`
<img src="fig/ml-preprocessing-workflow.png" alt="변수제거, 결측값, 중복정보 제거" width="100%" />
임의 결측값을 보스턴집값 데이터셋에 10개 넣은 후에 중위수 대체만 적용시켜 전처리하여 예측모형에 적합시킨 결과,
중위수 대체+중심화+척도조정 전처리하여 예측모형에 적합시킨 결과,
중위수 대체+중심화+척도조정+PCA 전처리하여 예측모형에 적합시킨 결과 RMSE 값을 비교하여 가장 적합한 전처리 방법을
선정한다.
``` {r boston-housing-price-pipeline}
##===============================================
## 03. 데이터 전처리 파이프라인
##===============================================
#------------------------------------------------
# 03.01. 전처리 파이프라인
#------------------------------------------------
# 임의 결측값 채워넣기
set.seed(777)
data("BostonHousing")
BostonHousing[sample(1:nrow(BostonHousing), 10), "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, 1:13]
# caret 예측모형 적합: 기준
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute"))
print(min(model$results$RMSE))
# caret 예측모형 적합: 전처리 기본 파이프라인 적용
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale"))
print(min(model$results$RMSE))
# caret 예측모형 적합: 전처리 전체 파이프라인 적용(PCA)
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale", "pca"))
print(min(model$results$RMSE))
# caret 예측모형 적합: 전처리 전체 파이프라인 적용(PCA)
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale", "spatialSign"))
print(min(model$results$RMSE))
```
## 변수 제거와 중복 변수 제거 {#feature-engineering-remove-duplicated-variable}
일부 변수에 정보가 없거나 매우 낮은 경우가 있다. 이를 기반으로 예측모형을 개발할 경우 쓸모 없는 변수가
예측모형에 포함되어 기대하지 않은 많은 문제가 야기된다.
- 상수 변수: 분산이 `0` 으로 변수의 모든 값이 동일.
- 거의 상수 변수: 분산이 매우 작아 변수의 모든 값이 특정 값에 몰려있는 경우.
`"zv"`, `"nzv"` 값을 `preProcess` 인자로 넣는 경우 상수 변수와 거의 상수 변수를 처리할 수 있다.
- `"zv"` : 상수 변수 제거
- `"nzv"` : 거의 상수 변수 제거
### 상수 변수 제거 {#feature-engineering-remove-constant-variable}
`X$variance_zero <- 7` 명령어로 임의로 상수 변수를 생성시킨다. `glm` 모형을 적합시키면 오류가 생성된다.
`preProcess`에서 `"zv"` 인자를 넣어 분산이 0 인 변수를 전처리하여 제거한 후 예측모형을 개발하면
모형적합이 제대로 됨이 확인된다.
``` {r boston-housing-price-constant, eval = FALSE}
##===============================================
## 05. 변수 전처리 - 변수제거와 차원축소
##===============================================
#------------------------------------------------
# 05.01. 상수 변수: 분산이 0
#------------------------------------------------
# 임의 결측값 채워넣기
set.seed(777)
data("BostonHousing")
BostonHousing[sample(1:nrow(BostonHousing), 10), "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, 1:13]
# 상수값으로만 구성된 변수 추가
X$variance_zero <- 7
## 모형적합
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale", "pca"))
## 모형적합: 상수 변수 제거
model <- train(x = X, y = Y, method="glm", preProcess = c("zv", "medianImpute", "center", "scale", "pca"))
print(min(model$results$RMSE))
```
### 거의 상수 변수 제거 {#feature-engineering-remove-almost-constant}
`"zv"` 인자 대신에 `"nzv"` 인자를 넣어도 좋지만, 명시적으로 `nearZeroVar()` 함수로
거의 상수 변수를 추출하여 이를 예측변수에 넣어 예측모형을 개발한다.
``` {r boston-housing-price-near-zero}
#-------------------------------------------------
# 05.02. 거의 상수 변수: 분산이 거의 0에 가까움
#-------------------------------------------------
# 임의 결측값 채워넣기
set.seed(777)
data("BostonHousing")
BostonHousing[sample(1:nrow(BostonHousing), 10), "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, 1:13]
## 거의 상수 변수 정의: freqCut
remove <- nearZeroVar(X, freqCut = 20/5, saveMetrics=TRUE)
X_small <- X[ , setdiff(names(X), remove)]
## 모형적합: 상수 변수 제거
model <- train(x = X_small, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale", "pca"))
print(min(model$results$RMSE))
```
### 중복변수 제거 {#feature-engineering-remove-duplicated-variables}
주성분 분석(Principal Component Analysis, PCA)을 통해 서로 상관관계가 높은 변수를 제거하여
다공선성(Collinearity) 문제를 해결하여 예측모형의 안정성을 높인다. `preProcess = c("pca")` 를 넣어주면
변수간에 상관관계가 높은 문제에 대한 전처리를 수행하게 된다.
``` {r boston-housing-price-near-pca}
#----------------------------------------------
# 05.03. 중복변수 제거: PCA
#----------------------------------------------
# 임의 결측값 채워넣기
set.seed(777)
data("BostonHousing")
BostonHousing[sample(1:nrow(BostonHousing), 10), "crim"] <- NA
# 예측모형: 설명변수와 종속변수 분리
Y <- BostonHousing$medv
X <- BostonHousing[, 1:13]
## 모형적합: 상수 변수 제거
model <- train(x = X, y = Y, method="glm", preProcess = c("medianImpute", "center", "scale", "pca"))
print(min(model$results$RMSE))
```
# `recipes` 팩키지 {#recipes}
![](fig/feature-engineering-recipe.png)
`recipes` 팩키지는 피처공학 데이터 전처를 위한 요리법을 작성하는 `recipe()` 단계와 재료를 손질하는 준비 `prep()`, 마지막으로 모형에 적합시킬 수 있는 형태 데이터로 변환시킨 `bake()` 함수를 적용하는 3단계로 나눠진다.
## `recipes` 친해지기 {#recipes-friends}
먼저, `recipes` 팩키지 3가지 단계를 익숙해지기 위해서 단계별로 `recipe()` → `prep()` → `bake()`를 진행해보자.
```{r basic-feature-engineering}
library(tidymodels)
data("BostonHousing")
splits <- initial_split(data = BostonHousing, prop = 0.8, strata = 'medv')
price_rec <- recipe(medv ~ . , data = training(splits)) %>%
step_log(medv, base = 10)
price_rec
```
`prep()` 단계를 통해 재료준비를 끝마쳐둔다.
```{r basic-fe-prep}
price_rec_prep <- price_rec %>%
prep(training = training(splits))
price_rec_prep
```
마지막으로 기계학습을 위한 데이터를 준비해보자.
`bake()` 함수를 통해 `medv` 변수가 로그 변환된 것을 확인할 수 있다.
```{r basic-fe-bake}
price_rec_prep %>%
bake(new_data = NULL)
```
동일한 `recipe`를 시험 데이터에도 적용시켜보자.
요리법은 동일하고 데이터만 바꿔주면 되기 때문에 시험데이터 `testing(splits)`을
구워주면 기계학습 예측모형의 성능을 평가하는데 필요한 만반의 준비가 완료되었다.
```{r basic-fe-bake-testing}
price_rec_prep %>%
bake(new_data = testing(splits))
```
## 숫자형과 범주형 변수 [^ordering-steps] {#numerical-categorical-variable}
[^ordering-steps]: [`recipes`, "Ordering of Steps"](https://recipes.tidymodels.org/articles/Ordering.html)
`chas` 변수를 제외한 나머지 변수는 모두 숫자형이라 각 변수 자료유형에 맞춰 적절한
변수 변환작업을 수행한다. 범주형 변수와 숫자형 변수의 변수변환 작업에 차이가 있고
상황에 맞춰 적절히 조합시켜 활용한다. `step_*()` 단계별 적용 방식은 다음과 같다.
1. Impute
1. Individual transformations for skewness and other issues
1. Discretize (if needed and if you have no other choice)
1. Create dummy variables
1. Create interactions
1. Normalization steps (center, scale, range, etc)
1. Multivariate transformation (e.g. PCA, spatial sign, etc)
```{r numeric-categorical-variable}
house_rec <- recipe(medv ~ . , data = training(splits)) %>%
step_log(medv, base = 10) %>%
step_normalize(all_numeric()) %>%
step_corr(all_numeric(), -all_outcomes(), threshold = 0.9) %>%
step_dummy(all_nominal(), -all_outcomes())
house_rec %>%
prep(training = training(splits)) %>%
bake(new_data = testing(splits))
```