본문 바로가기

문돌이 존버/데이터 분석

핸즈온 머신러닝 2 복습하기(챕터 2:처음부터 끝까지, 챕터 3: 분류)

반응형

모델 세부 튜닝

- 그리드 탐색으로 사이킷런의 GridSearchCV를 사용하면 된다. 그러면 가능한 모든 하이퍼파라미터 조합에 대해 교차 검증을 사용해 평가한다. 

from sklearn.model_selection import GridSearchCV

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

gird_search = GridSearchCV(forest_reg, param_grid, cv=5,
			   scoring='neg_mean_sqaured_error',
                           return_train_score=True)
                           
grid_search.fit(housing_prepared, housing_labels)

# 최적의 추정기에 직접 접근
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)
(참고) GridSearchCV(scoring='neg_mean_squared_error')
sklearn의 교차검증은 MSE에 음수를 취해서 오차를 점수로 바꾼다. 즉 본래는 MSE가 낮아야 좋은 것인데, 여기에 음수를 취하기 때문에 sklearn에선 해당 점수가 높아야 모델이 좋다고 판단한다.

param_grid 설정에 따라 먼저 첫 번째 dict에 있는 n_estimators와 max_features 하이퍼파라미터의 조합인 3 x 4 = 12개를 평가한다. 그 다음 하이퍼파라미터의 조합인 2 x 3 = 6개를 시도한다. 모두 합하면 18개 하이퍼파라미터 값 조합을 탐색하고, 각각 다섯 번 모델을 훈련시킨다(5겹 교차 검증 사용) 따라서 전체 훈련 횟수는 90이 된다. 

그리드 탐색 방법은 비교적 적은 수의 조합을 탐구할 때 괜찮다. 하지만 하이퍼파라미터 탐색 공간이 커지면 RandomizedSearchCV를 사용하는 것이 좋으며, 이는 가능한 모든 조합을 시도하는 대신 각 반복바다 하이퍼파라미터에 임의의 수를 대입하여 지정한 횟수만큼 평가한다. 

- 랜덤 탐색을 1000회 반복 실행하면 하이퍼파라미터마다 각기 다른 1000개의 값을 탐색(그리드 탐색은 하이퍼파라미터마다 몇 개의 값만 탐색)
- 단순히 반복 횟수를 조절하는 것만으로 하이퍼파라미터 탐색에 투입할 컴퓨팅 자원 제어
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_distribs = {
    'n_estimatros': randint(low=1, high=200), # 변수의 범위를 랜덤
    'max_feautres': randint(low=1, high=8)
}

forest_reg = RandomForestRegressor(random_state=42)
rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,
                                n_iter=10, cv=5, scoring='neg_mean_squared_error', random_state=42)
rnd_search.fit(housing_prepared, housing_labels)
cvres = rnd_search.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
    print(np.sqrt(-mean_score), params)

최상의 모델과 오차 분석

- 각 특성의 상대적인 중요도 파악

feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances

# 중요도 다음에 그에 대응하는 특성 이름 표시
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]

# wrap up: full_pipeline에서 cat_attribs=["ocean_proximity"]을 원-핫 인코딩으로 처리
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)

테스트 세트로 시스템 평가

final_model = grid_search.best_estimator_

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

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)

테스트 세트에서 예측 변수와 레이블을 얻은 후 full_pipeline을 사용해 데이터를 변환한다. 이때 주의할 점은 테스트 세트는 훈련하면 안 되므로 fit_transform()이 아닌 transform()을 호출해야 한다. 

이렇게 구한 일반화 오차의 추정이 론칭을 결정하기에 충분하지 않을 것이다. 추정값이 얼마나 정확한지 알고 싶다면 scipy.stats.t.interval()을 사용해 일반화 오차의 95% 신뢰 구간을 계산할 수 있다. 

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

하이퍼파라미터 튜닝을 많이 했다면 교차 검증을 사용해 측정한 것보다 조금 성능이 낮은 것이 보통이다. 우리 시스템이 검증 데이터에서 좋은 성능을 내도록 세밀하게 튜닝되었기 때문에 새로운 데이터셋에는 잘 작동하지 않을 가능성이 크다. 


챕터 3: 분류

MNIST

코드를 이용하여 MNIST 데이터셋을 다운로드한다. 

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
mnist.keys()
>>> dict_keys(['data', 'target', 'feature_names', 'DESCR', 'details', 'categories', 'url'])

X, y = mnist["data"], mnist["target"]
X.shape
>>> (70000, 784) # 특성 784개 = 이미지 28 x 28 픽셀
y.shape
>>> (70000,)
y = y.astype(np.unit8) # 문자열인 레이블을 정수로 변환

사이킷런에서 읽어들인 데이터셋들은 일반적으로 비슷한 딕셔너리 구조를 가지고 있다.

- 데이터셋을 설명하는 DESCR 키
- 샘플이 하나의 행, 특성이 하나의 열로 구성된 배열을 가진 data 키
- 레이블 배열을 담은 target 키 

실제 이미지 확인해보기 

import matplotlib as mpl
import matplotlib.pyplot as plt

# 샘플의 특성 벡터를 추출하여 28 x 28 배열로 변환
some_digit = X[0]
some_digit_image = some_digit.reshape(28, 28) 

plt.imshow(some_digit_image, cmap="binary")
plt.axis("off")
plt.show()

MNIST 데이터셋은 이미 훈련 세트(앞쪽 60,000개 이미지)와 테스트 세트(뒤쪽 10,000개 이미지)로 나누어놓은 상태다.

이진 분류기 훈련 

'5'와 '5 아님' 두 개 의 클래스를 구분하는 이진 분류기를 만들어보자. 

y_train_5 = (y_train == 5) # 5는 True고, 다른 숫자는 모두 False
y_test_5 = (y_test == 5)

사이킷런의 SGDClassifier 클래스를 사용해 확률적 경사 하강법(Stochastic Gradient Descent) 분류기로 시작해본다. SGD는 한 번에 하나씩 훈련 샘플을 독립적으로 처리한다. SGDClassifier와 SGDRegressor는 기본적으로 에포크마다 훈련 데이터를 다시 섞는다. 

from sklearn.linear_model import SGDClassifier

sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")

가끔 사이킷런이 제공하는 기능보다 교차 검증 과정을 더 많이 제어해야 할 필요가 있다. 이때 아래와 같이 교차 검증 기능을 직접 구현하면 된다. 

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

# shuffle=False 기본값을 두고 random_state를 지정하면 경고 발생, 사이킷런 0.24 버전부터 에러 발생 예정
skfolds = StratifiedKFold(n_splits=3, random_state=42, shuffle=True)

for train_index, test_index in skfolds.split(X_train, y_train_5):
    clone_clf = clone(sgd_clf)
    X_train_folds = X_train[train_index]
    y_train_folds = y_train_5[test_index]
    X_test_fold = X_train[test_index]
    y_train_fold = y_train_5[test_index]
    
    clone_clf.fit(X_train_folds, y_train_folds)
    y_pred = clone_clf.predict(X_test_fold)
    n_correct = sum(y_pred == y_test_fold)
    print(n_correct / len(y_pred))

정확도를 분류기의 성능 측정 지표로 선호하지 않는 이유

- 특히 불균형한 데이터셋(즉, 어떤 클래스가 다른 것보다 월등히 많은 경우)을 다룰 때 주의해야 한다. 이럴 경우, 정확도는 무의미하게 엄청 높게 나오기 때문이다. 

from sklearn.base import BaseEstimator

class Never5Classifier(BaseEstimator):
    def fit(self, X, y=None):
    	return self
    def predict(self, X):
    	return np.zeros((len(x), 1), dtype=bool)
728x90
반응형