사이킷런 찍먹시리즈(2) - 캘리포니아 주택가격 예측 모델

12 분 소요

서론

본 글은 작성자의 공부를 위해 쓰는 게시물입니다.

아래 작성하는 글은 틀린내용이 있을수도 있으니 참고하시기 바라며, 책은 핸즈온 머신러닝 2판을 사용하여 공부하고 있습니다.

머신러닝 모델 만들어보기


이번에는 어느정도 전처리와 보정이 필요한 데이터를 가지고 머신러닝 모델을 만들어 보도록 할것이다.

먼저, 이번에 사용할 캘리포니아 주택가격에 대한 데이터셋을 갖고 온다.

핸즈온 머신러닝 2판에서 깃허브에 추가해놓은 데이터를 불러오도록 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
import tarfile
import urllib

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"

def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
    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()

위 코드를 실행하면, 직접 지정된 링크로 들어가 datasets/housing위치에 있는 housing.tgz 파일을 내려받고 압축을 풀게된다.

​```python
​import pandas as pd
import numpy as np

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()
​```

이 파일을 load_housing_data 라는 함수를 만들어 판다스 데이터 프레임 객체로 불러온다.

이 함수는 사이킷런 데이터셋에 있는 load_ 함수의 형태이다.

이 함수를 호출하기 편하도록 housing 이라는 변수에 담아서 관리해주도록 하겠다.

1
print(housing.info())

데이터를 불러 왔다면, 가장 먼저 우리는 이 데이터들의 형태와 특성을 알아보아야 한다.

info()로 대략적인 이 데이터프레임의 특성을 알아 보자.


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

데이터를 살펴보면, 10개의 특성을 갖고있으며 20640개의 데이터를 갖고있다.

특이사항이 있는 특성을 살펴보자면, total_bedrooms의 경우 다른 데이터는 20640개인 것에 비해 20433개로 207개의 데이터가 누락되어있고, ocean_proximity는 다른데이터과 다르게 object 형태를 갖고있다.

이는 ocean_proximity 특성이 범주형 데이터라는 뜻인데, 어떠한 범주를 갖고있는지 알아보자

1
print(housing['ocean_proximity'].value_counts())

1H OCEAN 9136

INLAND 6551

NEAR OCEAN 2658

NEAR BAY 2290

ISLAND 5

Name: ocean_proximity, dtype: int64

총 다섯개의 범주를 갖고 있음을 알 수 있다.

이처럼 데이터가 한정된 정보중 하나의 정보를 갖고있는 것을 범주형 데이터라고 한다.

​이제 데이터들의 전처리를 위해 describe 함수로 유의미한 지표들을 얻어 보겠다.

1
housing.describe()

image1

descirbe 함수는 위와 같이 숫자 형태의 데이터 특성들의 값들을 요약하여 보여준다.

데이터 개수, 중앙값, 표준오차, 최솟값, 최댓값, 25,50,75%들은 우리가 데이터의 대략적인 맥락을 읽게 해준다.

​데이터의 대략적인 맥락을 숫자로 확인했으니, 시각화하여 보도록 하자.

시각화는 데이터들을 조금 더 직관적으로 볼 수 있도록 해준다.

​hist 메서드를 사용하면 간편하게 모든 숫자형 데이터에 대한 정보를 직관적으로 알 수있다.


​​```python
​import matplotlib.pyplot as plt

housing.hist(bins = 50,figsize = (20,15));
​```

image1

​이렇게 히스토그램으로 데이터들을 보면, 어떤 값이 가장 주를 이루고 있고,

데이터가 어떻게 분포되어 있는지 한눈에 알수 있다.

이제 대략적인 데이터들의 맥락을 파악했으니, 데이터 전처리를 통해 모델이 올바른 데이터를 갖고 학습할 수 있도록 해주자.

여기서 근데 한가지 주의할점이 있다.

데이터를 대략적으로 파악하는것 정도는 괜찮지만, 테스트셋과 트레인셋을 분리하기전에 데이터를 너무 자세하게 들여다봐서는 안된다.

테스트셋은 우리가 모르는 새로운 데이터라는 가정하에 분리되어야 하는데, 우리가 테스트셋을 들여다보게 된다면, 이미 정답을 보게 되는 것과 똑같은 것이 되기 때문에

편향적으로 데이터를 처리하게 될수 있다.

따라서 테스트셋이 섞여있는 데이터를 너무 자세하게 분석해서는 안된다.

​먼저 단순하게 테스트셋과 트레이닝셋을 분리해보면,

1
2
3
4
from sklearn.model_selection import train_test_split

train, test = train_test_split(housing, test_size=0.2, random_state=41)
    

이렇게 분류 할 수 있다. train_test_splitd은 테스트셋의 사이즈만 파라미터로 넣어주면,

랜덤으로 지정해준 사이즈만큼을 테스트셋으로 떼어준다.

하지만 이러한 순수 무작위 추출방식은, 데이터셋이 충분하게 크다면 괜찮지만

그렇지 않을경우에는 샘플링 편향이 생길 가능성이 크다.

어떤 데이터를 샘플링 할때는 그 샘플링한 비율이 모집단을 대표 할 수 있는 비율의 데이터로

추출하여야 샘플링 편향을 막을 수 있다.

따라서 우리는 더 정확한 학습을 위해 계층적 샘플링을 시도할건데,

저 특성들중에 계층이란 개념을 가질만한 특성은 median_income, 중간소득정도가 있을것이다.

따라서 우리는 중간소득을 계층화하여, 그 비율에 맞게 샘플링을 해볼것이다.

먼저, 중간소득 데이터를 계층화한다.

여러가지 방법이 있겠지만, 우리는 우선 4개의 카테고리로 나눠 볼것이다.

소득이 낮을수록 1에 가깝고, 소득이 높을수록 4에가깝도록 계층화 할것이다.

1
2
housing['income_cat'] = pd.cut(housing['median_income'], 
                               bins = [0, 1.5, 3.0, 4.5, np.inf], labels=[1,2,3,4] )

판다스의 cut메서드를 이용하면 굳이 긴 if문으로 계층을 나눌필요 없이 카테고리화 할 수 있다.

이렇게 housing 데이터 프레임에 추가된 income_cat을 이용하여 이 데이터를 계층적 샘플링 할것이다.

1
2
3
4
5
6
7
from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits= 1, test_size=0.2, random_state=41)

for train_index, test_index in split.split(housing, housing['income_cat']):
    train_set = housing.loc[train_index]
    test_set = housing.loc[test_index]


​StratifiedShuffleSplit은 계층화 샘플링을 도와주는 메서드로, 이 클래스의 split 함수는 계층화된 데이터 특성을 기반으로 그 특성을 대표하는 비율로 인덱스를 반환해준다.

따라서 train_set 과 test_set은 랜덤으로 계층화 샘플링한 트레이닝 셋과 테스트 셋이 된다.

​이렇게 나눈 트레인셋을 기반으로 데이터를 깊게 한번 들여다 보자.

​먼저 데이터 특성중 위도와 경도를 이용하여 이 데이터를 지도로 나타내어 보자.

위도와 경도를 각각 x축 y축으로 두어 산점도를 그리면 대략적인 캘리포니아의 지도와 비슷한 모습이 그려질 것이다.

1
train_set.plot(kind = 'scatter', x = 'longitude', y = 'latitude')

image1

​이렇게 산점도가 그려지게 되는데,

image1

​캘르포니아 지도와 일치하는 모습을 볼 수 있다.

위도와 경도를 바탕으로 산점도를 그렸으니, 우리가 유의미하게 해석할 수 있는 지표를 산점도에 나타내 보도록 하자.

​먼저 인구수를 각 산점도의 점의 크기로 만들도록 하여 지역별 인구수를 확인해보도록 한다.

python ​train_set.plot(figsize = (15,10),kind = 'scatter', x = 'longitude', y = 'latitude', s = train_set['population']/100, alpha = 0.2) ​

​​image1
​​
​​플롯의 크기 자체를 좀 키워주고, 각 점의 크기는 인구수를 나타내며, 인구수의 스케일이 크기때문에 점이 너무 작게 찍히지 않도록 100을 나누어 스케일링 해주었다.

또, 알파값을 주어 겹치는 부분의 점이 더욱 잘보이게 하였다.

이제 가장 유의미한 값인 가격을 표시해줄건데, 가격은 색깔로 눈에 한번에 들어오게 표시해 볼것 이다.

1
2
3
4
5
train_set.plot(figsize = (10,5),kind = 'scatter', 
               x = 'longitude', y = 'latitude',
               label = 'population', title = 'California_house_value',
               s = train_set['population']/100, 
               alpha = 0.4,colorbar = True, c = 'median_house_value', cmap = 'jet')

​​image1

​이제 훨씬 눈으로 보기 직관적이 되었다. cmap은 ‘jet’를 사용하여 얼핏보면 온도를 나타낸듯 하지만, 빨간색일 수록 높은 집값, 파란색에 가까울 수록 낮은 집값을 표시한 것이다.

이렇게 보니 바다쪽에 가까운 곳이 일수록 집값이 높은 경향을 보이며, 주로 샌프란시스코와 로스앤젤레스등 대도시가 있는 지역이 높은 집값을 가진다는 것을 알 수 있다.

​이제 대략적인 데이터의 모양을 확인헀으니, 특성값들이 우리가 예측하고자 하는 주택 가격과 얼마나 상관관계가 있는지 확인해 보면서 전처리를 하면서 최대한 상관관계가 있어보이는 값으로

데이터 전처리를 해주도록 한다.

1
2
3
corr = housing.corr()

corr['median_house_value']

corr 메서드를 이용해 주택가격과 다른 특성들의 상관관계를 알아낼 수 있다.


longitude -0.045967

latitude -0.144160

housing_median_age 0.105623

total_rooms 0.134153

total_bedrooms 0.049686

population -0.024650

households 0.065843

median_income 0.688075

median_house_value 1.000000

Name: median_house_value, dtype: float64

사실 계수중에 상관계수로써 유의미한 값을 가지는 특성은median_income 특성밖에는 보이지 않는다.

median_income 특성의 상관관계가 있는지 산점도로 한번 보도록 하자.

1
housing.plot(kind = 'scatter', x = 'median_income', y = 'median_house_value')

​​image1

위쪽에 수평으로 보이는 이상치들을 제외하면, 어느정도 1에가까운 우상향하는 상관관계를 가지는것을 눈으로 확인 할 수 있다.

​그럼, 조금 더 특성들의 상관관계를 높이려면 어떡해해야할까?

이제 데이터의 전처리를 통해 조금더 이 학습모델이 더 학습하기 쉬운 상태로 데이터를 가공해보도록 하자.

그러기 위해서는 데이터가 의미하는 것들을 한번 더 살펴봐야한다.

total_rooms 는 그저 모든 방의 합의 개수이다. 어떠한 지역의 모든방의 합의 개수가 그 지역 집값에 미치는 영향은 상식적으로 유효하다고 보긴 그 상관관계가 약할 것이다.

모든방의 개수의 합을 가구수로 나누어 한 가구당 방의 개수를 도출하는것이 훨씬 합리적인 특성일 것이다.

total_bedrooms 특성도 마찬가지다. 침대방의 개수 전체보다는 방개수 대비 침실의 개수가 훨씬 더 유용하다고 느껴진다.

​이러한 가설을 세워봤으니, 한번 실제로 상관관계를 확인해보도록 하자.

​​```python
​housing[‘room_per_household’] = housing[‘total_rooms’]/housing[‘households’]
housing[‘bedrooms_per_household’] = housing[‘total_bedrooms’]/housing[‘total_rooms’]

corr[‘median_house_value’]
​```

​————
​longitude -0.045967

latitude -0.144160

housing_median_age 0.105623

total_rooms 0.134153

total_bedrooms 0.049686

population -0.024650

households 0.065843

median_income 0.688075

median_house_value 1.000000

bedrooms_per_household -0.046739

bedrooms_per_rooms -0.255880

Name: median_house_value, dtype: float64

확인해보면, total_rooms를 가공한 room_per_household의 상관관계도 증가했지만, total_bedrooms를 가공한 bedrooms_per_rooms 의 상관관계는 눈에띄게 큰폭으로 증가했다.

이제 어느정도 데이터들이 갖는 값들에 대한 대략적인 방향을 보았으니, 데이터를 정제 해보도록 하자.

먼저, 우리의 예측값인 주택가격의 중앙값, median_house_value 를 분리해준다.

먼저 total_bedrooms의 빈곳을 채워준다. NaN값은 평균으로 채울수도 있고,

중앙값으로 채울수도 있고, 아예 빈 데이터는 삭제해버리는등 여러가지 방법이 있지만

중앙값을 채우는 방법으로 전처리를 해보도록 해보자.

1
housing[housing['total_bedrooms'].isnull() == True] = housing['total_bedrooms'].median()

위의 방식처럼 해결할 수도 있지만, 만약 한 데이터특성이 아니라 여러 특성에 군데군데 NaN값이 있다면,

일일이 저렇게 우리가 median값을 구하여 넣어주는 것은 많이 비효율적인 행동일 것이다.

따라서 우리는 사이킷런에서 제공하는 Imputer 라이브러리를 통해 빈칸을 채우는 법을 알아보도록 하자.

1
from sklearn.impute import SimpleImputer

사이킷런의 impute 라이브러리에 있는 SimpleImputer를 불러온다.

SimpleImputer는 숫자 형식의 데이터만을 읽고 활용 할 수 있기 때문에, 먼저 범주형 데이터인 ocean_proximity 특성을

제거해주어야 SimpleImputer를 사용할 수 있다.

1
housing_num = housing.drop('ocean_proximity', axis = 1)

drop 함수는 파라미터에 있는 특성을 제거한 뒤 나머지 데이터 프레임을 반환한다. axis 파라미터를 반드시 포함하여

내가 삭제할 특성이름의 열을 삭제할것이라고 표기 해주어야 정상적으로 삭제가 된다.

숫자만 있는 데이터 프레임이니, 변수이름에 _num을 붙여주겠다.

1
2
3
4
5
imputer = SimpleImputer(strategy = 'median')

imputer.fit_transform(housing_num)

housing_num.isnull().value_counts()

​​image1
​​
​​housing_num의 빈칸이 있는지 호출해보니, 모든 데이터가 NaN값이 False인것을 확인할 수 있었다.

모든 빈칸을 SimpleImputer가 알아서 그 해당 열의 중앙값으로 채워준것이다.

다음은, Ocean_proximity를 전처리 해볼것이다.

이러한 범주형 데이터는 문자로는 학습을 시킬 수 없기 때문에, 숫자의 형태로 바꾸어 주어야 컴퓨터가 알아들을 수 있다.

하지만 그냥 숫자 1,2,3,4 식의 라벨링을 한다면 우리는 카테고리의 의미로 숫자를 붙인것인데, 컴퓨터는 숫자를 실제 숫자 1,2,3,4의 의미로 이해한다.

따라서 이를 더미형 변수로 변화 해주어야 한다.

더미형 변수는 1과 0만을 갖는 변수로, 범주형 변수를 숫자형 변수화 하여 0과 1로만 나타냄으로써 숫자로써 갖는 의미를 없애주고, 오직 범주의 의미만을 숫자로 나타 낼 수 있게 해준다.

예를 들어 남,녀의 카테고리를 가진 범주형 변수를 더미변수화 한다고 가정하면,

하나의 카테고리를 변수로 지정하고 1과 0으로 표기한다.

남자를 기준으로 했다고 예를 들면, 1인 데이터는 남자, 0인 데이터는 여자인것이다.

​만약 카테고리의 개수가 3개라면 카테고리를 a,b,c 라고 가정 할때, a b의 더미형 변수가 생성되고 10 이면 a, 01이면 b, 00이면 c를 의미하게 된다.

이 값들은 0이 아니면 1이므로, 숫자의 의미로써 학습이 되지않고 학습모델로 하여금 해당 특성의 존재 유뮤만 포함시키게 할 수있다.

​이러한 더미 변수 변환을 위해 housing에서 범주형 변수였던 ocean_proximity를 떼어내보겠다.

1
housing_cat = housing['ocean_proximity']

더미형 변수로의 변환은 사이킷런의 OneHotEncoder를 사용하면 간편하게 할 수 있다.

1
2
3
4
5
6
from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder()

housing_cat_encoded = encoder.fit_transform(housing_num)

이렇게 OneHotEncoder를 사용하면, 범주형 변수를 손쉽게 더미변수화 할 수 있다.

​이제 어느정도 사이킷런의 도구들을 활용하여 전처리 과정들을 익혔으니, 파이프라인을 구축하여 한번에 위과정들을 진행하게 할것이다.

​먼저 위에서 상관관계를 높이기 위해 조작했던 특성조합으로 데이터셋을 바꿔주기 위한 변환기를 만들어보자.

​우리는 저 corr함수를 통해 특성들을 조합함으로써 더 높은 상관관계의 새로운 특성을 만들어 낼 수 있다는 것을 알아냈다.

따라서 우리는 방의 개수를 가구수로 나누고, 침실수를 방의개수로 나누고, 인구수를 가구수로 나누어 이 새로운 조합특성들을

기존 특성들 옆에다 새로 붙여주어야 한다.

​이러한 행동을 판다스 데이터프레임으로도 충분히 할 수 있겠지만, 그렇다면 새로운 데이터가 들어올때마다 우리가 코드에 접속하여 데이터를 전처리 해주어야 할것이다. 따라서 새로운 데이터가 들어와도 반복작업이 되도록

우리가 직접 변환기를 제작하여 파이프라인에 넣어 줄것인데, 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
from sklearn.base import BaseEstimator, TransformerMixin

class Adder(BaseEstimator, TransformerMixin):
    def fit(self, X, y = None):
        return self
    def transform(self, X):
        rooms_per_household = X[:,3]/X[:,6]
        bedrooms_per_rooms = X[:,4]/X[:,3]
        population_per_household = X[:,5]/X[:,6]
        return np.c_[X, rooms_per_household, bedrooms_per_rooms, population_per_household]

사이킷런에 있는 BaseEstimatior와 TransForemerMixin 메서드를 사용해주면 되는데, 우리가 새로운 클래스를 만든 후 이 두가지 메서드를 상속해주면 간단하게 다른 변환기들과 동일하게 작동하는

변환기를 만들어주어, 간편한게 우리가 원하는대로 데이터를 수정 할 수 있다.

1
2
3
​​adder = Adder()

housing_add = adder.fit_transform(housing.values)


​이렇게 BaseEstimatior와 TransForemerMixi 두가지를 상속한 변환기는 자동으로 fit_trainsform 메서드를 사용할 수 있게되고, 우리는 이를 사용함으로써 데이터를 변환할수 있다.

이렇게 조합한 특성이 포함된 데이터를 housing_add라는 이름으로 저장해주자.

​다음은 데이터의 스케일을 바꿔주어야 한다.

​​​image1
​​​
​​​우리가 가진 데이터를 보면 데이터별로 스케일의 차이가 매우 크다. 예를 들어 total_bedrooms의 경우 최댓값이

5471이지만, median_income의 경우 15밖에 되지않는다. 이렇게 데이터들간의 스케일이 크게 차이나면,

우리가 원하는 예측 모델을 얻어내기가 힘들고, 경사 하강법을 적용할때 시간이 굉장히 길어진다.

따라서 우리는 이 수치들을 비슷한 스케일로 바꿔줄건데, 이러한 스케일링 방법에는 몇가지 방법이 있지만 대표적으로 정규화와 표준화가 있다.

​먼저 정규화는 특성값에서 그 특성의 최솟값을 뺀값을 (특성 최댓값-특성 최솟값)으로 나누어 이 값이 이 특성에서 차지하는 위치를 0과 1사이의 숫자로 스케일링 하는 방법이고,

표준화는 특성값에서 특성의 평균을 뺸 값을 표준편차로 나누어, 0을 평균으로 1의 표준편차를 갖는, 즉, -1에서 1사이의 값을 갖는 값으로 스케일링 하는 방법이다.

이 두가지 방법도 마찬가지로 뭐가 더 성능이 좋다고 하기 보다는 상황에 맞게 알맞은 스케일링 방법을 적용하면된다.

​먼저 정규화의 경우 이상치에 민감한데, 만약 한 값이 굉장이 큰 이상치를 갖는다면 분모가 그 한값때문에 굉장히 커지게 되므로 올바른 스케일링 결과를 얻지 못할 수 있다. 따라서 정규화 전에는 꼭 이상치 탐지 후, 데이터를 수정해준뒤 정규화 해주어야 한다.

​우리는 따로 이상치 탐지를 하지 않았기 때문에, 우선 표준화를 통해 데이터를 스케일링 해볼 것이다.

표준화 스케일링을 위한 도구로 사이킷런에서 StandardScaler 메서드를 불러와 사용해 보도록하자.

이 메서드는 우리가 하나하나 데이터를 전처리 하지 않더라도 알아서 표준화를 진행해준다.

1
2
3
4
5
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

scaler.fit_transform(housing_num)

이렇게 하면 모든 데이터가 표준화된 값으로 바뀐다.

이제, 우리가 변환한것들이 각자 따로따로 작동하는 것이 아니라, 하나의 파이프라인으로 동작하게끔 만들것 이다.

그러기 위해서 파이프라인 라이브러리를 불러와주고, 파이프라인 메서드로 위의 변환기를 묶어준다.


1
2
3
4
5
6
7
8
9
10
​​​from sklearn.preprocessing import StandardScaler
​​​from sklearn.pipeline import Pipeline

num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('add', Adder()),
     ('std_scaler', StandardScaler()),
     ])

housing_num_tr = num_pipeline.fit_transform(housing_num)


​이렇게 하면, SimpleImputer 변환기가 작동하여 그 결과값을 Adder에 넘겨주고 그렇게

Adder로 변환된 값은 StandardScaler로 전달되어 반환된다.

이렇게 파이프라인으로 변환기를 묶으면, 일일히 fit_transform 으로 변환할 필요없이 한번에 작업을 진행할 수 있다.

​위에서는 housing_num, 숫자형 변수에대한 파이프라인이었기 때문에 housing_num만 변환했지만, 한번에 범주형과 숫자형을 동시에 처리한다면 더욱 편리할 것이다.

따라서 이번엔 ColumnTransformer를 불러와각각의 열을 변환시켜줄 것이다

1
2
3
4
5
6
7
8
9
10
11
from sklearn.compose import ColumnTransformer

num_att = list(housing_num)
cat_att = ['ocean_proximity']

full_pipeline = ColumnTransformer([
    ('num', num_pipeline, num_att),
    ('cat', OneHotEncoder(), cat_att)
])

housing_prepared = full_pipeline.fit_transform(housing)

위 코드는 ColumnTransformer를 불러와 숫자형 특성 따로, 범주형 특성 따로 변환기를 돌린 후, 하나의 데이터로 반환한다.

​이로써 모든 변환기를 묶은 파이프라인을 만드는데 성공했다. 우리는 이제 일일히 단계적으로 새로운 변수에 전처리된 데이터를 넣을 필요없이, 불러온 파일을 위 파이프라인에 올리기만 하면 우리가 의도한대로 전처리된 데이터를 얻을 수 있게되었다.

그럼 이 전처리된 데이터를 한번 학습시켜서 얼마나 정확한 결과를 내는지 테스트 해보겠다.

1
2
3
4
5
6
7
8
from sklearn.linear_model import LinearRegression

linear = LinearRegression()
linear.fit(housing_prepared, housing_labels)

linear.score(housing_prepared, housing_labels)

0.65033842487333

먼저 선형회귀를 한번 이 데이터에 적용해보면, 약 65퍼센트의 정확도를 보임을 알 수 있다.

정확도가 좀 낮으므로 다른 모델을 한번 적용 시켜보자

이번엔 결정트리 모델에 학습시켜보도록 할것이다.

1
2
3
4
5
6
7
8
from sklearn.tree import DecisionTreeRegressor

tree = DecisionTreeRegressor()
tree.fit(housing_prepared, housing_labels)

tree.score(housing_prepared, housing_labels)

1.0

100퍼센트의 정확도가 나왔다. 이 결과는 조금 이상하다.

아무리 정확도가 높다 한들 100퍼센트의 정확도로 예측하는것은 불가능에 가깝다.

이런경우 우리는 과적합을 의심할 수 있는데, 우리가 학습시킨데이터에서 학습하고 점수를 매기기 때문에

우리의 데이터에서만 완벽하게 작동하는 것이다.

​이럴경우 우리는 교차검증을 통해 이 모델이 과적합되었는지 확인 할 수 있다.

교차검증은 데이터를 쪼개서 트레이닝셋을 더 작은 트레이닝셋과 테스트셋으로 나누어 이 모델을 평가한다.

한 트레이닝셋을 쪼개서 여러번 테스트하기 때문에, 이 모델이 일반적인 모델에서도 일정한 성능을 과연 유지할 수 있는지 확인 할 수 있다.

교차 검증은 cross_val_scroe 메서드로 검증 해볼수 있다.

cv = 10 정도로 두어 10개의 서브셋으로 나누어 10번의 모델평가를 진행해보도록 하고, 그 평균값을 알아보면,

1
2
3
4
5
6
from sklearn.model_selection import cross_val_score

scored = cross_val_score(tree, housing_prepared, housing_labels, cv=10 

scored.mean()


0.6312

63퍼센트의 정확도를 나타낸다. 이는 과적합되지않은 일반적인 데이터에서는 선형회귀가 더 정확도가 높은 모델임을 보여준다.

더 높은 정확도를 갖는 모델을 찾기 위해, 다음은 랜덤 결정 트리모델을 적용시켜 보자.

1
2
3
4
5
from sklearn.ensemble import RandomForestRegressor
forest = RandomForestRegressor()

forest.fit(housing_prepared, housing_labels)
print(forest.score(housing_prepared,housing_labels))

0.974348438692335

97프로의 정확도가 나왔다. 이 정도면 아주 훌륭한 예측 모델이라고 할수있다. 하지만 이 모델도 마찬가지로 혹시나 과적합된것은 아닌지 확인 해보아야 한다.

동일하게 cross_val_score 메서드로 교차검증을 진행해보자.

1
2
3
scored = cross_val_score(forest, housing_prepared, housing_labels)

scored.mean()

0.810932415264363

교차검증에서는 정확도가 많이 떨어지긴 했지만, 그래도 81퍼센트면 아주 선형회귀나 결정트리 모델에 비해 아주 훌륭한 정확도를 갖고 있음을 알 수 있다.

그렇다면 이제 마지막으로 테스트셋에서 평가를 해봄으로써, 실제로 이 모델이 학습하지 않은 데이터에서도 비슷한 정확도를 낼 수 있는지 알아보자.

1
2
3
4
5
6
X_test = test_set.drop('median_house_value', axis = 1)
y_test = test_set['median_house_value'].copy()

X_test_prepared = full_pipeline.transform(X_test)

forest.score(X_test_prepared, y_test)

0.8061237130367995

약 81퍼센트의 정확도를 보였다.

이정도면 꽤 쓸만한 모델을 만들었다고 할 수 있다.

이로써 우리는 캘리포니아 지역의 집값 예측을 80퍼센트의 정확도로 예측하는 인공지능 모델을 만들어보며 간단하게 데이터 전처리와 시각화, 모델 적용을 해볼 수 있었다.

댓글남기기