머신러닝을 배울 때는 인공적으로 만들어진 데이터셋이 아닌 실제 데이터로 실험해보는 것이 가장 좋다.
이 장에서 다루는 데이터
캘리포니아의 블록 그룹
마다 인구
, 중간 소득
, 중간 주택 가격
등을 담고 있다.
목표는 데이터로 모델을 학습시켜서 다른 측정 데이터가 주어졌을 때 구역의 중간 주택 가격을 예측하는 것이다.
tip : 잘 훈련된 데이턱 과학자로서 해야할 첫번째 할일은 프로젝트 체크리스트를 준비하는 것이다.
*체크리스트
데이터 처리 "컴포넌트" 들이 연속되어 잇는 것을 파이프 라인 이라고 합니다.
=> 머신 러닝 시스템은 데이터를 조작하고 변환할 일이 많아서 파이프라인을 사용하는 일이 흔하다.
** 컴포넌트의 동작 방법
- 비 동기적으로 동작한다.
- 각 컴포넌트는 많은 데이터를 추출해 처리하고 그 결과를 다른 데이터 저장소로 보낸다.
그러면 일정 시간 후 파이프라인의 다음 컴포넌트가 그 데이터를 추출해 자신의 출력 결과를 만든다.
- 각 컴포넌트는 완전히 독립적이다. 즉, 컴포넌트 사이의 인터페이스는 데이터 저장소 뿐이다.
이는 (데이터 흐름도 덕분에) 시스템을 이해하기 쉽게 만들고, 각 팀은 각자의 컴포넌트에
집중할 수 있다.
주의! : 모니터링이 적절히 되지 않으면 고장난 컴포넌트는 한동안 모를 수 있다. => 즉, 데이터가 만들어진지 오래되면 전체 시스템의 성능이 떨어진다.
우리가 예를 들고 있는 데이터에는 레이블
된 훈련 샘플 값(각 샘플이 기대출력값, 구역의 중간 주택 가격을 가지고 있음)이 있으므로 지도학습
이다.
값을 예측해야 하므로 전형적인 회귀문제
이며, 예측에 사용될 특성이 여러 개(구역의 인구, 중간 소득 등)이므로 다중 회귀문제
이다.
각 구역마다 하나의 값을 예측하므로 단변량 회귀
이다.
구역마다 여러개의 값을 예측한다면 다변량 회귀 이다.
데이터에 연속적인 흐름이 없고, 데이터가 메모리에 들어갈 정도로 충분히 작으므로 일반적인 배치학습
이다.
Tip : 데이터가 매우 크면 (맵리듀스 기술을 이용해서) 배치학습을 여러 서버로 분할하거나, 대신 온라인 학습 기법을 사용할 수 있다.
- 맵 리듀스 : 대표적인 프레임워크는 아파치 하둡(Hadoop)이며, 일반적으로 맵 리듀스에서는 스파크(Spark)의 MLlib을 사용하는 것이 편리하고 성능도 뛰어납니다.
RMSE (평균 제곱근 오차)
이다.
-> 추정 값
MAE (평균 절대 오차)
를 고려할 수 있다.
마지막으로 지금까지 만든 가정을 나열하고 검사해보는 것이 좋다
데이터를 다운로드 하는 스크립트
import os
import tarfile
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):
os.makedirs(housing_path,exist_ok=True) ## 폴더를 만드는 함수
tgz_path = os.path.join(housing_path, 'housing.tgz')
urllib.request.urlretrieve(housing_url, tgz_path)
## tar 아카이브 파일을 압축 해제 하는 법
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()
데이터 head()만 불러오기
housing.head()
결과
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
0 | -122.23 | 37.88 | 41.0 | 880.0 | 129.0 | 322.0 | 126.0 | 8.3252 | 452600.0 | NEAR BAY |
1 | -122.22 | 37.86 | 21.0 | 7099.0 | 1106.0 | 2401.0 | 1138.0 | 8.3014 | 358500.0 | NEAR BAY |
2 | -122.24 | 37.85 | 52.0 | 1467.0 | 190.0 | 496.0 | 177.0 | 7.2574 | 352100.0 | NEAR BAY |
3 | -122.25 | 37.85 | 52.0 | 1274.0 | 235.0 | 558.0 | 219.0 | 5.6431 | 341300.0 | NEAR BAY |
4 | -122.25 | 37.85 | 52.0 | 1627.0 | 280.0 | 565.0 | 259.0 | 3.8462 | 342200.0 | NEAR BAY |
데이터의 간략한 설명과 전체 행 수, 각 특성의 데이터 타입과 null이 아닌 값의 갯수를 확인하기
housing.info()
결과
ocean_proximity 데이터 확인
housing['ocean_proximity'].head()
결과
카테고리별 갯수 확인하기
housing['ocean_proximity'].value_counts()
결과
숫자형 특성의 요약정보 확인하기
housing.describe()
결과
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | |
---|---|---|---|---|---|---|---|---|---|
count | 20640.000000 | 20640.000000 | 20640.000000 | 20640.000000 | 20433.000000 | 20640.000000 | 20640.000000 | 20640.000000 | 20640.000000 |
mean | -119.569704 | 35.631861 | 28.639486 | 2635.763081 | 537.870553 | 1425.476744 | 499.539680 | 3.870671 | 206855.816909 |
std | 2.003532 | 2.135952 | 12.585558 | 2181.615252 | 421.385070 | 1132.462122 | 382.329753 | 1.899822 | 115395.615874 |
min | -124.350000 | 32.540000 | 1.000000 | 2.000000 | 1.000000 | 3.000000 | 1.000000 | 0.499900 | 14999.000000 |
25% | -121.800000 | 33.930000 | 18.000000 | 1447.750000 | 296.000000 | 787.000000 | 280.000000 | 2.563400 | 119600.000000 |
50% | -118.490000 | 34.260000 | 29.000000 | 2127.000000 | 435.000000 | 1166.000000 | 409.000000 | 3.534800 | 179700.000000 |
75% | -118.010000 | 37.710000 | 37.000000 | 3148.000000 | 647.000000 | 1725.000000 | 605.000000 | 4.743250 | 264725.000000 |
max | -114.310000 | 41.950000 | 52.000000 | 39320.000000 | 6445.000000 | 35682.000000 | 6082.000000 | 15.000100 | 500001.000000 |
히스토그램
import matplotlib.pyplot as plt
housing.hist(bins= 50 , figsize = (20,15))
plt.show()
결과
그래프 해석 (세트 분리전 개요 느낌으로)
1) median_incom : 단위 (만 달러) 이며, scale 된 데이터 이다.
2) housing_median_age, median_house_value 는 최대값과 최솟값을 한정하였다.
- median_house_value 는 타깃속성(레이블)으로 사용되었기 때문에 심각한 문제가 된다.
=> 자칫하면 가격이 한곗값을 넘어가지 않도록 머신러닝 알고리즘이 학습될지도 모른다.
- 이때 median_house_value가 $500,000가 넘어가더라도 정확한 예측값이 필요하다면 두가지 방법이 있다.
A. 한계값 밖의 구역에 대한 정확한 레이블을 구한다.
B. 훈련 세트에서 $500,000을 넘어가는 구역을 제거한다. 평가 결과가 매우 나쁠 것이므로 테스트 세트에서도 제거한다.)
3) 스케일들이 서로 다르기 때문에 스케일링이 필요하다.
4) 히스토그램의 꼬리가 두껍다. 이런 분포는 머신러닝이 패턴을 찾기 어렵기 때문에 종 모양의 분포가 되도록 변형시킬 필요가 있다.
데이터 스누핑 (Data Snooping) 편향
이라고 한다.무작위로 어떤 샘플을 서택해서 데이터 셋의 20%정도를 떼어놓는 함수
import numpy as np
def split_train_set(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_inices = 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('train_set : ', len(train_set) , '\n' 'test_set', len(test_set))
결과
위의 코드로 데이터를 분리 했을 때의 문제점
프로그램을 다시 실행하면 다른 테스트 세트가 생성된다. 여러번 계속하면 (머신러닝 알고리즘이) 전체 데이터셋을 보는 셈이므로 이런 상황은 피해야 한다.
해결책
1) 첫 실행에 테스트 세트를 저장하고 다음번에 불러온다.
2) np.random.permutation()
전에 난수발생기의 초기값을 형성한다. (예를 들어 np.random.sedd(42)
)
데이터 셋을 업데이트 한 후에도 안정적인 훈련/ 테스트 분할을 위한 일반적인 해결책은
샘플의 식별자를 사용하여 테스트 세트로 버낼지 말지 정하는 것이다.
=> 샘플이 고유하고 변경 불가능한 식별자를 가지고 있다고 가정)
- 예를 들어
각 샘플마다 식별자의 해시값을 계산하여 해시 최대값의 20%보다 작거나 같은 샘플만 테스트 세트로 보낼 수 있다. => 이렇게 하면 여러번 반복 실행되면서 데이터 셋이 갱신되더라도 테스트 세트가 동일하게 적용, 새로운 테스트 세트는 새 샘플의 20%를 갖게 되지만 이전에 훈련시킨 샘플은 포함시키지 않을 것이다.
코드
from zlib import crc32
import numpy as np
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() # 행의 index를 식별자 컬럼으로 사용한다.
train_set , test_set = split_train_test_by_id(housing_with_id, 0.2 , 'index')
사이킷런에서 데이터셋을 여러 서브셋으로 나누는 다양한 방법을 제공한다.
코드
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()
plt.grid(False) # grid 지우는 함수
결과
소득 카테고리를 기반으로 계층 샘플링을 하는 방법 (Stratified ShuffleSplit 사용)
# 소득 카테고리를 기반으로 계층 샘플링
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)
결과
전체 데이터와 계층샘플링, 무작위 샘플링 비교하기
total_data | hierarchical_data | random_data | diff total, random | dif total, hierarchical | |
---|---|---|---|---|---|
1 | 0.039826 | 0.039971 | 0.040213 | -0.000388 | -0.000145 |
2 | 0.318847 | 0.318798 | 0.324370 | -0.005523 | 0.000048 |
3 | 0.350581 | 0.350533 | 0.358527 | -0.007946 | 0.000048 |
4 | 0.176308 | 0.176357 | 0.167393 | 0.008915 | -0.000048 |
5 | 0.114438 | 0.114341 | 0.109496 | 0.004942 | 0.000097 |
income_cat 특성을 삭제하여 데이터를 원래상태로 되돌린다.
## income_cat 특성 삭제
for set_ in (strat_train_set, strat_test_set, housing , test_set):
set_.drop('income_cat', axis = 1, inplace = True)
21.12.27 일단 오늘 여기까지 그동안 git과 colab을 연결하는 방법을 찾느라 한참 고생해서 결국 못찾아서 다시 공부시작합니다...