My Data Preprocessing
Machine Learning
데이터 전처리 과정 익히기
기본설정
# 파이썬 ≥3.5
import sys
assert sys.version_info >= (3, 5)
# 사이킷런 ≥0.20
import sklearn
assert sklearn.__version__ >= "0.20"
- 필요한 모듈 불러오기
import numpy as np
import os
- 깔끔한 그래프 출력을 위한 설정
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
- 그림 저장 위치 지정
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "end_to_end_project"
IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID)
os.makedirs(IMAGES_PATH, exist_ok=True)
def save_fig(fig_id, tight_layout=True, fig_extension="png", resolution=300):
path = os.path.join(IMAGES_PATH, fig_id + "." + fig_extension)
print("그림 저장:", fig_id)
if tight_layout:
plt.tight_layout()
plt.savefig(path, format=fig_extension, dpi=resolution)
1. 데이터 가져오기
- 온라인 상에서 저장된 압축 파일을 가져와서 csv파일로 저장
import os
import tarfile
import urllib.request
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/codingalzi/handson-ml/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "notebooks/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()
- cvs파일을 판다스 데이터프레임으로 불러오기
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()
all = housing.copy()
- 데이터셋 기본 정보 확인하기
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 |
- 수치형 데이터 특성별 히스토그램
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()
그림 저장: attribute_histogram_plots
2. 테스트/훈련세트 만들기
- 노트북의 실행결과가 동일하게 하기위함
np.random.seed(42)
- 테스트 세트와 훈련 세트 구별하여 만들기
import numpy as np
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"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")
test_set.head()
index | longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | id | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
59 | 59 | -122.29 | 37.82 | 2.0 | 158.0 | 43.0 | 94.0 | 57.0 | 2.5625 | 60000.0 | NEAR BAY | -122252.18 |
60 | 60 | -122.29 | 37.83 | 52.0 | 1121.0 | 211.0 | 554.0 | 187.0 | 3.3929 | 75700.0 | NEAR BAY | -122252.17 |
61 | 61 | -122.29 | 37.82 | 49.0 | 135.0 | 29.0 | 86.0 | 23.0 | 6.1183 | 75000.0 | NEAR BAY | -122252.18 |
62 | 62 | -122.29 | 37.81 | 50.0 | 760.0 | 190.0 | 377.0 | 122.0 | 0.9011 | 86100.0 | NEAR BAY | -122252.19 |
67 | 67 | -122.29 | 37.80 | 52.0 | 1027.0 | 244.0 | 492.0 | 147.0 | 2.6094 | 81300.0 | NEAR BAY | -122252.20 |
- 사이킷런의 무작위 구분 함수
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
test_set.head()
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
20046 | -119.01 | 36.06 | 25.0 | 1505.0 | NaN | 1392.0 | 359.0 | 1.6812 | 47700.0 | INLAND |
3024 | -119.46 | 35.14 | 30.0 | 2943.0 | NaN | 1565.0 | 584.0 | 2.5313 | 45800.0 | INLAND |
15663 | -122.44 | 37.80 | 52.0 | 3830.0 | NaN | 1310.0 | 963.0 | 3.4801 | 500001.0 | NEAR BAY |
20484 | -118.72 | 34.28 | 17.0 | 3051.0 | NaN | 1705.0 | 495.0 | 5.7376 | 218600.0 | <1H OCEAN |
9814 | -121.93 | 36.62 | 34.0 | 2351.0 | NaN | 1063.0 | 428.0 | 3.7250 | 278000.0 | NEAR OCEAN |
계층별 특성을 반영하지 못한다는 단점이 있음!
계층적 샘플링
- 전체 데이터셋 중간소득 히스토그램
housing["median_income"].hist()
<matplotlib.axes._subplots.AxesSubplot at 0x7f02877522d0>
-
대부분 구역의 중간 소득이 1.5~6.0(15,000~60,000$) 사이
-
소득 구간을 아래 숫자를 기준으로 5개로 구분
[0, 1.5, 3.0, 4.6, 6.0, np,inf]
-
5개의 카테고리를 갖는 특성 추가
- 특성값: 1, 2, 3, 4, 5
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"].value_counts()
3 7236
2 6581
4 3639
5 2362
1 822
Name: income_cat, dtype: int64
housing["income_cat"].hist()
<matplotlib.axes._subplots.AxesSubplot at 0x7f0285695f10>
- 계층별 샘플링 실행
housing["income_cat"]
기준
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)
3 0.350533
2 0.318798
4 0.176357
5 0.114583
1 0.039729
Name: income_cat, dtype: float64
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
- 무작위 샘플링과 계층별 샘플링 결과 비교
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
compare_props
Overall | Stratified | Random | Rand. %error | Strat. %error | |
---|---|---|---|---|---|
1 | 0.039826 | 0.039729 | 0.040213 | 0.973236 | -0.243309 |
2 | 0.318847 | 0.318798 | 0.324370 | 1.732260 | -0.015195 |
3 | 0.350581 | 0.350533 | 0.358527 | 2.266446 | -0.013820 |
4 | 0.176308 | 0.176357 | 0.167393 | -5.056334 | 0.027480 |
5 | 0.114438 | 0.114583 | 0.109496 | -4.318374 | 0.127011 |
-
데이터 되돌리기
-
income_cat 특성 삭제
for set_ in (strat_train_set, strat_test_set):
set_.drop("income_cat", axis=1, inplace=True)
3. 데이터 탐색과 시각화
- 훈련세트 원본을 그대로 두고 복사해서 사용.
- 훈련세트만을 대상으로 탐색과 시각화 적용
- 데이터 스누핑 편향 방지 용도
housing = strat_train_set.copy()
상관관계 조사
- 모든 수치형 특성 간의 표준 상관계수 계산
corr_matrix = housing.corr()
- 중간 주택 가격과 다른 특성 간의 상관관계 확인
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
- 특성들 사이의 상관관계를 나타내는 산점도
from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
save_fig("scatter_matrix_plot")
그림 저장: scatter_matrix_plot
- 중간 소득 대 중간 주택 가격 산점도
housing.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
4. 특성 조합으로 실험
- 구역별 방의 총 개수와 침실의 총 개수 대신 아래 특성이 보다 유용함
- 가구당 방의 개수
- 방당 침실 개수
- 가구당 인원
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]
att = housing.copy()
- 상관관계 다시 확인
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687160
rooms_per_household 0.146285
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population_per_household -0.021985
population -0.026920
longitude -0.047432
latitude -0.142724
bedrooms_per_room -0.259984
Name: median_house_value, dtype: float64
6. 머신러닝 알고리즘을 위한 데이터 준비
- 훈련세트를 위해 레이블 삭제
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
-
데이터 정제
## 수치형 특성
- 결측값이 있는 데이터 확인
sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|
4629 | -118.30 | 34.07 | 18.0 | 3759.0 | NaN | 3296.0 | 1462.0 | 2.2708 | <1H OCEAN |
6068 | -117.86 | 34.01 | 16.0 | 4632.0 | NaN | 3038.0 | 727.0 | 5.1762 | <1H OCEAN |
17923 | -121.97 | 37.35 | 30.0 | 1955.0 | NaN | 999.0 | 386.0 | 4.6328 | <1H OCEAN |
13656 | -117.30 | 34.05 | 6.0 | 2155.0 | NaN | 1039.0 | 391.0 | 1.6675 | INLAND |
19252 | -122.79 | 38.48 | 7.0 | 6837.0 | NaN | 3468.0 | 1405.0 | 3.1662 | <1H OCEAN |
- 결측값을 중간값으로 대체하는 방식으로 선택 !
median = housing["total_bedrooms"].median()
sample_incomplete_rows["total_bedrooms"].fillna(median, inplace=True)
sample_incomplete_rows
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|
4629 | -118.30 | 34.07 | 18.0 | 3759.0 | 433.0 | 3296.0 | 1462.0 | 2.2708 | <1H OCEAN |
6068 | -117.86 | 34.01 | 16.0 | 4632.0 | 433.0 | 3038.0 | 727.0 | 5.1762 | <1H OCEAN |
17923 | -121.97 | 37.35 | 30.0 | 1955.0 | 433.0 | 999.0 | 386.0 | 4.6328 | <1H OCEAN |
13656 | -117.30 | 34.05 | 6.0 | 2155.0 | 433.0 | 1039.0 | 391.0 | 1.6675 | INLAND |
19252 | -122.79 | 38.48 | 7.0 | 6837.0 | 433.0 | 3468.0 | 1405.0 | 3.1662 | <1H OCEAN |
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
- 중간값은 수치형 특성에서만 가능하니 텍스트 특성을 없앤다.
housing_num = housing.drop("ocean_proximity", axis=1)
- 변환기를 적용한다.
imputer.fit(housing_num)
SimpleImputer(add_indicator=False, copy=True, fill_value=None,
missing_values=nan, strategy='median', verbose=0)
- 변환기를 통해 구해진 중간값이 확인된다.
imputer.statistics_
array([-118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. ,
408. , 3.5409])
- 수동으로 구한 값들과 비교하여 모든 중간값들이 맞는지 확인하자
housing_num.median().values
array([-118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. ,
408. , 3.5409])
-
동일함을 확인할 수 있다.
-
훈련세트 변환
중간값으로 결측값이 채우지는것이다.
X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index=housing_num.index)
housing_tr.loc[sample_incomplete_rows.index.values]
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | |
---|---|---|---|---|---|---|---|---|
4629 | -118.30 | 34.07 | 18.0 | 3759.0 | 433.0 | 3296.0 | 1462.0 | 2.2708 |
6068 | -117.86 | 34.01 | 16.0 | 4632.0 | 433.0 | 3038.0 | 727.0 | 5.1762 |
17923 | -121.97 | 37.35 | 30.0 | 1955.0 | 433.0 | 999.0 | 386.0 | 4.6328 |
13656 | -117.30 | 34.05 | 6.0 | 2155.0 | 433.0 | 1039.0 | 391.0 | 1.6675 |
19252 | -122.79 | 38.48 | 7.0 | 6837.0 | 433.0 | 3468.0 | 1405.0 | 3.1662 |
- 중간값으로 채워짐이 확인된다.
housing_tr.head()
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | |
---|---|---|---|---|---|---|---|---|
17606 | -121.89 | 37.29 | 38.0 | 1568.0 | 351.0 | 710.0 | 339.0 | 2.7042 |
18632 | -121.93 | 37.05 | 14.0 | 679.0 | 108.0 | 306.0 | 113.0 | 6.4214 |
14650 | -117.20 | 32.77 | 31.0 | 1952.0 | 471.0 | 936.0 | 462.0 | 2.8621 |
3230 | -119.61 | 36.31 | 25.0 | 1847.0 | 371.0 | 1460.0 | 353.0 | 1.8839 |
3555 | -118.59 | 34.23 | 17.0 | 6592.0 | 1525.0 | 4459.0 | 1463.0 | 3.0347 |
범주형 특성 (텍스트)
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(10)
ocean_proximity | |
---|---|
17606 | <1H OCEAN |
18632 | <1H OCEAN |
14650 | NEAR OCEAN |
3230 | INLAND |
3555 | <1H OCEAN |
19480 | INLAND |
8879 | <1H OCEAN |
13685 | INLAND |
4937 | <1H OCEAN |
4861 | <1H OCEAN |
- 범주형 특성을 OrdinalEncoder 변환기를 통해 수치형으로 바꿔주었다.
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]
array([[0.],
[0.],
[4.],
[1.],
[0.],
[1.],
[0.],
[1.],
[0.],
[0.]])
즉, 숫자의 크기가 각 카테고리를 의미한다.
ordinal_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
하지만 숫자의 크기가 비교될 수 있기 때문에 잘못된 학습을 할 수 있으므로 원핫을 사용하자.
- 원핫
원핫 변환기는 숫자의 크기가 아닌 위치를 표시함으로써 각 카테고리를 분류한다.
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
<16512x5 sparse matrix of type '<class 'numpy.float64'>'
with 16512 stored elements in Compressed Sparse Row format>
각 카테고리에 따른 1의 위치가 다름을 알 수 있다.
housing_cat_1hot.toarray()
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.]])
각 1의 위치에 따른 카테고리 분류이다.
cat_encoder.categories_
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
나만의 변환기 만들기
- 추가 특성을 위한 나만의 변환기
3가지의 특성을 추가하는 변환기이다. “add_bedroos_per_room” 특성의 추가 여부는 사용자의 선택에 따라 추가/제외 할 수 있도록 구현되어있다.
from sklearn.base import BaseEstimator, TransformerMixin
col_names = "total_rooms", "total_bedrooms", "population", "households"
rooms_ix, bedrooms_ix, population_ix, households_ix = [
housing.columns.get_loc(c) for c in col_names] # 열 인덱스 구하기
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room=True): # *args 또는 **kargs 없음
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self # 아무것도 하지 않습니다
def transform(self, X):
rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
population_per_household = X[:, population_ix] / X[:, households_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household,
bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.to_numpy())
데이터셋에 추가된 것을 확인 할 수 있다. 예시에서는 add_bedrooms_per_room 특성의 추가를 하지 않음을 선택하였다.
housing_extra_attribs = pd.DataFrame(
housing_extra_attribs,
columns=list(housing.columns)+["rooms_per_household", "population_per_household"],
index=housing.index)
housing_extra_attribs.head()
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | ocean_proximity | rooms_per_household | population_per_household | |
---|---|---|---|---|---|---|---|---|---|---|---|
17606 | -121.89 | 37.29 | 38 | 1568 | 351 | 710 | 339 | 2.7042 | <1H OCEAN | 4.62537 | 2.0944 |
18632 | -121.93 | 37.05 | 14 | 679 | 108 | 306 | 113 | 6.4214 | <1H OCEAN | 6.00885 | 2.70796 |
14650 | -117.2 | 32.77 | 31 | 1952 | 471 | 936 | 462 | 2.8621 | NEAR OCEAN | 4.22511 | 2.02597 |
3230 | -119.61 | 36.31 | 25 | 1847 | 371 | 1460 | 353 | 1.8839 | INLAND | 5.23229 | 4.13598 |
3555 | -118.59 | 34.23 | 17 | 6592 | 1525 | 4459 | 1463 | 3.0347 | <1H OCEAN | 4.50581 | 3.04785 |
- 수치형 특성 전처리를 위한 파이프 라인
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
- 결측치 채우는 변환기, 특성추가 변환기, 표준화 변환기가 있다.
housing_num_tr
array([[-1.15604281, 0.77194962, 0.74333089, ..., -0.31205452,
-0.08649871, 0.15531753],
[-1.17602483, 0.6596948 , -1.1653172 , ..., 0.21768338,
-0.03353391, -0.83628902],
[ 1.18684903, -1.34218285, 0.18664186, ..., -0.46531516,
-0.09240499, 0.4222004 ],
...,
[ 1.58648943, -0.72478134, -1.56295222, ..., 0.3469342 ,
-0.03055414, -0.52177644],
[ 0.78221312, -0.85106801, 0.18664186, ..., 0.02499488,
0.06150916, -0.30340741],
[-1.43579109, 0.99645926, 1.85670895, ..., -0.22852947,
-0.09586294, 0.10180567]])
- 범주형 특성 전처리 범주형 특성 전처리와 결합하여 하나의 큰 파이프 라인
from sklearn.compose import ColumnTransformer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
full_pipeline = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared
array([[-1.15604281, 0.77194962, 0.74333089, ..., 0. ,
0. , 0. ],
[-1.17602483, 0.6596948 , -1.1653172 , ..., 0. ,
0. , 0. ],
[ 1.18684903, -1.34218285, 0.18664186, ..., 0. ,
0. , 1. ],
...,
[ 1.58648943, -0.72478134, -1.56295222, ..., 0. ,
0. , 0. ],
[ 0.78221312, -0.85106801, 0.18664186, ..., 0. ,
0. , 0. ],
[-1.43579109, 0.99645926, 1.85670895, ..., 0. ,
1. , 0. ]])
- 총 16개의 특성이 전처리 되어 훈련에 사용됨.
- 기존의 수치형 특성 8 개 + 추가 특성 3개 + 원-핫 인코딩 카테고리 5개
- 훈련 세트 크기: 16512 개
housing_prepared.shape
(16512, 16)
7. 모델 선택과 훈련
선형 회귀 모델
준비된 데이터로 선형회귀 모델을 훈련시켜본다.
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
- RMSE 평가하기
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
68628.19819848923
- MAE와 비교
from sklearn.metrics import mean_absolute_error
lin_mae = mean_absolute_error(housing_labels, housing_predictions)
lin_mae
49439.89599001897
MAE와 비교해보니 성능이 좋지 못하다.
# 변환 이전의 모델의 성능 저장
ori_rmse = lin_rmse
이후 변환 모델과의 성능 비교를 위해 저장해둔다.
결정 트리 모델
DecisionTreeRegressor로 결정트리 모델을 훈련 시켜보자
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)
DecisionTreeRegressor(ccp_alpha=0.0, criterion='mse', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=42, splitter='best')
- RMSE를 확인하여 보자
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse
0.0
- RMSE가 0 이므로 과대적합 즉, 믿을 수 없다.
랜덤 포레스트 회귀 모델
RandomForestRegressor을 통해 랜덤포레스트 회귀모델을 훈련시킨다.
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(housing_prepared, housing_labels)
RandomForestRegressor(bootstrap=True, ccp_alpha=0.0, criterion='mse',
max_depth=None, max_features='auto', max_leaf_nodes=None,
max_samples=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
n_estimators=100, n_jobs=None, oob_score=False,
random_state=42, verbose=0, warm_start=False)
- RMSE
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
18603.515021376355
성능이 좋아 보인다. 하지만 한번의 실험으로 알 수 없으니 교차 검증을 해보자
교차 검증
def display_scores(scores):
print("점수:", scores)
print("평균:", scores.mean())
print("표준 편차:", scores.std())
- 선형 회귀 모델을 cross_val_score를 이용해 교차 검증을 해 성능을 테스트해본다
cv = 10 즉 10번의 훈련을 해서 평균값을 추출한다.
from sklearn.model_selection import cross_val_score
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)
점수: [66782.73843989 66960.118071 70347.95244419 74739.57052552
68031.13388938 71193.84183426 64969.63056405 68281.61137997
71552.91566558 67665.10082067]
평균: 69052.46136345083
표준 편차: 2731.674001798344
- 결정 트리 모델을 cross_val_score를 이용해 교차 검증을 해 성능을 테스트해본다
scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
display_scores(tree_rmse_scores)
점수: [70194.33680785 66855.16363941 72432.58244769 70758.73896782
71115.88230639 75585.14172901 70262.86139133 70273.6325285
75366.87952553 71231.65726027]
평균: 71407.68766037929
표준 편차: 2439.4345041191004
- 랜덤 포레스트 회귀 모델을 cross_val_score를 이용해 교차 검증을 해 성능을 테스트해본다
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(housing_prepared, housing_labels)
display_scores(lin_rmse_scores)
점수: [66782.73843989 66960.118071 70347.95244419 74739.57052552
68031.13388938 71193.84183426 64969.63056405 68281.61137997
71552.91566558 67665.10082067]
평균: 69052.46136345083
표준 편차: 2731.674001798344
- 랜덤 포레스트 회귀 모델도 과대 적합으로 보인다. 따라서 선형 회귀모델로 선택한다.
<과제>과제>
<변환 1>을 적용 & 성능 비교
변환 1 : 수평선 삭제
- 수평선 제거한 데이터 셋을 horizon에 담을 것이다.
housing = strat_train_set.copy()
horizon = housing.copy()
45만 선 삭제
median_house_value의 45만 부근 확인
원래 데이터셋에서 45만 부근의 데이터를 확인하기 위해 45만 오차범위 5000이내의 값의 데이터들만 추출해보았다.
near_45 = horizon[ (horizon["median_house_value"] <= 455000.0) & (horizon["median_house_value"] >= 445000.0)]
near_45
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
18040 | -121.97 | 37.23 | 22.0 | 2781.0 | 523.0 | 1291.0 | 516.0 | 4.6065 | 445900.0 | <1H OCEAN |
15774 | -122.45 | 37.76 | 52.0 | 1457.0 | 292.0 | 621.0 | 315.0 | 4.6477 | 450000.0 | NEAR BAY |
10654 | -117.86 | 33.67 | 16.0 | 20.0 | 5.0 | 15.0 | 5.0 | 3.8750 | 450000.0 | <1H OCEAN |
4717 | -118.38 | 34.06 | 29.0 | 3946.0 | 1008.0 | 1676.0 | 876.0 | 2.7824 | 450000.0 | <1H OCEAN |
18203 | -122.05 | 37.38 | 23.0 | 3200.0 | 907.0 | 2029.0 | 866.0 | 3.5649 | 450000.0 | <1H OCEAN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
4714 | -118.37 | 34.06 | 52.0 | 843.0 | 160.0 | 333.0 | 151.0 | 4.5192 | 446000.0 | <1H OCEAN |
4676 | -118.34 | 34.07 | 52.0 | 2066.0 | 319.0 | 981.0 | 297.0 | 5.8632 | 450000.0 | <1H OCEAN |
6583 | -118.21 | 34.21 | 41.0 | 1676.0 | 263.0 | 757.0 | 255.0 | 4.7734 | 450800.0 | <1H OCEAN |
1616 | -122.12 | 37.85 | 18.0 | 5252.0 | 686.0 | 1870.0 | 657.0 | 8.0074 | 454100.0 | NEAR BAY |
10400 | -117.55 | 33.52 | 15.0 | 426.0 | 62.0 | 133.0 | 45.0 | 5.1360 | 447400.0 | <1H OCEAN |
78 rows × 10 columns
이 데이터들이 수평선을 만드는지 산점도를 통해 확인하였다.
near_45.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 45만에서 수평선이 나타남을 확인할 수 있다. 정확히 45만에서 나타나는지 확인해보자
near_45 = horizon[housing.median_house_value == 450000.0]
near_45.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 희미하지만 나타남을 확인할 수 있다. 따라서 45만 수평선을 제거한다.
!= 을 이용해 해당하는 데이터를 삭제하였다.
horizon = horizon[horizon.median_house_value != 450000.0]
horizon.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 분명히 없어짐이 확인 되었다.
35만 선 삭제
median_house_value의 35만 부근 확인
원래 데이터셋에서 35만 부근의 데이터를 확인하기 위해 35만 오차범위 5000이내의 값의 데이터들만 추출해보았다.
near_35 =horizon[ (horizon["median_house_value"] <= 355000.0) & (horizon["median_house_value"] >= 345000.0)]
near_35
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
16969 | -122.31 | 37.54 | 42.0 | 1159.0 | 261.0 | 465.0 | 247.0 | 3.1842 | 352800.0 | NEAR OCEAN |
8709 | -118.35 | 33.85 | 34.0 | 1770.0 | 291.0 | 916.0 | 289.0 | 5.0000 | 354200.0 | <1H OCEAN |
18684 | -121.82 | 36.95 | 16.0 | 2599.0 | 430.0 | 1417.0 | 445.0 | 4.6611 | 349300.0 | <1H OCEAN |
10163 | -117.95 | 33.89 | 17.0 | 1665.0 | 247.0 | 755.0 | 254.0 | 6.5764 | 349000.0 | <1H OCEAN |
5300 | -118.45 | 34.07 | 19.0 | 4845.0 | 1609.0 | 3751.0 | 1539.0 | 1.5830 | 350000.0 | <1H OCEAN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
16904 | -122.35 | 37.58 | 30.0 | 5039.0 | 1564.0 | 2129.0 | 1536.0 | 3.3469 | 345000.0 | NEAR OCEAN |
8725 | -118.36 | 33.83 | 35.0 | 2828.0 | 487.0 | 1439.0 | 490.0 | 5.6013 | 350200.0 | <1H OCEAN |
1545 | -121.93 | 37.73 | 8.0 | 831.0 | 231.0 | 404.0 | 224.0 | 3.3750 | 350000.0 | <1H OCEAN |
5618 | -118.23 | 33.78 | 20.0 | 59.0 | 24.0 | 69.0 | 23.0 | 2.5588 | 350000.0 | NEAR OCEAN |
15270 | -117.29 | 33.08 | 18.0 | 3225.0 | 515.0 | 1463.0 | 476.0 | 5.7787 | 346700.0 | NEAR OCEAN |
236 rows × 10 columns
산점도에서 이 데이터들이 수평선을 이루고 있음을 확인하자
near_35.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 정확히 35만에서 수평선이 나타남을 확인할 수 있다. 정확히 35만에서 나타나는지 확인해보자
near_35 = horizon[horizon.median_house_value == 350000.0]
near_35.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 희미하지만 나타남을 확인할 수 있다. 따라서 35만 수평선을 제거한다.
!= 을 이용해 해당하는 데이터를 삭제하였다.
horizon = horizon[horizon.median_house_value != 350000.0]
horizon.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 분명히 없어짐이 확인 되었다.
28만 선 삭제
median_house_value의 28만 부근 확인
원래 데이터셋에서 28만 부근의 데이터를 확인하기 위해 28만 오차범위 5000이내의 값의 데이터들만 추출해보았다.
near_28 = horizon[ (horizon["median_house_value"] <= 285000.0) & (horizon["median_house_value"] >= 275000.0)]
near_28
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
5825 | -118.30 | 34.19 | 14.0 | 3615.0 | 913.0 | 1924.0 | 852.0 | 3.5083 | 280900.0 | <1H OCEAN |
10461 | -117.65 | 33.46 | 19.0 | 7034.0 | 1139.0 | 2824.0 | 1068.0 | 6.0873 | 277300.0 | <1H OCEAN |
1537 | -122.04 | 37.90 | 20.0 | 5467.0 | 1044.0 | 2310.0 | 963.0 | 5.6986 | 275800.0 | NEAR BAY |
10449 | -117.67 | 33.46 | 24.0 | 3571.0 | 722.0 | 1409.0 | 543.0 | 4.6518 | 277800.0 | <1H OCEAN |
17212 | -119.71 | 34.43 | 47.0 | 1572.0 | 417.0 | 790.0 | 384.0 | 2.6429 | 279200.0 | <1H OCEAN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
14415 | -117.24 | 32.79 | 18.0 | 2539.0 | 616.0 | 964.0 | 526.0 | 3.4306 | 275000.0 | NEAR OCEAN |
5742 | -118.25 | 34.16 | 24.0 | 5131.0 | 1436.0 | 2690.0 | 1371.0 | 2.5668 | 280000.0 | <1H OCEAN |
15827 | -122.42 | 37.75 | 52.0 | 1564.0 | 396.0 | 1162.0 | 374.0 | 3.0000 | 275000.0 | NEAR BAY |
6287 | -117.90 | 34.04 | 15.0 | 11989.0 | 2185.0 | 6652.0 | 2081.0 | 4.5554 | 278300.0 | <1H OCEAN |
108 | -122.24 | 37.82 | 52.0 | 3481.0 | 751.0 | 1444.0 | 718.0 | 3.9000 | 275700.0 | NEAR BAY |
313 rows × 10 columns
산점도를 통해 수평선을 확인하여보자
near_28.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 28만에서 수평선이 나타남을 확인할 수 있다. 정확히 28만에서 나타나는지 확인해보자
exact_28 = horizon[horizon.median_house_value == 280000.0]
exact_28.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 정확히 28만에서는 생기지 않았다. near_28의 분포를 확인해보자
가장 많은 분포값을 알기 위해 value_counts()메소드를 사용해보았다.
near_28["median_house_value"].value_counts()
275000.0 46
283300.0 8
281300.0 7
283200.0 7
279300.0 6
..
275600.0 1
280900.0 1
282900.0 1
281000.0 1
284700.0 1
Name: median_house_value, Length: 93, dtype: int64
- 275000에서 수평선이 생긴 것임이 확인되었다. 이제 275000을 지우자.
!= 을 이용해 해당하는 데이터를 삭제하였다.
exact_275 = horizon[horizon.median_house_value == 275000.0]
exact_275.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
horizon = horizon[horizon.median_house_value != 350000.0]
horizon.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
- 확연히 눈에 띄진 않지만 수평선이 사라졌다.
50만 선 삭제
- 50만 이상의 데이터들을 삭제시킨다.
.drop() 메소드를 활용하였다.
horizon = horizon.drop(horizon.index[horizon["median_house_value"] >= 500000.0])
- 확인해보자
horizon.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
save_fig("income_vs_house_value_scatterplot")
그림 저장: income_vs_house_value_scatterplot
-
확실히 50만 수평선이 삭제됨을 확인할 수 있다. 따라서 모든 수평선 제거도 완료하였다.
-
레이블을 따로 분리하였다.
housing = horizon.drop("median_house_value", axis=1)
housing_labels = horizon["median_house_value"].copy()
- 수치형과 범주형 특성 구분
housing_num = housing.drop("ocean_proximity", axis=1)
housing_cat = housing[["ocean_proximity"]]
- 수치형과 범주형 특성을 전처리하는 파이프라인 적용
housing_prepared = full_pipeline.fit_transform(housing)
- 선형 회귀 모델 적용
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
- RMSE 비교(성능)
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
58503.243939773165
# 원래 모델의 RMSE
ori_rmse
68628.19819848923
58503.243939773165 (변환1 모델) < 68628.19819848923 (원래 모델)
###<결과 평가="">결과>
확실히 변환1의 모델 성능이 좋아졌음을 확인 할 수 있다.
수평선에 있는 이상치들 제거가 성능 향상을 보여주고 있다.
<변환 2>을 적용 & 성능 비교
housing = att.copy()
“rooms_per_household” “bedrooms_per_room” “population_per_household” 의 특성이 추가된 훈련 데이터 셋을 가져온다.
변환 2 : 상관관계 따른 특성 삭제
- 중간 주택 가격과 상관 계수의 절댓값이 0.2보다 작은 값의 인덱스 확인
cancel = corr_matrix["median_house_value"].drop(corr_matrix.index[abs(corr_matrix["median_house_value"]) > 0.2 ]).index
cancel
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
'total_bedrooms', 'population', 'households', 'rooms_per_household',
'population_per_household'],
dtype='object')
- 데이터셋에서 0.2보다 작은 특성들을 삭제한다.
not_necessary = housing.drop(cancel, 1)
확인해보니 남은 특성은 4개이다.
not_necessary
median_income | median_house_value | ocean_proximity | bedrooms_per_room | |
---|---|---|---|---|
17606 | 2.7042 | 286600.0 | <1H OCEAN | 0.223852 |
18632 | 6.4214 | 340600.0 | <1H OCEAN | 0.159057 |
14650 | 2.8621 | 196900.0 | NEAR OCEAN | 0.241291 |
3230 | 1.8839 | 46300.0 | INLAND | 0.200866 |
3555 | 3.0347 | 254500.0 | <1H OCEAN | 0.231341 |
... | ... | ... | ... | ... |
6563 | 4.9312 | 240200.0 | INLAND | 0.185681 |
12053 | 2.0682 | 113000.0 | INLAND | 0.245819 |
13908 | 3.2723 | 97800.0 | INLAND | 0.179609 |
11159 | 4.0625 | 225900.0 | <1H OCEAN | 0.193878 |
15775 | 3.5750 | 500001.0 | NEAR BAY | 0.220355 |
16512 rows × 4 columns
- 전체 수치형 데이터 다시 확인
not_necessary.describe()
median_income | median_house_value | bedrooms_per_room | |
---|---|---|---|
count | 16512.000000 | 16512.000000 | 16354.000000 |
mean | 3.875589 | 206990.920724 | 0.212878 |
std | 1.904950 | 115703.014830 | 0.057379 |
min | 0.499900 | 14999.000000 | 0.100000 |
25% | 2.566775 | 119800.000000 | 0.175304 |
50% | 3.540900 | 179500.000000 | 0.203031 |
75% | 4.744475 | 263900.000000 | 0.239831 |
max | 15.000100 | 500001.000000 | 1.000000 |
- 레이블을 따로 분리하였다.
housing = not_necessary.drop("median_house_value", axis=1)
housing_labels = not_necessary["median_house_value"].copy()
- 수치형과 범주형 특성 구분
housing_num = housing.drop("ocean_proximity", axis=1)
housing_cat = housing[["ocean_proximity"]]
- 수치형/범주형 특성 전처리 파이프라인 적용
특성 추가에 필요한 열들을 삭제 했으므로 특성추가 변환은 생략하도록 하자. 파이프를 수정해야한다.
not_necessary
median_income | median_house_value | ocean_proximity | bedrooms_per_room | |
---|---|---|---|---|
17606 | 2.7042 | 286600.0 | <1H OCEAN | 0.223852 |
18632 | 6.4214 | 340600.0 | <1H OCEAN | 0.159057 |
14650 | 2.8621 | 196900.0 | NEAR OCEAN | 0.241291 |
3230 | 1.8839 | 46300.0 | INLAND | 0.200866 |
3555 | 3.0347 | 254500.0 | <1H OCEAN | 0.231341 |
... | ... | ... | ... | ... |
6563 | 4.9312 | 240200.0 | INLAND | 0.185681 |
12053 | 2.0682 | 113000.0 | INLAND | 0.245819 |
13908 | 3.2723 | 97800.0 | INLAND | 0.179609 |
11159 | 4.0625 | 225900.0 | <1H OCEAN | 0.193878 |
15775 | 3.5750 | 500001.0 | NEAR BAY | 0.220355 |
16512 rows × 4 columns
- 파이프라인 수정 - 특성추가 변환기를 제거 한 파이프를 따로 만들었다.
#수치형 수정
num_pipeline2 = Pipeline([
('imputer', SimpleImputer(strategy="median")),
('std_scaler', StandardScaler()),
])
# 전체 파이프라인
num_attribs2 = list(housing_num)
cat_attribs2 = ["ocean_proximity"]
full_pipeline2 = ColumnTransformer([
("num", num_pipeline2, num_attribs2),
("cat", OneHotEncoder(), cat_attribs2),
])
- 데이터 전처리 파이프 라인 적용
housing_prepared = full_pipeline2.fit_transform(housing)
- 준비된 데이터를 이용해 선형 회귀 모델 훈련시킨다.
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
- RMSE 비교(성능)
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
73335.47595627588
# 원래 모델의 RMSE
ori_rmse
68628.19819848923
73335.47595627588(변환2 모델) > 68628.19819848923(원래 모델)
###<결과 평가="">결과>
오히려 변환2 모델의 성능이 떨어졌음이 확인된다.
상관계수 절댓값 0.2의 설정이 너무 크기 때문 일 것이라고 예측된다. 상관계수의 절댓값 크기의 기준을 낮출 필요가 있어 보인다.
<변환 3>을 적용 & 성능 비교
변환 3 : 로그 변환
-
방의 총 개수(total_rooms), 침실 총 개수(total_bedrooms), 인구(population), 가구수(households), 중간소득(median_income)에 대해서 로그변환
-
밑의 코드는 왜도를 보여주는데 해당 특성들의 왜도가 0.75 보다 크다는 것을 확인 할 수 있다.
from scipy.stats import skew, kurtosis
log_housing = all.copy()
numeric_features = log_housing.select_dtypes(exclude = "object").columns.tolist()
skewed_features = log_housing[numeric_features].apply(lambda x: skew(x.dropna()))
skewed_features = skewed_features[skewed_features > 0.75].index
skewed_features
Index(['total_rooms', 'total_bedrooms', 'population', 'households',
'median_income', 'median_house_value'],
dtype='object')
- 해당 값들을 로그 변환하고 히스토그램으로 확인하여보자
log_housing[skewed_features] = np.log1p(log_housing[skewed_features])
log_housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()
그림 저장: attribute_histogram_plots
-
확연히 정규분포에 가까워짐을 확인할 수 있다.
- 훈련세트에 대해서만 다시 로그변환을 하여 훈련시켜본다.
훈련세트를 다시 가져온다.
housing = strat_train_set.copy()
housing
longitude | latitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | median_house_value | ocean_proximity | |
---|---|---|---|---|---|---|---|---|---|---|
17606 | -121.89 | 37.29 | 38.0 | 1568.0 | 351.0 | 710.0 | 339.0 | 2.7042 | 286600.0 | <1H OCEAN |
18632 | -121.93 | 37.05 | 14.0 | 679.0 | 108.0 | 306.0 | 113.0 | 6.4214 | 340600.0 | <1H OCEAN |
14650 | -117.20 | 32.77 | 31.0 | 1952.0 | 471.0 | 936.0 | 462.0 | 2.8621 | 196900.0 | NEAR OCEAN |
3230 | -119.61 | 36.31 | 25.0 | 1847.0 | 371.0 | 1460.0 | 353.0 | 1.8839 | 46300.0 | INLAND |
3555 | -118.59 | 34.23 | 17.0 | 6592.0 | 1525.0 | 4459.0 | 1463.0 | 3.0347 | 254500.0 | <1H OCEAN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
6563 | -118.13 | 34.20 | 46.0 | 1271.0 | 236.0 | 573.0 | 210.0 | 4.9312 | 240200.0 | INLAND |
12053 | -117.56 | 33.88 | 40.0 | 1196.0 | 294.0 | 1052.0 | 258.0 | 2.0682 | 113000.0 | INLAND |
13908 | -116.40 | 34.09 | 9.0 | 4855.0 | 872.0 | 2098.0 | 765.0 | 3.2723 | 97800.0 | INLAND |
11159 | -118.01 | 33.82 | 31.0 | 1960.0 | 380.0 | 1356.0 | 356.0 | 4.0625 | 225900.0 | <1H OCEAN |
15775 | -122.45 | 37.77 | 52.0 | 3095.0 | 682.0 | 1269.0 | 639.0 | 3.5750 | 500001.0 | NEAR BAY |
16512 rows × 10 columns
히스토그램을 통해 분포의 변화를 살펴보자
housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()
그림 저장: attribute_histogram_plots
왜도가 0.75보다 큰 특성들을 골라내어 확인하는 코드이다.
log_housing = housing.copy()
t = log_housing.select_dtypes(exclude = "object").columns.tolist()
s = log_housing[t].apply(lambda x: skew(x.dropna()))
s = s[s> 0.75].index
s
Index(['total_rooms', 'total_bedrooms', 'population', 'households',
'median_income', 'median_house_value'],
dtype='object')
해당 특성들에 대해 로그변환을 적용한다.
log_housing[s] = np.log1p(log_housing[s])
히스토그램으로 분포를 다시 확인하자
log_housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()
그림 저장: attribute_histogram_plots
로그 변환이 되었음이 확인되었다.
- 레이블을 따로 분리하였다.
housing = log_housing.drop("median_house_value", axis=1)
housing_labels = log_housing["median_house_value"].copy()
- 수치형/범주형 특성 구분
housing_num = housing.drop("ocean_proximity", axis=1)
housing_cat = housing[["ocean_proximity"]]
- 데이터세트 전처리 파이프 라인 적용
housing_prepared = full_pipeline.fit_transform(housing)
- 준비된 데이터세트로 선형 회귀 모델를 훈련시켰다.
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
- RMSE 비교(성능)
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
0.3093901226841342
# 원래 모델의 RMSE
ori_rmse
68628.19819848923
0.3093901226841342(변환3 적용) ««« 68628.19819848923(원래 모델)
###<결과 평가="">결과>
변환3 모델 성능의 어마어마한 향상이 확인된다.
로그를 적용하여 값 크기를 전체적으로 낮춘 것이 성능 향상의 원인이 아닐까 하고 예측하여본다.
오히려 과대적합이 아닐까 의심이 되며 적합한 변환이었는지 의구심이 든다.
변환을 하나씩 적용하기로 선택한 이유
- <변환1> & <변환3> : 로그를 하는 이유는 이상치로 인해 데이터가 정규분포를 따르지 않는 것을 보완하기 위함인데 <변환 1>에서 이상치를 삭제해주니까 큰 의미가 없다고 생각하였습니다. 변환3>변환1>
- <변환1> & <변환2> : <변환2>를 전체데이터에 적용하면 median_income이 없어지므로 <변환1>의 이상치 기준은 median_income에 대한 median_house_value의 그래프를 보고 하니까 할 수 없다고 생각하였습니다. 변환1>변환2>변환2>변환1>
- <변환2> & <변환3> : <변환2>를 적용하면 <변환3>의 로그를 적용할 특성들이 사라지기 때문에 의미가 없다고 생각하였습니다. 변환3>변환2>변환3>변환2>