Tutorial : 구역의 데이터들을 기반으로 중간 주택 가격을 예측하기.
#데이터 다운로드: housing.csv 를 압축한 housing.tgz 파일
- 온라인에서 tgz 파일을 내려받고 csv 로 자동 압축 해제하는 스크립트
* 다운로드 url에 접근하기 위해 urllib, tgz파일 처리를 위해 tarfile 라이브러리를 이용했음
* 필자는 파일을 저장할 디렉토리를 기본 디렉토리 + datasets + housing으로 설정
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 파일을 만듦.
#데이터 읽어들이기: pandas 라이브러리를 활용하여 csv 파일을 읽는 함수를 작성
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)
head() 함수를 이용하여 데이터셋의 처음 다섯 행 확인
housing = load_housing_data()
housing.head()
- column 에 해당하는 특성의 개수는 10개, 그 중 9개는 데이터 float64 타입, 맨 오른쪽 ocean_proximity는 object(여기선 text일 것임)타입.
- info() 함수를 이용하여 데이터에 대한 간략한 설명과 전체 행 수, 각 특성의 데이터 타입과 non-null값의 개수를 확인 할 수 있다.
housing.info()
- ocean_proximity 필드의 데이터 타입은 object이며, 처음 다섯 행에서 ocean_proximity값이 반복적이었기에 이 특성은 범주형(categorical) 일 것. value_counts() 함수를 이용해 카테고리의 종류와 카테고리별 얼마나 많은 구역이 있는지 확인
housing['ocean_proximity'].value_counts()
- describe() 함수를 이용해 숫자형 특성의 요약 정보를 확인
housing.describe()
- std : 값이 퍼져 있는 정도를 측정하는 표준편차.
- 데이터의 형태를 빠르게 검토하기 위해 각 숫자형 특성을 히스토그램으로 그려보자. hist() 메서드를 이용해 모든 숫자형 특성에 대한 히스토그램을 출력할 수 있다.
%matplotlib inline #jupyter notebook 매직 명령
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
plt.show
- %matplotlib inline 을 사용하여 화면에 그래프를 그리기 위한 사용자 컴퓨터의 그래픽 백엔드를 주피터 자체의 백엔드로 사용하도록 설정함.
* median_income을 보면, 중간소득 특성이 US달러로 표현되어 있지 않다는 것을 알 수 있음. 취합한 데이터의 스케일을 조정하고, 상한이 15/ 하한이 0.5 가 되도록 만듦. 머신러닝에서 흔한 경우인 데이터 전처리의 결과임을 알 수 있다.
보완점
- 중간 주택 연도와 중간 주택 가격 역시 최댓값과 최솟값을 한정함. 하지만 중간 주택 가격의 경우 타깃 레이블로 사용되기 때문에 심각한 문제가 될 수 있음. (알고리즘이 한곗값을 넘어가지 않도록 학습할 수 있다.) 이때 취할 수 있는 방법은 1) 한곗값 밖의 구역에 대한 정확한 레이블을 구하거나 2) 훈련 세트/테스트 세트에서 한곗값 밖의 구역을 제거한다.
- 특성들의 스케일이 제각각 다르기 때문에 특성 스케일링이 필요함
- 많은 히스토그램의 꼬리가 두꺼움. (데이터가 편향되어있음). 이러한 특성들을 좀 더 종모양의 분포가 되도록 변형시킬 필요가 있다.
# 테스트 세트 만들기
- 간단하게 무작위적으로 샘플을 선택해서 데이터셋의 20%정도를 분리해놓으면 문제 발생: 프로그램을 실행할 때마다 다른 테스트 세트가 생성, 프로그램은 결과적으로 전체 데이터셋을 보게 됨.
- 해결책으로 처음 실행시 테스트 세트를 저장하고 다음번 실행부터 이를 불러들이는 것과, 난수 인덱스를 고정시켜 난수 발생기의 초깃값을 지정하는 것.
- 그러나 두가지 방법 모두 업데이트된 데이터셋을 사용할때 문제가 발생. 따라서 샘플의 식별자를 사용하여 테스트 세트를 결정한다. 여기서는 각 샘플마다 식별자의 해시값을 계산하여, 해시의 마지막 바이트의 값이 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]
- 주택 데이터셋에는 식별자 column이 없으므로 행의 인덱스를 ID 로 사용한다.
housing_with_id = housing.reset_index() #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']
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, '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)
테스트 세트를 추출할 때는 전체 표본을 대표하도록 잘 추출해야 하는데, 계층에 따라서 비율이 동일하도록 테스트 세트를 추출하는 것을 계층적 샘플링(stratified sampling)이라 한다.
이번 프로젝트에서는 중간 소득(median_income)이 중간 주택 가격을 예측하는데 아주 중요하다고 가정하므로 테스트 세트가 전체 데이터셋에 있는 여러 소득 카테고리를 잘 대표해야 한다. 따라서 소득에 대한 카테고리 특성을 만들어 중간 소득의 히스토그램을 자세히 들여다 볼 필요가 있다.
# 소득 카테고리 개수제한을 위해 1.5로 나눔
housing['income_cat'] = np.ceil(housing['median_income']/1.5)
# 5 이상은 5로 레이블
housing['income_cat'].where(housing['income_cat']<5, 5.0, inplace=True)
housing['income_cat'].hist()
소득 카테고리를 기반으로 계층 샘플링을 사이킷런의 StratifiedShuffleSplit을 사용하여 수행한다.
* StratifiedShuffleSplit는 StratifiedKFold의 계층 샘플링과 ShuffleSplit의 랜덤 샘플링을 합친 것으로 test_size와 train_size의 매개변수의 합을 1 이하로 지정할 수 있다.
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]
의도대로 되었는지 살펴보기 위해 전체 주택 데이터셋에서 소득 카테고리의 비율을 살펴본다.
housing['income_cat'].value_counts()/len(housing)
아래의 코드는 전체 데이터셋 대비 무작위 샘플링과 계층 샘플링의 소득 카테고리 비율을 비교한 것이다.
def income_cat_proportions(data):
return data["income_cat"].value_counts() / len(data)
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
compare_props = pd.DataFrame({
"Overall": income_cat_proportions(housing),
"Stratified": income_cat_proportions(strat_test_set),
"Random": income_cat_proportions(test_set),
}).sort_index()
compare_props["Rand. %error"] = 100 * compare_props["Random"] / compare_props["Overall"] - 100
compare_props["Strat. %error"] = 100 * compare_props["Stratified"] / compare_props["Overall"] - 100
- Random 샘플링에 비해 Stratified 샘플링이 전체 데이터셋에 있는 소득 카테고리의 비율과 거의 같은 것을 볼 수 있다.
'Artificial Intelligence > Machine Learning' 카테고리의 다른 글
핸즈온 머신러닝(3) - 머신러닝 프로젝트 4 (0) | 2020.01.21 |
---|---|
핸즈온 머신러닝(3) - 머신러닝 프로젝트 3 (0) | 2020.01.20 |
핸즈온 머신러닝(3) - 머신러닝 프로젝트 1 (0) | 2020.01.20 |
핸즈온 머신러닝(2)- 머신러닝의 주요 도전 과제 요약 (0) | 2020.01.20 |
핸즈온 머신러닝(1) - 머신러닝 시스템의 종류 (0) | 2020.01.20 |
댓글