[ 핸즈 온 머신러닝 2판 ] pandas, sklearn을 통한 데이터 전처리는 어떻게 하는걸까? (1)
어제보다 나은 사람이 되기

걱정보단 실행을, 그러나 계획적으로

Box World 자세히보기

AI/Hands-On Machine Learning 2판

[ 핸즈 온 머신러닝 2판 ] pandas, sklearn을 통한 데이터 전처리는 어떻게 하는걸까? (1)

Box형 2020. 6. 9. 20:37
반응형

발견에는 항상 뜻밖의 재미가 있다

- 제프 베조스(Amazon CEO) -

Chapter 2

이번 포스팅을 시작으로 3번에 걸쳐 하나의 머신러닝 프로젝트가 어떻게 구성되고 진행되는지 알아보겠습니다. 우선 주요 단계는 다음과 같습니다.

1) 큰 그림 보기

2) 데이터 구하기

3) 데이터 탐색 및 시각화

4) 모델 학습을 위한 데이터 전처리

5) 모델 선택 및 훈련

6) 모델 튜닝

7) 솔루션 제시

8) 시스템 론칭, 모니터링 및 유지 보수


2.1 실제 데이터로 작업하기

머신러닝을 적용하기 위해서 가장 먼저 필요한 것은 바로 데이터가 될텐데요. 특히 우리가 공부할 때는 인공적인 데이터셋이 아닌 실제 데이터셋을 직접 다뤄보는 것이 더욱 효과적입니다. 다음은 유명한 대표적인 데이터 저장소들입니다.

- UC 얼바인 머신러닝 저장소(https://archive.ics.uci.edu/ml/datasets.html)

- 캐글 (https://www.kaggle.com/)

- 아마존 AWS 데이터셋 (https://registry.opendata.aws/)

설명을 위해 우리가 사용할 데이터셋은 StarLib 저장소에 있는 캘리포니아 주택 가격입니다. 이제 데이터셋이 어떻게 구성되어 있는지 살펴보겠습니다.

2.2 큰 그림 보기

데이터는 인구(population), 중간 소득(median income), 중간 주택 가격(median housing price) 등으로 구성되어 있습니다. 우리는 이제부터 주어진 데이터를 모델에 학습시킨 후 새로운 데이터가 주어졌을 때 해당 구역의 중간 주택 가격을 예측하게끔 해야합니다.

2.2.1 문제 정의

머신러닝 모델은 'Tool' 말 그대로 도구입니다. 다시 말해서 머신러닝 자체가 목적이 아니기 때문에 머신러닝이 사용되는 1)비즈니스의 목적이 무엇인지를 가장 먼저 파악하고 이에 따라 모델의 방향성을 결정해야 하는게 중요하겠습니다.

우리가 만들 '중간 주택 가격 예측 모델'은 부동산 투자를 위하여 구역의 가격을 예측하여 이것이 다른 데이터들과 결합하여 투자를 결정하는데 사용됩니다. 수익과 바로 직결되기 때문에 올바른 예측은 더욱 중요할 수 밖에 없습니다.

두번째로 파악해야할 것은 이러한 주택 가격을 예측하기 위해 2)기존에는 어떤 솔루션을 사용했냐는 것입니다. 예를 들어 그 동안은 구역의 주택 가격을 전문가가 수동으로 측정했다면, 정보를 모으고 복잡한 규칙을 사용하여 추정을 하기까지 비용과 시간도 많이 들고 결과가 안좋았다면, 이러한 이유로 회사는 구역의 데이터를 기반으로 모델을 훈련시켜 예측하게 하는 것이 훨씬 합리적일 것입니다.

그리고 최종적으로 문제 정의를 하게 됩니다. 문제 정의에는 해결해야하는 문제 그리고 데이터에 기반하여 지도, 비지도, 강화 학습 중 무엇인지, 분류인지 회귀인지 혹은 배치 학습인지 온라인 학습인지 등을 결정해야 합니다. 우리가 해결할 문제는 다음과 같이 정의할 수 있습니다.


- 캘리포니아 데이터셋은 Labeled Training set이므로 지도 학습(Supervised Learning)입니다.

- 가격 즉 값을 예측하는 것이므로 회귀(Regression)입니다.

- 예측을 위해 사용할 feature에는 인구, 중간 소득 등 여러 개 이므로 다중 회귀(Multiple Regression)입니다.

- 구역 별로 하나의 값을 예측하므로 단변량 회귀(Univariate Regression)입니다. 논외로 하나의 구역마다 여러 값을 예측해야 한다면 다변량 회귀(Multivariate Regression)입니다.

- (데이터가 매우 크다면 맵리듀스(MapReduce)를 이용하여 배치 학습을 여러 서버로 분할하거나 온라인 학습을 할 수 있습니다.)


2.2.2 성능 측정 지표 선택

다음 단계는 성능 측정 지표를 선택하는 것인데, 대표적으로는 평균 제곱근 오차(RMSE)가 있습니다. 이는 오차가 커질수록 값이 커지기 때문에 예측에 얼마나 많은 오류가 있는지 파악할 수 있습니다.

$$RMSE(X,h) = \sqrt{\frac{1}{n}\Sigma_{i=1}^{n}{({h(x^i) -y^i})^2}}$$

책의 내용과 별도로 제가 알아본 바로는 RMSE는 루트라는 무거운 연산에 비해 크게 메리트가 없어 다음과 같은 MSE를 좀 더 보편적으로 쓴다고 합니다.

$$MSE(X,h) = \frac{1}{n}\Sigma_{i=1}^{n}{({h(x^i) -y^i})^2}$$


2.3 데이터 가져오기

이제 본격적으로 실제 데이터를 가져와 직접 데이터를 다뤄보도록 하겠습니다. 저는 Google의 Colab 환경에서 코드 작성을 진행하였습니다.

2.3.1 데이터 다운로드

다음 코드에서는 다운로드 링크를 통해 csv 파일 형식으로 데이터셋을 받아온 후 별도의 디렉토리에 넣어줍니다.

import os
import tarfile
from six.moves import urllib

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"

def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
    if not os.path.isdir(housing_path):
        os.makedirs(housing_path)
    tgz_path = os.path.join(housing_path, "housing.tgz")
    urllib.request.urlretrieve(housing_url, tgz_path)
    housing_tgz = tarfile.open(tgz_path)
    housing_tgz.extractall(path=housing_path)
    housing_tgz.close()

fetch_housing_data()

그리고 pandas 라이브러리를 이용하여 데이터를 읽어들입니다.

pandas란 python의 데이터 분석 라이브러리로써 행과 열로 이루어진 데이터 객체를 만들어 대용량의 데이터를 효과적으로 다루게 해줍니다. 이는 다른 포스팅에서 추후에 좀 더 구체적으로 다뤄보겠습니다.

import pandas as pd

def load_housing_data(housing_path=HOUSING_PATH):
  csv_path = os.path.join(housing_path, "housing.csv")
  return pd.read_csv(csv_path)

load_housing_data()

2.3.2 데이터 구조 훑어보기

이제 좀 더 구체적으로 데이터가 어떤 특성을 가지고 어떻게 구성되어있는지 알아보겠습니다. 우선 head()를 사용하여 처음 다섯 행을 확인해보겠습니다. 여기서 하나의 행은 하나의 구역을 나타내며 특성은 longitude부터 ocean_proximity까지 총 10개입니다.

info() 메서드를 통해 데이터에 대한 간략한 설명 그 중에서도 전체 행 수, 각 feature의 데이터 타입과 Null이 아닌 값들의 개수 등을 확인할 때 유용합니다.


- 데이터셋에는 20640개의 샘플로 상당히 적은 양에 속하지만, 초보자가 다루기엔 충분합니다.

- 4번째 feature인 total_bedrooms만 20433개의 Null이 아닌 값을 가지는데, 이는 나머지 207개의 구역이 이 feature를 가지고 있지 않기 때문입니다.

- ocean_proximity를 제외한 모든 feature의 데이터 타입은 숫자형입니다. ocean_proximity는 head()에서 데이터가 반복되는 것으로 보아서 범주형(categorical) 데이터이기 때문에 value_contents()를 통해 각 카테고리별 속해있는 구역을 확인해봅니다.

describe()를 통해 ocean_proximity 이외에 숫자형 feature의 요약 정보를 확인해보겠습니다. count, mean, std 등은 다들 통계를 공부해봤다면 알법한 쉬운 정보들로 구성되어있습니다.

데이터를 훑어보는데는 많은 방법들이 있겠지만, 직접 그래프로 시각화하여 보는 것만큼 효과적인 방법은 없다고 생각합니다. 다음은 숫자형 feature를 히스토그램으로 그려본 결과입니다.


2.3.4 테스트 세트 만들기

다음 코드를 통해 모델의 성능을 측정하기 위한 테스트 데이터셋을 20% 정도 떼어 놓겠습니다. 우선 데이터를 쪼갤 함수를 만든 후 쪼갠 후, train set과 data set의 크기를 비교해보았습니다.

import numpy as np

def split_train_test(data, test_ratio):
  shuffled_indices = np.random.permutation(len(data))
  test_set_size = int(len(data)*test_ratio)
  test_indices = shuffled_indices[:test_set_size]
  train_indices = shuffled_indices[test_set_size:]
  return data.iloc[train_indices], data.iloc[test_indices]

그런데 위와 같은 방식으로 했을 때의 문제점은 함수를 실행할 때마다 다른 test set을 생성한다는 것입니다. 즉 모델이 전체 데이터셋을 보게 되는 것이죠. 이를 피하기 위해 np.random.permutation(np.random.seed(42))와 같이 랜덤값 생성 시 초깃값을 지정할 수 있습니다.

하지만 이마저도 업데이트 시 데이터는 바뀌었는데 초깃값은 그대로니 test set이 변경된다는 문제가 있습니다. 따라서 안정적인 데이터 분할을 위해선 각 샘플에 identifyer를 사용하여 test set에 보낼지 말지 결정하는 것입니다.

 예를 들어 identifyer의 해시값이 20%보다 작다면 test set으로 보내는 식입니다. 이를 통해 데이터셋이 업데이트 되더라도 기존에 training set에 있던 데이터가 test set에 들어가는 것을 방지할 수 있습니다.

from zlib import crc32

def test_set_check(identifier, test_ratio):
  return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32

def split_train_test_by_id(data, test_ratio, id_column):
  ids = data[id_column]
  in_test_set = ids.apply(lambda id : test_set_check(id, test_ratio))
  return data.loc[~in_test_set],data.loc[in_test_set]

 이때 원래 데이터셋에는 identifyer에 대한 column이 없기 때문에 column의 인덱스를 ID로 사용하겠습니다.

housing_with_id = housing.reset_index()
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

 그러나 이렇게 행의 인덱스를 identifyer로 사용하기 위해서는 새로운 데이터셋은 끝에 추가되어야하며 어떤 행도 삭제가 되면 안됩니다. 좀 더 안정적인 identifyer를 사용하기 원한다면 구역의 longitude와 latitude처럼 몇백년이 지나도 변하지 않는 features를 연결하여 다음과 같이 ID를 만들 수 있습니다.

'사이킷런'도 데이터셋을 여러 서브셋으로 나누는 기능을 제공합니다. 가장 간단한 함수인 train_test_split은 난수 초깃값을 지정할 random_state 매개변수가 있고, 행의 개수가 같은 여러 데이터셋을 동시에 넘겨 같은 인덱스를 기반으로 나누게 할 수 있습니다.

from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size = 0.2, random_state = 42)

지금까지 우리가 본 것은 순수한 무작위 샘플링 방식입니다. 그러나 이러한 방식은 데이터의 대표성을 보장할 수 없습니다. 예를 들어 남자 400명, 여자 600명으로 구성된 1000개의 데이터셋에서 200개를 test set으로 분할할 때 200개 모두 남자일 수도 있다는 말이 됩니다.

 우선 우리 데이터셋의 소득 분포는 어떻게 되어있는지 보겠습니다. pd.cut() 함수를 이용하여 소득을 5개의 카테고리로 구분한 후 이를 히스토그램으로 표현합니다.

housing["income_cat"] = pd.cut(housing["median_income"],
                               bins=[0,1.5,3.0,4.5,6,np.inf],
                               labels=[1,2,3,4,5])

 이제 사이킷런의 StratifiedShuffleSplit을 사용하여 비율을 유지하여 샘플링을 하는 계층 샘플링을 하고 비율을 확인해보겠습니다.

from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state = 42)
for train_index, test_index in split.split(housing,housing["income_cat"]):
  strat_train_set = housing.loc[train_index]
  strat_test_set = housing.loc[test_index]


 여기까지가 프로젝트 진행을 위해 문제를 정의하고, pandas와 sklearn을 활용하여 데이터의 특성을 파악하는 법을 알아보았습니다. 다음 포스팅에 계속 이어서 데이터를 어떻게 탐색하고 시각화하는지 공부해보겠습니다.


box-world.tistory.com/43

 

[ 핸즈온 머신러닝 2판 ] pandas, sklearn을 통한 데이터 전처리는 어떻게 하는걸까? (2)

 저번 포스팅에서는 캘리포니아 주택 가격 데이터셋을 가지고 pandas, sklearn을 이용하여 데이터의 특성을 탐색하고, 모델 학습을 위해 test set을 분리하는 다양한 방법에 대해 알아보았습니다.  ��

box-world.tistory.com

box-world.tistory.com/44

 

[ 핸즈 온 머신러닝 2판 ] pandas, sklearn을 통한 모델 학습과 튜닝은 어떻게 하는 것일까? (3)

 이전 2개의 포스팅에 결쳐 우리는 지금까지 문제를 정의하고 데이터를 읽어들여 탐색하였습니다. 그리고 데이터를 training set과 test set으로 나누고 학습을 위한 머신러닝 알고리즘에 주입할 데�

box-world.tistory.com

 

반응형