실제 데이터로 작업하기

머신러닝을 배울 때는 인공적으로 만들어진 데이터셋이 아닌 실제 데이터로 실험해보는 것이 가장 좋습니다.

현재 여러 분야에 걸쳐 공개된 데이터셋이 아주 많이 존재합니다. 다음은 데이터를 구하기 좋은 곳입니다.

 

 

캘리포니아 주택 가격 예측 프로젝트

이번 포스팅에서는 캘리포니아 주택 가격 데이터셋을 사용할 것입니다. (데이터셋 다운받기)

캘리포니아 주택 집값을 예측할 수 있는 프로젝트를 진행한다고 가정을 해봅니다.

부동산 회사에 고용된 데이터 과학자로서 할 일은?

  • 캘리포니아 인구조사 데이터를 사용하여 캘리포니아 주택 가격 모델 생성을 합니다
  • 생성한 모델한테 새로운 측정 데이터를 주었을 때, 해당 구역의 중간 주택 가격을 예측할 수 있게 만들어야 합니다
  • 데이터셋에 대한 분석이 필요합니다.
    - 캘리포니아의 블록그룹마다 인구, 중간 소득, 중간 주택 가격, 위도, 경도 등 여러 특성들이 존재하고 있습니다.
블록그룹이란 미국 인구조사국에서 샘플 데이터를 발표하는데 사용하는 최소한의 지리적 단위(하나의 블록은 보통 600~3000명 포함)
  • 무엇보다 프로젝트를 진행하기 전에 주택 모델 생성의 최종 목적이 무엇인가를 파악하는 것이 매우 중요합니다.
    ✓ 알고리즘 선택, 모델 평가를 위한 성능 지표 선택, 모델 조정 (tweaking)을 위한 시간 투자 등 결정 요소

 

문제 정의

  • 작업 목적에 맞게 ML 시스템을 설계해야 합니다 -> 집값을 예측하기 위해서는 어떤 학습을 통해 모델을 만들어야 할까?
    Q: 지도, 비지도, 준지도, 강화 학습?
        ✓ A: 레이블 된 훈련 샘플 (각 구역 별 중간 주택 가격이 제시됩니다) -> 지도 학습
    Q: 분류 작업, 회귀 작업?, 지도 학습에서 어떤 알고리즘을 사용해야 할까?
        ✓ A: 주어진 구역의 중간 주택 가격을 예측 -> 회귀 작업
    Q: 배치 학습, 온라인 학습?
        ✓ A: 빠르게 변하는 데이터에 적응 하지 않음 -> 배치 학습 (오프라인 학습)

 

성능 측정 지표 선택

  • 모델 학습을 위한 성능 측정 지표 선택
    ✓ (학습하는 동안) 모델의 예측 결과에 얼마만큼의 오차가 있는가?
    ✓ 예로서, 모델이 예측한 주택 값과 레이블로 주어진 실제 값 사이에 오차가 커질 수록 예측에 얼마나 오류가 있는지 확인가능
  • RMSE (root mean square error, 평균 제곱근 오차)
    회귀 (regression) 문제에서 사용하는 전형적인 성능 지표입니다

RMSE (Root Mean Square Error) 란

Error(에러) 값에 (Square)제곱을 한 후 Mean(평균) 값을 구한 다음 Root(제곱근)을 씌우는 것입니다.

 

RMSE평균 제곱근 오차라고 불리며 실제 값과 예측 값의 오차를 나타내는 정도입니다. 즉 예측에 오류가 얼마나 많이 있는지를 가늠하게 해줍니다. 물론 값이 작으면 작을수록 좋습니다.

 

RMSE에서 수식을 분석해보겠습니다.

 

  • m: 인스턴스의 수 (행), ex. 인스턴스1: (경도, 위도, 거주자수, 중간 소득, •••)
  • x(i): i 번째 인스턴스의 전체 특성값 벡터
  • y(i): i 번째 인스턴스의 기계가 예측한 전체 특성값 벡터
  • X: RMSE(X,h)에서 X는 데이터셋의 모든 인스턴스 특성값들을 포함하는 행렬(레이블 제외)
  • h: 가설, 즉 기계가 백터 값으로 예측값을 출력할 수 있도록 만드는 예측 함수
    - 만약 h(x(1)) = (-118.29, 33.91, 1,416, 38,372 •••) 이면 h(예측 함수)로 값을 계산(예측)을 합니다 (158,400)
  • 마지막으로 수식의 1부터 m까지의 값을 모두 (h(x(i) - y(i))^2의 구해 평균을 구하면 평균 제곱근 오차 값을 구할 수 있게 됩니다. 

 

데이터 준비하기

데이터를 학습하고 예측하기 위해서는 아나콘다 쥬피터 노트북 또는 VScode처럼 작업할 수 있는 환경이 필요로 합니다.

 

데이터 가져오기

1. 기본 설정 후 학습할 데이터와 학습한 데이터를 기반으로 테스트할 데이터를 가져옵니다.

import os #Python의 내장 모듈 중 하나로, 파일 및 디렉터리 관리를 위한 함수와 클래스를 제공합니다.
import tarfile #Python의 내장 모듈 중 하나로, tar 파일을 조작하기 위한 함수와 클래스를 제공합니다.
import urllib.request #URL을 통해 데이터를 다운로드하기 위한 모듈입니다.

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/" #데이터를 다운로드할 기본 URL을 지정합니다
HOUSING_PATH = os.path.join("datasets", "housing") #데이터를 저장할 로컬 디렉터리 경로를 설정합니다
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz" #다운로드할 데이터 파일의 URL을 설정합니다

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) #지정된 URL에서 데이터 파일을 다운로드 후 tgz_path 경로에 저장
    housing_tgz = tarfile.open(tgz_path) #다운로드한 tar 파일 열기
    housing_tgz.extractall(path=housing_path) #tar 파일을 압축 해제 후 압축 해제된 파일들을 housing_path 디렉터리에 저장
    housing_tgz.close() #tar 파일 닫기
fetch_housing_data()

 

가져온 데이터 저장하고 읽어오기

fetch_housing_data 메서드를 사용하여 가져온 csv 파일을 가져오는 메서드를 만듭니다.

import pandas as pd

def load_housing_data(housing_path=HOUSING_PATH):
    csv_path = os.path.join(housing_path, "housing.csv") #housing_path 경로에 housing.csv를 추가
    return pd.read_csv(csv_path) #지정한 디렉터리 위치에 있는 housing.csv 파일읽기

 

데이터가 어떻게 되어 있는지 확인하기

데이터를 housing 변수로 담아낸 후 head 메서드를 사용하여 최초 5개의 행을 출력합니다.

housing = load_housing_data() #함수를 호출하여 데이터셋을 로드하고, 그 결과를 housing 변수에 저장합니다. 이렇게 하면 데이터셋이 메모리에 로드되고 데이터프레임 형태로 저장됩니다.
housing.head() #데이터프레임의 처음 다섯 개의 행을 출력
#head() 메서드는 데이터프레임의 상위 몇 개의 행을 반환하고,기본적으로 처음 다섯 개의 행을 반환

housing.info() #데이터프레임인 housing의 정보를 요약하여 보여주는 메서드

 

info를 통해 확인해보면 다음과 같이 데이터를 분석할 수 있습니다.

  • 데이터셋에 20,640 인스턴스가 있음을 확인할 수 있습니다
  • 207개 인스턴스는 total_bedrooms 특성이 없습니다
  • ocean_proximity 특성을 제외한 나머지는 모두 숫자형입니다 (float64)
  • ocean_proximity 특성은 값이 반복되므로 범주형 특성입니다 (텍스트)

데이터 시각화

데이터를 시각화하여 확인하면 좀 더 직관적으로 분석할 수 있게 됩니다.

시각화를 위해서는 여러 라이브러리가 있으며 다음은 matplotlib를 사용하여 시각화를 한 결과입니다.

import matplotlib.pyplot as plt

housing.hist(bins=50, figsize=(12, 8))
plt.show()

 

데이터를 확인했을 때 데이터에 상한 (upper limit)이 있음을 확인할 수 있습니다.

추가로 중간 소득 특성 ($)이 스케일링 되었음을 확인할 수 있습니다. -> 0~50$면..

 

가져온 데이터 무작위 셔플링

가져온 데이터를 그냥 사용하기보다 무작위로 섞는 과정을 진행합니다. 무작위로 섞어서 훈련셋과 테스트셋을 나눕니다.

np.random.seed(42) #42를 시드로 설정한다, 발생되는 랜덤 값을 예측할 수 있다.
np.random.rand(5) #시드값은 보통 시간 등을 사용해 설정하지만 사람이 수동으로 설정할 수도 있다.
import numpy as np

# For illustration only. Sklearn has train_test_split()
def split_train_test(data, test_ratio): #데이터와 테스트셋의 비율을 인자로 받는다.
    shuffled_indices = np.random.permutation(len(data)) #데이터 인덱스를 무작위로 섞은 배열을 생성, permutation: 순열, 몇 개를 골라 순서를 고려해 나열한 경우의 수
    test_set_size = int(len(data) * test_ratio) #테스트 세트의 크기를 결정
    test_indices = shuffled_indices[:test_set_size] #섞인 인덱스 배열에서 처음부터 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) #테스트셋 비율을 0.2로 설정
len(train_set) #훈련셋의 크기를 확인한다. # 16512
len(test_set) #데이터셋의 크기를 확인한다. # 4128

 

위에는 무작위로 테스트셋과 훈련셋 나누는 작업을 직접 구현하였지만 sklearn에서는 train_test_split 클래스를 제공합니다.

sklearn 에서 제공하는 train_test_split 을 사용하면 별 다른 구현 없이 데이터를 테스트셋과 훈련셋으로 나눌 수 있습니다.

from sklearn.model_selection import train_test_split
# 난수를 이용하여 랜덤하지 않게 훈련셋과 테스트셋으로 나눈다.
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

 

무작위로 샘플링했을 때 발생하는 문제점

위에서 train_test_split 메서드로 무작위로 테스트셋과 훈련셋을 나눴지만 무작위로 나누면 편향될 수 있습니다.

  • 만약에 전문가가 중간 소득이 중간 주택 가격을 예측하는데 매우 중요하다고 얘기했다는 가정을 해본다면 이 경우 테스트 셋이 전체 데이터셋에 있는 여러 소득 카테고리를 잘 대표해야 합니다.
  • 중간 소득은 수치형 특성이므로 소득에 대한 카테고리 특성을 만들어야 합니다.
  • 중간 소득 대부분은 $15,000~$60,000 사이에 모여 있지만 일부는 $60,000를 넘기기도 합니다.
  • 계층별로 데이터셋에 충분한 샘플 수가 있어야지 아니면 계층의 중요도를 추정하는데 편향이 발생한다는 것 즉 -> 계층을 너무 많이 나누면 안되고 각 계층이 충분히 커야 합니다(양).

해결 방법 (계층적 샘플링)

위처럼 무작위 샘플링을 통해 편향적인 데이터셋을 만들어내는 것에 해결할 수 있는 방법은 전체 데이터셋에서 무작위로 데이터를 뽑는 것이 아닌 비율에 따라서 뽑는 방법을 사용할 수 있겠습니다. -> 계층적 샘플링이라고 불립니다.

 

물론 sklearn에서는 이런 작업을 대신 해주는 메서드를 만들었으며 그것이 바로 StratifiedShuffleSplit() 메서드입니다.

 

# median_income 특성을 이용해서 소득을 나눌 구간(bins)를 설정하고 각 구간에 대응하는 카테고리 레이블을 정의한다.
housing["income_cat"] = pd.cut(housing["median_income"], #median_income 열을 기반으로 income_cat 열을 생성한다.
    bins=[0., 1.5, 3.0, 4.5, 6., np.inf], #소득을 나눌 구간(카테고리)을 정의, 0에서 1.5, 1.5에서 3.0, 3.0에서 4.5, 4.5에서 6.0 그리고 6.0 이상의 다섯 개의 구간을 설정한다.
    labels=[1, 2, 3, 4, 5]) #각 구간에 대응하는 카테고리 레이블을 정의한다. 구간에 따라 소득 카테고리가 1부터 5까지 할당된다.
  • 먼저 income_cat 이름의 적당한 계층 개수로 특성을 만들었습니다.
  • 카테고리를 1, 2, 3, 4, 5로 나누고, 카테고리 1은 0에서 1.5까지 범위(즉$15,000 이하)이고 카테로기 2는 1.5에서 3까지 범위가 되는 식입니다
  • 무작위로 막 뽑기에는 2~6 구간이 너무 많아서 그쪽 소득으로 편향될 가능성이 있기 때문에 계층으로 나눠 각 계층에서 일정 몇%씩 뽑는 그런 메커니즘으로 진행하면 공정해집니다.
    -> 즉 방금 한 구간나누기는 계층적 샘플링을 하기 위해서 각 값이 본인의 카테고리를 잘 대표할 수 있도록 구간을 나눈 것

중간 소득을 카테고리 특성으로 나누기 전(좌), 후(우)

이제 위에서 만든 소득 카테고리를 기반으로 계층적샘플링을 할 준비가 끝났습니다.

- 계층적샘플링이란? -> 자신이 속한 구간을 잘 대표하는 데이터들을 편향하지 않게 잘 뽑는 것입니다.

중간소득을 기반으로 만든 카테고리 특성을 sklearn의 StratifiedShuffleSplit을 사용하여 계층 샘플링을 할 차례입니다.

from sklearn.model_selection import StratifiedShuffleSplit #계층적 셈플링
#                      데이터를 분할하는 횟수=1, 테스트셋 비율=0.2, 난수 초기값을 설정
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

#위에서 설정한 계층적 샘플링 객체인 split에 split메서드를 사용하여 housing 데이터에서 새로만든 소득 카테고리를 가지고 계층적 샘플링을 한다
for train_index, test_index in split.split(housing, housing["income_cat"]): # housing데이터프레임에서 imcome_cat을 사용하여 분할, split메서드를 호출하여 데이터를 분할한다.
    strat_train_set = housing.loc[train_index] #계층적셈플링 훈련셋 ,loc: pandas 라이브러리에서 데이터프레임에서 특정 행을 선택하거나 조건에 맞는 데이터를 인덱싱하는 기술
    strat_test_set = housing.loc[test_index] #계층적셈플링 테스트셋
    # loc:열만 가져오기, df.loc[칼럼슬라이싱]
#이를 통해 전체 데이터셋에서 각 소득 카테고리가 차지하는 비율을 확인할 수 있다.
housing["income_cat"].value_counts() / len(housing) #income_cat열의 각 소득 카테고리별 데이터 포인트 비율을 계산하는 코드다.

 

 

계층적샘플링을 했을때와 무작위로 뽑았을때 나타나는 오류 오차범위를 확인할 수 있습니다.

-> 확인결과 랜덤으로 뽑을때 비율이 방금 계층 샘플링으로 만든 housing["income_cat"]보다 부정확하다는 것을 확인 할 수 있습니다

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() #Overall:전체 데이터셋, Stratified: 테스트셋, Random: 랜덤 테스트셋
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

중간 소득을 카테고리 특성으로 나누기 전(좌), 후(우)

- 계층 샘플링(구간을 나눠)을 사용해 만든 테스트셋의 소득 카테고리 비율이 전체 데이터셋 비율과 가까운 것을 확인할 수 있다.(공평)

 

계층적 샘플링을 통해서 데이터도 잘 뽑았겠다 이제 사용한 (방금 만들었던)  income_cat 열을 지워줍니다.

for set_ in (strat_train_set, strat_test_set):
    #axis=0: 행삭제, axis=1: 열삭제
    set_.drop("income_cat", axis=1, inplace=True)

 

계층적 샘플링 후 시각화

계층적샘플링을 통해서 분리한 훈련셋을 이용해 시각화를 합니다.

#복사를 해서 사용하는 이유는 기존 훈련셋을 손상시키지 않기 위해 복사본을 만드는 것이다.
housing = strat_train_set.copy() #계층 샘플링을 사용해 만든 테스트셋, copy()는 값만 복사하는 것, 참조 X
housing.plot(kind="scatter", x="longitude", y="latitude")
save_fig("bad_visualization_plot") #생성된 시각화를 파일로 저장하기

하지만 위는 안좋은 시각화 파일입니다. 좋은 시각화 파일을 만들려면 투명도를 설정해야합니다.

housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
save_fig("better_visualization_plot")

다음처럼 설정하면 그래프 유형, 축 이름, 컬러맵, 투명도 등을 설정하여 데이터 시각화 할 수 있습니다

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,
             sharex=False)
plt.legend()
save_fig("housing_prices_scatterplot")

 

상관관계

다음으로 상관관계 corr()를 조사해야합니다. 피어슨의 r 이라고 불리는 표준 상관 계수는 -1 < r < 1 의 범위를 갖습니다.

  • 1에 가까우면 강한 양의 상관관계   (예: 중간 주택 가격이 상승하면 중간 소득도 증가하는 경향)
  • -1에 가까우면 강한 음의 상관관계 (예: 위도가 올라갈 수록 중간 주택 가격은 조금씩 내려가는 경향)
  • 특징으로는 상관계수는 선형적인 상관관계만 측정할 수 있으며 비선형적인 상관관계는 측정할 수 없습니다
corr_matrix = housing.corr(numeric_only=True) #각 열(특성) 간의 상관 관계를 계산한다.
corr_matrix

상관계수를 계산한 값

 

상관관계의 값에 따라 데이터는 여러 형태로 존재할 수 있습니다.

여러가지데이터셋에나타난표준상관계수(출처: 위키백과- 퍼블릭도메인이미지)

 

corr_matrix["median_house_value"].sort_values(ascending=False)
#"median_house_value" 열과 다른 모든 열 간의 상관 관계를 가져와 내림차순으로 정렬

 

다음은 상관계수를 시각화한 결과입니다.

# from pandas.tools.plotting import scatter_matrix # For older versions of Pandas
from pandas.plotting import scatter_matrix

attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"] # 4x4
scatter_matrix(housing[attributes], figsize=(12, 8))# 선택한 특성 간의 모든 산점도를 생성한다. 그림 크기 설정
save_fig("scatter_matrix_plot")

  • 중간 주택 가격(median_house_value)을 예측하는데 가장 유용할 것 같은 특성을 중간 소득(median_income)이므로 상관관계 산점도를 확대대하여 분석해봅니다.
    ✓ 위에서 상관관계는 선형적인 상관관계만 측정할 수 있다고 하였는데 아래에서 
housing.plot(kind="scatter", x="median_income", y="median_house_value",
             alpha=0.1)
plt.axis([0, 16, 0, 550000]) # 가격 제한 50000이나 이상한 데이터를 학습하지 않게 제거작업이 필요할 수 있다.
save_fig("income_vs_house_value_scatterplot")

중간 주택 가격과 중간 소득 간 상관계수가 1에 가까운 값을 보이는 것을 알 수 있다.

- 상관관계가 매우 강하다는 것을 알 수 있으며

- 1. 위쪽으로 향하는 경향을 볼 수 있고, 포인트들이 너무 널리 퍼져 있지 않음 -> 수직일수록 좋고 주변에 지져분한게 없어야 좋음.

- 2. 앞서 본 가격 제한값이 $500,000에서 수평선을 잘 보임.

 

이렇게하여 median_income 과 median_house_value 가 집값을 예측하는데 관계가 높음을 알 수 있습니다.

 

특성 조합하기 (특성공학)

특성을 조합해서 집값을 예측할 수 있는 유의미한 새로운 특성을 만들 수 있습니다.

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"]

 

새로 만든 특성들과 median_house_value (중간 주택 가격)과의 상관관계(피어슨의 r)을 확인해보고 유의미성을 확인합니다.

corr_matrix = housing.corr() # 새로운 조합으로 만들어진 특성간의 상관관계 파악
corr_matrix["median_house_value"].sort_values(ascending=False)

새로 만든 특성들이 추가된 상태에서 새로 상관관계를 출력합니다.

- 새로운 rooms_per_household는 양의 상관관계를 보이고

- 새로운 bedrooms_per_room은 음의 상관관계를 보이고

- 새로운 population_per_household도 음의 상관계수를 보입니다.

 

이후 새로운 rooms_per_household 특성과 중간 주택 가격의 산점도(시각화)로 상관관계도 파악해볼 수 있습니다.

# 특성을 조합해서 새로운 특성을 가지고 그림그리기
housing.plot(kind="scatter", x="rooms_per_household", y="median_house_value",
             alpha=0.2) # x축의 데이터 값을 rooms_per_household, y축의 데이터 값을 median_house_value 값으로 설정
plt.axis([0, 5, 0, 520000])
#x축은 0에서 5까지, y축은 0에서 520,000까지의 범위로 지정합니다. 이렇게 범위를 설정함으로써 산점도의 축의 스케일을 조절할 수 있습니다
plt.show()

 

데이터 정제

머신러닝 알고리즘을 위한 데이터를 준비해야하는데 이 작업은 수동으로 하는 대신 함수를 만들어서 자동화를 해야합니다. 이유는

* 어떤 데이터셋에 대해서도 데이터 변화을 손쉽게 반복할 수 있습니다. (다음에 새로운 데이터셋을 사용할 때)

* 향후 프로젝트에 사용할 수 있는 변환 라이브러리를 점진적으로 구축할 수 있습니다.

* 실제 시스템에서 알고리즘에 새 데이터를 주입하기 전에 변환시키는 데 이 함수를 사용할 수 있습니다.

* 여러 가지 데이터 변환을 쉽게 시도해볼 수 있고 어떤 조합이 가장 좋은지 확인하는 데 편리합니다.

 

다음은 레이블 데이터에 같은 변형을 적용하지 않기 위해 훈련 데이터셋에서 레이블(정답)을 분리해서 따로 저장하는 것입니다

# drop은 지우는 것이 아니라 해당 값만 임시로 뺀다는 것이다, axis=1은 열을 기준으로 삭제하라는 의미
housing = strat_train_set.drop("median_house_value", axis=1)
#데이터프레임에서 median_house_value 열만 선택하여 housing_labels 시리즈에 저장 
housing_labels = strat_train_set["median_house_value"].copy()

 

NULL 값 대응하기

다음으로 housing에서 null 값이 들어있는 행을 나타냅니다.

sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows

total_bedrooms이 비어있는 것을 확인할 수 있다.

비어있는 null값이 들어있는 데이터를 가지고 모델을 훈련하게 될 경우 오류가 날 수 있습니다.

해결할 수 있는 방안에는 다음 3가지가 있습니다.

1. 비어있는 해당 인스턴스를 제거한다.

sample_incomplete_rows.dropna(subset=["total_bedrooms"])    # option 1: 해당 인스턴스 제거 dropna(drop not a number)

2. 비어있는 해당 특성을 삭제한다.

sample_incomplete_rows.drop("total_bedrooms", axis=1)       # option 2, 해당 특성 제거, total_bedrooms 삭제

3. 비어있는 값을 다른 값으로 대체한다(중간값, 평균 등등)

median = housing["total_bedrooms"].median()
sample_incomplete_rows["total_bedrooms"].fillna(median, inplace=True) # option 3: 다른 값으로 채우기, 중간 값으로 채움

 

SimpleImputer 클래스로 대응하기

비어있는 값을 채우기 위한 sklearn에서 만든 SimpleImputer 클래스가 존재합니다.

 

● SimpleImputer는 여러 가지 전략을 통해 결측값을 대체할 수 있습니다

  • 평균값(mean): 각 열의 평균값으로 결측값을 대체합니다. 수치형 데이터에 적합합니다.
  • 중앙값(median): 각 열의 중앙값으로 결측값을 대체합니다. 수치형 데이터에 적합하며, 이상치(outliers)의 영향을 줄일 수 있습니다.
  • 최빈값(most_frequent): 각 열의 최빈값으로 결측값을 대체합니다. 수치형 및 범주형 데이터 모두에 적합합니다.
  • 상수값(constant): 사용자 정의 상수값으로 결측값을 대체합니다. 특정 값을 지정하여 결측값을 대체하고 싶을 때 사용합니다.
평균값: 1,2,3,4,5 = (1+2+3+4+5) / 5
중앙값: 1,2,3,4,5 = (1+5) / 2

 

● 집값을 예측하는 현재 데이터에서는 median을 사용하여 중간값으로 채웁니다.

from sklearn.impute import SimpleImputer #데이터셋에 비어있는 특성값을 채우기 위한 변환기
imputer = SimpleImputer(strategy="median") #클래스를 중간값 채우기로 설정
# (strategy="median"은 중간값으로 채운다는것을 의미한다)

 

● 중간값이 수치형 특성에서만 계산될 수 있기 때문에 텍스트 특성인 ocean_proximity를 제외한 데이터 복사본을 생성합니다. (중요)

housing_num = housing.drop("ocean_proximity", axis=1) # 숫자가 아닌 특성인 ocean_proximity 제거
# alternatively: housing_num = housing.select_dtypes(include=[np.number])

 

● 원본 데이터에서 housing에서 ocean_proximity 특성을 뺀 나머지 데이터에서 비어있는 값이 있으면 중간값으로 대체합니다. 

    - fit = 학습

# imputer 객체를 사용하여 housing_num 데이터프레임에서 중간값을 계산한다.
# fit()메서드를 호출하면 imputer객체가 housing_num 데이터프레임에서 각 열의 중간값 계산
imputer.fit(housing_num) # imputer클래스한테 housing_num 데이터의 중간값을 계산하고 비어있는 곳에 채우도록 학습

 

● 다음 명령어를 수행하면 채워질 각 특성들의 중간값을 확인할 수 있습니다.

- imputer는 각 특성의 중간값을 계산해서 그 결과를 객체의 statistics_ 속성에 저장합니다.

imputer.statistics_ # 중간값 확인 (채워질 값)

 

● SimpleInputer와 median 함수를 사용하여 결과를 비교하여 같은지 확인할 수도 있습니다.

# SimpleImputer와 직접 median()을 이용해 계산한 결과 비교
housing_num.median().values

학습된 imputer 객체를 사용해 훈련 세트에서 누락된 값을 학습한 중간값으로 바꿀 수 있습니다.

- fit(학습)된 imputer 객체로 housing_num에 비어있는 값을 transform(변환)시켜준다.

- imputer.fit_transform(housing_num)메서드로 훈련과 변환을 한번에 시킬 수 있다.

# housing_num에서 빠져있던 값들이 imputer에 설정된 중간값으로 채워진다.
X = imputer.transform(housing_num) # 빠져있던 것들이 채워진다.

아래는 변형된 특성들이 들어 있는 평범한 numpy 배열이다. 이를 다시 pandas DataFrame으로 되돌린다.

# housing_tr: 새로운 데이터프레임을 저장할 변수
# pd.DataFrame() 생성자를 사용해 새로운 데이터프레임을 만든다
# X = 비어있던 값을 중간값으로 채워넣은 데이터(housing_num)가 들어간다.
# columns: 열 이름을 지정한다. housing_num의 열 이름과 동일하게 설정
# index=housing.index: housing(원본) 인덱스를 그대로 사용해 행 인덱스를 원본과 동일하게 설정
housing_tr = pd.DataFrame(X, columns=housing_num.columns, #훈련셋의 누락된 값을 중간값으로 채우기
                          index=housing.index) #NumPy 배열을 Pandas DataFrame으로 되돌리기

 

# 비어있던 특성의 행에 중간값들이 채워진게 보여진다.
housing_tr.loc[sample_incomplete_rows.index.values]

 

숫자가 아닌 값에 대응하기

추가로 숫자가 아닌 데이터를 처리해야하는 경우가 있을 수 있습니다. housing 데이터에서 범주형특성인 "ocean_proximity" 열 값을 housing_cat에 담습니다.

housing_cat = housing[["ocean_proximity"]]
housing_cat.head(10)

 

텍스트에서 숫자로 변환하기 (해결방법 X)

범주형 특성은 각 범주에 대응하는 숫자로 변환하되 숫자로만 표현하면 실제 값의 의미하고는 멀어질 수 있는 값이 배정될 수 있습니다.

그러므로 one-hot인코딩(이진특성)을 만들어서 해결할 수 있습니다. 이를 위해 sklearn의 OrdinalEncoder 클래스를 사용합니다.

from sklearn.preprocessing import OrdinalEncoder

ordinal_encoder = OrdinalEncoder() #각 범주를 대응하는 숫자로 변환
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]
# housing_cat: ocean_proximity의 값을 갖고 
# fit_transform(): 학습과 변환을 한 번에  housing_cat을 이용하여 학습과 변환

 

값은 array([[0.], [0.], [4.], [1.], [0.], [1.], [0.], [1.], [0.], [0.]]) 입니다. 

  • 하지만 위처럼 값을 표현하면가까이 있는 두 값이 떨어져 있는 두 값보다 유사하다고 판단하는 문제가 발생합니다.
  • 예를 들어 0과 4보다 0과 2가 더 가깝습니다 (숫자 상으로는), 그러나 실제로는 4( NEAR OCEAN)이 2(ISLAND)보다 0 (<1H OCEAN)과 유사합니다.
  • 정수보다 이진 특성을 만들어서 해결할 수 있습니다 -> one-hot 인코딩

텍스트에서 숫자로 변환하기 (해결방법 O)

sklearn에서 제공하는 one-hot 인코딩을 사용한다면 범주의 값을 원-핫 벡터로 바꾸기 위한 OneHotEncoder 클래스를 제공합니다.

from sklearn.preprocessing import OneHotEncoder
# 두개의 백터의 값을 제곱해서 차를 더하고 제곱근을 씌워준다.
cat_encoder = OneHotEncoder()
# housing_cat = housing[["ocean_proximity"]] 범주형 특성이다.
# fit_transform(): 학습과 변환을 한 번에  housing_cat을 이용하여 학습과 변환
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
housing_cat_1hot.toarray() # 범주형 특성을 가지고 핫인코딩한 결과

사진 처럼 보이는게 희소 행렬이라고 한다.

# 밀집 배열을 출력하도록 설정, True면 희소 행열을 출력
# cat_encoder 객체를 사용하여 범주형 데이터인 Housing_cat을 원-핫 인코딩한다.
cat_encoder = OneHotEncoder(sparse_output=False)
# fit_transform 메서드로 housing_cat 데이터프레임의 각 범주형 특성을 원-핫 인코딩하여 수치형 특성으로 변환
# 결과 데이터는 housing_cat_1hot에 저장, fit_transform으로 변환 작업 수행
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot

 

변환기 함수로 만들기

추가로 나만의 변환기를 만들어서 새로운 특성을 만들 수 있습니다.

- init 으로 add_bedrooms_per_room을 True로 만든다면 add_bedrooms_per_room 특성을 생성하는 클래스입니다.

- rooms_per_household (방 수 / 인구수) 특성을 만듭니다

- population_per_household ((방 수 / 인구수) / 방 수) 특성을 만듭니다.

- 이후 새로 bedrooms_per_room 특성을 만들지 말지를 결정하고 새롭게 만든 특성들을 반환합니다

from sklearn.base import BaseEstimator, TransformerMixin
#사용자 정의 변환기를 정의하고 해당 변환기를 사용하여 데이터셋으로 새로운 특성을 만들기
# column index, 데이터셋의 열 인덱스를 나타내는 변수들, 특정 열을 접근할 때 사용
rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

# 사용자 정의 변환기를 정의합니다. 이 클래스는 BaseEstimator, TransformerMixin 클래스를 상속받는다.
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    # add_bedrooms_per_room은 나중에 특성을 추가할 때 침실 수 대비 방의 비율을 추가할지 여부를 결정
    def __init__(self, add_bedrooms_per_room=True): # no *args or **kargs
        self.add_bedrooms_per_room = add_bedrooms_per_room
        # 학습 데이터셋을 입력받아 아무 작업을 수행하지 않고 self를 반환
        # 변환기는 아무런 학습을 하지 않기때문에 빈 메서드로 정의
    def fit(self, X, y=None):
        return self  # nothing else to do
        # 변환을 수행하는 메서드로, 주어진 데이터셋 X에 대해 새로운 특성을 계산하고 반환
    def transform(self, X):
        # 가구당 방의 수를 계산한다
        # X에서 rooms_ix에 해당하는 열과 Households_ix에 해당하는 열의 값을 나눈다.
        rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
        # 가구당 인구 수를 계산한다. 인구수 / 가구수
        # X에서 population_ix에 해당 열과 households_ix에 해당 열의 값을 나눈다.
        population_per_household = X[:, population_ix] / X[:, households_ix]
        # 매개변수가 True인 경우 침실 수 대비 방의 비율을 계산
        if self.add_bedrooms_per_room: # 
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            # return np.c_[인자값, 가구당 방의 수, 가구당 인구 수, 방당 침대 수]
            # np.c_: 두 개의 1차원 배열을 칼럼으로 세로로 붙여서 2차원 배열 만들기
            return np.c_[X, rooms_per_household, population_per_household,
                         bedrooms_per_room]
        else: # 침실 수 대비 방의 비율을 추가하지 않는다.
        	# np.c_: 두 개의 1차원 배열을 칼럼으로 세로로 붙여서 2차원 배열 만들기
            return np.c_[X, rooms_per_household, population_per_household]

# CombinedAttributesAdder클래스의 attr_adder 객체를 만든다.
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)

# 위에서 만든 변환기로 housing의 값들로 X(housing.values), rooms_per_household, 
# population_per_household 값들을 2차원 배열로 반환한다.
housing_extra_attribs = attr_adder.transform(housing.values)

 

 

특성 스케일링

  • 데이터에 적용할 가장 중요한 변환 중 하나가 특성 스케일링입니다. 입력 숫자 특성들의 스케일이 많이 다르면 잘 동작하지 않습니다.
  • 즉, 전체 방 개수의 범위는 6에서 39,320인 반면에 중간 소득의 범위는 0에서 15까지인 경우가 예시로 들 수 있겠습니다.
  • 스케일링을 하는 방법은 min-max 스케일링(정규화)이 가장 간단합니다, sklearn에서는 MinMaxScaler 변환기를 제공한다.
    -> 0~1 범위에 들도록 값을 이동하고 스케일을 조정하면 된다, 데이터에서 최솟값을 뺀 후 최대값과 최솟값의 차이를 나누면 이렇게. 할 수 있다.
  • 하지만 표준화는 다릅니다. 먼저 평균을 뺀 후 표준편차로 나눠 결과 분포의 분산이 1이 되도록 합니다.
    -> sklearn에서 StandardScaler 변환기 제공

변환 파이프라인

앞에 있었던 내용들을 순서대로 실행해야합니다.

  • sklearn에는 연속된 변환을 순서대로 처리할 수 있도록 도와주는 Pipeline 클래스가 있습니다.
  • Pipline 클래스를 이용하면 여러 작업을 Pipline 으로 설정하여 마지막에 fit_transform 메서드로 차례로 작업을 진행합니다.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# StandardScaler: 분산 1을 갖도록 변환하는 표준화 스케일링 함수
# 이 객체는 여러 단계의 데이터 전처리를 연속적으로 적용할 수 있도록 도와주며 이름과 해당 단계를 지정한다.
num_pipeline = Pipeline([
    # 빈 값을 대체할 값을 중간값으로 설정한다.
        ('imputer', SimpleImputer(strategy="median")), #imputer.fit(데이터값)해야함
    # 새로운 특성을 추가하는 작업을 수행 하지만 여기서는 추가안하는듯, 이 작업을 attribs_adder라는 이름으로 지정
        ('attribs_adder', CombinedAttributesAdder()),
    # 데이터의 표준화를 수행하는 작업을 수행한다. std_scaler라는 이름으로 작업 이름 지정하고 StandardScaler 객체를 사용
        ('std_scaler', StandardScaler()),
    ])
# 파이프라인을 훈련 데이터셋인 housing_num(범주형특성을 제외한 계층적샘플링을 통해 선별한 값)에 적용
# 즉 위에서 3가지 작업을 연속으로 지정할 전처리를 housing_num을 대상으로 하여 
# 작업이 끝난 값을 housing_num_tr에 넣는다.
# fit_transform: 데이터를 학습하고 주어진 데이터를 변환하는 것이다.
housing_num_tr = num_pipeline.fit_transform(housing_num)
# num_pipeline를 활용한다.
# 1.비어있는 값을 중간값으로 설정한다.
# 2.새로운 특성을 추가한다. (rooms_per_household, population_per_household)
# 3.표준화 알고리즘으로 데이터를 스케일링한다.
housing_num_tr

 

이제 full_pipeline을 만듭니다. 위에서 만들어준 Pipline과 OneHotEncoding을 동시에 작업할 수 있도록 sklearn에서 제공하는 ColumnTransformer가 추가되었습니다.

- ColumnTransformer를 사용하여 fit_transform 를 해줌으로 파이프라인을 훈련시킴과 동시에 housing 데이터를 변환할 수 있다.

from sklearn.compose import ColumnTransformer
#scikit-learn 라이브러리에서 ColumnTransformer 클래스를 가져온다
#ColumnTransformer는 데이터프레임의 열에 대한 다양한 전처리 단계를 쉽게 적용할 수 있도록 도와주는 도구다

#list는 데이터프레임 또는 배열의 모든 열을 리스트 형태로 변환한다.
#ocean_procimity 값이 빠진 수치형 특성의 열 이름을 저장
num_attribs = list(housing_num)
#범주형 특성인 ocean_proximity 열에 대해 OneHotEncoder를 사용하여 원핫 인코딩 수행
cat_attribs = ["ocean_proximity"]

#특성의 열에 따라 맞는 처리를 위해(즉, 숫자 변환, 텍스트/범주 변환) 
#ColumnTransformer 클래스 활용
full_pipeline = ColumnTransformer([
# 위에서 만든 num_pipline을 사용하여 num_attribs에 포함된 숫자형 특성들을 전처리한다.
        ("num", num_pipeline, num_attribs), 
# OneHotEncoder를 사용하여 ocean_proximity 열을 원-핫인코딩한다.
        ("cat", OneHotEncoder(), cat_attribs),
    ])

#원본 데이터프레임을 가지고 모든 전처리 단계를 거쳐서 가공된 데이터를 Housing_prepared에 넣는다.
housing_prepared = full_pipeline.fit_transform(housing)

 

모델 훈련

위에 과정을 통해 따라오면 이제 모델을 사용하여 데이터 훈련시킬 수 있습니다.

 

1. 먼저 선형 회귀 모델 알고리즘인 LinearRegression()를 사용하여 전처리가 끝난 데이터와 정답을 가지고 모델을 생성 및 학습시킵니다

from sklearn.linear_model import LinearRegression
#선형 회귀 모델을 생성한다.
lin_reg = LinearRegression()
#housing_prepared: 원본 데이터프레임을 가지고 모든 전처리 단계를 거쳐서 가공된 데이터를 Housing_prepared에 넣는다.
#housing_labels: 계층적샘플링을 통해서 얻은 실제집값
#선형 회귀 알고리즘으로 학습시킨다.
# fit은 훈련 -> 모델 파라미터를 찾아낸다.
# housing_prepared: 전처리가 끝난 데이터, housing_labels: 레이블(정답,실제 집값)데이터
lin_reg.fit(housing_prepared, housing_labels) 
#값을 예측하는 모델을 만든 것이다.

 

2. 선형 회귀 알고리즘으로 훈련된 모델을 사용해 값을 예측해봅니다

- 원본 데이터 프레임에서 5개의 값만 뽑아서 값을 예측하는 것입니다.

# let's try the full preprocessing pipeline on a few training instances
# 전체 데이터셋에서 5개 인스턴스를 가지고 학습한 모델에 적용하여 결과값 예측

#some_data: 원본 데이터프레임에서 처음 5개의 데이터 행을 선택하여 저장한다.
some_data = housing.iloc[:5]

#some_labels: 학습 데이터에 대한 실제 타겟값을 나타내는 housing_labels에서 처음 5개의 레이블을 선택하여 저장
#some_labels는 모델이 예측한 결과와 비교하기 위한 실제값이다.
#계층적샘플링을한 것
some_labels = housing_labels.iloc[:5]

#전처리 파이프라인을 사용하여 some_data에 대한 전처리를 수행하고 전처리된 데이터를 저장한다.
some_data_prepared = full_pipeline.transform(some_data)

#선형 회귀 알고리즘을 사용하여 fit(훈련)된 lin_reg 모델을 사용하여 predict(예측)을 수행
#lin_reg 모델을 사용하여 some_data_prepared에 대한 예측을 수행하고 예측값을 출력한다.
print("Predictions:", lin_reg.predict(some_data_prepared))
#실제 집값으로 전처리 파이프라인을 사용해 전처리한 데이터를 가지고 선형회귀 모델로 예측한 값과
#아래에 비교하기 위한 실제 집값을 의미한다.
print("Labels:", list(some_labels))

훈련된 모델로 예측한 집값

 

 

지금까지 한 작업들을 간략하게 정리하면

-> 중위소득이 집값을 예측하는데 매우 중요하다고 해서 중위소득(정답 레이블)을 제외한 특징들을 가공(전처리)한다

-> 가공된 특징 데이터들을 가지고 머신러닝 알고리즘 모델을 만들어 모델을 학습시킨다.

-> 중위소득을 기반으로 집값을 예측하는 모델을 만들었으면 그 모델에 값을 넣어서 테스트셋 값을 예측해보라고 시킨다.

-> 중위소득을 기반으로 집값을 예측하도록 훈련된 모델이 테스트셋 값으로 예측한 집값을 출력한다.

 

하지만 첫 번째 문장을 보면 중위 소득이 집값을 예측하는데 매우 중요하다고 해서 중위 소득을 기반으로 데이터를 가공해서 모델을 훈련시켰지만 만약에 중위 소득이 아니라 다른 특성이 더 집값하고 긴밀한 관계가 있다고 하면 문제가 발생합니다.

- 그래서 피어슨의 r 을 사용해서 상관관계를 파악하는 것입니다.

- 만약 새로운 특성을 만들었는데 해당 특성이 중위 소득보다 더 상관관계가 깊다면? 그 특성을 기반으로 다시 데이터를 가공하고 모델을 훈련시킨 후 훈련된 모델로 값을 예측하면 됩니다.

 

모델의 측정값 RMSE로 평가

모델을 훈련하고 훈련된 모델로 집값을 예측해봤으면 이제 그 값이 얼마나 정확한지, 오류가 어느정도 있는지를 확인해야힙니다.

- 그러기 위해서는 평균 오차 제곱근을 구해서 평균적인 오차 범위를 구할 수 있습니다.

- full_pipeline 을 통해서 가공이 끝난 원본 데이터(housing_prepared)로 값을 예측해서 housing_predictions 에 값을 넣습니다.

- 계층적샘플링으로 나눈 집값(즉 정답 레이블)과 housing_predictions(예측한 값)을 가지고 평균 오차 제곱을 구합니다.

- 평균 오차 제곱을 구한 값에 제곱근(sqrt)를 씌워서 RMSE 로 변환합니다 -> RMSE 의 값은 68633.40810776998 이 나옵니다.

RMSE: 회귀(regression) 문제에서 사용하는 전형적인 성능 지표

from sklearn.metrics import mean_squared_error
#mean_squared_error로 평균 오차 제곱을 구하고
#sqrt를 하여 평균 오차 제곱 근을 구해 평균적인 오차 범위를 알 수 있다.

#housing_prepared는 원본 데이터프레임(housing)을 가지고 모든 전처리 단계를 거쳐서 가공된 데이터
#lin_reg 선형회귀 모델을 사용하여 학습 데이터에 대한 예측을 수행하고, 이 예측값을 
#housing_predictions에 저장한다. 즉, 모델이 학습 데이터에 대한 주택 가격 예측을 수행한 결과
#array([[-0.94135046,  1.34743822,  0.02756357, ...,  0.        ,0.        ,  0.        ] 이런 값들을 가지고 계산해준다.
housing_predictions = lin_reg.predict(housing_prepared)

#housing_labels: 계층적샘플링으로 나눈 집값
#housing_predictions: 원본 데이터를 선형회귀 모델로 값을 구한 집값
#함수를 사용하여 예측값과 실제값 사이의 평균 제곱 오차를 계산한다.
#lin_mse는 예측 오차의 제곱을 모든 데이터 포인트에 대해 평균한 값
lin_mse = mean_squared_error(housing_labels, housing_predictions) # 둘이 순서 바꿔도 똑같다
#계산된 mse값을 제곱근을 씌워서 RMSE로 변환
lin_rmse = np.sqrt(lin_mse)
#실제 중간 주택 가격과 약 $68633 오차가 있다는 것을 의미한다. 줄일수록 좋은것
lin_rmse #

위에서는 오차범위가 68633이나 되며 이를 해결하기 위해서는 더욱 강력한 모델을 사용하는 방법이 있습니다.

아래는 결정 트리 회귀 모델을 사용한 것이고 훈련 방법은 선형 회귀하고 똑같습니다.

- 값이 매번 랜덤하지 않도록 seed값을 42로 설정합니다.

- full_pipeline 을 거쳐서 가공(전처리)된 housing_prepared 데이터로 계층적샘플링으로 구한 housing_labels(실제 집값)을 맞추도록 학습시킵니다.

from sklearn.tree import DecisionTreeRegressor
#선형회귀모델은 약해서 언더피팅 모델이다. 
#그러기 위해서 더 강력한 모델인 결정 트리 회귀 모델을 사용하는 것이다.

tree_reg = DecisionTreeRegressor(random_state=42)
# housing_prepared: 전처리가된 원본 데이터
# 계층적 샘플링으로 나눈 집값 (답안지라고 부르심)
tree_reg.fit(housing_prepared, housing_labels)
# 값을 예측하기 위한 모델 만들기, 모델.fit(x(i)값, y(i)값)

 

위에서 훈련된 데이터로 값을 예측한 값으로 평균 오차 제곱근을 구하면  값이 0이라는 값이 나오는데 이는 의심해봐야하는 상황입니다.

#housing_prepared: 전처리된 원본 데이터
housing_predictions = tree_reg.predict(housing_prepared)
# h(x) = housing_labels, y=housing_predictions
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse

 

교차검증

교차 검증을 통해서 학습 모델을 평가할 수 있습니다.

교차 검증이란?

  • 훈련셋을 k-폴드로 나눕니다
  • k-1 폴드를 이용해서 ML 모델을 훈련하고 나머지 폴드를 이용해 모델 성능을 검증합니다
  • 위 과정을 k번 반복하여 최적의 파라미터 (모델, 하이퍼파라미터)를 찾아 모델 확정합니다
  • sklearn에서 k-폴드 교차 검증 CV 함수cross_val_score 를 제공하고 있습니다.

 

모델마다 다른 결과

추가로 아래를 확인했을 때와 같이 모델도 종류마다 성능이 다릅니다.

#lin_reg: 선형회귀 모델을 사용해서 얻은 결과값
#housing_prepared는 전처리된 주택 데이터의 특성들, 교차 검증에 사용할 데이터셋
#housing_labels는 계층적샘플링을 통해서 얻은 집값들로 답안지
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)

 

 

 

아래는 결정회귀 회귀 모델을 사용한 코드입니다.

from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor

tree_reg = make_pipeline(preprocessing, DecisionTreeRegressor(random_state=42))
tree_reg.fit(housing, housing_labels)

scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
                         scoring="neg_mean_squared_error", cv=10)
#tree_rmse_scores = np.sqrt(-scores): 교차 검증에서 계산된 음수 MSE를 다시 양수로 변환하고 루트를 씌워 제곱근 RMSE값을 계산한다.
tree_rmse_scores = np.sqrt(-scores)

 

앞에 선형회귀 모델을 사용했을떄 RSME가 $2,731 이였지만, 교차 검증을 통해서 확인해본 결과 평균오차가 $71,407, 표준편차가 $2,439라는 것을 확인할 수 있습니다.

#교차검증을 통해서 얻은 결과값
def display_scores(scores):
    print("Scores:", scores) # 점수 
    print("Mean:", scores.mean()) # 평균
    print("Standard deviation:", scores.std()) # 표준편차

display_scores(tree_rmse_scores)

 

 

 

 

 

ytw_developer