머신 러닝 프로젝트 시작하기-데이터 수집

4 minute read

2. 데이터를 수집합니다

순서

새로운 데이터를 쉽게 얻을 수 있도록 최대한 자동화하기

  1. 필요한 데이터와 양을 나열합니다.
  2. 데이터를 얻을 수 있는 곳을 찾아 기록합니다.
  3. 얼마나 많은 공간이 필요한지 확인합니다.
  4. 법률상 의무가 있는지 확인하고 필요하다면 인가를 받습니다.
  5. 접근 권한을 획득합니다.
  6. 작업 환경을 만듭니다(충분한 저장 공간으로).
  7. 데이터를 수집합니다.
  8. 데이터를 조작하기 편리한 형태로 변환합니다(데이터 자체는 바꾸지 않습니다).
  9. 민감한 정보가 삭제되었거나 보호되었는지 검증합니다(예를 들면 개인정보 비식별화).
  10. 데이터의 크기와 타입(시계열, 표본, 지리정보 등)을 확인합니다.
  11. 테스트 세트를 샘플링하여 따로 떼어놓고, 절대 들여다보지 않습니다(데이터 염탐 금지!)

데이터를 얻을 수 있는 곳

캐글 데이터로 머신러닝을 공부하려고 한다. 그래서 캐글 데이터만 다루는 도서를 사서 공부할 예정이다.

[관련 도서]

핸즈온 머신러닝에서는 캘리포니아 주택 가격 데이터셋을 사용한다.

import os
import tarfile
import urllib.request

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/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):
    os.makedirs(housing_path, exist_ok=True)
    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()
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)

판다스

처음 행을 확인한다.

housing = load_housing_data()
housing.head()

info()

데이터에 대한 간략한 설명과 특히 전체 행 수, 각 특성의 데이터 타입과 널이 아닌 값의 개수를 확인하는 데 유용하다.

housing.info()

value_counts()

어떤 카테고리가 있고 각 카테고리 마다 얼마나 많은 구역이 있는지 확인한다.

housing["ocean_proximity"].value_counts()

describe()

숫자형 특성의 요약 정보를 보여준다.

housing.describe()

hits()

데이터의 형태를 빠르게 검토하는 다른 방법이다. 각 숫자형 특성을 히스토그램으로 그려보는 것이다.

%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
plt.show()

데이터세트 나누기

데이터를 깊게 들여다보기전에 테스트 세트를 따로 떼어놓아야한다.
테스트 세트를 만드는 방법엔 두 가지가 제시된다.

1. 사용자 정의 함수 split_train_test() 만들기

python의 sklearn 라이브러리에 내장된 데이터 세트 나누기 함수가 있지만 사용자 임의로 데이터를 나누어야 할 때도 있다.

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]

train_set, test_set = split_train_test(housing, 0.2)
len(train_set)

이 방법의 문제점은 프로그램을 다시 실행할 때 마다 다른 테스트 세트가 생성되므로 여러 번 계속하면 우리는 전체 데이터 셋을 보는셈이 된다는 것이다.

[해결 방법]

  1. 처음 실행에서 테스트세트를 저장하고 다음번 실행에서 이를 불어들인다.
  2. 항상 같은 난수 인덱스가 생성되도록 np.random.permutation()을 호출하기 전에 난수 발생기의 초깃값을 지정한다.

하지만 업데이트된 데이터셋을 사용하는데 문제가 있다. 해결책으로, 샘플의 식별자(여기선 id)를 사용하여 테스트 세트로 보낼지 말지 정하면 된다. 샘플마다 식별자의 해시값을 계산하여 해시 최댓값의 20%보다 작거나 같은 샘플만 테스트 세트로 보낼 수 있다.

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]

housing_with_id = housing.reset_index()

train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
# 행의 인덱스를 고유 식별자로 사용할 때 새 데이터는 데이터셋의 끝에 추가되어야 한다.(식별자 칼럼)

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
# 이것이 불가능할 땐 고유 식별자를 만드는 것도 방법이다. 여기서는 위도와 경도를 이용했다.
# 하지만 이는 여러 구역의 ID가 동일해지는 경우가 생길 수 있긴 하다.

train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

2. 사이킷런의 train_test_split() 함수 사용하기

이 함수는 두 가지 특징이 더 있다.

  1. 앞서 설명한 난수 초깃값을 지정할 수 있는 random_state 매개변수가 있다.
  2. 행의 개수가 같은 여러 개의 데이터셋을 넘겨서 같은 인덱스를 기반으로 나눌 수 있다.

train_test_split() 함수는 파이썬 리스트, 넘파이 배열, 판다스 데이터프레임, 판다스 시리즈객체 등을 입력으로 받을 수 있다.

from sklearn.model_selection import train_test_split

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

계층적 샘플링

샘플링 편향이 생기지 않도록 전체 데이터의 비율을 샘플에서도 유지하는 것이다.

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])
housing["income_cat"].hist()

StratifiedShuffleSplit

StratifiedKFold의 계층 샘플링과 ShuffledSplit의 랜덤 샘플링을 합친 것이다.

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]

strat_test_set["income_cat"].value_counts() / len(strat_test_set)

예시

여기서는 StatLib 데이터 저장소에서 제공되는 데이터를 사용한다.
이 데이터셋은 1990년 캘리포니아 인구조사 데이터 를 기반으로 한다.

1~6. 의 과정을 예시로 살펴보자

데이터를 찾은 뒤 먼저 보안 자격과 접근 권한이 있는지 확인한다. 또한 개인 정보가 담긴 필드가 안전하지 않은 저장소로 복사되지 않는 등의 법적 제약도 검토한다. 이 때문에 정보보안기사 공부를 했던 것도 있다. 데이터 사용에 있어서 법률을 알아야 한다고 생각했다. 그 후 로컬에 jupyter로 작업 환경을 구성한다.

  1. 데이터를 수집합니다.
    데이터 수집에서 2가지 방법이 제시된다.
    1. 웹 브라우저에서 파일을 내려받고 압축을 풀어 CSV 파일을 얻는다. jupyter 노트북에 넣는다.
    2. 최근 데이터를 받기 위해 함수를 작성한다.(혹은 스케쥴링하여 주기적으로 받는다)
  2. 데이터를 조작하기 편리한 형태로 변환합니다(데이터 자체는 바꾸지 않습니다).
    판다스를 이용하여 내려받은 CSV 파일을 내려받는다. 데이터를 읽어 들이는 간단한 함수도 만든다. 모든 데이터를 담은 판다스 데이터프레임 객체를 반환한다.
    이 과정때문에 판다스에 대한 공부로 따로 해야하지 않나 싶다.
    [관련 도서]
  3. 데이터의 크기와 타입(시계열, 표본, 지리정보 등)을 확인합니다.
    Pandas 참고

  4. 테스트 세트를 샘플링하여 따로 떼어놓고, 절대 들여다보지 않습니다(데이터 염탐 금지!)
    데이터세트 나누기 참고