열렬히.뛰기

2. 머신러닝 프로젝트 처음부터 끝까지

머신러닝 & 딥러닝 > 핸즈온 머신러닝 2 > 2. 머신러닝 프로젝트 처음부터 끝까지

주요 단계

  1. 큰 그림
  2. 데이터 구하기
  3. 탐색, 시각화
  4. 데이터 준비
  5. 모델 선택 & 훈련
  6. 솔루션 제시
  7. 시스템 론칭, 모니터링, 유지보수

1. 데이터로 작업하기

다양한 공개 데이터 저장소

http://archive.ics.uci.edu/ml
http://www.kaggle.com/datasets
https://registry.opendata.aws

http://dataportals.org
http://opendatamonitor.eu
http://quandl.com

https://goo.gl/SJHN2k
https://homl.info/10
http://www.reddit.com/r/datasets

https://goo.gl/QgRbUL

2. 큰 그림 보기

문제 정의

어떠한 문제를 해결할 것인가?

성능 측정 지표 선택

크게 2가지 예시를 들고 있다.

  1. RMSE = root mean square error = 평균 제곱근 오차
RMSE(X,\,h) = \sqrt {\frac{1}{m} \sum_{i=1}^{m} (h(\underline{x}^{(i)}) -y^{(i)})^2}
  • X= 행렬
  • h = 예측 함수
  • \underline{x}^{(i)} = 행렬 X의 i번째 행.
  • y^{(i)} = 기댓값
  • h(\underline{x}^{(i)}) = \hat y = 추정량임.
  • \hat y - y 를 제곱한 것 <=> 잔차제곱의 합

즉, RMSE 는 잔차제곱의 합을 샘플 수로 나눈 것에 제곱근을 씌운 것.

  1. MAE = mean absolute error

    MAE(X, h) = \frac {1}{m} \sum_{i=1}^{m} |h(\underline{x}^{(i)})-y^{(i)}|

두 방향 모두 예측값 벡터와 타깃값 벡터의 거리를 제는 방법 (=잔차제곱합)

가정 검사

지금까지의 가정이 맞는가 확인

3. 데이터 가져오기

1. 파이썬 설치 :

  • 스파이더 설정하기
  • 구글 코랩도 괜찮다.
  • 주피터 노트북 설정하기 Untitled

2. 데이터 끌어들이기

  • 여기서는 캘리포니아의 주택 가격 데이터를 사용한다.
코드

데이터 잡아오기 (fetch)

python
import os
import tarfile
import urllib.request

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

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

데이터를 판다스로 옮기기

python
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)

데이터의 특징 보기

python
housing = load_housing_data()
housing.head()
housing.info()
housing.describe()

3. 테스트 세트 만들기

  • 데이터 스누핑 편향을 막기 위해 테스트 세트를 생성
    • 샘플링 과정이라고 생각하면 될 것 같다.

(1) 무작위 추출 - numpy에서 난수를 생성해서 테스트를 만든다.

코드
python
import numpy as np
np.random.seed(42)
def split_train_test(data, test_ratio):
    shuffled_indices = np.random.permutation(len(data))
    test_set_size = int(len(data) * test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
    return data.iloc[train_indices], data.iloc[test_indices]

train_set, test_set = split_train_test(housing, 0.2)
print(len(train_set))
print(len(test_set))
  • 그러나, 이 방법을 적용해도 업데이트된 데이터셋을 사용하게 되면 문제가 생김.

두 번째 : 샘플의 식별자를 사용해 테스트 세트로 보낼지 말지를 결정한다.

(1) test_set_check() 을 사용한다.

test_set_check()을 만들때 hashlib을 사용해야 Python 3에서는 작동하는 듯.

python
import hashlib

def test_set_check(identifier, test_ratio, hash=hashlib.md5):
    return bytearray(hash(np.int64(identifier)).digest())[-1] < 256 * test_ratio

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]

# `index` 열이 추가된 데이터프레임을 반환
housing_with_id = housing.reset_index()  
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

# "id"열 추가
# train_test를 아이디에 따라 나눈 뒤 train_set과 test_set으로 나눈다.
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

# 확인해 보면 "id"열이 추가된 것을 볼 수 있다.
test_set.head()

(2) 무작위 추출 - sklearn을 이용해 데이터셋을 서브셋으로 나누기

코드

sklearn 중 train_test_split() 은 두 가지 특징이 있다.

  1. 파라미터로 난수 초깃값을 설정 가능하다. (random_state)
  2. 같은 행 갯수를 가진 데이터셋들을 같은 인덱스 기반으로 나눌 수 있다.
python
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

(3) 카테고리를 만들어 추출 - 우선 카테고리를 만들어주고 추출한다.

코드
  1. “소득” 카테고리 만들어주기
python
housing["income_cat"] = pd.cut(housing["median_income"],
                               bins = [0., 1.5, 3.0, 4.5, 6., np.inf],
                               labels = [1, 2, 3, 4, 5]
                              )
housing["income_cat"].hist()
  1. “소득” 카테고리 기반 샘플링
python
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]
  1. 샘플 속 “소득”별 비중이 원 데이터 속 “소득”별 비중과 비슷한가?
python
# 샘플 데이터
>>> 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

# 원 데이터 vs 무작위 추출 vs 계층 추출
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

→ 해보면 무작위 추출보다 계층 추출이 원 데이터와 계층별 비중이 비슷하다는 것을 알 수 있다.

  1. 마지막으로, “income_cat” 특성 삭제

    python
    for set_ in (strat_train_set, strat_test_set):
        set_. drop("income_cat", axis=1, inplace=True)
    
  • 왜 (3)번을 택했는가?
    • 카테고리를 만들지 않으면….

4. 데이터 이해를 위한 시각화

지리적 데이터 시각화

  • 훈련 세트에 대해서만 탐색.
코드와 시각화 이미지
python
housing = strat_train_set.copy()
housing.plot(kind = "scatter", x="longitude", 
						 y="latitude", alpha = 0.1)

→ Bay area, LA, San Diego, Central Valley 쪽의 집값이 높다.

좀 더 두드러진 패턴 만들어내기

python
housing.plot(kind = "scatter", x="longitude", y="latitude", alpha=0.4,
            s = housing["population"]/100, label="population", figsize=(10,7),
            c = "median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,
            )
plt.legend()

→ 주택 가격이 지역 & 인구밀도와 상관있다는 것을 알 수 있다.

→ 군집 알고리즘을 사용해 주요 군집을 찾고, 군집의 중심까지의 거리를

재는 특성을 추가할 수 있다.

상관관계 조사

코드와 결과
  • corr() 함수를 이용한다.
python
corr_mat = housing.corr()
corr_mat["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

"""
  • 상관계수의 시각화
python
from pandas.plotting import scatter_matrix

attributes = ["median_house_value", "median_income",
             "total_rooms", "housing_median_age"]

scatter_matrix(housing[attributes], figsize=(12,8))

이 중에서 “중간 주택 가격”과 “중간 소득” 간의 상관관계를 보자.

python
housing.plot(kind = "scatter", x = "median_income",
            y = "median_house_value", alpha = 0.1)

  • 결과 해석
    • 상관관계가 너무 강하다
    • 일부 값들이 수평선으로 잘 보인다. (이상한 데이터)
  • 데이터의 모양이 위처럼 이상하다면 재조정을 해야 한다.

특성 조합으로 실험

  • 여러가지 데이터들의 특성 조합을 실험해 보기

    ex. 가구 당 방 갯수

    코드와 결과
    python
    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"]
    
    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
    

5. 머신러닝 알고리즘을 위한 데이터 준비

  • 먼저 원래 훈련 세트로 복원.
  • 예측 변수와 타깃값에 같은 변형을 적용하지 않기 위해 예측 변수와 레이블을 분리.
python
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

데이터 정제 (수치형)

  • 대부분의 머신러닝 알고리즘을 누락된 특성을 다루지 못함.
  • 따라서 이를 처리할 수 있는 함수가 필요.
  • dropna(), drop(), fillna() 를 사용해 만들 수 있음.
python
housing.dropna(subset=["total_bedrooms"]) # 옵션 1
housing.drop("total_bedrooms", axis=1) # 옵션 2
median = housing["total_bedrooms"].median() # 옵션 3
housing["total_bedrooms"].fillna(median, inplace=True)
  • sklearn의 simpleimputer로 누락된 값 보충
    • 누락값 = 특성의 중간값으로 대체
python
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy = "median")
# imputer로 중간값 계산

housing_num = housing.drop("ocean_proximity", axis=1)
# 수치형이 아닌 ocean_proximity 제거

imputer.fit(housing_num)
# 각 수치형의 중간값 계산
imputer.statistics_ 
housing_num.median().values

X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
                         index = housing_num.index)

텍스트와 범주형 특성 다루기

  • 여기서는 범주형이 “ocean_proximity” 하나밖에 없음.
python
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 OCEA
"""

범주형(텍스트) → 숫자로 변환하기

  • sklearn의 ordinalEncoder 클래스 이용
python
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]

# 카테고리 목록 얻기
ordinal_encoder.categories_
  • ocean_proximity의 특성
    • 1H OCEAN : 0
    • near ocean: 1
    • inland : 4
  • 0번 지역과 1번 지역의 거리보다 0번 지역과 4번 지역의 거리가 가깝다.
  • 따라서, 카테고리별 이진 특성을 만든다. 1H OCEAN 이면 한 특성이 1, 그 외 0 inland 이면 한 특성이 0, 그 외 1
  • 이를 one-hot encoding이라고 하며, 새로운 특성을 더미특성이라고 함.
python
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot

"""
결과: 사이파이 희소행렬(0으로 대부분 채워진 행렬)
<16512x5 sparse matrix of type '<class 'numpy.float64'>'
	with 16512 stored elements in Compressed Sparse Row format>
"""

# 사이파이 희소행렬을 넘파이 배열로
housing_cat_1hot.toarray()

# 카테고리 리스트 추출
cat_encoder.categories_

나만의 변환기

특별한 정제작업을 위해 나만의 변환기를 만들 필요가 있음.

python
# 나만의 변환기
from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True):
        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.values)

특성 스케일링

머신러닝 알고리즘은 입력 숫자 특성들의 스케일이 많이 다르면 잘 작동하지 않음.

따라서 특성의 범위를 잘 조정해야 함.

  1. min-max 스케일링 = 정규화
    • 0~1 사이에 들어오도록 값 조정
  2. 표준화
    • 평균을 빼고 분산이 1이 되도록 조정

변환 파이프라인

변환 단계가 많을 때는 연속된 변환을 순서대로 처리해야 한다.

python
# 변환 파이프라인
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([[-0.94135046,  1.34743822,  0.02756357, ...,  0.01739526,
         0.00622264, -0.12112176],
       [ 1.17178212, -1.19243966, -1.72201763, ...,  0.56925554,
        -0.04081077, -0.81086696],
       [ 0.26758118, -0.1259716 ,  1.22045984, ..., -0.01802432,
        -0.07537122, -0.33827252],
       ...,
       [-1.5707942 ,  1.31001828,  1.53856552, ..., -0.5092404 ,
        -0.03743619,  0.32286937],
       [-1.56080303,  1.2492109 , -1.1653327 , ...,  0.32814891,
        -0.05915604, -0.45702273],
       [-1.28105026,  2.02567448, -0.13148926, ...,  0.01407228,
         0.00657083, -0.12169672]])
"""

사이킷런에서는 Pipeline 클래스를 사용한다.

  • 하나의 변환기로 범주형/수치형 모두 처리하는 ColumnTransformer 사용.
python
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),
    ])
"""
수치형은 아까 만든 파이프라인에 넣어준다. (밀집행렬 반환)
범주형은 OneHotEncoder()에 넣어준다. (희소행렬 반환)

full_pipeline은 밀집/희소 행렬이 섞이면 최종 행렬의
밀집 정도를 추정한다. 임계값보다 낮으면 희소행렬, 그렇지 않으면
밀집 행렬을 반환한다.
"""
housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared

6. 모델 선택과 훈련

훈련 세트에서 훈련

선형회귀 모델 훈련

python
from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
  • 어느 정도의 정확성을 보장하는 예측이 나왔다.
python
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("예측:", lin_reg.predict(some_data_prepared))
print("레이블:", list(some_labels))
  • 과연 이 모델은 어느 정도의 오차를 가지고 있는가? RMSE 측정
python
# MSE 측정
from sklearn.metrics import mean_squared_error as mse
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mse(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
print(lin_rmse)

# 결과: 68628.19819848922
  • 예측 오차가 약 $68628 → 과소적합된 사례

결정트리 모델 훈련

python
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)

housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mse(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)

# 결과 : 0.0
  • 예측 오차가 0 ⇒ 과대적합된 사례 (이런 모델은 없는거나 마찬가지!)

교차검증

  • 두 모델 중 어느 것이 적합한지 판단하기
  • k-fold cross-validation : 10개의 fold로 훈련 세트를 분할. 이후 결정 트리모델을 10번 훈련 & 평가.

결정트리 교차검증

python
from sklearn.model_selection import 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)

def display_scores(scores):
    print("점수", scores)
    print("평균:", scores.mean())
    print("표준편차:", scores.std()) 

display_scores(tree_rmse_scores)

결과를 보면 다음과 같다.

python
"""
결과 : 별로 좋지 않다.
점수 
[69327.01708558 65486.39211857 71358.25563341 69091.37509104
 70570.20267046 75529.94622521 69895.20650652 70660.14247357
 75843.74719231 68905.17669382]
평균: 70666.74616904806
표준편차: 2928.322738055112
"""

선형회귀 교차검증

python
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)

결과는 다음과 같다.

python
"""
결과 : 조금 더 낫다
점수 :
[66782.73843989 66960.118071   70347.95244419 74739.57052552
 68031.13388938 71193.84183426 64969.63056405 68281.61137997
 71552.91566558 67665.10082067]
평균: 69052.46136345083
표준편차: 2731.6740017983443
"""

결정트리 모델이 더 과대적합되었다고 볼 수 있다.

랜덤포레스트 모델의 교차검증

python
from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(housing_prepared, housing_labels)

housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse

from sklearn.model_selection import cross_val_score

forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,
                                scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)

scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring="neg_mean_squared_error", cv=10)
pd.Series(np.sqrt(-scores)).describe()

7. 모델 세부 튜닝

그리드 탐색

하이퍼파라미터 자동 조정하는 것.

python
from sklearn.model_selection import GridSearchCV

param_grid = [
    # 12(=3×4)개의 하이퍼파라미터 조합을 시도.
    {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
    # bootstrap은 False로 하고 6(=2×3)개의 조합을 시도합니다.
    {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
  ]

forest_reg = RandomForestRegressor(random_state=42)
# 다섯 개의 폴드로 훈련하면 총 (12+6)*5=90번의 훈련 실현.
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                           scoring='neg_mean_squared_error',
                           return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)

grid_search.best_params_
grid_search.best_estimator_

# 평가 점수 확인
cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)
pd.DataFrame(grid_search.cv_results_)

랜덤 탐색

하이퍼파라미터 탐색 공간이 커질 때 사용하는 기법

앙상블 탐색

모델을 세밀하게 튜닝하는 또 다른 방법은 최상의 모델을 연결해보는 것.

모델의 그룹이 최상의 단일 모델보다 더 나은 성능 발휘할 때가 많음.

자세한 내용은 7장에서 → 클릭!

최상의 모델과 오차 분석

  • 최상의 모델을 분석해 모델 통찰.
  • 여기서는 RandomForestRegressor를 통해 각 특성의 상대적 중요도를 이야기해줌
python
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
python
array([6.96542523e-02, 6.04213840e-02, 4.21882202e-02, 1.52450557e-02,
       1.55545295e-02, 1.58491147e-02, 1.49346552e-02, 3.79009225e-01,
       5.47789150e-02, 1.07031322e-01, 4.82031213e-02, 6.79266007e-03,
       1.65706303e-01, 7.83480660e-05, 1.52473276e-03, 3.02816106e-03])
  • (중요도, 특성이름) 순으로 표시
python
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)
python
"""
결과
[(0.36615898061813423, 'median_income'),
 (0.16478099356159054, 'INLAND'),
 (0.10879295677551575, 'pop_per_hhold'),
 (0.07334423551601243, 'longitude'),
 (0.06290907048262032, 'latitude'),
 (0.056419179181954014, 'rooms_per_hhold'),
 (0.053351077347675815, 'bedrooms_per_room'),
 (0.04114379847872964, 'housing_median_age'),
 (0.014874280890402769, 'population'),
 (0.014672685420543239, 'total_rooms'),
 (0.014257599323407808, 'households'),
 (0.014106483453584104, 'total_bedrooms'),
 (0.010311488326303788, '<1H OCEAN'),
 (0.0028564746373201584, 'NEAR OCEAN'),
 (0.0019604155994780706, 'NEAR BAY'),
 (6.0280386727366e-05, 'ISLAND')]
"""

이걸 바탕으로 덜 중요한 특성들을 제외할 수 있음.

테스트 세트로 시스템 평가하기

  • full_pipeline을 사용해 데이터 변환
  • 이후 최종 모델 평가
python
final_model = grid_search.best_estimator_

X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)

final_rmse = np.sqrt(mse(y_test, final_predictions))
final_rmse

신뢰 구간 계산

python
from scipy import stats
confidence = 0.95
squared_errors = (final_predictions - y_test) ** 2
np.sqrt(stats.t.interval(confidence, len(squared_errors) - 1,
                         loc=squared_errors.mean(),
                         scale=stats.sem(squared_errors)))
python
"""
결과값: array([45893.36082829, 49774.46796717])
"""

8. 론칭, 모니터링, 시스템 유지 보수

  • 론칭: 웹앱에 넣으면 된다. (플라스크, 스프링 등등)
  • 구글 클라우드에 뿌리든, AWS를 이용하든 배포는 자유.
  • 다만, 그 이후 모니터링도 필수다.