My Data Preprocessing

21 minute read

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

output_23_1

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>

output_38_1

  • 대부분 구역의 중간 소득이 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>

output_44_1

  • 계층별 샘플링 실행
    • 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

output_63_1

  • 중간 소득 대 중간 주택 가격 산점도
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

output_65_1

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

output_162_1

  • 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

output_164_1

  • 희미하지만 나타남을 확인할 수 있다. 따라서 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

output_167_1

  • 분명히 없어짐이 확인 되었다.

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

output_173_1

  • 정확히 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

output_175_1

  • 희미하지만 나타남을 확인할 수 있다. 따라서 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

output_178_1

  • 분명히 없어짐이 확인 되었다.

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

output_184_1

  • 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

output_186_1

  • 정확히 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

output_192_1

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

output_193_1

  • 확연히 눈에 띄진 않지만 수평선이 사라졌다.

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

output_200_1

  • 확실히 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

output_249_1

  • 확연히 정규분포에 가까워짐을 확인할 수 있다.

  • 훈련세트에 대해서만 다시 로그변환을 하여 훈련시켜본다.

훈련세트를 다시 가져온다.

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

output_255_1

왜도가 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

output_261_1

로그 변환이 되었음이 확인되었다.

  • 레이블을 따로 분리하였다.
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>에서 이상치를 삭제해주니까 큰 의미가 없다고 생각하였습니다.
  • <변환1> & <변환2> : <변환2>를 전체데이터에 적용하면 median_income이 없어지므로 <변환1>의 이상치 기준은 median_income에 대한 median_house_value의 그래프를 보고 하니까 할 수 없다고 생각하였습니다.
  • <변환2> & <변환3> : <변환2>를 적용하면 <변환3>의 로그를 적용할 특성들이 사라지기 때문에 의미가 없다고 생각하였습니다.

Updated: