캘리포니아 인구조사 데이터를 사용해 캘리포니아의 주택 가격 예측 모델 만들기 → 특정 구역의 중간 주택 가격에 대한 예측
회귀 문제의 경우 전형적으로 평균 제곱근 오차 root mean square error (RMSE)를 성능 지표로 사용한다.
cf) 이상치로 보이는 구역이 많은 경우, 성능 측정 지표로 평균 절대 오차 mean absolute error (MAE)를 사용할 수도 있다.
(RMSE와 MAE 모두 예측값의 벡터와 타깃값의 벡터 사이의 거리를 재는 방법)
데이터 구조 훑어보기
housing = pd.read_csv('housing.csv')
housing.head()
DataFrame의 head()
메서드를 사용해 처음 다섯 행을 확인
housing.info()
info()
메서드를 사용해 데이터에 대한 간략한 설명(전체 행 수, 각 특성의 데이터 타입과 null이 아닌 값의 개수) 확인
total_bedrooms
특성은 20,433개만 null 값이 아님. → 207개의 구역은 이 특성을 가지고 있지 않다는 것을 의미ocean_proximity
를 제외한 모든 특성은 숫자형 → value_counts()
메서드를 통해 'ocean_proximity'에 어떤 카테고리가 있고, 각 카테고리마다 얼마나 많은 구역이 있는지 확인housing.describe()
describe()
메서드를 통해 숫자형 feature의 요약 정보 확인 (null 값 제외)
housing.hist(bins=50, figsize=(20, 15))
plt.show()
hist()
메서드를 호출하여 모든 숫자형 특성에 대한 히스토그램을 출력, 데이터의 형태를 파악한다.
housing_median_age
와 median_housing_value
히스토그램은 오른쪽에서 그래프가 심하게 높아지면서 히스토그램이 끝나는 것으로 보아 마지막 값으로 한정되었음을 짐작할 수 있다.test set 만들기
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
함수 사용random_state
매개변수를 통해 난수 초깃값을 지정 → 실행 결과가 항상 동일하도록참고) stratified sampling을 위한 코드
테스트 세트에서 소득 카테고리의 비율 확인
사이킷런의 StratifiedShuffleSplit
사용 → 계층 샘플링을 사용해 만든 테스트 세트가 전체 데이터셋의 소득 카테고리 비율과 거의 같은 것을 알 수 있다.
income_cat
특성을 삭제하여 데이터를 원래 상태로 되돌린다.
housing = strat_train_set.copy()
train 세트를 손상시키지 않기 위해 복사본을 만들어 사용한다.
데이터 시각화
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
alpha
옵션을 0.1로 주어 데이터 포인트가 밀집된 영역을 확인할 수 있다.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()
매개변수 조절
s
: 원의 반지름은 구역의 인구를 나타냄c
: 색상은 주택 가격을 나태냄→ 빨간색은 높은 가격, 파란색은 낮은 가격, 큰 원은 인구가 밀집된 지역을 의미한다.
상관관계 조사
상관계수는 선형적인 상관관계(x가 증가하면 y는 증가하거나 감소)만 측정하며, 비선형적인 관계는 잡을 수 없다.
corr_matrix = housing.corr()
corr()
메서드를 이용하여 계산한다.
corr_matrix["median_house_value"].sort_values(ascending=False)
중간 주택 가격과 다른 feature 사이의 상관관계를 확인한다.
median_house_value
는 median_income
이 올라갈 때 증가하는 경향이 있다.from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
판다스의 scatter_matrix
함수를 사용하여 숫자형 특성 사이의 산점도를 그릴 수 있다.
(다른 수치형 특성에 대한 각 수치형 특성의 산점도와 히스토그램을 출력)
housing.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
plt.axis([0, 16, 0, 550000])
median_house_value와 median_income의 상관관계 산점도 확인
특성 조합으로 실험
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"]
가구당 방 개수, 가구당 인원 등 여러 특성의 조합을 시도해보고 다시 상관관계 행렬을 확인한다.
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)
housing = strat_train_set.drop("median_house_value", axis=1) # 훈련 세트를 위해 레이블 삭제
housing_labels = strat_train_set["median_house_value"].copy()
예측 변수와 레이블을 분리한다.
데이터 정제
일반적인 방법
housing.drop("total_bedrooms", axis=1)
→ 'total_bedrooms' 컬럼을 삭제한다.
housing["total_bedrooms"].fillna(median, inplace=True)
→ 중간값을 계산하고 누락된 값을 이 값으로 채운다.
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
사이킷런의 SimpleImputer
로도 누락된 값을 쉽게 다룰 수 있다. → 모든 수치형 특성의 누락된 값을 feature의 중간값으로 대체하는 객체 생성
housing_num = housing.drop("ocean_proximity", axis=1)
중간값이 수치형 특성에서만 계산될 수 있기 때문에 텍스트 특성 'ocean_proximity'를 삭제한다.
imputer.fit(housing_num)
각 특성의 중간값을 계산하여 그 결과를 객체의 statistics_
속성에 저장
X = imputer.transform(housing_num)
훈련 세트를 변환한다.
housing_tr = pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
변환된 feature들이 들어있는 넘파이 배열을 다시 판다스 데이터프레임으로 되돌린다.
범주형 특성 다루기
범주형 입력 특성인 ocean_proximity 전처리
housing_cat = housing[["ocean_proximity"]]
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
사이킷런의 OneHotEncoder 클래스를 사용해 카테고리별 이진 특성을 만든다.
한 특성만 1이고 나머지는 0 → 원-핫 인코딩
출력은 희소행렬이며, 이는 수천 개의 카테고리가 있는 범주형 특성일 경우 매우 효율적이다.
feature scaling
머신러닝 알고리즘은 입력 숫자 feature들의 스케일이 많이 다른 경우 잘 작동하지 않는다.
( 주택 가격 데이터의 경우, 전체 방 개수의 범위는 6~39,320인 반면 중간 소득의 범위는 0~15이다.)
타깃값에 대한 스케일링은 일반적으로 필요하지 X
MinMaxScaler
변환기를 이용할 수 있다.StandardScaler
변환기를 이용참고) 모든 변환기에서 스케일링은 test 세트가 포함된 전체 데이터가 아닌, train 데이터에 대해서만 fit()
메서드를 적용해야 한다. 그런 다음 train 세트와 test 세트, 새로운 데이터에 대해 transform()
메서드를 사용한다.
변환 파이프라인
col_names = "total_rooms", "total_bedrooms", "population", "households"
rooms_ix, bedrooms_ix, population_ix, households_ix = [
housing.columns.get_loc(c) for c in col_names] # 열 인덱스 구하기
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
from sklearn.compose import ColumnTransformer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
full_pipeline = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
수치형과 범주형 feature를 전처리하는 과정을 하나의 파이프라인으로 만든다.
1.ColumnTransformer
클래스 임포트
2. 수치형 열 이름의 리스트와 범주형 열 이름의 리스트 만들기
3. ColumnTransformer 클래스 객체 만들기
(수치형 열은 num_pipeline을 사용해 변환되고, 범주형 열은 OneHotEncoder를 사용해 변환됨)
4. ColumnTransformer를 주택 데이터에 적용
선형 회귀 모델
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("예측:", lin_reg.predict(some_data_prepared))
print("레이블:", list(some_labels))
train 세트의 몇 개의 샘플에 적용하여 실제값과 비교,
mean_square_error 함수를 사용해 전체 훈련 세트에 대한 RMSE 측정
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
DecisionTreeRegressor
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse
오차 → 0
( 모델이 데이터에 너무 심하게 overfitting 된 것을 확인할 수 있다. )
교차 검증을 사용한 평가
from sklearn.model_selection import cross_val_score
scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
train 세트를 fold라 불리는 10개의 서브셋으로 무작위 분할하고, 모델을 10번 훈련하고 평가한다.
(매번 다른 폴드를 선택해 평가에 사용하고 나머지 9개 폴드는 훈련에 사용)
def display_scores(scores):
print("점수:", scores)
print("평균:", scores.mean())
print("표준 편차:", scores.std())
print('DecisionTree')
print()
display_scores(tree_rmse_scores)
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)
print('선형회귀모델')
print()
display_scores(lin_rmse_scores)
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor(n_estimators=100, random_state=42)
forest_reg.fit(housing_prepared, housing_labels)
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
forest_rmse 출력 : 18650.698705770003
from sklearn.model_selection import cross_val_score
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)
print('RandomForest')
print()
display_scores(forest_rmse_scores)
그리드 탐색
하이퍼파라미터 조정 → 사이킷런의 GridSearchCV
사용
( 가능한 모든 하이퍼파라미터 조합에 대해 교차 검증을 사용해 평가한다. )
from sklearn.model_selection import GridSearchCV
param_grid = [
# 12(=3×4)개의 하이퍼파라미터 조합 시도
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
# bootstrap은 False로 하고 6(=2×3)개의 조합 시도
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor(random_state=42)
# 다섯 개의 폴드로 훈련, 총 (12+6)*5=90번의 훈련
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error',
return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
RandomForestRegressor에 대한 최적의 하이퍼파라미터 조합을 탐색하는 코드
grid_search.best_params_
최상의 파라미터 조합을 확인할 수 있다.
cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
print(np.sqrt(-mean_score), params)
그리드서치에서 테스트한 하이퍼파라미터 조합의 점수를 확인할 수 있다.
랜덤 탐색
그리드 탐색 방법은 비교적 적은 수의 조합을 실행할 때 좋다.
하지만 하이퍼파라미터 탐색 공간이 커지면 RandomizedSearchCV
를 사용하는 것이 더 좋다.
RandomizedSearchCV는 GridSearchCV와 거의 같은 방식으로 사용하지만, 가능한 모든 조합을 시도하는 대신 각 반복마다 하이퍼파라미터에 임의의 수를 대입하여 지정한 횟수만큼 평가한다.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_distribs = {
'n_estimators': randint(low=1, high=200),
'max_features': 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)
테스트 세트로 시스템 평가하기
test dataset에서 최종 모델을 평가한다.
final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_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) # => 47,730.0 출력
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)))
scipy.stats.t.interval()
을 사용해 일반화 오차의 95% 신뢰 구간을 계산할 수 있다.