머신러닝 프로젝트 과정
1. 큰그림 보기
2. 데이터 구하기
3. 데이터로부터 인사이트를 얻기 위해 탐색하고 시각화하기
4. 데이터를 준비 후 모델을 선택하고 훈련시키기
5. 모델 미세 튜닝하기
6. 솔루션 제시하기
7. 시스템을 론칭, 모니터링 후 유지 보수하기
2.1 실제 데이터로 작업하기
• 유명한 공개 데이터 저장소
- OpenML(https://openml.org)
- 캐글(https://kaggle.com/datasets)
- PapersWithCode(https://paperswithcode.com/datasets)
- UC 어바인 머신러닝 저장소(https://archive.ics.uci.edu/ml)
- 아마존 AWS 데이터셋(https://registry.opendata.aws)
- 먼서플로 데이터셋(https://tensorflow.org/datasets)
• 메타 포털(공개 데이터 저장소가 나열되어 있는 페이지)
- 데이터 포털 (https://dataportals.org)
- 오픈 데이터 모니터 (https://opendatamonitor.eu)
• 인기 있는 공개 데이터 저장소가 나열되어 있는 다른 페이지
- 위키백과 머신러닝 데이터셋 목록(https://homl.info/9)
- Quora(https://homl.info/10)
- 데이터셋 서브레딧(https://www.reddit.com/r/datasets)
여기서는 StatLib 저장소에 있는 캘리포니아 주택 가격 데이터셋을 사용한다고 하네요. 교육 목적으로 사용하기 위해 범주형 특성을 추가하고 몇 가지 특성을 제외했다고 합니다.
2.2 큰 그림 보기
캘리포니아 인구 조사 데이터를 사용해 캘리포니아의 주택 가격 모델을 만들어야 합니다.
여기 있는 데이터는 캘리포니아의 블록 그룹마다 인구, 중간 소득, 중간 주택 가격 등을 담고 있습니다. 여기서 블록 그룹은 미국 인구 조사국에서 샘플 데이터를 발표하는 데 사용하는 최소한의 지리적 단위입니다.
이 데이터를 통해 모델을 학습시켜 다른 측정 데이터가 주어졌을 때 구역의 중간 주택 가격을 예측해야 합니다.
2.2.1 문제 정의
'비즈니스의 목적이 정확히 무엇인가요?'에 대해서 아는 것이 가장 중요합니다.
목적을 아는 것은 문제를 어떻게 구성하고, 어떤 알고리즘을 선택하고, 모델 평가에 어떤 성능 지표를 사용하고, 모델 튜닝을 위해 얼마 만큼의 노력을 투여할지 결정하기 때문입니다.
부동산 투자를 위한 머신러닝 파이프라인을 예시로 들면 다음과 같습니다. 이 모델의 출력, 즉 아까 말했던 구역의 중간 주택 가격에 대한 예측이 여러 가지 다른 신호와 함께 다른 신호와 함께 다른 머신러닝 시스템에 입력으로 사용됩니다. 뒤따르는 시스템은 해당 지역에 투자할 가치가 있는지 결정하게 됩니다.

다음으로는 '현재 솔루션은 어떻게 구성되어 있나요?'입니다.
한 팀이 구역에 관한 최신 정보를 모으고 있는데 중간 주택 가격을 얻을 수 없을 때는 복잡한 규직을 시용하여 추정한다고 하였을 때 좋지 않은 방법이면, 다른 방법을 활용하게 됩니다. 예를 들어 구역의 데이터를 기반으로 중간 주택 가격을 예측하는 모델을 훈련시키는 것과 같이 말이죠.
이런 정보를 가지고 시스템을 설계하게 됩니다. 먼저 모델 훈련에 어떤 지도 방식이 필요한지 결정합니다.
지금 데이터에서는 레이블된 훈련 샘플이 있으므로 지도 학습 작업이며, 모델이 값을 예측해야 하므로 전형적인 회귀 문제입니다. 좀 더 구체적으로는 예측에 사용할 특성이 여러 개이므로 다중 회귀 문제입니다. 그리고 구역마다 하나의 값을 예측하므로 단변량 회귀 문제입니다. 구역마다 여러 개의 값을 예측한다면 다변량 회귀 문제입니다.
2.2.2 성능 지표 선택
다음은 성능 측정 지표를 선택해야 합니다. 회귀 문제의 전형적인 성능 지표는 평균 제곱근 오차(RMSE)입니다.
공식은 다음과 같습니다.

또한 이상치로 보이는 구역이 많다면 평균 절대 오차(MAE, 평균 절대 편차라고도 합니다.)를 생각해볼 수 있습니다.

RMSE와 MAE 모두 예측값의 벡터와 타깃값의 벡터 사이의 거리를 재는 방법입니다.
2.2.3 가정 검사
마지막으로 지금까지 만든 가정을 나열하고 검사해보는 것이 좋습니다. 이 과정에서 심각한 문제가 발견될 수 있기 때문입니다.
여기까지 다 했다면 모든 것이 준비되었고 출발선에 섰으니 코딩을 시작할 수 있게 되는 것입니다.
2.3 데이터 가져오기
이제 Google Colab을 통해서 노트북을 실행하고 예제 코드를 따라해봅시다.
2.3.1 구글 코랩을 사용하여 예제 코드 실행하기
https://colab.research.google.com/github/rickiepark/handson-ml3/blob/main/
Google Colab Notebook
Run, share, and edit Python notebooks
colab.research.google.com
여기 들어가면 제작자분께서 만드신 예제 코드에 대해서 알 수 있습니다.
2.3.2 코드와 데이터 저장하기
Colab에서 수정한 내용은 브라우저 탭이 열려있는 동안 유지되지만 닫으면 변경 내용이 사라집니다.
수정한 내용을 유지하려면 Drive에 사본 저장을 하여 노트북 사본을 구글드라이브에 저장하거나 .ipynb를 다운로드하여 내 컴퓨터에 다운로드할 수 있습니다.
2.3.3 대화식 환경의 편리함과 위험
각 셀을 하나씩 실행하다가 도중에 멈추고 새로운 셀을 추가한 다음 코드를 입력하여 실행할 수 있습니다. 그 다음 다시 돌아가서 이전 셀을 실행할 수도 있습니다. 하지만 잘못된 순서로 셀을 실행하기 매우 쉬우니 조심해야 합니다.
2.3.4 책의 코드와 노트북의 코드
제작자분께서 코드를 최대한 읽기 편하고 단순하게 작성했다고 합니다. 이제 데이터를 다운로드 해봅시다.
2.3.5 데이터 다운로드
이 프로젝트에서 데이터를 다운받기 위해서 모든 데이터가 들어 있는 CSV 파일인 housing.csv를 압축한 housing.tgz 파일을 내려받기만 하면 됩니다.
데이터를 추출하고 로드하는 함수의 예시입니다.
from pathlib import Path
import pandas as pd
import tarfile
import urllib.request
def load_housing_data():
tarball_path = Path("datasets/housing.tgz")
if not tarball_path.is_file():
Path("datasets").mkdir(parents=True, exist_ok=True)
url = "https://github.com/ageron/data/raw/main/housing.tgz"
urllib.request.urlretrieve(url,tarball_path)
with tarfile.open(tarball_path) as housing_tarball:
housing_tarball.extractall(path="datasets")
return pd.read_csv(Path("datasets/housing/housing.csv"))
housing = load_housing_data()
2.3.6 데이터 구조 훑어보기

처음 다섯 행을 활용해보면 10개의 특성이 나옵니다.

info() 메서드는 데이터에 관한 간략한 설명을 보여줍니다.

describe() 메서드는 숫자형 특성의 요약 정보를 보여줍니다.
이 때 25%, 50%, 75% 행은 백분위수를 나타냅니다. 백분위수는 전체 관측값에서 주어진 백분율이 속하는 하위 부분의 값을 나타냅니다.

전체 데이터셋에 대해 hist() 메서드를 호출하여 모든 숫자형 특성에 대한 히스토그램을 출력할 수도 있습니다.
2.3.7 테스트 세트 만들기
이제 테스트 세트를 생성해야 합니다. 그런데 테스트 세트에서 겉으로 들어난 어떠한 패턴에 속아 특정 머신러닝을 선택할 수도 있습니다. 이렇게 일반화 오차를 추정하면 매우 낙관적인 추정이 되며 시스템을 론칭했을 때 기대한 성능이 나오지 않을 것 입니다. 이를 데이터 스누핑 편향이라고 합니다.
테스트 세트를 생성하기 위해 랜덤으로 어떤 샘플을 선택해서 데이터셋의 20% 정도를 떼어놓습니다.
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
데이터셋이 충분히 크지 않다면 샘플링 편향이 생길 수 있습니다. 예를 들어 전체 인구를 대표할 수 있는 1,000명을 선택할 때 남녀 비율이 7:3이라면, 샘플 역시 7:3 비율을 유지해야 합니다. 이러한 것을 계층적 샘플링이라고 합니다.
2.4 데이터 이해를 위한 탐색과 시각화
전체 훈련 세트에 대해서 다양한 변환을 실험하기 때문에 나중에 되돌릴 수 있도록 원본 데이터의 복사본을 만듭니다.
housing = strat_train_set.copy()
2.4.1 지리적 데이터 시각화하기
일단 지리 정보에 대한 모든 구역을 산점도로 만들어 데이터를 시각화해봅니다.
housing.plot(kind="scatter", x="longitude", y="latitude", grid=True)
plt.xlabel("경도")
plt.ylabel("위도")
plt.show()

alpha 옵션을 0.2로 주면 데이터 포인트가 밀집된 영역을 잘 보여줍니다.
housing.plot(kind="scatter", x="longitude", y="latitude", grid=True, alpha=0.2)
plt.xlabel("경도")
plt.ylabel("위도")
plt.show()

이제 주택 가격에 대해서 나타내보겠습니다. 원의 반지름은 구역의 인구, 색상은 가격을 나타냅니다.
housing.plot(kind="scatter", x="longitude", y="latitude", grid=True,
s=housing["population"] / 100, label="인구",
c="median_house_value", cmap="jet", colorbar=True,
legend=True, figsize=(10, 7))
cax=plt.gcf().get_axes()[1]
cax.set_ylabel("중간 주택 가격")
plt.xlabel("경도")
plt.ylabel("위도")
plt.show()

2.4.2 상관관계 조사하기
데이터셋이 너무 크지 않으므로 모든 특성 간의 표준 상관계수(피어슨의 r) corr() 메서드를 이요해 쉽게 계산 가능합니다.
corr_matrix = housing.corr(numeric_only=True)
특성 사이의 상관관계를 확인하는 방법은 이런 식으로 할 수 있습니다.

또 다른 방법은 판다스의 scatter_matrix 함수를 활용해 숫자형 특성 간 산점도를 그려보는 것입니다.
from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
plt.show()

산점도 행렬을 보면 중간 주택 가격을 예측하는 데 중간 소득이 가장 유용해보입다. 이 산점도를 확대해보겠습니다.
housing.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1, grid=True)
plt.xlabel("중간 소득")
plt.ylabel("중간 주택 가격")
plt.show()

2.4.3 특성 조합으로 실험하기
머신러닝 알고리즘용 데이터를 준비하기 전에 마지막으로 할 수 있는 일은 특성을 여러 가지로 조합해보는 것입니다.
housing["rooms_per_house"] = housing["total_rooms"] / housing["households"]
housing["bedrooms_ratio"] = housing["total_bedrooms"] / housing["total_rooms"]
housing["people_per_house"] = housing["population"] / housing["households"]
corr_matrix = housing.corr(numeric_only=True)
corr_matrix["median_house_value"].sort_values(ascending=False)

2.5 머신러닝 알고리즘을 위한 데이터 준비
이제 머신러닝 알고리즘을 위해 데이터를 준비해야 합니다.
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
2.5.1 데이터 정제
대부분의 머신러닝 알고리즘은 누락된 특성을 다루지 못하므로 이를 처리할 수 있는 함수를 만들어야 합니다.
1. 해당 구역을 제거합니다.
2. 전체 특성을 삭제합니다.
3. 누락된 값을 어떤 값으로 채웁니다.(0, 평균, 중간값 등). 이를 대체라고 합니다.
pandas 데이터프레임의 dropna(), drop(), fillna() 메서드를 활용하여 쉽게 처리 가능합니다.
housing.dropna(subset=["total_bedrooms"], inplace=True) # 옵션 1
housing.drop("total_bedrooms", axis=1) # 옵션 2
median = housing["total_bedrooms"].median() # 옵션 3
housing["total_bedrooms"].fillna(median, inplace=True)
또한 SimpleImputer 클래스를 통해서 훈련 세트뿐만 아니라 검증 세트와 테스트 세트 그리고 모델에 주입될 새로운 데이터에 있는 누락된 값을 대체할 수 있습니다.
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
2.5.2 텍스트와 범주형 특성 다루기
텍스트 특성도 살펴보겠습니다. 일단 처음 몇 개 샘플에서 이 특성값을 확인합니다.
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(8)

대부분의 머신러닝 알고리즘은 숫자를 다루므로 이 카테고리를 텍스트에서 숫자로 변환합니다. 이를 위해서 OrdinalEncoder 클래스를 사용합니다.
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
이제 여기서 인코딩된 몇 개의 값을 확인합니다.

categories_ 인스턴스 변수를 사용해 카테고리 리스트를 얻을 수 있습니다.

여기서 ocean_proximity 열에는 가까이 있는 두 값이 떨어져 있는 두 값보다 더 비슷하다고 생각합니다. 이러한 경우에 일반적으로 카테고리별 이진 특성을 만들어 해결합니다. 카테고리가 '1H OCEAN'일 때 한 특성이 1이고(그 외 특성은 0), 카테고리가 'INLAND'일 때 다른 한 특성이 1이 되는 식입니다. 한 특성만 1이고 나머지는 0이므로 이를 원-핫 인코딩이라고 부릅니다. 새로운 특성을 더미 특성이라고 부릅니다.
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
기본적으로 OneHotEncoder의 출력은 넘파이 배열이 아니라 사이파이 희소 행렬입니다.
희소 행렬은 0이 대부분인 행렬을 매우 효율적으로 표현합니다. 내부적으로 0이 아닌 값과 그 위치만 저장합니다. 하나의범주형 특성에 수백 수천 개의 카테고리가 있을 때 원-핫 인코딩은 행마다 하나만 1이고 나머지는 0으로 채워진 매우 큰 행렬을 만듭니다.
2.5.3 특성 스케일의 변환
데이터에 적용할 중요한 변환 한 가지는 특성 스케일링입니다. 대부분의 머신러닝 알고리즘은 입력된 숫자 특성들의 스케일이 많이 다르면 제대로 작동하지 않습니다.
모든 특성의 범위를 같게 만들어주는 방법으로 min-max 스케일링과 표준화가 널리 사용됩니다.
min-max 스케일링은 각 특성에 대해서 0~1 범위에 들도록 값을 이동하고 스케일을 조정합니다. 데이터에서 최솟값을 뺀 후 최댓값과 최솟값의 차이로 나누면 이렇게 할 수 있습니다. 사이킷런에는 MinMaxScaler 변환기를 제공합니다.
from sklearn.preprocessing import MinMaxScaler
min_max_scaler = MinMaxScaler(feature_range=(-1, 1))
housing_num_min_max_scaled = min_max_scaler.fit_transform(housing_num)
표준화는 먼저 평균을 뺀 후 표준편차로 나눕니다. 표준화는 특정 범위로 값을 제한하지는 않지만 이상치에 영향을 덜 받습니다. 사이킷런에서는 표준화를 위한 StandardScaler 변환기가 있습니다.
from sklearn.preprocessing import StandardScaler
std_scaler = StandardScaler()
housing_num_std_scaled = std_scaler.fit_transform(housing_num)
2.5.4 사용자 정의 변환기
어떤 훈련도 필요하지 않는 변환의 경우 넘파이 배열을 입력으로 받고 변환된 배열을 출력하는 함수를 작성하면 됩니다.
from sklearn.preprocessing import FunctionTransformer
log_transformer = FunctionTransformer(np.log, inverse_func=np.exp)
log_pop = log_transformer.transform(housing[["population"]])
사용자 정의 변환 함수는 추가적인 인수로 하이퍼파라미터를 받을 수 있습니다. 또한 사용자 정의 변환기는 특성을 합칠 때도 유용합니다. 예를 들어 첫 번째 입력 특성과 두 번째 특성 사이의 비율을 계산하는 FunctionTransformer입니다.
ratio_transformer = FunctionTransformer(lambda X: X[:, [0]] / X[:, [1]])
ratio_transformer.transform(np.array([[1., 2.], [3., 4.]]))

fit() 메서드에서 특정 파라미터를 학습하고 나중에 transform() 메서드에서 이를 사용하기 위해 훈련 가능한 변환기가 필요하다면 어떻게 할까요? 사용자 정의 클래스를 작성해야 합니다. 이를 위해 필요한 것은 fit(), transform(), fit_transform() 세 개의 메서드 입니다.
from sklearn.cluster import KMeans
class ClusterSimilarity(BaseEstimator, TransformerMixin):
def __init__(self, n_clusters=10, gamma=1.0, random_state=None):
self.n_clusters = n_clusters
self.gamma = gamma
self.random_state = random_state
def fit(self, X, y=None, sample_weight=None):
self.kmeans_ = KMeans(self.n_clusters, n_init=10, random_state=self.random_state)
self.kmeans_.fit(X, sample_weight=sample_weight)
return self # 항상 self를 반환합니다!
def transform(self, X):
return rbf_kernel(X, self.kmeans_.cluster_centers_, gamma=self.gamma)
def get_feature_names_out(self, names=None):
return [f"클러스터 {i} 유사도" for i in range(self.n_clusters)]
k-평균은 데이터에 있는 클러스터를 찾는 군집 알고리즘입니다. k-평균은 확률적인 알고리즘입니다. 즉 클러스터를 찾기 위해 무작위성에 의존합니다. 따라서 결과를 동일하게 재현하려면 random_state 매개변수를 지정해야 합니다.
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
similarities = cluster_simil.fit_transform(housing[["latitude", "longitude"]],
sample_weight=housing_labels)


2.5.5 변환 파이프라인
변환 단계는 올바른 순서대로 실행되어야 합니다. 다행히 사이킷런은 변환을 순서대로 처리하도록 도와주는 Pipeline 클래스를 제공합니다. 다음은 수치 특성에서 누락된 값을 대체하고 스케일을 조정하는 간단한 파이프라인입니다.
from sklearn.pipeline import Pipeline
num_pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("standardize", StandardScaler()),
])
변환기의 이름을 짓는 게 귀찮다면 make_pipeline() 함수를 사용할 수 있습니다.
from sklearn.pipeline import make_pipeline
num_pipeline = make_pipeline(SimpleImputer(strategy="median"), StandardScaler())

파이프라인은 인덱싱을 지원합니다. 예를 들어 pipeline[1]은 파이프라인에 있는 두 번째 추정기를 반환합니다. pipeline[: -1]은 마지막 추정기를 제외한 모든 추정기를 담은 파이프라인 객체를 반환합니다.
df_housing_num_prepared = pd.DataFrame(
housing_num_prepared, columns=num_pipeline.get_feature_names_out(),
index=housing_num.index)
또한 이름/추정기 쌍의 리스트인 steps 속성을 통해 추정기를 참조할 수 있습니다. 아니면 이름과 추정기를 매핑한 딕셔너리인 named_steps 속성을 사용할 수도 있습니다.
이 모든 것을 조합하면 다음과 같이 됩니다.
def column_ratio(X):
return X[:, [0]] / X[:, [1]]
def ratio_name(function_transformer, feature_names_in):
return ["ratio"] # get_feature_names_out에 사용
def ratio_pipeline():
return make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(column_ratio, feature_names_out=ratio_name),
StandardScaler())
log_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(np.log, feature_names_out="one-to-one"),
StandardScaler())
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
default_num_pipeline = make_pipeline(SimpleImputer(strategy="median"),
StandardScaler())
preprocessing = ColumnTransformer([
("bedrooms", ratio_pipeline(), ["total_bedrooms", "total_rooms"]),
("rooms_per_house", ratio_pipeline(), ["total_rooms", "households"]),
("people_per_house", ratio_pipeline(), ["population", "households"]),
("log", log_pipeline, ["total_bedrooms", "total_rooms", "population",
"households", "median_income"]),
("geo", cluster_simil, ["latitude", "longitude"]),
("cat", cat_pipeline, make_column_selector(dtype_include=object)),
],
remainder=default_num_pipeline) # 남은 특성: housing_median_age
2.6 모델 선택과 훈련
이제 머신러닝 모델을 선택하고 훈련시켜봅시다.
2.6.1 훈련 세트에서 훈련하고 평가하기
아주 간단한 선형 회귀 모델을 훈련시키겠습니다.
from sklearn.linear_model import LinearRegression
lin_reg = make_pipeline(preprocessing, LinearRegression())
lin_reg.fit(housing, housing_labels)
이제 RMSE를 사용하여 성능을 측정해봅시다. 사이킷런의 mean_squared_error() 함수에 squared 매개변수를 False로 지정하여 RMSE를 계산할 수 있습니다.

그렇게 좋은 점수는 아닙니다. 대부분 구역의 중간 주택 가격은 120,000달러에서 265,000달러이므로 이는 모델이 훈련 데이터에 과소적합된 사례입니다.
이번에는 DecisionTreeRegressor를 훈련시켜보겠습니다.
from sklearn.tree import DecisionTreeRegressor
tree_reg = make_pipeline(preprocessing, DecisionTreeRegressor(random_state=42))
tree_reg.fit(housing, housing_labels)
이제 이것을 평가해보겠습니다.

0.0의 오차가 뜨는 것은 모델이 완벽하다는 건데 이는 심하게 과대적합되어있을 가능성이 높습니다. 이제 이를 확인해보아야 합니다.
2.6.2 교차 검증으로 평가하기
첫 번째 방법으로는 우선 train_test_split 함수를 사용해 훈련 세트를 더 작은 훈련 세트와 검증 세트로 나눈 다음 더 작은 훈련 세트에서 모댈을 훈련시키고 검증 세트로 모델을 평가하는 방법이 있습니다.
다른 대안으로는 k-폴드 교차 검증 기능을 사용할 수 있습니다.
from sklearn.model_selection import cross_val_score
tree_rmses = -cross_val_score(tree_reg, housing, housing_labels,
scoring="neg_root_mean_squared_error", cv=10)
결과를 살펴보도록 하면

교차 검증을 통해서 평균 RMSE가 약 66,868이고 표준편차가 2,061이라는 것을 알았습니다. 따라서 결정 트리 모델이 선형 회귀 모델보다 아주 조금 나은 것 같지만 심각한 과대적합 때문에 차이가 미미합니다.
RandomFroestRegressor 모델도 있습니다.
랜덤 포레스트는 특성을 랜덤으로 선택해서 많은 결정 트리를 만들고 예측의 평균을 구하는 방식으로 작동합니다. 서로 다른 모델들로 구성된 이런 모델을 앙상블이라고 하는데, 앙상블은 기반 모델의 성능을 높입니다.
from sklearn.ensemble import RandomForestRegressor
forest_reg = make_pipeline(preprocessing,
RandomForestRegressor(random_state=42))
forest_rmses = -cross_val_score(forest_reg, housing, housing_labels,
scoring="neg_root_mean_squared_error", cv=10)

랜덤 포레스트가 이 작업에 아주 잘 맞아 보입니다. 하지만 훈련 세트에서 RMSE을 측정하면 17,474를 얻습니다. 이는 여전히 많이 과대적합되어 있다는 것을 의미합니다.
이를 해결하는 방법은 모델을 단순화하거나, 제한을 하거나(규제), 더 많은 훈련 데이터를 모으는 것입니다.
2.7 모델 미세 튜닝
이제 이 모델들을 미세 튜닝해야 합니다. 몇 가지 미세 튜닝 방법을 알아봅시다.
2.7.1 그리드 서치
사이킷런의 GridSearchCV를 사용합니다.
탐색하고자 하는 하이퍼파라미터와 시도해볼 값을 지정하기만 하면 됩니다. 그러면 교차 검증을 사용해 가능한 모든 하이퍼파라미터 조합을 평가합니다. 이 코드는 RandomForestRegressor에 대한 최적의 하이퍼파라미터 조합을 탐색합니다.
from sklearn.model_selection import GridSearchCV
full_pipeline = Pipeline([
("preprocessing", preprocessing),
("random_forest", RandomForestRegressor(random_state=42)),
])
param_grid = [
{'preprocessing__geo__n_clusters': [5, 8, 10],
'random_forest__max_features': [4, 6, 8]},
{'preprocessing__geo__n_clusters': [10, 15],
'random_forest__max_features': [6, 8, 10]},
]
grid_search = GridSearchCV(full_pipeline, param_grid, cv=3,
scoring='neg_root_mean_squared_error')
grid_search.fit(housing, housing_labels)
파이프라인이나 ColumnTransformer가 추정기를 겹겹이 감싸고 있더라도 추정기의 모든 하이퍼파라미터를 지정할 수 있습니다.
param_grid에는 두 개의 닥셔너리가 있습니다 GridSearchCV가 먼저 첫 번째 딕셔너리에 지정된 n_clusters와 max_features 하이퍼파라미터 값의 3x3=9개 조합을 모두 평가합니다. 그다음 두 번째 딕셔너리에 있는 하이퍼파라미터 값의 2x3=6개 조합을 모두 평가합니다. 따라서 그리드 서치는 총 9+6 = 15개의 하이퍼파라미터 조합을 탐색합니다.
grid_search.best_estimator_ 속성을 사용해 최상의 추정기를 얻고, 평가 점수는 grid_search.cv_results_ 속성으로 얻을 수 있습니다.
2.7.2 랜덤 서치
그리드 서치 방법과 달리 하이퍼파라미터 탐색 공간이 커지면 RandomizedSearchCV가 종종 선호됩니다.
이는 가능한 모든 조합을 시도하는 대신 각 반복마다 하이퍼파라미터에 임의의 수를 대입하여 지정한 횟수만큼 평가합니다.
이와 같이 하이퍼파라미터마다 가능한 값의 리스트나 확률 분포를 제공해야 합니다.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_distribs = {'preprocessing__geo__n_clusters': randint(low=3, high=50),
'random_forest__max_features': randint(low=2, high=20)}
rnd_search = RandomizedSearchCV(
full_pipeline, param_distributions=param_distribs, n_iter=10, cv=3,
scoring='neg_root_mean_squared_error', random_state=42)
rnd_search.fit(housing, housing_labels)
사이킷런은 HalvingRandomSearchCV와 HalvingGridSearchCV 하이퍼파라미터 탐색 클래스도 제공합니다. 빠르게 훈련하고 대규모 하이퍼파라미터 공간을 탐색하기 위해 계산 자원을 더 효율적으로 시용합니다.
2.7.3 앙상블 방법
모델을 세밀하게 튜닝하는 또 다른 방법은 최상의 모델을 연결해보는 것입니다.
특히 개별 모델이 각기 다른 형태의 오차를 만들때 그렇습니다. 예를 들면 k-최근접 이웃모델을 훈련하고 미세 튜닝한 다음 이 모델의 예측과 랜덤 포레스트의 예측을 평균하여 예측으로 삼는 앙상블 모델을 만들 수 있습니다.
2.7.4 최상의 모델과 오차 분석
최상의 모델을 분석하면 문제에 대한 좋은 인사이트를 얻는 경우가 많습니다.
예를 들어 RandomForestRegressor는 정확한 예측을 만들기 위한 각 특성의 상대적인 중요도를 알려줍니다.
시스템이 특정한 오차를 만들었다면 왜 그런 문제가 생겼는지 이해해야 합니다. 그리고 추가 특성을 포함시키거나, 불필요한 특성을 제거하거나, 이상치를 제외하는 등 해결 방법을 찾아야 합니다.
2.7.5 테스트 세트로 시스템 평가하기
이제 테스트 세트에서 최종 모델을 평가할 차례입니다. 테스트 세트의 특성과 레이블을 사용해 final_model을 실행하여 데이터를 변환하고 예측을 만듭나다. 그 다음 이 예측을 평가합니다.
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()
final_predictions = final_model.predict(X_test)
final_rmse = mean_squared_error(y_test, final_predictions, squared=False)
print(final_rmse)
41424.40026462184
여기서 이때 scipy.stats.t.interval()을 사용해 일반화 오차의 95% 신뢰 구간을 계산할 수 있습니다.
구간은 39,275와 43,467 사이로 꽤 크며 이전 점추정값인 41,424는 대략 중간에 해당합니다.
하이퍼파라미터 튜닝을 많이 했다면 교차 검증을 사용해 측정한 것보다 성능이 조금 낮은 것이 보통입니다.
하지만 이런 경우가 생기더라도 테스트 세트에서 성능 수치를 좋게 하려고 하이퍼파라미터를 튜닝하려 시도해서는 안 됩니다. 그렇게 향상된 성능은 새로운 데이터에 일반화되기 어렵습니다.
2.8 론칭, 모니터링, 시스템 유지 보수
이제 제품 시스템에 적용하기 위한 준비를 해야 합니다.
가장 기본적인 방법은 훈련된 최상의 모델을 저장하고 제품 환경으로 이 파일을 전달하여 로드하는 것입니다.
다음과 같이 joblib 라이브러리를 사용해 모델을 저장 할 수 있습니다.
import joblib
joblib.dump(final_model, "my_california_housing_model.pkl")
모델이 제품 환경으로 전달되고 나면 이를 로드하여 사용할 수 있습니다.
import joblib
# 추가 코드 – 책에는 간결함을 위해 제외함
from sklearn.cluster import KMeans
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.metrics.pairwise import rbf_kernel
def column_ratio(X):
return X[:, [0]] / X[:, [1]]
#class ClusterSimilarity(BaseEstimator, TransformerMixin):
# [...]
final_model_reloaded = joblib.load("my_california_housing_model.pkl")
new_data = housing.iloc[:5] # 새로운 구역이라 가정
predictions = final_model_reloaded.predict(new_data)
예를 들면 모델이 웹 사이트 안에서 사용될 수 있습니다. 혹은 웹 애플리케이션이 REST API를 통해 질의할 수 있는 전용 웹서비스로 모델을 감쌀 수 있습니다.

2.9 직접 해보세요!
이 장을 통해서 머신러닝 프로젝트가 무엇인지에 대해서 알아보았습니다.
이제 직접 실천할 차례겠죠. 노트북을 통해 관심 있는 데이터셋을 선택해서 처음부터 끝까지 직접 전체 프로세스를 만들어보아요!!!
'머신러닝 스터디' 카테고리의 다른 글
| [핸즈온 머신러닝 3판] 5장 서포트 벡터 머신 (0) | 2024.11.05 |
|---|---|
| [핸즈온 머신러닝 3판] 6장 결정트리 (0) | 2024.10.29 |
| [핸즈온 머신러닝 3판] 4장 모델 훈련 (2) | 2024.10.01 |
| [핸즈온 머신러닝 3판] 3장 분류 (1) | 2024.09.24 |
| [핸즈온 머신러닝 3판] 1장 한눈에 보는 머신러닝 (3) | 2024.09.10 |