Hands-On Marchine Learning w/Scikit-Learn & TensorFlow - 2. End-to End ML Project [Boston Housing]

Like Sunnysideup·2024년 11월 2일

머신러닝

목록 보기
1/2

[Data Split]

from sklearn.model_selection > import train_test_split

  • 데이터셋을 훈련 세트와 테스트 세트로 분할하는 함수
  • 주요 매개변수
    - shuffle
    : True(기본값)이면 데이터를 분할하기 전에 무작위로 섞음
    : 시계열 데이터와 같이 순서를 유지해야 하는 경우 False로 설정
    - stratify
    : 특정 열에 대해 계층적 샘플링을 수행해 훈련 세트와 테스트 세트에 같은 비율로 데이터가 포함되도록 함
    : 예를 들어, stratify=y로 설정하면 y의 클래스 비율이 훈련 세트와 테스트 세트에 동일하게 유지됩니다.
from sklearn.model_selection import train_test_split

# 특성과 타겟 데이터 예제
X = housing.drop(columns='median_house_value')  # 특성 데이터 (target 열을 제외)
y = housing['median_house_value']               # 타겟 데이터

# 데이터를 80:20 비율로 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print("훈련 세트 크기:", X_train.shape)       
print("테스트 세트 크기:", X_test.shape)

from sklearn.model_selection > import StratifiedShuffleSplit

  • StratifiedShuffleSplit은 train_test_split의 stratify보다 더 유연한 계층적 분할 방식을 제공
  • n_splits 매개변수를 통해 데이터를 여러 번 분할할 수 있어 교차 검증에 계층적 샘플링을 적용하고 싶을 때 유용
from sklearn.model_selection import StratifiedShuffleSplit

# StratifiedShuffleSplit 객체 생성
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

# 분할 수행
for train_index, test_index in split.split(X, y):
    X_train = X.loc[train_index]
    X_test = X.loc[test_index]
    y_train = y.loc[train_index]
    y_test = y.loc[test_index]
# StratifiedShuffleSplit : 계층적 샘플링(train/ test)
from sklearn.model_selection import StratifiedShuffleSplit 

# StratifiedShuffleSplit 객체 생성
# 데이터셋을 한 번 분할 
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

# train_index/ test_index : 훈련/테스트 세트에 할당할 행의 인덱스를 저장 
# split() 메서드(두 번째 split)는 데이터를 훈련 세트와 테스트 세트로 나누는 역할 
split을 사용하여 훈련/테스트 세트 인덱스 생성 (income_cat열 기준)
# housing : 전체 데이터셋을 나타내는 변수
# housing['income_cat'] : split할 때 계층화 기준이 되는 열. 
# income_cat의 각 카테고리 값이 훈련/테스트 세트에 동일한 비율로 분포되도록 데이터 인덱스 선택 
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]



[Imputer]

from sklearn.impute > import SimpleImputer

  • 결측값을 특정 값(예: 평균, 중앙값, 최빈값)으로 대체
from sklearn.impute import SimpleImputer

# SimpleImputer 객체 생성
imputer = SimpleImputer(strategy='median')

# 범주형 변수 제외
housing_num = housing.drop('ocean_proximity', axis=1)

# 각 열의 중앙값 계산
imputer.fit(housing_num)
# 계산의 결과를 객체의 statistics_ 속성에 저장
print(imputer.statistics_)
print(housing_num.median().values)

# 결측값 대체
X = imputer.transform(housing_num)

# X ; 넘파이 배열 -> 판다스 데이터프레임으로 전환
housing_tr = pd.DataFrame(X, columns = housing_num.columns, index = list(housing.index.values))
housing_tr

[Encoder]

from sklearn.preprocessing > import OrdinalEncoder

  • 범주형 변수를 '정수형 값'으로 인코딩
  • 범주형 데이터에 순서가 있는 경우 (예: ['low', 'medium', 'high']) 사용
  • 정수로만 인코딩하므로, 순서가 없는 범주형 데이터에는 부적합
  • OneHotEncoder 불필요
from sklearn.preprocessing import OrdinalEncoder

# OrdinalEncoder 객체 생성
cat_encoder = OrdinalEncoder()

housing_cat_reshaped = housing_cat.values.reshape(-1,1)
housing_cat_1hot = cat_encoder.fit_transform(housing_cat_reshaped)
housing_cat_1hot
# 원본 범주를 확인
encoder.categories_

factorize()

  • 범주형 변수를 '정수형 값'으로 인코딩
  • 변환된 정수 배열(housing_cat_encoded)과 고유 범주의 리스트(housing_categories)를 반환
housing_cat_encoded, housing_categories = housing_cat.factorize()
housing_cat_encoded[:10]

from sklearn.preprocessing > import OneHotEncoder (USE IT!)

  • 범주형 변수(정수, numpy배열)를 원-핫 인코딩하여 각 범주를 '이진 특성'으로 변환
  • NumPy 배열일 때만 작동 (factorize 먼저 진행 or values로 numpy배열로 변환)
from sklearn.preprocessing import OneHotEncoder

# OneHotEncoder 객체 생성
encoder = OneHotEncoder()

# housing_cat_encoded는 이미 factorize()로 변환된 배열이므로 .values를 사용하지 않아도 됨
# fit_transform 결과는 희소 행렬로 반환됨
# 2차원 배열로 변환하기 위해 reshape 사용
# -1 : 행의 개수를 자동으로 계산한다는 뜻 (데이터 길이에 맞춰 결정됨)
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.values.reshape(-1, 1))

# 희소 행렬(sparse matrix)을 밀집 행렬(dense array)로 변환
# 희소 행렬: OneHotEncoder의 fit_transform()이 반환하는 기본 형식으로, 메모리 효율성을 위해 특수한 형태로 저장
housing_cat_1hot.toarray()
  • (USE IT!) 원래의 문자열 형태의 범주형 데이터를 직접 원-핫 인코딩
from sklearn.preprocessing import OneHotEncoder

# OneHotEncoder 객체 생성
# 희소 행렬 = False 
cat_encoder = OneHotEncoder(sparse_output=False)

# 데이터를 2차원 배열로 변환하고 원-핫 인코딩 수행
housing_cat_1hot = cat_encoder.fit_transform(housing_cat.values.reshape(-1,1))

# 결과 확인
housing_cat_1hot

참고) from sklearn.preprocessing > import LabelEncoder

  • 범주형 데이터(예: 문자열 형태의 레이블)를 숫자형으로 변환
from sklearn.preprocessing import LabelEncoder

# 샘플 데이터
labels = ['red', 'green', 'blue', 'green', 'red', 'blue']

# LabelEncoder 객체 생성
encoder = LabelEncoder()

# 레이블 인코딩
encoded_labels = encoder.fit_transform(labels)

# 결과 확인
print("Original labels:", labels)
print("Encoded labels:", encoded_labels)

# 결과 출력 
Original labels: ['red', 'green', 'blue', 'green', 'red', 'blue']
Encoded labels: [2 1 0 1 2 0]

[Estimator / Transformer]

* - Estimator, Transformer

1) Estimator (추정기)

  • 모델을 학습하고, 예측하는 역할
  • 데이터의 패턴을 학습하고 새로운 데이터에 대해 예측 수행
  • ex) LinearRegression, DecisionTreeClassifier, KNeighborsRegressor
  • 메서드
    • fit() : 데이터를 사용해 모델 학습. 예를 들어, LinearRegression은 fit() 메서드를 통해 회귀 계수 학습
    • predict() : 학습된 모델을 사용해 새로운 데이터에 대한 예측 수행
    • score() : 모델의 성능을 평가하는 메서드
from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(X_train, y_train) # X_train과 y_train을 사용해 모델 학습
y_pred = model.predict(X_test) # 새로운 데이터에 대한 예측

2) Transformer

  • 주로 데이터 전처리에 사용
  • 데이터를 입력으로 받아 특성을 변환하거나, 새로운 특성을 생성하거나, 스케일을 조정
  • ex) SimpleImputer, OneHotEncoder, StandardScaler, PCA
  • 메서드
    • fit() : 데이터의 통계적 특성을 학습하는 메서드 (예측x). 예를 들어, StandardScaler는 fit() 메서드에서 평균과 표준편차를 계산
    • transform() : fit()에서 학습한 정보를 바탕으로 데이터 변환. 예를 들어, StandardScaler는 각 특성을 표준화
    • fit_transform() : fit()과 transform()을 한 번에 수행하는 메서드
  • 파이프라인의 마지막 단계가 변환기인 경우, fit()을 호출하면 마지막 변환기에서도 fit_transform()이 호출됩니다. 추정기(모델)가 포함되지 않으면 데이터 전처리를 위한 파이프라인으로만 작동합니다.
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train)  # X_train의 평균과 표준편차를 계산하여 scaler 객체에 저장
X_train_scaled = scaler.transform(X_train)  # 학습된 정보를 바탕으로 데이터 변환

from sklearn.base > import BaseEstimator, TransformerMixin (사용자 정의 transformer)

1) BaseEstimator
: 상속받으면 모든 파라미터를 자동으로 저장
: get_params()와 set_params() 메서드 사용 가능
: 이를 통해 CombinedAttributesAdder가 일관된 인터페이스와 파라미터 관리 기능을 가지게 됨
2) TransformerMixin
: 상속하면 fit_transform() 메서드가 자동으로 구현됨
: scikit-learn에서 제공하는 transformer(StandardScaler, OneHotEncoder 등)에는 fit_transform() 메서드가 이미 포함되어 있어 별도의 추가 작업 없이 바로 사용할 수 있음
: 반면에, 사용자 정의 transformer를 만들 때는 fit(), transformer() 메서드가 자동으로 포함되지 않으므로, 이때는 TransformerMixin을 상속해 fit_transform()을 추가

from sklearn.base import BaseEstimator, TransformerMixin

# 각 열의 인덱스 
rooms_ix, bedrooms_ix, population_ix, household_ix = 3,4,5,6

# 사용자 정의 변환기 
# CombinedAttributesAdder 클래스는 BaseEstimator와 TransformerMixin을 상속받아 scikit-learn 변환기처럼 동작하도록 만들어졌음
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):

    # 초기화 : 객체가 생성될 때 add_bedrooms_per_room 매개변수를 받아들임
    # BaseEstimator를 상속하므로 get_params()와 set_params() 메서드로 파라미터(add_bedrooms_per_room)를 쉽게 관리
    def __init__(self, add_bedrooms_per_room=True):
        self.add_bedrooms_per_room = add_bedrooms_per_room

    # fit 메서드는 필수 포함 
    # 주로 변환기의 학습 단계에서 필요한 처리를 할 때 사용
    # 여기서는 특성 조합만 수행하기 때문에 아무 연산도 하지 않음
    def fit(self, X, y=None):
        return self

    # y는 주로 타겟 변수 또는 라벨 데이터를 의미 
    # 지도 학습에서 타겟 변수가 필요할 때 사용 
    # 하지만 비지도 학습이나 데이터 변환에서는 타겟 변수가 필요하지 않기 때문에 기본값으로 None
    # NumPy 배열에서는 X[:, rooms_ix] 형태로 인덱싱 (Pandas DataFrame이라면 loc, iloc를 사용하여 열에 접근)
    def transform(self, X, y=None):
        rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
        population_per_household = X[:, population_ix] / X[:, household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            # np.c: 새로운 특성을 원래 데이터 X의 마지막 열로 추가하여 반환
            return np.c_[X, rooms_per_household,population_per_household, bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household,population_per_household]

# CombinedAttributesAdder 클래스의 인스턴스인 attr_adder 객체를 생성
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)

# CombinedAttributesAdder 클래스 내부에 정의된 transform 메서드 호출
# housing.values : Pandas DataFrame을 NumPy 배열로 변환 & transform 메서드의 매개변수 X로 전달됨 (CUZ. transform 메서드가 NumPy 배열 형식의 입력을 기대)
housing_extra_attribes = attr_adder.transform(housing.values)
from sklearn.base import BaseEstimator, TransformerMixin

# 필요한 열을 선택해 데이터프레임을 넘파이 배열로 바꿈
# 수치형만 다루는 파이프라인을 쉽게 만듬 
class DataFrameSelector(BaseEstimator, TransformerMixin):
    # attribute_names 매개변수로 선택할 열의 이름들을 리스트로 전달받음
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    
    def fit(self, X, y=None):
        return self

    # 데이터프레임 X에서 self.attribute_names열만 포함된 서브 데이터프레임 반환
    # .values를 사용하여 NumPy 배열 형태로 반환
    def transform(self, X):
        return X[self.attribute_names].values

[Scaler]

from sklearn.preprocessing > import StandardScaler

  • 데이터를 표준화(정규화)하기 위해 사용
  • 각 feature를 평균이 0, 표준 편차가 1인 정규 분포 형태로 데이터를 변환
  • 데이터 스케일이 다른 feature들 간의 차이를 줄여주는 효과가 있어, 특히 거리 기반 알고리즘(예: K-최근접 이웃)이나 선형 모델에서 유용
from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
scaler = StandardScaler()

# 학습 데이터에 fit_transform() 메서드 적용하여 표준화 수행
X_scaled = scaler.fit_transform(X_train)

# 테스트 데이터에는 transform() 메서드만 적용하여 표준화
X_test_scaled = scaler.transform(X_test)

[Pipeline]

from sklearn.pipeline > import Pipeline

  • 여러 데이터 전처리 과정을 하나의 파이프라인으로 결합
  • 이 파이프라인은 누락된 값 처리, 새로운 특성 추가, 표준화의 세 가지 전처리 작업을 순서대로 적용
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# Pipeline 객체를 생성하고, 단계별 전처리 작업을 리스트로 정의
# 각 단계는 (이름, 변환기) 형태로 지정
# 정의된 순서대로 변환기를 적용
num_pipeline = Pipeline([
    # 누락된 값을 채우기 위해 SimpleImputer를 사용하여 중앙값으로 결측값을 채움
    ('imputer', SimpleImputer(strategy='median')),
    # CombinedAttributesAdder 변환기를 사용해 새로운 특성(특성 조합)을 추가
    ('attribss_adder', CombinedAttributesAdder()),
    # StandardScaler를 사용해 각 특성을 표준화(평균 0, 분산 1)
    ('std_scaler', StandardScaler())
])

# fit_transform 메서드를 호출하여 housing_num 데이터에 대해 파이프라인을 학습(fit)하고 변환(transform)**을 수행
# 이 파이프라인 객체에서 fit()을 호출하면, 파이프라인 내부에 있는 모든 변환기와 추정기의 fit() 또는 fit_transform() 메서드가 순서대로 호출
housing_num_tr = num_pipeline.fit_transform(housing_num)

from sklearn.pipeline > import FeatureUnion

  • 파이프라인들을 병합하는 기능 수행
  • 여러 개의 변환기를 병렬로 적용하고, 결과를 하나의 배열로 합치는 역할
  • 수치형과 범주형 데이터를 각각 다른 방식으로 전처리한 후 병합할 때 매우 유용
  • 아래 예시) num_pipeline과 cat_pipeline을 병합
    : 수치형 특성과 범주형 특성에 각각 다른 전처리 과정을 적용한 후, 최종적으로 하나의 데이터셋으로 결합
  • scikit-learn 라이브러리에 포함된 기본 제공 클라스 : SimpleImputer / StandardScaler
  • 사용자 정의 transformer : DataFrameSelector / CombinedAttributesAdder
  • Pipeline을 사용하는 경우, 모든 변환기가 fit()과 transform() 메서드를 구현하고 있어야 파이프라인 내에서 작동할 수 있음. scikit-learn에서 제공하는 변환기들은 이미 이 메서드들이 구현되어 있어 Pipeline에 넣기만 하면 바로 사용할 수 있지만, 사용자 정의 변환기는 fit()과 transform()을 직접 정의해야 함
num_attribs = list(housing_num) # 수치형 DF의 칼럼들
cat_attribs = ['ocean_proximity']

# 수치형 파이프라인
# DataFrameSelector, CombinedAttributesAdder는 사용자 정의 transformer
num_pipeline = Pipeline([
    ('selector', DataFrameSelector(num_attribs)),
    ('imputer', SimpleImputer(strategy='median')),
    # CombinedAttributesAdder 변환기를 사용해 새로운 특성(특성 조합)을 추가
    ('attribss_adder', CombinedAttributesAdder()),
    # StandardScaler를 사용해 각 특성을 표준화(평균 0, 분산 1)
    ('std_scaler', StandardScaler())
])

# 범주형 파이프라인
cat_pipeline = Pipeline([
    ('selector', DataFrameSelector(cat_attribs)),
    ('cat_encoder', OneHotEncoder(sparse_output=False))
])

from sklearn.pipeline import FeatureUnion

# FeatureUnion으로 두 파이프라인을 병합
# transformer_list: FeatureUnion 클래스의 매개변수
full_pipeline = FeatureUnion(transformer_list = [
    ('num_pipeline', num_pipeline),
    ('cat_pipeline', cat_pipeline)
])

housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared
housing_prepared.shape # 결과: (16512, 16)


[ML Models]

from sklearn.linear_model > import LinearRegression

  • 선형회귀(linear Regression) 모델을 생성하고 훈련
from sklearn.linear_model import LinearRegression 

# LinearRegression 객체 생성
lin_reg = LinearRegression()

# fit() 메서드로 모델 훈련 = attribute 데이터와 타겟 값 간의 관계를 학습
# 입력 데이터(housing_prepared)와 정답 데이터(housing_labels)를 사용해 모델을 학습
# housing_prepared는 feature 데이터로, 각 행이 하나의 샘플을 나타내고 각 열이 하나의 특성을 나타냄 
# housing_labels는 타겟 값(label)으로, 모델이 예측하려는 값
lin_reg.fit(housing_prepared, housing_labels)

* - 모델이 학습한 내용을 평가

  • 모델의 예측 결과와 실제 레이블을 비교하여 모델이 학습한 내용을 평가
# 데이터 샘플 선택 
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]

# full_pipeline.transform()을 사용해 some_data를 모델의 입력 형식에 맞게 전처리
# 수치형 데이터와 범주형 데이터를 모두 변환하여 모델이 바로 사용할 수 있는 형태로 데이터를 준비
# some_data가 test data이기 때문에, fit 진행x
# train 데이터에서는 fit_transform()을 사용해 fit과 transform을 동시에 수행해 각 transformer가 데이터를 학습
# test 데이터에서는 이미 학습된 파이프라인이나 transformer를 사용해 변환만 수행하기 때문에 transform()만 호출해 기존 학습 정보를 적용
some_data_prepared = full_pipeline.transform(some_data)

# 모델이 전처리된 데이터를 입력받아 예측한 결과를 반환
print('예측: ', lin_reg.predict(some_data_prepared))

# some_labels는 실제 레이블이므로, 예측 값과 함께 출력하여 모델의 예측 정확도를 시각적으로 비교
print('레이블: ', list(some_labels))

from sklearn.metrics > import mean_squared_error

  • 선형 회귀 모델의 예측 성능을 평가
  • MSE (평균 제곱 오차) , RMSE (평균 제곱근 오차)
# 전체 훈련 세트에 대한 회귀 모델의 RMSE 측정
from sklearn.metrics import mean_squared_error

# 모델이 예측한 타겟 값 생셩 
housing_predictions = lin_reg.predict(housing_prepared)

# mean_squared_error() 함수
# 실제 타겟 값(housing_labels)과 모델의 예측 값(housing_predictions) 간의 평균 제곱 오차(MSE) 계산
# MSE는 실제값, 예측값의 차이를 제곱하여 평균을 구한 값으로, 오차의 제곱 평균
# 값이 작을수록 모델의 예측이 실제 값에 가깝다는 것을 의미
lin_mse = mean_squared_error(housing_labels, housing_predictions)

# MSE의 제곱근을 구하여 평균 제곱근 오차(RMSE)를 계산
# RMSE는 MSE보다 오차의 크기를 실제 단위로 해석할 수 있게 해 주므로, 해석이 더 직관적
# RMSE 값이 작을수록 모델의 예측이 실제 데이터에 더 가까움을 의미
lin_rmse = np.sqrt(lin_mse)
lin_rmse # 결과: 68627.87390018745

  • 대부분 구역의 중간 주택 가격은 $120,000에서 $265,000 사이
  • 예측오차가 $68,628로 상당히 큼 = 실제 주택 가격과 상당히 차이 나는 예측을 하고 있음
  • 모델이 훈련 데이터에 과소적합
    = 충분한 정보를 제공하지 못했거나, 모델이 충분히 강력하지 못했음
  • 훈련 알고리즘에 더 좋은 feature를 주입하거나 / 모델의 규제를 감소시키거나 / 더 강력한 모델 선택

from sklearn.tree > import DecisionTreeRegressor

  • 결정 트리 회귀 모델 (Decision Tree Regressor)을 생성하고 학습
  • 주어진 데이터의 구조를 분할하여 예측을 수행하는 모델로, 복잡한 데이터 구조에 대해 빠르고 직관적인 학습이 가능한 장점
  • 비선형 데이터 처리 가능
  • 오버피팅 경향: 결정 트리는 데이터에 과적합(오버피팅)되기 쉽기 때문에, 모델의 최대 깊이(max_depth)나 최소 샘플 수 등의 하이퍼파라미터를 조정해 일반화 성능을 높이는 것이 중요
from sklearn.tree import DecisionTreeRegressor

# 결정 트리 회귀 모델 객체 생성
tree_reg = DecisionTreeRegressor()

# 모델 학습 
# 데이터를 여러 분할 기준에 따라 나누고 예측 값을 결정하는 구조를 학습
# 순서) feature data > target data
tree_reg.fit(housing_prepared, housing_labels)

# 모델 예측
housing_predictions = tree_reg.predict(housing_prepared)

# MSE / RMSE 계산 
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_mse # 결과 : 0.0 (과대적합) 

from sklearn.model_selection > import cross_val_score

  • K-fold cross-validation
  • 훈련 세트를 fold라 불리는 10개의 서브셋으로 무작위 분할
  • 결정 트리 모델을 10번 훈련하고 평가
  • 매번 다른 폴드를 선택해 평가에 사용하고 나머지 9개 폴드는 훈련에 사용
  • 모델이 다양한 데이터 샘플에 대해 얼마나 일관되게 성능을 내는지 평가하는 방법
  • scikit-learn에서 cross_val_score는 높은 점수가 좋은 성능을 나타낸다는 기본 가정하에 작동. 그러나 평균 제곱 오차(MSE)는 값이 작을수록 성능이 좋음을 나타내는 지표이므로, cross_val_score에서 이를 적용할 때는 MSE의 음수 버전(negative MSE)을 반환하여 사용
from sklearn.model_selection import cross_val_score

# 교차 검증으로 tree_reg 모델 성능 평가 
# cross_val_score 함수는 tree_reg 모델을 사용해 housing_prepared와 housing_labels 데이터에 대해 10-fold 교차 검증(cv=10)을 수행
# scoring='neg_mean_squared_error'는 평균 제곱 오차(MSE)를 음수 형태로 반환하는 설정
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, 
                        scoring='neg_mean_squared_error', cv=10)

# 평균 제곱근 오차(RMSE) 계산
tree_rmse_scores = np.sqrt(-scores)

# 교차 검증으로 lin_reg 모델 성능 평가 
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels, 
                            scoring='neg_mean_squared_error', cv=10)

# 평균 제곱근 오차(RMSE) 계산
lin_rmse_scores = np.sqrt(-lin_scores)

# 결과 출력 함수 정의
def display_scores(scores):
    print('scores: ', scores)
    print('mean: ', scores.mean())
    print('standard deviation: ', scores.std())

# 결과 출력 
display_scores(tree_rmse_scores)
display_scores(lin_rmse_scores)
  • 결정 트리 모델이 과대적합되어 선형 회귀 모델보다 성능이 안 좋음


from sklearn.ensemble > import RandomForestRegressor

  • RandomForestRegressor는 여러 개의 결정 트리를 학습하고 그 결과를 앙상블하여, 보다 강력하고 안정적인 예측을 수행하는 모델
from sklearn.ensemble import RandomForestRegressor

# 랜덤 포레스트 모델 생성 및 학습
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)

# 모델 예측 
housing_predictions = forest_reg.predict(housing_prepared)

# MSE 및 RMSE 계산
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse # 결과 : 18758.40079026517

# 교차 검증으로 forest_reg 모델 성능 평가 
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)
  • 다른 모델보다 score는 좋으나, 훈련 세트에 대한 점수가 검증 세트에 대한 점수보다 훨씬 낮으므로, 이 모델도 여전히 훈련 세트에 과대적합 되어 있음

* - Decision Tree / Random Forest

  1. Decision Tree
  • 정의
    : 데이터를 분할하여 예측을 수행하는 지도 학습 알고리즘
    : 특정 기준에 따라 데이터를 여러 단계로 나누며 학습을 진행하고, 트리 구조로 예측 과정을 시각화할 수 있어 직관적인 해석이 가능

  • 분할 기준

    • 회귀 문제 : 분할된 노드의 MSE(평균 제곱 오차)나 MAE(평균 절대 오차)를 최소화하는 방향으로 분할
    • 분류 문제 : 정보 이득(Information Gain)을 최대화하기 위해 엔트로피(entropy)나 지니 불순도(gini impurity)를 기준으로 분할
  • 학습 과정
    1) feature 선택: 모든 feature를 검사해 데이터 분할에 가장 좋은 feature를 선택
    2) 조건 설정: 선택된 feature에 대해 최적의 분할 기준(예: x > 5)을 설정
    3) 데이터 분할: 설정한 조건에 따라 데이터를 두 그룹으로 분할
    4) 반복: 각 그룹에서 위 과정을 반복하며, 데이터가 충분히 작은 그룹으로 나눠질 때까지(또는 설정한 종료 기준에 도달할 때까지) 재귀적으로 트리를 확장

  1. Random Forest
  • 정의 : 여러 개의 결정 트리를 앙상블하여 예측하는 기법으로, 각 트리를 독립적으로 학습시켜 최종 예측에서 과적합을 방지하고 안정성을 높이는 데 효과적

  • 학습 과정
    1) 부트스트랩 샘플링 : 전체 데이터에서 일부 데이터를 복원 추출하여 각 결정 트리가 학습할 샘플을 만듦. 각 결정 트리는 서로 다른 샘플을 학습하게 되어 모델이 다양한 학습 샘플로 학습
    2) 무작위 feature 선택 : 각 결정 트리의 분기점에서 최적의 분할 기준을 찾을 때 모든 feature를 고려하지 않고, 임의로 선택한 일부 feature만 사용 (특정 feature에 과도하게 의존하지 않도록 해 과적합을 방지하는 효과)
    3) 여러개의 결정 트리 학습 : 지정된 n_estimators 개수만큼 여러 개의 결정 트리를 독립적으로 학습
    4) 앙상블 예측 : 각 트리의 예측 결과를 합산하거나 평균하여 최종 예측값을 결정. 분류 문제에서는 각 트리의 예측 중 가장 많이 나온 클래스를 최종 예측으로 선택하고, 회귀 문제에서는 모든 트리의 예측값을 평균하여 최종 예측값으로 사용

from sklearn.model_selection > import GridSearchCV

  • GridSearchCV는 여러 하이퍼파라미터 조합을 시도해 가장 성능이 좋은 조합을 선택해주는 도구
  • GridSearchCV를 사용해 랜덤 포레스트 모델의 최적 하이퍼파라미터를 찾는 과정
from sklearn.model_selection import GridSearchCV

# param_grid는 하이퍼파라미터 값의 범위를 지정하는 딕셔너리의 리스트
# 이 그리드를 통해 GridSearchCV가 각 하이퍼파라미터의 모든 조합을 시도하고, 최적의 조합을 찾음
# 각 딕셔너리는 하나의 설정 조합을 나타내고, GridSearchCV는 이 두 조합을 별도로, 동시에 평가
# 첫 번째 딕셔너리 >>
#) n_estimators :  트리 개수. 적은 값에서 시작해 성능이 안정되는 시점까지 증가시키며 실험. 일반적으로 100~200 정도가 적당 
#) max_features : 각 트리에서 사용할 최대 feature 수. 회귀에서는 total_features / 3, 분류에서는 sqrt(total_features) ~ 절반 정도가 일반적인 값
#) bootstrap=True : 부트스트랩 샘플링 (복원추출) 여부. 일반적으로 True를 사용하며, 랜덤 포레스트의 성능 향상에 기여
#) 총 3x4 = 12개의 조합을 평가 
# 두 번째 딕셔너리>>
#) bootstrap=False로 설정한 경우의 하이퍼파라미터 조합을 정의
#) 범위가 좁은 것은 계산 비용을 줄이고, 부트스트랩 없이도 성능이 좋은지 간단히 확인하기 위함 
#) 총 2x3 = 6개의 조합을 평가 

param_grid = [
    {'n_estimators': [3,10,30], 'max_features': [2,4,6,8]},
    {'bootstrap':[False], 'n_estimators':[3,10], 'max_features':[2,3,4]}
]

# RandomForestRegressor 모델 객체를 생성합니다.
forest_reg = RandomForestRegressor()

# GridSearchCV는 주어진 모델(forest_reg)과 하이퍼파라미터 그리드(param_grid)를 기반으로 모든 하이퍼파라미터 조합을 시도하고, 5-fold 교차 검증(cv=5)을 사용해 성능을 평가
# 전체 훈련 회수는 18x5 = 90 (총 조합 개수 x 5겹 교차 검증 - 각 모델마다 다섯번 훈련) 
# return_train_score=True : 훈련 세트의 점수도 반환해 학습 과정을 자세히 분석
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                          scoring='neg_mean_squared_error',
                          return_train_score=True)

# housing_prepared와 housing_labels 데이터를 사용해 각 하이퍼파라미터 조합을 학습
grid_search.fit(housing_prepared, housing_labels)

# 최적의 하이퍼파라미터 조합을 딕셔너리 형태로 반환하는 속성
# 가장 좋은 성능을 보인 조합을 best_params_ 속성에 저장
grid_search.best_params_ 
# 결과: {'max_features': 6, 'n_estimators': 30}
  • GridSearchCV의 교차 검증 결과 출력
  • 각 하이퍼파라미터 조합과 그에 해당하는 평균점수 확인
# grid_search.cv_results_ : cv_results_는 GridSearchCV 객체의 속성으로, 각 하이퍼파라미터 조합에 대한 교차 검증 결과를 담고 있는 딕셔너리
cvres = grid_search.cv_results_

# mean_test_score: 각 하이퍼파라미터 조합에 대해 5-fold 교차 검증의 평균 점수를 저장한 리스트
# params : 각 조합의 하이퍼파라미터 설정을 저장한 딕셔너리
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
    
    # mean_test_score: scoring='neg_mean_squared_error'로 설정되어 음수 형태의 MSE가 저장되어 있음
    print(np.sqrt(-mean_score), params)

* - 테스트 세트에서 최종 모델 평가

# 최종 모델 준비 
# grid_search.best_estimator_ : GridSearchCV에서 찾은 최적의 하이퍼파라미터로 학습된 모델
final_model = grid_search.best_estimator_

# test 데이터셋 준비 
# strat_test_set : stratified sampling을 사용해 미리 분리해둔 test 데이터셋
X_test = strat_test_set.drop('median_house_value', axis=1)
y_test = strat_test_set['median_house_value'].copy()

# test 데이터 전처리
# train 데이터에 사용한 동일한 전처리 과정을 test 데이터(X_test)에 적용해 데이터의 일관성을 유지
X_test_prepared = full_pipeline.transform(X_test)

# 예측 수행
final_predictions = final_model.predict(X_test_prepared)

# 성능 평가 
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)

# 결과 출력 
final_rmse # 결과 : 48125.40498236006
profile
Perfect timing to be a Newbie

0개의 댓글