Machine learning 16 (캘리포니아 주택 가격 예측 upgrade)
캘리포니아 주택 가격 예측 모델 만들기
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn
1. 데이터 가져오기
housing = pd.read_csv('./datasets/housing.csv')
2. 데이터 훑어보기
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 |
housing.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 longitude 20640 non-null float64
1 latitude 20640 non-null float64
2 housing_median_age 20640 non-null float64
3 total_rooms 20640 non-null float64
4 total_bedrooms 20433 non-null float64
5 population 20640 non-null float64
6 households 20640 non-null float64
7 median_income 20640 non-null float64
8 median_house_value 20640 non-null float64
9 ocean_proximity 20640 non-null object
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
범주형 특성 탐색
housing['ocean_proximity'].value_counts()
<1H OCEAN 9136
INLAND 6551
NEAR OCEAN 2658
NEAR BAY 2290
ISLAND 5
Name: ocean_proximity, dtype: int64
수치형 특성 탐색
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 |
수치형 특성별 히스토그램
housing.hist(bins=50, figsize=(20, 15))
plt.show()
3. 데이터 세트 분리
- 훈련 데이터/ 테스트 데이터
계층적 샘플링(Straityfied sampling)
bins = [0, 1.5, 3.0, 4.5, 6.0, np.inf]
labels = [1, 2, 3, 4, 5]
housing['income_cat'] = pd.cut(housing['median_income'], bins=bins, labels=labels)
housing['income_cat'].value_counts() # 도수
3 7236
2 6581
4 3639
5 2362
1 822
Name: income_cat, dtype: int64
housing['income_cat'].value_counts() / len(housing) # 상대도수
3 0.350581
2 0.318847
4 0.176308
5 0.114438
1 0.039826
Name: income_cat, dtype: float64
from sklearn.model_selection import train_test_split
# 무작위 샘플링
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
# 계층적 샘플링
strat_train_set, strat_test_set = train_test_split(housing, stratify= housing['income_cat'], test_size=0.2, random_state=42)
strat_test_set['income_cat'].value_counts() / len(strat_test_set)
3 0.350533
2 0.318798
4 0.176357
5 0.114583
1 0.039729
Name: income_cat, dtype: float64
데이터 되돌리기
strat_train_set = strat_train_set.drop('income_cat', axis=1)
strat_test_set = strat_test_set.drop('income_cat', axis=1)
4. 데이터 탐색
# 훈련세트만을 대상으로 데이터 탐색할 예정 (strat_test_set는 최종 예측에 사용)
housing = strat_train_set.copy()
4.1 지리적 데이터 시각화
# longitude(경도) : 동서
# latitude(위도) : 남북
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.3, grid=True)
<AxesSubplot:xlabel='longitude', ylabel='latitude'>
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.3, grid=True,
c='median_house_value', cmap='jet', colorbar=True, figsize=(10, 7), # color 를 통해서 주택가격 표시
s= housing['population']/100, sharex=False) # size 를 통해서 상대적인 인구수를 표시
<AxesSubplot:xlabel='longitude', ylabel='latitude'>
지리적 데이터 분석 결과 : 해안가이면서 밀집 지역일수록 주택 가격이 높음
4.2 상관관계 조사
- 상관계수
# 모든 수치형 특성간의 상관계수 확인(타깃 포함)
corr_matrix = housing.corr()
corr_matrix
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | |
---|---|---|---|---|---|---|---|---|---|
longitude | 1.000000 | -0.924478 | -0.105848 | 0.048871 | 0.076598 | 0.108030 | 0.063070 | -0.019583 | -0.047432 |
latitude | -0.924478 | 1.000000 | 0.005766 | -0.039184 | -0.072419 | -0.115222 | -0.077647 | -0.075205 | -0.142724 |
housing_median_age | -0.105848 | 0.005766 | 1.000000 | -0.364509 | -0.325047 | -0.298710 | -0.306428 | -0.111360 | 0.114110 |
total_rooms | 0.048871 | -0.039184 | -0.364509 | 1.000000 | 0.929379 | 0.855109 | 0.918392 | 0.200087 | 0.135097 |
total_bedrooms | 0.076598 | -0.072419 | -0.325047 | 0.929379 | 1.000000 | 0.876320 | 0.980170 | -0.009740 | 0.047689 |
population | 0.108030 | -0.115222 | -0.298710 | 0.855109 | 0.876320 | 1.000000 | 0.904637 | 0.002380 | -0.026920 |
households | 0.063070 | -0.077647 | -0.306428 | 0.918392 | 0.980170 | 0.904637 | 1.000000 | 0.010781 | 0.064506 |
median_income | -0.019583 | -0.075205 | -0.111360 | 0.200087 | -0.009740 | 0.002380 | 0.010781 | 1.000000 | 0.687160 |
median_house_value | -0.047432 | -0.142724 | 0.114110 | 0.135097 | 0.047689 | -0.026920 | 0.064506 | 0.687160 | 1.000000 |
# 중간 주택가격(타깃)가 특성들간의 상관관계 확인
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687160
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population -0.026920
longitude -0.047432
latitude -0.142724
Name: median_house_value, dtype: float64
- 산점도
attributes = ['median_house_value', 'median_income', 'total_rooms', 'housing_median_age']
pd.plotting.scatter_matrix(housing[attributes], figsize=(12, 8), alpha=0.3)
plt.show()
# 중간 주택가격(타깃)과 중간소득의 산점도
housing.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1, grid=True)
<AxesSubplot:xlabel='median_income', ylabel='median_house_value'>
4.3 특성 조합을 실험
# 가구당 방의갯수
# 전체방에서 침실방 차지 비율
# 가구당 인구수
housing['rooms_per_households']= housing['total_rooms'] / housing['households']
housing['bedrooms_per_rooms']= housing['total_bedrooms'] / housing['total_rooms']
housing['population_per_households']= housing['population'] / housing['households']
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687160
rooms_per_households 0.146285
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population_per_households -0.021985
population -0.026920
longitude -0.047432
latitude -0.142724
bedrooms_per_rooms -0.259984
Name: median_house_value, dtype: float64
5. 데이터 전처리
# strat_train_set (데이터 탐색, 데이터 전처리)
# strat_test_set (최종 예측측)
# 특성(X)과 레이블(y)을 분리
housing = strat_train_set.drop('median_house_value', axis=1) # 특성 (X 데이터)
housing_label = strat_train_set['median_house_value'].copy() # 레이블 (y 데이터)
housing.shape, housing_label.shape
((16512, 9), (16512,))
5.1 데이터 전처리(1) - 결손값 처리
결손값(Null/NaN) 처리 방법
- 옵션1 : 해당 구역 제거
- 옵션2 : 전체 특성 삭제
- 옵션3 : 어떤 값으로 대체(0, 평균, 중간값 등)
scikit-learn의 전처리기를 이용하여 옵션3 을 처리
# <scikit-learn의 전처리기(변환기)들 예시>
# PolynomialFeatures : 다항 특성 추가
# StandardScaler : 표준화(평균 0, 분산 1)
# MinMaxScaler : 정규화(최소 0, 최대 1)
# LabelEncoder, OrdinalEncoder : 숫자로 변환
# OneHotEncoder : OneHot Encoding
# SimpleImputer : 누락된 데이터 대체
# 함수를 이용한 전처리기
# 나만의 전처리기
# 수치형 데이터만 준비
housing_num = housing.drop('ocean_proximity', axis=1)
housing_num.columns
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
'total_bedrooms', 'population', 'households', 'median_income'],
dtype='object')
# 수치형 데이터만 준비(또다른 방법)
# housing_num = housing.select_dtypes(include=[np.number])
# housing_num.columns
# SimpleImputer를 결측값을 대체(옵션3) 할 수 있음
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='median') # 변환기 객체 생성 (중앙값을 대체)
imputer.fit(housing_num) # 변환할 준비 (중앙값 구하기)
SimpleImputer(strategy='median')
imputer.statistics_
array([-118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. ,
408. , 3.5409])
# 위에서 imputer가 구해준 중앙값과 동일일
housing_num.median()
longitude -118.5100
latitude 34.2600
housing_median_age 29.0000
total_rooms 2119.5000
total_bedrooms 433.0000
population 1164.0000
households 408.0000
median_income 3.5409
dtype: float64
X = imputer.transform(housing_num) # 변환 (중앙값으로 대체)
# transform의 결과는 numpy이므로 df로 바꿔서 확인인
# X_df = pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
# X_df.info()
5.2 데이터 전처리(2) - 데이터 인코딩
- 데이터 인코딩을 하는 이유는 머신러닝에서 수치값만 기대하기 때문
housing_cat = housing[['ocean_proximity']] # 2차원 데이터프레임으로 준비
(1) 레이블 인코딩
# pandas
pd.factorize(housing['ocean_proximity'])
(array([0, 0, 1, ..., 2, 0, 3], dtype=int64),
Index(['<1H OCEAN', 'NEAR OCEAN', 'INLAND', 'NEAR BAY', 'ISLAND'], dtype='object'))
# scikit-learn 변환기
from sklearn.preprocessing import OrdinalEncoder # LabelEncoder는 1차원 데이터를 기대대
ordinal_encoder = OrdinalEncoder()
ordinal_encoder.fit_transform(housing_cat)
array([[0.],
[0.],
[4.],
...,
[1.],
[0.],
[3.]])
(2) 원핫 인코딩
숫자의 크기가 모델 훈련과정에서 잘못된 영향을 줄 수 있으므로 원핫 인코딩
# pandas
pd.get_dummies(housing_cat)
ocean_proximity_<1H OCEAN | ocean_proximity_INLAND | ocean_proximity_ISLAND | ocean_proximity_NEAR BAY | ocean_proximity_NEAR OCEAN | |
---|---|---|---|---|---|
17606 | 1 | 0 | 0 | 0 | 0 |
18632 | 1 | 0 | 0 | 0 | 0 |
14650 | 0 | 0 | 0 | 0 | 1 |
3230 | 0 | 1 | 0 | 0 | 0 |
3555 | 1 | 0 | 0 | 0 | 0 |
... | ... | ... | ... | ... | ... |
6563 | 0 | 1 | 0 | 0 | 0 |
12053 | 0 | 1 | 0 | 0 | 0 |
13908 | 0 | 1 | 0 | 0 | 0 |
11159 | 1 | 0 | 0 | 0 | 0 |
15775 | 0 | 0 | 0 | 1 | 0 |
16512 rows × 5 columns
# scikit-learn 변환기
from sklearn.preprocessing import OneHotEncoder
onehot_encoder = OneHotEncoder(sparse=False)
onehot_encoder.fit_transform(housing_cat)
array([[1., 0., 0., 0., 0.],
[1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1.],
...,
[0., 1., 0., 0., 0.],
[1., 0., 0., 0., 0.],
[0., 0., 0., 1., 0.]])
onehot_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
5.3 데이터 전처리(3) - 특성 스케일링
- 표준화 (Z score Standardize) : 평균 0, 표준편차 1
- 정규화 (Min Max Scaling) : 0~1 사이로 정규화 (참고 : 특잇값에 영향을 받음)
- 로그 스케일링 : 데이터의 분포가 왜곡되어 있을때 주로 사용
arr = np.arange(9).reshape(3, 3)
arr
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
Z_arr = (arr - arr.mean())/arr.std()
Z_arr.mean(), Z_arr.std()
(0.0, 1.0)
M_arr = (arr - arr.min())/(arr.max()-arr.min())
M_arr.min(), M_arr.max()
(0.0, 1.0)
# pandas
def minmax_normalize(arr):
return (arr - arr.min())/(arr.max()-arr.min())
def zscore_standardize(arr):
return (arr - arr.mean())/arr.std()
# scikit-learn 변환기
# (1) 표준화
from sklearn.preprocessing import StandardScaler
std_scaler = StandardScaler()
housing_num_std = std_scaler.fit_transform(housing_num)
housing_num_std.mean(0), housing_num_std.std(0) # 컬럼별로 확인 axis=0
(array([-4.35310702e-15, 2.28456358e-15, -4.70123509e-17, 7.58706190e-17,
nan, -3.70074342e-17, 2.07897868e-17, -2.07628918e-16]),
array([ 1., 1., 1., 1., nan, 1., 1., 1.]))
# (2) 정규화
from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler()
housing_num_mm = min_max_scaler.fit_transform(housing_num)
housing_num_mm.min(0), housing_num_mm.max(0)
(array([ 0., 0., 0., 0., nan, 0., 0., 0.]),
array([ 1., 1., 1., 1., nan, 1., 1., 1.]))
# (3) 로그 스케일링
from sklearn.preprocessing import FunctionTransformer
log_transformer = FunctionTransformer(np.log)
log_population = log_transformer.fit_transform(housing_num['population'])
# 로그 변환전
housing_num['population'].hist(bins=50)
plt.show()
# 로그 변환후
log_population.hist(bins=50)
plt.show()
5.4 데이터 전처리(4) - 변환 파이프라인
housing.columns # 9개 특성성
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
'total_bedrooms', 'population', 'households', 'median_income',
'ocean_proximity'],
dtype='object')
from sklearn.pipeline import Pipeline
# 나만의 변환기 만들기
# 1. 아래 형식을 그대로 가져가기
from sklearn.base import BaseEstimator, TransformerMixin
class 나만의변환기(BaseEstimator, TransformerMixin):
def __init__():
# todo
def fit(self, X, y=None):
# todo
return self
def transform():
# todo
return 변형된 데이터
# 2. fit과 transform 함수 위주로 코드를 채우기
# 3. 변환기 초기 값이 필요하다면 __init__함수 채우기
수치형 파이프라인 (1)
- 특성 조합 실험(비율 특성 추가)
# housing['rooms_per_households']= housing['total_rooms'] / housing['households']
# housing['bedrooms_per_rooms']= housing['total_bedrooms'] / housing['total_rooms']
# housing['population_per_households']= housing['population'] / housing['households']
# 위와 같이 특성의 조합으로 또다른 비율을 만들어내는 변환기
from sklearn.base import BaseEstimator, TransformerMixin
rooms_idx = 0
bedrooms_idx = 1
population_idx = 2
households_idx = 3
class CombinedAttributeAdder(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def fit(self, X, y=None):
return self
def transform(self, X):
# print(type(X)) # <class 'numpy.ndarray'>
rooms_per_households = X[:, rooms_idx] / X[:, households_idx]
bedrooms_per_rooms = X[:, bedrooms_idx] / X[:, rooms_idx]
population_per_households= X[:, population_idx] / X[:, households_idx]
return np.c_[rooms_per_households, bedrooms_per_rooms, population_per_households]
# (1) 변환기
# adder = CombinedAttributeAdder()
# adder.fit_transform(housing[['total_rooms', 'total_bedrooms', 'population', 'households']].values)
# (2) 변환기가 들어간 pipeline
ratio_add_pipeline = Pipeline([("imputer", SimpleImputer(strategy='median')), # 누락된값 처리하는 변환기
("ratio_adder", CombinedAttributeAdder()), # 특성의 비율을 추가하는 변환기
("std_scaler", StandardScaler()) # 표준화화
])
X_txed = ratio_add_pipeline.fit_transform(housing[['total_rooms', 'total_bedrooms', 'population', 'households']])
X_txed.shape # 3개의 새로운 특성
(16512, 3)
# 참고
# (1) 변환기에 DataFrame이 입력으로 들어왔을 경우
# ratio_transformer = FunctionTransformer(lambda X:X.iloc[:, 0]/X.iloc[:, 1])
# ratio_transformer.fit_transform(housing[['total_rooms', 'total_bedrooms']])
# (2) 변환기에 Numpy가가 입력으로 들어왔을 경우
# ratio_transformer = FunctionTransformer(lambda X:X[:, 0]/X[:, 1])
# ratio_transformer.fit_transform(housing[['total_rooms', 'total_bedrooms']].values)
수치형 파이프라인 (2)
- 왜곡된 수치형 데이터 로그 변환
log_attribs = ['total_rooms', 'total_bedrooms', 'population', 'households', 'median_income']
# (1) 변환기
# log_transformer = FunctionTransformer(np.log)
# log_transformer.fit_transform(housing[log_attribs])
# (2) 변환기가 들어간 pipeline
log_pipeline = Pipeline([("imputer", SimpleImputer(strategy='median')), # 누락된값 처리하는 변환기
("log_transformer", FunctionTransformer(np.log)), # 로그 변환한 변환기
("std_scaler", StandardScaler()) # 표준화
])
X_txed = log_pipeline.fit_transform(housing[log_attribs])
X_txed.shape
(16512, 5)
수치형 파이프라인 (3)
- 지리 정보를 이용한 유사도 특성 추가
# 군집(clustering)
from sklearn.cluster import KMeans
kmeans = KMeans(10, random_state=42) # 임으로 n_clusters = 10로 잡음, 튜닝으로 통해 조절할 수 있음
kmeans.fit(housing[["longitude", "latitude"]])
KMeans(n_clusters=10, random_state=42)
kmeans.labels_ # 각 지리 정보(위경도)가 어느 그룹(군집)에 들어가 있는지 알 수 있음
array([3, 3, 0, ..., 9, 4, 8])
kmeans.labels_.shape, np.unique(kmeans.labels_) # 16512개의 샘플이 0~9까지의 어느 그룹에 속해있음음
((16512,), array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
kmeans.cluster_centers_ # 10개 그룹의 대표가 되는 위치 (centroid)
array([[-116.93039241, 32.94387835],
[-120.98844304, 37.79105063],
[-119.67640496, 34.92431129],
[-121.93487754, 37.24841847],
[-118.28601504, 34.05003133],
[-121.27772651, 38.92353188],
[-123.05567568, 40.52867568],
[-119.58903465, 36.6092698 ],
[-122.32390873, 37.94684921],
[-117.63027119, 33.91575424]])
cluster_centers = kmeans.cluster_centers_ # 10개의 중심점 위치
cluster_labels = np.unique(kmeans.labels_) # 10개의 unique한 그룹 이름름
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.3, grid=True,
c=housing_label, cmap='jet', colorbar=True, figsize=(10, 7), # color 를 통해서 주택가격 표시
s= housing['population']/100, sharex=False) # size 를 통해서 상대적인 인구수를 표시
plt.scatter(cluster_centers[:, 0], cluster_centers[:, 1], c= cluster_labels,
marker='^', linewidths=2, edgecolors='k', s=100)
for cluster_label in cluster_labels:
plt.annotate(cluster_label, (cluster_centers[cluster_label, 0], cluster_centers[cluster_label, 1]))
plt.show()
kmeans.cluster_centers_
array([[-116.93039241, 32.94387835],
[-120.98844304, 37.79105063],
[-119.67640496, 34.92431129],
[-121.93487754, 37.24841847],
[-118.28601504, 34.05003133],
[-121.27772651, 38.92353188],
[-123.05567568, 40.52867568],
[-119.58903465, 36.6092698 ],
[-122.32390873, 37.94684921],
[-117.63027119, 33.91575424]])
from sklearn.metrics.pairwise import rbf_kernel
# rbf_kernel(원본특성, 랜드마크(군집의 중심점))
similarity = rbf_kernel(housing[['longitude', 'latitude']], kmeans.cluster_centers_, gamma=1.0)
# gamma값이 클수록 종모양이 얇아짐, 조금만 랜드마크에서 벗어나도 유사도 값이 급격히 떨어짐
housing[['longitude', 'latitude']].head(3)
longitude | latitude | |
---|---|---|
17606 | -121.89 | 37.29 |
18632 | -121.93 | 37.05 |
14650 | -117.20 | 32.77 |
kmeans.labels_[:3]
array([3, 3, 0])
similarity[:3].round(2)
array([[0. , 0.35, 0. , 1. , 0. , 0.05, 0. , 0. , 0.54, 0. ],
[0. , 0.24, 0. , 0.96, 0. , 0.02, 0. , 0. , 0.38, 0. ],
[0.9 , 0. , 0. , 0. , 0.06, 0. , 0. , 0. , 0. , 0.22]])
# 위에서 similarity를 구하기까지 과정을 나만의 변환기로 만들어보기
class ClusterSimilarity(BaseEstimator, TransformerMixin):
def __init__(self, n_clusters=10, gamma=1.0, random_state=None):
self.n_clusters = n_clusters
self.gamma = gamma
self.random_state = random_state
def fit(self, X):
# 랜드마크로 사용할 군집의 중심점 찾기
self.kmeans = KMeans(self.n_clusters, random_state=self.random_state)
self.kmeans.fit(X) # self.kmeans.cluster_centers_ : 군집의 중심점
return self
def transform(self, X):
# 유사도 특성 with rbf_kernel()
return rbf_kernel(X, self.kmeans.cluster_centers_, self.gamma)
cluster_similarity = ClusterSimilarity(n_clusters=10, gamma=1.0, random_state=42)
X_txed = cluster_similarity.fit_transform(housing[['longitude', 'latitude']])
X_txed.shape # 10개의 유사도 특성으로 변환
(16512, 10)
수치형 파이프라인 (4)
- 위에서 처리 안된 나머지 특성들에 대해 처리할 파이프라인
# 남은 수치형 데이터에 대해서 기본 변환을 수행행
# 누락된 데이터를 중앙값으로 대체 -> 표준화
from sklearn.pipeline import Pipeline
num_pipeline = Pipeline([('imputer', SimpleImputer(strategy='median')),
('std_scaler', StandardScaler())
])
X_txed = num_pipeline.fit_transform(housing[['housing_median_age']]) # pipeline의 중간결과를 확인하고 싶을 때
X_txed.shape
(16512, 1)
# 범주형 데이터
# (1) 원핫 인코딩
oh_encoder = OneHotEncoder(sparse=False)
X_txed = oh_encoder.fit_transform(housing_cat)
X_txed.shape # 1개의 범주 데이터가 5개의 특성으로 변환환
(16512, 5)
oh_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
# 수치형 파이프라인과 범주형 변환기를 한번에 연결할 파이프라인
from sklearn.compose import ColumnTransformer
ratio_attribs = ['total_rooms', 'total_bedrooms', 'population', 'households']
log_attribs = ['total_rooms', 'total_bedrooms', 'population', 'households', 'median_income']
geo_attribs = ['longitude', 'latitude']
num_attribs = ['housing_median_age']
cat_attrib = ['ocean_proximity']
full_pipeline = ColumnTransformer([('ratio_add_pipeline', ratio_add_pipeline, ratio_attribs), # 4->3
('log_pipeline', log_pipeline, log_attribs), # 5->5
('cluster_similarity', cluster_similarity, geo_attribs), # 2->10
('num_pipeline', num_pipeline, num_attribs), # 1->1
('oh_encoder', OneHotEncoder(), cat_attrib) # 1->5
])
from sklearn import set_config
set_config(display='diagram')
full_pipeline
ColumnTransformer(transformers=[('ratio_add_pipeline', Pipeline(steps=[('imputer', SimpleImputer(strategy='median')), ('ratio_adder', CombinedAttributeAdder()), ('std_scaler', StandardScaler())]), ['total_rooms', 'total_bedrooms', 'population', 'households']), ('log_pipeline', Pipeline(steps=[('imputer', SimpleImputer(strategy='median')), ('log_transformer', Func... StandardScaler())]), ['total_rooms', 'total_bedrooms', 'population', 'households', 'median_income']), ('cluster_similarity', ClusterSimilarity(random_state=42), ['longitude', 'latitude']), ('num_pipeline', Pipeline(steps=[('imputer', SimpleImputer(strategy='median')), ('std_scaler', StandardScaler())]), ['housing_median_age']), ('oh_encoder', OneHotEncoder(), ['ocean_proximity'])])
['total_rooms', 'total_bedrooms', 'population', 'households']
SimpleImputer(strategy='median')
CombinedAttributeAdder()
StandardScaler()
['total_rooms', 'total_bedrooms', 'population', 'households', 'median_income']
SimpleImputer(strategy='median')
FunctionTransformer(func=<ufunc 'log'>)
StandardScaler()
['longitude', 'latitude']
ClusterSimilarity(random_state=42)
['housing_median_age']
SimpleImputer(strategy='median')
StandardScaler()
['ocean_proximity']
OneHotEncoder()
housing_prepared = full_pipeline.fit_transform(housing) # 9->24
housing.shape, housing_prepared.shape # full_pipeline의 변환과정 참조(3+5+10+1+5=24)
((16512, 9), (16512, 24))
6. 모델 선택과 훈련
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
lin_reg = LinearRegression()
tree_reg = DecisionTreeRegressor(random_state=42)
rf_reg = RandomForestRegressor(random_state=42)
# LinearRegression 교차검증
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_label, scoring="neg_mean_squared_error", cv=10, n_jobs=-1)
lin_rmse = np.sqrt(-lin_scores.mean())
lin_rmse
70465.2204537786
# DecisionTree 교차검증
tree_scores = cross_val_score(tree_reg, housing_prepared, housing_label, scoring="neg_mean_squared_error", cv=10, n_jobs=-1)
tree_rmse = np.sqrt(-tree_scores.mean())
tree_rmse
66020.64913260654
# RandomForest 교차검증
rf_scores = cross_val_score(rf_reg, housing_prepared, housing_label, scoring="neg_mean_squared_error", cv=10, n_jobs=-1)
rf_rmse = np.sqrt(-rf_scores.mean())
rf_rmse
47437.993049142875
7. 모델 세부 튜닝
그리드 탐색
from sklearn.model_selection import GridSearchCV
rf_reg = RandomForestRegressor(random_state=42)
param_grid = {'n_estimators' : [30, 50, 100], 'max_features' : [2, 4, 6, 8]} # 3 * 4 = 12가지 조합의 파라미터로 설정된 모델 준비
grid_search = GridSearchCV(rf_reg, param_grid, scoring='neg_mean_squared_error', cv=5, n_jobs=-1) # 3 * 4 * 5 = 60번의 학습과 검증
%time grid_search.fit(housing_prepared, housing_label)
Wall time: 1min 56s
GridSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42), n_jobs=-1, param_grid={'max_features': [2, 4, 6, 8], 'n_estimators': [30, 50, 100]}, scoring='neg_mean_squared_error')
RandomForestRegressor(random_state=42)
grid_search.best_params_
{'max_features': 6, 'n_estimators': 100}
grid_search.best_estimator_
RandomForestRegressor(max_features=6, random_state=42)
cv_results = grid_search.cv_results_
for mean_score, params in zip(cv_results['mean_test_score'], cv_results['params']):
print(np.sqrt(-mean_score), params)
47891.34288823899 {'max_features': 2, 'n_estimators': 30}
47169.125867866416 {'max_features': 2, 'n_estimators': 50}
46690.46070410521 {'max_features': 2, 'n_estimators': 100}
45477.42757836075 {'max_features': 4, 'n_estimators': 30}
45068.23348933645 {'max_features': 4, 'n_estimators': 50}
44841.58911329538 {'max_features': 4, 'n_estimators': 100}
45568.51047423452 {'max_features': 6, 'n_estimators': 30}
45087.90182566366 {'max_features': 6, 'n_estimators': 50}
44828.68681565303 {'max_features': 6, 'n_estimators': 100}
45949.02401992588 {'max_features': 8, 'n_estimators': 30}
45467.35380478292 {'max_features': 8, 'n_estimators': 50}
45223.3852492662 {'max_features': 8, 'n_estimators': 100}
랜덤 탐색
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_distribs = {'n_estimators' : randint(low=1, high=200),
'max_features' : randint(low=1, high=8)}
rnd_search = RandomizedSearchCV(rf_reg, param_distribs, n_iter=10, scoring='neg_mean_squared_error', cv=5, n_jobs=-1, random_state=42)
%time rnd_search.fit(housing_prepared, housing_label)
Wall time: 2min 7s
RandomizedSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42), n_jobs=-1, param_distributions={'max_features': <scipy.stats._distn_infrastructure.rv_frozen object at 0x000001A811AF15E0>, 'n_estimators': <scipy.stats._distn_infrastructure.rv_frozen object at 0x000001A811AF1EE0>}, random_state=42, scoring='neg_mean_squared_error')
RandomForestRegressor(random_state=42)
rnd_search.best_params_
{'max_features': 5, 'n_estimators': 100}
rnd_search.best_estimator_
RandomForestRegressor(max_features=5, random_state=42)
cv_results = rnd_search.cv_results_
for mean_score, params in zip(cv_results['mean_test_score'], cv_results['params']):
print(np.sqrt(-mean_score), params)
45041.730292303386 {'max_features': 7, 'n_estimators': 180}
46752.37041884076 {'max_features': 5, 'n_estimators': 15}
45650.379226623554 {'max_features': 3, 'n_estimators': 72}
46164.83291573049 {'max_features': 5, 'n_estimators': 21}
45133.48593404881 {'max_features': 7, 'n_estimators': 122}
45613.17814850227 {'max_features': 3, 'n_estimators': 75}
45600.50228507359 {'max_features': 3, 'n_estimators': 88}
44836.98033742228 {'max_features': 5, 'n_estimators': 100}
45398.46016007823 {'max_features': 3, 'n_estimators': 150}
59191.59539622242 {'max_features': 5, 'n_estimators': 2}
# 참고
# 지수분포
# https://en.wikipedia.org/wiki/Exponential_distribution
# 로그 유니폼 분포
# https://en.wikipedia.org/wiki/Reciprocal_distribution
# param_distribs = {
# 'kernel': ['linear', 'rbf'],
# 'C': reciprocal(20, 200000), # 로그유니폼분포
# 'gamma': expon(scale=1.0), # 지수분포
# }
# reciprocal : 주어진 범위 안에서 균등 분포로 샘플링. 하이파라미터의 스케일에 대해 잘 모를때 사용용
# expon : 하이파라미터의 스케일에대해 어느 정도 알고 있을 때 사용용
best_model = grid_search.best_estimator_
모델의 특성 중요도
- 특성 중요도는 트리 기반 모델만 제공
feature_importances = best_model.feature_importances_
ratio_attribs2 = ['rooms_per_households', 'bedrooms_per_rooms', 'population_per_households'] # 4->3
log_attribs2 = ['log_total_rooms', 'log_total_bedrooms', 'log_population', 'log_households', 'log_median_income'] #5->5
geo_attribs2 = [f"cluster {i} simil" for i in range(10)] # 2->10
num_attribs2 = ['housing_median_age']
onehot_encoder = full_pipeline.named_transformers_['oh_encoder']
cat_attribs2 = list(onehot_encoder.categories_[0])
attributes = ratio_attribs2 + log_attribs2 + geo_attribs2 + num_attribs2 + cat_attribs2
sorted(zip(feature_importances, attributes), reverse=True)
[(0.2576956816996954, 'log_median_income'),
(0.11703279268644332, 'INLAND'),
(0.08015137648745436, 'population_per_households'),
(0.06985873996329428, 'bedrooms_per_rooms'),
(0.06285966855845389, 'rooms_per_households'),
(0.05868250612061571, 'cluster 7 simil'),
(0.04506986699596869, 'cluster 4 simil'),
(0.03374684970693329, 'cluster 2 simil'),
(0.03128206488726454, 'cluster 0 simil'),
(0.03049414177281283, 'cluster 5 simil'),
(0.030155937532237793, 'cluster 3 simil'),
(0.027123602400565244, 'cluster 9 simil'),
(0.02409421115929644, 'housing_median_age'),
(0.02343349222053902, 'cluster 8 simil'),
(0.0200291417344945, 'cluster 1 simil'),
(0.016661105497784944, 'cluster 6 simil'),
(0.013747960320276175, 'log_total_rooms'),
(0.01366812936377742, 'log_population'),
(0.01228796968230117, '<1H OCEAN'),
(0.01190708007699941, 'log_total_bedrooms'),
(0.011874893652075296, 'log_households'),
(0.00565657900627869, 'NEAR OCEAN'),
(0.002440776125258964, 'NEAR BAY'),
(4.543234917849789e-05, 'ISLAND')]
8. 모델 예측과 성능 평가
- 테스트 데이터 변환
X_test = strat_test_set.drop('median_house_value', axis=1)
y_test = strat_test_set['median_house_value'].copy()
X_test.shape, y_test.shape
((4128, 9), (4128,))
# 훈련데이터에 대해서 전처리 했던 것들
# (1) 수치 데이터 -> 4개의 pipeline 모음
# (2) 범주 데이터 -> 원핫인코딩
# ==> 테스트에 대해서도 동일하게 처리해주자!
# 훈련 데이터를 변경할때는 파이프라인의 fit_transform()을 사용
# 테스트 데이터를 변경할때는 파이프라인의 transform()을 사용
X_test_prepared = full_pipeline.transform(X_test)
X_test_prepared.shape
(4128, 24)
- 예측과 평가
from sklearn.metrics import mean_squared_error
final_predictions = best_model.predict(X_test_prepared)
final_rmse = mean_squared_error(y_test, final_predictions, squared=False) # RMSE
final_rmse
42559.78806521266
- 테스트 데이터의 변환과 예측을 한번에
# 전처리와와 모델을 파이프라인으로 연결해서 예측
full_pipeline_with_predictor = Pipeline([('preparation', full_pipeline),
('final', best_model)
])
final_predictions = full_pipeline_with_predictor.predict(X_test)
final_rmse = mean_squared_error(y_test, final_predictions, squared=False) # RMSE
final_rmse
42559.78806521266
일반화 오차 추정
- 테스트 RMSE에 대한 95% 신뢰 구간
from scipy.stats import t
# 추정량 (오차의 제곱들의 합)
squared_erros = (final_predictions - y_test)**2
# 95% 신뢰구간
confidence = 0.95
# 표본의 크기
n = len(squared_erros)
# 자유도 (degree of freedom)
dof = n-1
# 추정량의 평균
m_squared_error = np.mean(squared_erros)
# 표본의 표준편차 (비편상 분산으로 구함)
sample_std = np.std(squared_erros, ddof=1) # n-1로 나눔 (그림에서 U)
# 표준 오차
std_err = sample_std/n**0.5 # (그림에서 U/n**0.5)
mse_ci = t.interval(confidence, dof, m_squared_error, std_err)
rmse_ci = np.sqrt(mse_ci)
rmse_ci
array([40667.20145804, 44371.72349462])
9. 모델 저장
import joblib
joblib.dump(full_pipeline_with_predictor, 'my_model.pkl')
['my_model.pkl']
# 다시 불러오기
loaded_model = joblib.load('my_model.pkl')
final_predictions2 = loaded_model.predict(X_test)
mean_squared_error(y_test, final_predictions2, squared=False) # RMSE
42559.78806521266
댓글남기기