본문 바로가기

데이터사이언스 공부

인구조사 데이터를 활용하여 캘리포니아의 주택 가격 모델 만들기

반응형

1. 작업환경 만들기


export ML_PATH="$HOME/ml"
mkdir -p $ML_PATH
pip3 --version
pip3 install --upgrade pip
pip3 install --user --upgrade virtualenv // 에러 -> sudo -H pip install virtualenv

1-2. 독립적인 환경 만들기


cd $ML\_PATH
virtualenv env
source env/bin/activate // 활성화

환경이 활성화되면 어떤 패키지든 독립된 이 환경에 설치되고 파이썬은 이 패키지만 사용하게 된다.

1-3. 패키지 설치


pip3 install --upgrade jupyter matplotlib numpy pandas scipy scikit-learn

pip 명령으로 필요한 패키지와 의존성으로 연결된 다른 패키지를 모두 설치한 후, 설치 확인을 위해 다음과 같이 패키지를 모두 임포트 해본다.


python3 -c "import jupyter, matplotlib, numpy, pandas, scipy, sklearn"

어떤 에러나 메시지도 출력되지 않은 것을 확인하면, jupyter notebook 명령어를 통해 주피터를 실행한다.

데이터 가져오기

일반적으로 우리가 다룰 데이터는 관계형 데이터베이스(또는 다른 데이터 저장소)에 들어있고 여러 테이블, 문서, 파일로 나누어져있다. 이런 데이터에 접근하려면 먼저 보안 자격과 접근 권한이 있어야 하고 그 데이터의 구조를 잘 아ㅏㄹ고 있어야 한다. 하지만 이 프로젝트에서는 모든 데이터가 CSV파일인 housing.csv를 압축한 housing.tgz파일을 내려받기만 하면 가능하다.

tar xzf housing.tgz 명령을 실행해서 압축을 풀 수도 있지만, 간단한 함수를 만들어 사용하면 편하다. 특히 데이터가 정기적으로 변경되면 최근 데이터가 필요할 때마다 스크립트를 실행하면 되니 유용하다(또는 스케줄링하여 주기적 자동실행 가능). 데이터를 내려받는 일을 자동화하면 여러 기기에 데이터셋을 설치할 때도 편리하다.


import os
import tarfile
from six.moves import urllib

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):
    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()를 호출하면 datasets/housing 디렉터리를 만들고 housing.tgz파일을 내려받고 같은 디렉터리에 압축을 풀어 housing.csv파일을 만든다.

데이터 살펴보기

일반적으로 housing.describe(), housing.info() 등을 사용하여 데이터를 살펴볼 수 있다.
데이터의 형태를 빠르게 검토하는 다른 방법은 각 숫자형 특성을 히스토그램으로 그려보는 것이다. 히스토그램은 주어진 값의 범위에 속한 샘플 수(수직축)을 나타낸다.


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

hist() 메서드는 맷플롯립을 사용하고 결국 화면에 그래프를 그리기 위해 사용자 컴퓨터의 그래픽 백엔드를 필요로 한다. 그래서 그래프를 그리기 전에 맷플롯립이 사용할 백엔드를 지정해줘야 한다. 주피터의 매직 명령 %matplotlib inline을 사용하면 편리하다. 이 명령은 맷플롯립이 주피터 자체의 백엔드를 사용하도록 설정한다.

테스트 세트 만들기

테스트 세트에서 겉으로 드러난 패턴에 속아 머신러닝 모델을 선택하게 하는 것을 막기 위해 데이터 일부를 떼어놉니다. 테스트 세트로 일반화 오차를 추정하면 매우 낙관적인 추정이 되어 기대한 성능이 나오지 않게 되는데 이것을 데이터 스누핑(data snooping)편향 이라고 합니다.


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)
print(len(train_set), "train +",len(test_set), "test")

테스트 비율만큼 랜덤 순열 인덱스들을 shuffled_indices에 저장합니다. 이는 무작위로 샘플을 선택해서 데이터셋의 20% 정도를 떼어놓는 함수입니다.

이와 같이 테스트 세트를 구현할 수는 있지만 프로그램을 다시 실행하면 다른 테스트 세트가 생성되고 이것이 반복되면 결국 전체 데이터셋을 보는 셈입니다.

이에 따른 첫번째 해결책은 테스트 세트를 저장하고 다음번 실행에서 불러들이는 것과, 같은 난수 인덱스가 생성되도록 npmrandom.seed(42)를 사용하여 초깃값을 지정하는 것입니다.

하지만 이 두 해법 모두 다음번 업데이트 된 데이터셋을 사용하면 문제가 됩니다. 일반적인 해결책은 샘플의 식별자를 사용하여 테스트 세트로 보낼지 말지 정하는 것인데 예를 들어 각 샘플마다 식별자의 해시값을 계산하여 해시의 마지막 바이트의 값이 51(256의 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() #인덱스 열이 추가된 데이터 프레임 반환
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
# print(housing)
# print(housing_with_id)
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

전문가가 중간 소득이 중간 주택 가격 예측에 매우 중요하다고 언급했다면 테스트 세트가 전체 데이터 셋에 있는 여러 소득 카테고리를 잘 대표해야 한다. 중간 소득이 연속적인 숫자형 특성이므로 소득에 대한 카테고리 특성을 만들어야 한다. 골고루 계층을 분리하기 위해 중간소득을 1.5로 나누고 올림해서 소득 카테고리 특성을 만들고, 5보다 큰 카테고리는 5로 합친다.


housing["income_cat"] = np.ceil(housing["median_income"]/1.5)
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)

계층 샘플링은 사이킷런의 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]

계층 샘플링을 사용하여 만든 테스트 세트가 전체 데이터셋에 있는 소득 카테고리의 비율과 거의 같음을 비교할 수 있다.


pd.DataFrame({'전체':housing["income_cat"].value_counts()/len(housing),
            '계층':strat_test_set["income_cat"].value_counts()/len(strat_test_set),
             '무작위':test_set["income_cat"].value_counts()/len(test_set)}, index=list(range(1,6)))
반응형