
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 객체 생성
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]

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
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_

housing_cat_encoded, housing_categories = housing_cat.factorize()
housing_cat_encoded[:10]
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()
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
# 샘플 데이터
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]
1) Estimator (추정기)
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
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train) # X_train의 평균과 표준편차를 계산하여 scaler 객체에 저장
X_train_scaled = scaler.transform(X_train) # 학습된 정보를 바탕으로 데이터 변환


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

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))
# 전체 훈련 세트에 대한 회귀 모델의 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

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
# 교차 검증으로 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
# 랜덤 포레스트 모델 생성 및 학습
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)

정의
: 데이터를 분할하여 예측을 수행하는 지도 학습 알고리즘
: 특정 기준에 따라 데이터를 여러 단계로 나누며 학습을 진행하고, 트리 구조로 예측 과정을 시각화할 수 있어 직관적인 해석이 가능
분할 기준
학습 과정
1) feature 선택: 모든 feature를 검사해 데이터 분할에 가장 좋은 feature를 선택
2) 조건 설정: 선택된 feature에 대해 최적의 분할 기준(예: x > 5)을 설정
3) 데이터 분할: 설정한 조건에 따라 데이터를 두 그룹으로 분할
4) 반복: 각 그룹에서 위 과정을 반복하며, 데이터가 충분히 작은 그룹으로 나눠질 때까지(또는 설정한 종료 기준에 도달할 때까지) 재귀적으로 트리를 확장
정의 : 여러 개의 결정 트리를 앙상블하여 예측하는 기법으로, 각 트리를 독립적으로 학습시켜 최종 예측에서 과적합을 방지하고 안정성을 높이는 데 효과적
학습 과정
1) 부트스트랩 샘플링 : 전체 데이터에서 일부 데이터를 복원 추출하여 각 결정 트리가 학습할 샘플을 만듦. 각 결정 트리는 서로 다른 샘플을 학습하게 되어 모델이 다양한 학습 샘플로 학습
2) 무작위 feature 선택 : 각 결정 트리의 분기점에서 최적의 분할 기준을 찾을 때 모든 feature를 고려하지 않고, 임의로 선택한 일부 feature만 사용 (특정 feature에 과도하게 의존하지 않도록 해 과적합을 방지하는 효과)
3) 여러개의 결정 트리 학습 : 지정된 n_estimators 개수만큼 여러 개의 결정 트리를 독립적으로 학습
4) 앙상블 예측 : 각 트리의 예측 결과를 합산하거나 평균하여 최종 예측값을 결정. 분류 문제에서는 각 트리의 예측 중 가장 많이 나온 클래스를 최종 예측으로 선택하고, 회귀 문제에서는 모든 트리의 예측값을 평균하여 최종 예측값으로 사용

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}
# 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