주택 가격을 예측하는 회귀 모델regression model의 훈련과정을 이용하여 머신러닝 프로젝트의 일반적인 진행 과정을 살펴본다.

특히 데이터 정제 및 전처리 과정으로 구성된 데이터 준비를 상세히 소개한다.
2.1머신러닝 모델 훈련용 데이터¶
머신러닝으로 문제를 해결하려면 먼저 관련된 데이터를 구하고 기초적인 정보를 확인한 후에 어떤 모델을 어떻게 훈련시킬 것인가를 판단한다.
2.1.1데이터 기초 정보¶
여기서는 1990년 미국 캘리포니아 주에서 수집한 주택 가격 데이터를 사용하며, 아래 그림은 원본 csv 파일의 일부 내용을 보여준다.

1990년도에 시행된 미국 캘리포니아 주의 20,640개 구역별 주택 가격 데이터는 위 엑셀 파일 그림의 1번 행에 표시된 대로 경도, 위도, 주택 건물 중위연령, 총 방 수, 총 침실 수, 인구, 가구 수, 중위소득, 중위 주택가격, 해안 근접도 등 총 10개의 특성feature을 포함한다. 참고로 특성은 데이터프레임의 각 열에 사용된 이름, 즉 열별 데이터를 대변하는 이름이다. 통계 분야에서는 특성을 변수 또는 변인 등으로 부르지만 머신러닝 분야에서는 특성이라 부르는 게 일반적이다.

2.1.2머신러닝 훈련 모델 선택 기준¶
머신러닝 훈련 모델을 선택을 하려면 먼저 모델이 예측하는 값의 종류, 즉 타깃을 확인해야 한다.
타깃
10개의 특성 중에서 중위 주택가격이 부동산과 관련해서 매우 중요하다. 따라서 어떤 구역에 대해 중위 주택가격을 제외한 9개의 특성이 주어졌을 때 해당 구역의 중위 주택가격을 타깃target으로 예측하는 시스템을 머신러닝 모델로 구현한다.
훈련 모델
구역별 중위 주택가격을 타깃으로 예측하는 시스템에 활용될 회귀 모델을 지도학습 방식으로 훈련시키고자 한다. 훈련시킬 모델의 특성은 다음과 같다.
지도학습: 구역별 중위 주택가격을 타깃, 즉 최대한 정확하게 예측해야 하는 목표로 지정한다.
회귀: 연속형 데이터인 중위 주택가격을 예측한다. 보다 세분화하면 다중 회귀이자 단변량 회귀 모델이다.
다중 회귀multiple regression: 구역별로 여러 특성을 주택 가격 예측에 사용
단변량 회귀univariate regression: 구역별로 한 종류의 값만 예측
배치 학습: 빠르게 변하는 데이터에 적응할 필요가 없으며, 데이터셋의 크기도 충분히 작기에 데이터셋 전체를 대상으로 훈련을 진행한다.
2.1.3데이터 구하기¶
캘리포니아 주택 가격 데이터는 매우 유명하여 많은 공개 저장소에서 다운로드할 수 있다.
여기서는 직접 정의한 load_housing_data() 함수를 이용하여
개인 깃허브 리포지토리에 압축파일로 저장한 파일을 다운로드를 한 후에
판다스 데이터프레임 객체로 불러와서 사용한다.
2.2데이터셋 탐색¶
데이터셋 탐색은 본격적인 훈련을 시작하기 전에 훈련에 사용될 데이터셋의 기본 특징을 살펴보는 과정이다.
2.2.1데이터프레임 활용¶
판다스 데이터프레임으로 적재된 데이터셋의 기본적인 데이터 구조는 다음과 같다.

데이터셋의 정보 요약은 다음과 같다.
구역 수: 20,640개.
구역별로 경도, 위도, 주택 건물 중위연령, 해안 근접도 등 총 10개의 조사 항목.
해안 근접도를 뜻하는
ocean_proximity특성은 범주형categorical이고 나머지는 수치형numerical 특성임.특성의
Dtype이object: 범주형 데이터특성의
Dtype이float64: 수치형 데이터
총 방 수를 뜻하는
total_bedrooms특성은 207개의 null 값, 즉 결측치 포함.

2.2.2범주형 대 수치형¶
범주형 특성
ocean_proximity (해안 근접도) 특성은 dtype이 object인데 이는 보통 문자열 자료형을 가리킨다.
즉 해안 근접도 특성에 포함된 값은 문자열이며,
사용된 문자열은 모두 아래 5개 문자열 값중에 하나이며,
각 문자열 특성값의 의미는 다음과 같다.
| 특성값 | 설명 |
|---|---|
| <1H OCEAN | 해안에서 1시간 이내 |
| INLAND | 내륙 |
| NEAR OCEAN | 해안 근처 |
| NEAR BAY | 샌프란시스코의 Bay Area 구역 |
| ISLAND | 섬 |
이처럼 구분을 위한 몇 개의 값으로 구성된 특성을 범주형 특성이라 부르며 각각의 값은 범주category라 부른다.
수치형 특성
범주형 특성과는 다르게 수치형 특성은 항상 정수 또는 부동소수점으로 구성된다. 캘리포니아 주택 가격 데이터셋에서는 해안 근접도를 제외한 나머지 특성들 모두 수치형 특성이다. 수치형 특성 각각에 대해서는 평균값, 표준편차, 사분범위 등의 정보를 확인할 수 있다.

수치형 특성별로 히스토그램을 통해 다음 정보를 얻을 수 있다.
각 특성마다 사용되는 단위와 스케일이 다르다. 한 자리 수부터 다섯 자리, 즉 만 단위의 수까지 다양하다.
일부 특성은 데이터가 한쪽으로 치우쳐저 있다. 예를 들어
total_rooms,total_bedrooms,population,households등의 특성값들이 오른쪽 꼬리를 길게 갖는다.일부 특성은 값의 크기를 제한한 것으로 보인다. 예를 들어
housing_median_age,median_house_value등의 특성값 상한값이 임의로 지정되어 잘린 것처럼 보인다.

2.3훈련셋과 테스트셋¶
모델 훈련을 시작하기 전에 전체 데이터셋을 보통 훈련셋training set과 테스트셋test set으로 나눈다. 테스트셋은 훈련 과정중에 전혀 사용되지 않으며 보통 전체 데이터셋의 최대 10~20% 정도로 선택하며, 전체 데이터셋의 크기에 따라 테스트셋의 크기가 너무 크지 않게 비율을 적절히 조절한다.
훈련셋
머신러닝 모델 훈련에 사용되는 데이터셋을 가리킨다. 이후에 실제 모델 훈련을 시작하기 전에 다시 입력 데이터셋과 타깃셋으로 특성을 기준으로 쪼개진다.
테스트셋
훈련을 마친 모델의 성능을 평가하기 위해 사용하는 데이터셋이다. 훈련 과정에서는 어떤 형식으로든 절대로 활용하지 않는다. 훈련될 모델을 이용하여 테스트되기 이전에 훈련셋처럼 동일한 기준으로 입력 데이터셋과 타깃셋으로 나뉘어진다.
2.3.1무작위 샘플링¶
무작위 샘플링Random Sampling은 전체 데이터에서 무작위로 샘플을 추출하는 방식이다. 데이터셋이 매우 크다면 모집단을 잘 대표할 수 있지만, 그렇지 않을 경우 샘플링 편향이 발생해 특정 특징을 가진 데이터가 과하게 많거나 적게 추출될 위험이 있다.
2.3.2계층 샘플링¶
계층 샘플링Stratified Sampling은 전체 데이터를 의미 있는 여러 계층(그룹)으로 미리 나눈 뒤, 전체 데이터셋에서 각 계층이 차지하는 비율과 동일하게 샘플을 추출하는 방식이다. 데이터의 중요한 특성 비율이 훈련셋과 테스트셋에 그대로 유지되므로 샘플링 편향을 방지할 수 있다.
예를 들어, 특정 소득 구간에 포함된 샘플이 과하게 적거나 과하게 많으면 해당 계층의 중요도가 과소 혹은 과대 평가되어 데이터의 실제 특성과 차이(편향)를 보일 수 있다.
캘리포니아 주택 가격 데이터셋을 훈련셋과 테스트셋으로 나눌 때 중위소득 특성을 계층 샘플링의 기준으로 삼는다. 이유는 여기서 훈련시키고자 하는 모델이 구역별 중위 주택가격을 예측하는 모델인데 아무래도 중위 주택 가격이 구역에 사는 가구의 중위소득과 밀접하게 연관되어 있을 것이기 때문이다.
먼저 구역별 중위소득 특성을 대상으로 히스토그램을 그려보면 대부분 구역의 중위소득이 1.5 ~ 6.0, 즉 15,000에서 60,000 달러 사이인 것을 알 수 있다.

2.3.3무작위 샘플링 대 계층 샘플링¶
따라서 중위소득 구간을 아래처럼 5개로 구분한 다음에 계층 샘플링을 이용하여 계층별 빈도를 유지하면서 훈련셋과 테스트셋을 구분하면 좋을 것 같아 보인다.
| 구간 | 범위 |
|---|---|
| 1 | 0.0 ~ 1.5 |
| 2 | 1.5 ~ 3.0 |
| 3 | 3.0 ~ 4.5 |
| 4 | 4.5 ~ 6.0 |
| 5 | 6.0 ~ |
아럐 표는 캘리포니아 데이터셋을 8 대 2로 훈련셋과 테스트셋으로 분류할 때 계층 샘플링이 무작위 샘플링보다 계층별 샘플의 비율을 훨씬 잘 유지함을 확인해준다.
| 소득 구간 | 전체(%) | 계층 샘플링(%) | 무작위 샘플링(%) |
|---|---|---|---|
| 1 | 3.98 | 4.00 | 4.24 |
| 2 | 31.88 | 31.88 | 30.74 |
| 3 | 35.06 | 35.05 | 34.52 |
| 4 | 17.63 | 17.64 | 18.41 |
| 5 | 11.44 | 11.43 | 12.09 |
2.4훈련셋 살펴보기¶
데이터 살펴보기는 지금까지와는 달리 훈련셋만을 대상으로 진행한다. 훈련셋은 캘리포니아 전체 데이터셋에서 앞서 설명한 계층 샘플링을 이용하여 생성되었다고 가정한다. 훈련셋의 크기는 전체 데이터셋의 크기인 20,640의 80%인 16,512다.
2.4.1훈련셋 시각화¶
데이터 시각화는 히스토그램, 산점도 등 다양한 방식으로 가능하다. 여기서는 데이터셋에 포함된 경도와 위도 정보를 이용한 시각화를 진행한다. 점의 클 수록 해당 구역의 인구가 많음을 의미한다.
훈련셋에 포함된 16,512개 구역의 경도와 위도 정보를 이용하여 구역 정보를 산포도로 나타내면 지역별 인구의 밀집 정도가 다름을 확인할 수 있다. 예를 들어, 일부 지역(샌프란시스코의 Bay Area, LA, 샌디에고 등 유명 대도시)에 인구가 많은 구역이 모여 있는 반면에, 내륙으로 들어가거가 북쪽으로 올라갈 수록 인구 밀도가 낮아지는 경향이 있다.

2.4.2피어슨 상관관계¶
앞으로 훈련시킬 모델은 어떤 구역의 중위 주택 가격을 제외한 다른 특성이 주어졌을 때 해당 구역의 중위 주택 가격을 예측해야 한다. 따라서 중위 주택 가격과 상관관계가 높은 특성을 미리 확인해보아야 한다.
특성들 사이의 선형 상관관계를 피어슨 상관계수로 계산한다. 단, 수치형 특성만 대상으로 한다.

Seaborn 라이브러리의 히트맵으로 상관관계수를 시각화할 수 있다.

중위 주택가격과 중위소득의 상관계수가 0.68로 상당히 높다. 이는 중위소득이 올라가면 중위 주택가격도 상승하는 경향이 꽤 강하게 있음을 의미한다. 아래 산점도가 이 사실을 잘 확인시켜준다.

또한 50만 달러에서 보이는 수평선은 50만 달러 이상은 모두 50만달러로 지정한 결과로 보여진다. 그렇게 인위적으로 조작된 데이터는 모델 훈련에 도움이 되지 않아 제거하는 것이 일반적으로 좋지만 여기서는 그대로 두고 사용한다.
2.5타깃 대 입력 데이터셋¶
지도학습 방식으로 중위 주택 가격을 예측하는 모델을 훈련시키려면 훈련셋을 타깃셋과 입력 데이터셋으로 분리해야 한다.
타깃: 모델이 최대한 정확하게 예측해야 하는 특성이다.
타깃셋: 타깃 특성으로만 구성된 데이터셋이다.
입력 데이터셋: 타깃 특성을 제외한 나머지 특성으로 구성된 데이터셋을 가리킨다. 입력 데이터셋 또한 훈련셋으로 불린다.
캘리포니아 주택 가격 데이터셋을 활용한 모델 훈련에 사용된 타깃과 훈련셋(입력 데이터셋)은 다음과 같다.
타깃: 중위 주택 가격(
median_house_value)훈련셋(입력 데이터셋): 타깃인
median_house_value특성을 제외한 나머지 특성들로 구성된 데이터셋
2.6데이터 정제와 전처리¶
데이터 탐색을 통해 확인한 지도학습 회귀 모델을 훈련시키기 위해 먼저 적절한 훈련셋(입력 데이터셋)을 준비해야 한다. 적절한 훈련셋(입력 데이터셋) 준비는 데이터 정제와 데이터 전처리 과정으로 이루어진다.
2.6.1데이터 정제¶
먼저 데이터 정제Data Cleanign는
일반적으로 훈련셋(입력 데이터셋)에 포함된 결측치 처리, 이상치와 노이즈 제거 등을 의미한다.
캘리포니아 주택 가격 데이터셋의 경우 구역별 총 방 수를 의미하는 total_rooms 특성에
포함되어 있는 결측치를 어떻게 다를 것인지 결정해야 한다.
이상치와 노이즈에 대해서는 여기서는 다루지 않는다.
2.6.2데이터 전처리¶
데이터 전처리Data Preprocessing는 모델 훈련에 적합한 훈련셋(입력 데이터셋)을 만들어가는 과정을 가리킨다. 예를 들어, 캘리포니아 주택 가격 데이터셋에 포함된 수치형 특성과 범주형 특성에 대해 각각 아래 전처리 과정을 거친다.
범주형 특성 전처리: 원-핫-인코딩
수치형 특성 전처리: 특성 스케일링과 특성 조합
이에 더해 다음 전처리 과정도 진행한다.
비율 특성 추가: 침실 비율, 가구당 방 수, 가구당 평균 가구원 수
로그 변환 대상 특성:
"total_bedrooms","total_rooms","population","households","median_income"구역 군집 특성 추가: 위도와 경도 정보를 활용하여 유사한 구역끼리 구성된 군집으로 분류
2.7사이킷런 API 활용¶
데이터 정제와 전처리 전과정을 사이킷런 라이브러리에서 제공하는 API를 활용한다. 먼저 사이킷런 API의 기본 특성을 살펴본 다음에 앞서 언급된 정제와 전처리 내용을 처리하는 각각의 API를 하나씩 살펴본다. 그런 다음 사이킷런 API를 연동하여 정제와 전처리 전 과정을 한꺼번에 순차적으로 처리하는 파이프라인pipeline으로 구성하여 자동화는 방식까지 소개한다.
2.7.1변환기 대 예측기¶
사이킷런이 제공하는 머신러닝 모델의 훈련에 특화된 API는 일반적으로 다음 두 클래스의 인스턴스로 생성된다.
변환기transformer
fit()메서드와transform()메서드를 함께 지원하는 객체일반적으로 데이터 정제 특성 스케일링 등 데이터 전처리 과정에서 사용됨
fit()메서드: 데이터 변환에 필요한 파라미터(예: 평균, 표준편차 등)를 학습 및 계산transform()메서드:fit()메서드가 계산한 정보를 활용하여 실제 데이터를 변환fit_transform()메서드:fit()과transform()을 연속으로 수행 (일반적으로 두 메서드를 따로 호출하는 것보다 최적화되어 있어 속도가 더 빠름)
예측기predictor
fit()및predict()메서드를 함께 지원일반적인 머신러닝 모델이 바로 예측기에 해당함
fit()메서드: 모델의 훈련 과정 총괄predict()메서드: 모델 훈련 종료 후, 새로운 데이터에 대한 예측값을 계산할 때 활용score()메서드: 예측 성능을 측정하기 위해 일반적으로 함께 지원됨일부 예측기는 예측값의 신뢰도를 평가하는 기능(예:
predict_proba())도 함께 제공함
2.7.2결측치 처리¶
입력 데이터셋의 총 침실 수를 의미하는 total_bedrooms 특성에 168개 구역의 값이 NaN(Not a Number)으로 표시되어 있는데
이는 168개 구역에 대해 총 침실 수 정보가 누락되어 있음을 의미한다.

머신러닝 모델은 일반적으로 결측치가 있는 데이터셋을 잘 활용하지 못하며, 일반적으로 아래 방법 중 하나를 선택해서 결측치를 없애는 데이터 정제를 실행한다.
방법 1: 해당 구역 샘플 제거
방법 2: 해당 특성 삭제
방법 3: 평균값, 중위수, 최빈값, 0, 또는 주변에 위치한 값 등 특정 값으로 결측치 채우기
여기서는 중위수median로 결측치를 대체하는 방법 3을 적용하며,
이를 위해 사이킷런의 SimpleImputer 변환기를 이용한다.

2.7.3원-핫 인코딩¶
해안 근접도 특성(ocean_proximity)은 5 개의 범주를 나타내는 문자열을 값으로 사용한다.
그런데 사이킷런의 머신러닝 모델은 일반적으로 문자열과 같은 텍스트 데이터를 다루지 못한다.
이와 같은 범주형 특성은
원-핫 인코딩one-hot encoding 방식으로 수치화 한다.
원-핫 인코딩을 적용하면 해안 근접도 특성을 삭제하고 대신 다섯 개의 범주 전부를 새로운 특성으로 추가한다.
예를 들어, INLAND를 해안 근접도 특성값으로 갖던 샘플은
INLAND라는 ocean_proximity 특성 대신에
다음 모양의 특성값을 갖게 된다.
[0.0, 1.0, 0.0, 0.0, 0.0]위 리스트에 포함된 다섯 개 각각의 값은 차례대로 다음 특성에 해당하는 값을 가리킨다.
'<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'더미 특성
원-핫 인코딩은 수치화된 범주들 사이의 크기 비교를 피하기 위해 더미dummy 특성을 활용한다.
예를 들어 INLAND 특성값을 길이가 5인 어레이로 만들기 위해
원래는 불필요한 네 개의 0을 추가로 활용하였다.
새롭게 생성된 5개의 특성은 다음과 같다.
['ocean_proximity_<1H OCEAN',
'ocean_proximity_INLAND',
'ocean_proximity_ISLAND',
'ocean_proximity_NEAR BAY',
'ocean_proximity_NEAR OCEAN']5개 특성은 기존에 주어진 특성 대신 사용되는 특성이라는 의미에서 더미 특성으로 불린다. 여기서는 해안 근접도 특성에 사용된 실제 값을 대변하는 특성으로 사용된다.
사이킷런의 OneHotEncoder 변환기가 원-핫-인코딩을 지원하며
해안 근접도를 변환한 결과는 다음과 같다.

2.7.4특성 스케일링¶
머신러닝 알고리즘은 훈련셋(입력 데이터셋)에 포함된 특성값들의 스케일scale이 비슷할 때 보다 잘 훈련된다. 따라서 특성의 스케일을 통일시키는 스케일링scaling 전처리가 많이 활용된다.
스케일링은 보통 아래 두 가지 방식을 사용한다.
정규화Normalization
표준화Standardization
| 용어 | 정의 |
|---|---|
| 정규화 (Normalization) | 데이터 값을 일정한 범위로 맞추는 일반적인 과정 |
| 표준화 (Standardization) | 평균을 0, 표준편차를 1로 맞추는 과정 |
min-max 스케일링
min-max 스케일링은 정규화의 한 방식이다. 아래 식을 이용하여 특성값 를 0에서 1 사이의 값으로 변환한다. 와 은 각각 해당 특성값들의 최댓값과 최솟값을 가리킨다.
min-max 스케일링은 이상치에 매우 민감하다.
예를 들어 이상치가 매우 크면 분모가 분자에 비해 훨씬 크게 되어 변환된 값이 0 근처에 몰리게 된다.
사이킷런의 MinMaxScaler 변환기가 min-max 스케일링을 지원한다.
표준화
표준화standardization는 아래식을 이용하여 특성값 를 변환한다. 단, 와 는 각각 해당 특성값들의 평균값과 표준편차를 가리킨다.
표준화 스케일링으로 변환된 특성은
평균값은 0, 표준편차는 1인 분포를 따르며, 이상치에 상대적으로 덜 영향을 받는다.
사이킷런의 StandardScaler 변환기가 표준화 스케일링을 지원한다.
2.7.5로그 변환¶
데이터셋이 두터운 꼬리 분포를 따르는 경우, 즉 히스토그램이 지나치게 한쪽으로 편향된 경우 스케일링을 적용하기 전에 먼저 로그 함수를 적용하여 어느 정도 좌우 균형이 잡힌 분포로 변환할 것을 권장한다. 좌우 균형이 잘 잡힌 특성들을 활용하면 머신러닝 모델의 훈련이 보다 잘된다.
아래 그림은 구역별 인구로 구성된 population 특성값에 로그함수를 적용할 때 분포가 보다 균형잡히는 것을 잘 보여준다.

아래 언급된 특성들에 대해 로그 변환을 적용할 예정이며,
사이킷런의 FunctionTransformer 변환기를 활용한다.
"total_bedrooms", "total_rooms", "population", "households", "median_income"FunctionTransformer 변환기
min-max 스케일링을 진행하려면 먼저 각 특성의 최댓값과 최솟값을, 표준화를 진행하려면 먼저 각 특성의 평균값과 표준편차를 알아야 하며,
이를 위해 MinMaxScaler 또는 Standardscaler 변환기의 fit() 메서드를 이용한다.
따라서 위 코드에서처럼 fit_transform() 메서드를 데이터셋에 적용하면 먼저 fit() 메서드가 실행되어 변환에 필요한 값들을 생성하고 이후에 이들을 이용하여 transform() 메서드가 데이터를 변환한다.
반면에 로그 변환처럼 미리 어떤 정보를 확인할 필요 없이 바로 데이터 변환을 진행할 수
있다면, fit() 메서드를 굳이 사용할 필요가 없다.
이런 경우 FunctionTransformer 변환기를 활용한다.
2.7.6비율 변환¶
두 개의 특성 사이의 비율을 계산하여 새로운 특성을 생성할 때 사용한다.
비율 변환을 적용하여 아래 특성을 새롭게 생성할 예정이며,
앞서 설명한 FunctionTransformer 변환기를 활용한다.
침실 비율:
housing['total_bedrooms'] / housing['total_rooms']가구당 방 수:
housing['total_bedrooms'] / housing['households']가구당 평균 가구원수:
housing['population'] / housing['households']
2.7.7사용자 정의 변환기¶
훈련셋 준비 과정에서 경우에 따라 사용자가 직접 변환기를 구현해야할 필요가 있다. 여기서는 훈련셋에 포함된 구역들의 위도와 경도 정보를 이용하여 서로 가깝게 위치한 구역들로 묶어 총 10개의 군집cluster으로 구분하는 변환기를 직접 정의하여 활용한다.
변환기를 사용자가 직접 정의할 때
사이킷런의 다른 변환기와 호환이 되도록 하기 위해서는
fit() 과 transform() 등 몇 개의 메서드를 직접 구현해야 한다.
여기서는 캘리포니아 주 20,640개 구역을 경도와 위도를 기준으로 가까운 구역들로 묶어
총 10개의 군집cluster으로 구분하는 변환기 객체 생성에 필요한
ClusterSimilarity 클래스를 다음과 같이 선언한다.
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.metrics.pairwise import rbf_kernel
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, random_state=self.random_state)
self.kmeans_.fit(X, sample_weight=sample_weight)
return self # always return 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"Cluster {i} similarity" for i in range(self.n_clusters)]위 코드에 대한 자세한 설명은 비지도 학습에서 자세히 다룬다. 여기서는 대신 다음 세 가지만을 기억해 두어야 한다.
첫째, 변환기를 선언할 때 BaseEstimator와 TransformerMixin 두 개의 클래스를 상속해야 한다.
BaseEstimator상속: 이어서 소개할 그리드 탐색, 랜덤 탐색 등 모델 미세 조정fine-tuning에 필요한get_params()와set_params()메서드를 제공한다. 단, 이를 위해서는 생성자__init__에*args나kwargs를 사용하면 안 된다.TransformerMixin상속: 사용자가fit()과transform()메서드만 정의하면, 두 메서드를 연속해서 호출하는fit_transform()메서드를 자동으로 제공한다.

둘째, get_feature_names_out() 메서드를 재정의해서 변환기에 의해 새로 생성되는 특성들의 이름을 지정한다.
셋째, ClusterSimilarity 변환기를 지정한 다음 위도와 경도 정보를 입력하여 새로운 특성을 생성하는 아래 코드를 실행하면,
각 구역을 n_clusters=10개의 군집으로 분류하기 위해 fit()과 transform() 메서드가 하는 일은 다음과 같다.
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)
similarities = cluster_simil.fit_transform(housing[["latitude", "longitude"]])fit()메서드: 주어진 구역들의 위도와 경도 정보를 바탕으로 모든 구역을 총n_clusters개의 군집으로 분류하기 위해 각 군집의 중심centroid에 해당하는 위도와 경도 정보를 계산한다. 이때KMeans모델을 활용하며, 계산된 군집 중심 정보는Kmeans모델의cluster_centers_속성에 저장한다.KMeans모델에 대한 자세한 설명은 비지도 학습에서 다룬다.transform()메서드: 역시 비지도 학습에서 자세히 소개하는rbf_kernel()함수를 이용하여 구역 샘플 각각에 대해 10개 군집의 중심과의 거리를 반영한 가까움 정도를 수치화한 유사도를 계산한다.처음 3개의 구역 샘플에 대해
transform()메서드가 생성한 10개 군집 중심 각각과의 유사도 점수는 다음과 같다.array([[0.46, 0. , 0.08, 0. , 0. , 0. , 0. , 0.98, 0. , 0. ], [0. , 0.96, 0. , 0.03, 0.04, 0. , 0. , 0. , 0.11, 0.35], [0.34, 0. , 0.45, 0. , 0. , 0. , 0.01, 0.73, 0. , 0. ]])예를 들어 첫째 샘플의 경우 7번 인덱스의 값이 0.98로 가장 큰데, 이는 첫째 샘플이 8번째 군집의 중심과 가장 유사함(가까움)을 의미한다. 따라 첫재 샘플은 8번 군집에 속하는 것으로 분류된다. 동일한 이유로 둘째와 셋째 샘플은 각각 2번 군집과 8번 군집에 분류된다.
아래 그래프는 10개 군집의 중심과 각 구역의 위도, 경도 정보를 활용한 산점도이다.
🗙 표시는 각 군집의 중심, 즉 각 군집의 중심 구역을 표시한다.
색상은 구역이 속한 군집의 중심과의 유사도를 가리킨다. 빨간색이 강해질 수록 해당 구역이 속한 군집의 중심과의 유사도(위도와 경도 기준)가 커짐을, 즉 가까워짐을 의미한다.
점의 크기는 구역 인구에 비례한다.

아래 그래프와 비교해보면 일부 군집, 특히 해안가에 위치한 밀도가 높은 군집에서는 군집 중심에 가까울 수록 중위 주택가격이 높아짐을 알 수 있다. 머신러닝 모델이 이런 특성을 활용할 수 있도록 단순한 위도, 경도 정보대신 군집과 유사도 정보를 새로운 특성으로 제공하면 모델의 성능이 보다 좋아질 수 있다.

2.8파이프라인¶
데이터 정제와 전처리의 모든 단계가 정확한 순서대로 진행되어야 한다. 사이킷런은 여러 변환기를 순차적으로 또는 병렬적으로 실행하는 파이프라인 기능을 지원한다.
사이킷런에서 제공하는 파이프라인 관련 주요 API는 다음과 같다.
| API | 설명 |
|---|---|
Pipeline 클래스 | 이름으로 구분된 여러 추정기(변환기, 예측기)를 순차적으로 연결하여 파이프라인 구성. 단 예측기가 사용되는 경우 맨 마지막에 추가 |
make_pipeline() 함수 | 추정기 이름 지정 없이 간편하게 Pipeline 객체 생성 |
ColumnTransformer 클래스 | 특성마다 다른 변환기를 병렬 적용 후 결과 병합 |
make_column_transformer() 함수 | 이름 지정 없이 간편하게 ColumnTransformer 객체 생성 |
2.8.1Pipeline 클래스¶
예를 들어, 수치형 특성을 대상으로 결측치를 중위수로 채우고 표준화를 연속적으로 수행하는 파이프라인은 다음과 같이 정의한다.
num_pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("standardize", StandardScaler())
])Pipeline 인스턴스를 생성할 때는 ("이름", 추정기) 쌍으로 이루어진 튜플의 리스트를 전달한다.
단, 마지막 단계를 제외한 이전의 모든 추정기는 반드시 데이터 변환을 위한 fit_transform()(또는 fit()과 transform()) 메서드를 지원하는 변환기이어야 한다.
생성된 파이프라인 객체의 최종 성격은 파이프라인의 마지막 추정기가 무엇인지에 따라 결정된다.
마지막 추정기가 변환기인 경우: 파이프라인 전체가 하나의 변환기로 작동
마지막 추정기가 예측기인 경우: 파이프라인 전체가 전처리 과정을 모두 포함한 예측기로 작동
예를 들어 앞서 정의한 num_pipeline은 마지막 추정기(StandardScaler)가 변환기이므로, 생성된 파이프라인 전체가 하나의 변환기로 작동한다.
생성된 파이프라인 객체의 fit() 메서드를 호출하면 내부적으로 마지막 추정기 이전 단계까지의 모든 변환기에 대해 fit_transform() 메서드를 순차적으로 호출하여 데이터를 계속 변환해 전달한다.
그리고 마지막 추정기에서는 fit() 메서드를 호출한다.
또한 파이프라인의 fit_transform()을 호출하면 마지막 추정기에서도 fit_transform()이 호출된다.
이러한 작동 방식 때문에 파이프라인의 마지막 추정기를 제외한 이전의 모든 추정기는 반드시 변환기이어야 한다.
현재 단계에서는 데이터 전처리를 위한 변환기 파이프라인만 구성하여 살펴본다. 이후 머신러닝 모델을 본격적으로 훈련할 때, 예측기를 마지막 추정기로 지정하는 파이프라인을 구성할 것이다.
2.8.2make_pipeline() 함수¶
파이프라인에 포함되는 추정기의 이름이 중요하지 않다면 make_pipeline() 함수를 이용하여
Pipeline 객체를 생성할 수 있다. 이름은 자동으로 지정된다.
위 파이프라인과 동일한 파이프라인 객체를 다음과 같이 생성할 수 있다.
num_pipeline = make_pipeline(SimpleImputer(strategy="median"),
StandardScaler())2.8.3ColumnTransformer 클래스¶
ColumnTransformer 클래스는 특성별로
파이프라인 변환기를 지정하여 특성별 전처리를 실행할 수 있다.
예를 들어, 수치형 특성엔 num_pipeline 변환기를, 범주형 특성엔 OneHotEncoder 변환기를 적용하는
변환기를 다음과 같이 구현할 수 있다.
# 수치형 특성 리스트 지정
num_attribs = ["longitude", "latitude", "housing_median_age", "total_rooms",
"total_bedrooms", "population", "households", "median_income"]
# 범주형 특성 리스트 지정
cat_attribs = ["ocean_proximity"]
# 범주형 특성 변환 파이프라인
cat_pipeline = make_pipeline(
SimpleImputer(strategy="most_frequent"),
OneHotEncoder(handle_unknown="ignore"))
# 전체 특성 변환기
preprocessing = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", cat_pipeline, cat_attribs),
])make_column_selector() 함수
파이프라인에 포함되는 각 변환기를 적용할 특성을 일일이 나열하는 일이 어려울 수 있다.
이때 지정된 자료형을 사용하는 특성들만을 선택해주는 make_column_selector() 함수를
유용하게 활용할 수 있다.
make_column_selector(dtype_include=np.number): 수치형 특성 모두 선택make_column_selector(dtype_include=object): 범주형 특성 모두 선택
따라서 위 preprocessing 변환기를 아래와 같이 정의할 수 있다.
preprocessing = ColumnTransformer([
("num", num_pipeline, make_column_selector(dtype_include=np.number)),
("cat", cat_pipeline, make_column_selector(dtype_include=object)
])2.8.4make_column_transformer() 함수¶
ColumnTransformer 인스턴스에 포함되는 파이프라인의 이름이 중요하지 않다면
make_column_transformer() 함수를 이용할 수 있다.
사용 방식은 make_pipeline() 함수와 유사하다.
예를 들어 앞서의 preprocessing 변환기를 아래와 같이 정의할 수 있다.
preprocessing = make_column_transformer(
(num_pipeline, make_column_selector(dtype_include=np.number)),
(cat_pipeline, make_column_selector(dtype_include=object)),
)2.8.5캘리포니아 데이터셋 변환 파이프라인¶
ColumnTransformer 클래스와 Pipeline 클래스를 이용하여
캘리포니아 주택 가격 데이터의 입력 데이터셋을 한꺼번에 변환하는
변환기를 다음 네 개의 변환기를 이용하여 구현한다.
(1) 비율 변환기
가구당 방 수, 침실 비율, 가구당 평균 가구원수 등 비율을 사용하는 특성을 새로 추가할 때 사용되는 변환기를 다음과 같이 정의한다.
def column_ratio(X):
return X[:, [0]] / X[:, [1]] # 1번 특성에 대한 0번 특성의 비율율
def ratio_name(function_transformer, feature_names_in):
return ["ratio"] # 새로 생성되는 특성값들의 특성명
ratio_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(column_ratio, feature_names_out=ratio_name),
StandardScaler()
)(2) 로그 변환기
데이터 분포가 두터운 꼬리를 갖는 특성을 대상으로 로그 함수를 적용하는 변환기를 지정한다.
로그 변환기를 지정할 때 사용되는 feature_names_out="one-to-one"는 로그 변환되어 생성되는
특성값들의 특성명을 이전 특성명과 동일하게 지정하라는 의미이다.
보다 자세한 의미는 실제로 활용될 때 한 번 더 설명한다.
log_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
FunctionTransformer(np.log, feature_names_out="one-to-one"),
StandardScaler()
)(3) 군집 변환기
구역의 위도와 경도를 이용하여 구역들의 군집 정보를 새로운 특성으로 추가하는 변환기를 지정한다.
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)(4) 기본 수치형 특성 변환기
특별한 변환이 필요 없는 경우에도 기본적으로 결측치 처리와 표준화 스케일링은 적용한다.
default_num_pipeline = make_pipeline(
SimpleImputer(strategy="median"),
StandardScaler()
)종합
앞서 언급된 모든 변환기를 특성별로 적용하는 변환기를
ColumnTransformer 클래스를 이용하여 정의한다.
remainder=default_num_pipeline는
그때까지 언급되지 않은 나머지 특성들을 처리하는 변환기를
키워드 인자로 지정한다.
remainder 매개변수의 키워드 인자로 나머지 특성을 삭제하는 것을 지정하는
drop 이 기본값이며, 그 이외에 passthrough는 나머지 특성은
변환하지 않고 그대로 두어야 함을 의미한다.
아래 코드에서는 주택 중위연령을 가리키는 housing_median_age 특성에는
기본 변환기를 적용한다.
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```아래 코드는 위 변환기를 이용하여 정제와 전처리를 입력 데이터셋의 모든 특성에 대해 특성별로 실행한다.
housing_prepared = preprocessing.fit_transform(housing)변환된 데이터셋의 특성은 총 24개이며 다음과 같다.
비율 변환기 적용: 3개의 새로운 특성 추가.
위도-경도 군집 특성: 10개의 특성으로 변환. 위도와 경도 2개의 특성 대신 10개 특성 새로 추가.
해안 근접도 더미 특성: 5개로 변환. 기존 해안 근접도 특성 제거 후 5개 더미 특성 추가.
나머지 특성: 1개 (중위 주택연령) 변환. 특성수는 그대로 유지됨
preprocessing.get_feature_names_out()
array(['bedrooms__ratio', 'rooms_per_house__ratio',
'people_per_house__ratio', 'log__total_bedrooms',
'log__total_rooms', 'log__population', 'log__households',
'log__median_income', 'geo__Cluster 0 similarity',
'geo__Cluster 1 similarity', 'geo__Cluster 2 similarity',
'geo__Cluster 3 similarity', 'geo__Cluster 4 similarity',
'geo__Cluster 5 similarity', 'geo__Cluster 6 similarity',
'geo__Cluster 7 similarity', 'geo__Cluster 8 similarity',
'geo__Cluster 9 similarity', 'cat__ocean_proximity_<1H OCEAN',
'cat__ocean_proximity_INLAND', 'cat__ocean_proximity_ISLAND',
'cat__ocean_proximity_NEAR BAY', 'cat__ocean_proximity_NEAR OCEAN',
'remainder__housing_median_age'], dtype=object)2.9모델 선택과 훈련¶
preprocessing에 의해 변환되는 데이터프레임은 예측기 모델의 훈련에
바로 사용될 수 있다.
즉, 이제 머신러닝 예측기 모델의 훈련셋으로 바로 사용할 수 있다.
하지만 여기서는 데이터 변환과 모델 훈련을 분리해서 진행하는 대신 변환기와 예측기를 하나의 파이프라인으로 묶어 데이터 변환과 모델 훈련을 동시에 진행하는 방법을 선택해서 소개한다.
preprocessing이 가리키는 변환기와 함께 묶여 하나의 파이프라인으로 구성될 예측기로
사이킷런의 회귀 모델 세 개를 활용한다.
각 모델의 자세한 특징과 상세 설명은 이어지는 장에서 하나씩 소개할 예정이며,
여기서는 모델 선택에 따른 성능과 보다 좋은 모델을 훈련시키는 방법을 자세히 소개한다.
2.9.1모델 훈련과 평가¶
사이킷런의 회귀 모델을 훈련시키고 모델의 훈련 결과를 평가하는 방식은 거의 동일하다. 여기서는 세 종류의 모델을 캘리포니아 주택가격 데이터셋에 대해 훈련시키고 그 결과를 비교한다. 세 모델은 각각 다음 세 클래스의 인스턴스로 생성된다.
LinearRegressionDecisionTreeRegressorRandomForestRegressor
언급된 세 모델 모두 회귀 모델이며,
회귀 모델을 사용하는 이유는 구역별 중위 주택가격인 연속형 데이터를 예측해야 하기 때문이다.
또한 세 모델 각각을 앞서 구현한 preprocessing 변환기와 함께 하나의 파이프라인으로 구성하여,
전처리 과정과 모델 훈련 및 예측을 한꺼번에 실행하는 에측기로 정의해서 활용한다.
선형 회귀 모델:
make_pipeline(preprocessing, LinearRegression())결정트리 회귀 모델:
make_pipeline(preprocessing, DecisionTreeRegressor())랜덤 포레스트 회귀 모델:
make_pipeline(preprocessing, RandomForestRegressor())
회귀 모델의 성능 평가는 일반적으로 RMSE(root-mean-squared error, 평균 제곱근 오차)로 계산된다. RMSE(평균 제곱근 오차)는 예측 오차의 제곱의 평균값에 루트를 쒸운 값이며, 0에 가까울 수록 모델의 예측 성능이 좋다. 훈련된 모델의 RMSE 계산 방법은 모델 훈련 자세히 설명한다.
언급된 세 모델의 훈련 결과는 다음과 같다.
| 모델명 | 훈련셋 RMSE | 모델 훈련 평가 및 특징 |
|---|---|---|
| 선형 회귀 모델 | 약 68,688 | - 성능 수치가 높게(나쁘게) 나옴 - 훈련셋에 대한 성능이 매우 낮은 과소적합 현상 발생 - 보다 좋은 특성을 찾거나 더 강력한 모델을 적용해야 함 |
| 결정트리 회귀 모델 | 0 | - 오차가 0으로 완벽해 보이나, 훈련셋에 심각하게 과대적합됨 - 실전 상황에서 RMSE가 0이 되는 것은 불가능함 - 실제 새로운 데이터(테스트셋)에 대한 예측 오차는 매우 높음 |
| 랜덤 포레스트 회귀 모델 | 약 17,474 | - 선형 회귀 모델보다 RMSE가 훨씬 낮아 성능이 좋음 - 테스트셋에 대한 RMSE가 훈련셋보다 높아 과대적합이 발생하긴 하였음 - 단, 결정트리 모델의 과대적합에 비해서는 그 정도가 훨씬 약함 |
2.9.2교차 검증¶
훈련된 모델의 RMSE를 활용한 모델 평가 보다는 교차 검증cross validation을 활용한 모델의 성능 평가가 보다 객관적이다. 교차 검증은 훈련 과정에 진행되는 모델 성능 평가 기법이다.
사이킷런이 제공하는 k-겹 교차 검증 과정은 다음과 같다.
폴드 생성: 훈련셋을 폴드fold라 불리는 k-개의 부분 집합으로 무작위로 분할
모델 훈련: 총 k 번 훈련
매 훈련마다 하나의 폴드를 선택하여 검증 데이터셋으로 지정
나머지 (k-1) 개의 폴드를 대상으로 훈련
매 훈련이 끝날 때마다 선택된 검증 데이터셋을 이용하여 모델 평가
매번 다른 폴드 활용
최종평가: k-번 훈련 평가 결과의 평균값 활용
아래 그림은 5-겹 교차 검증을 묘사한다.

사이킷런의 cross_val_score() 함수
cross_val_score() 함수는 지정된 모델을 k-겹 교차 검증을 활용하여 훈련과 평가를 동시에 진행한다.
교차검증은 다만 모델 평가용도로만 폴드를 구분하여 훈련할 뿐 훈련된 모델 객체 자체를 반환하지는 않는다.
예를 들어 아래 코드는 결정트리 모델에 대해 교차 검증을 실행한다.
cross_val_score() 함수 호출에 사용된 키워드 인자는 다음과 같다.
scoring="neg_mean_squared_error"옵션훈련중인 모델의 성능을 측정하는 효용함수 지정
모델의 성능 측정값은 높을 수록 좋은 성능으로 평가되기에 회귀 모델의 경우 일반적으로 RMSE의 음숫값을 사용함.
cv=10: 10-겹 교차 검증 진행
cross_val_score() 함수의 반환값은 scoring="neg_mean_squared_error" 옵션으로 인해 음수값이다.
따라서 다시 양수로 만들어서 tree_rmses 변수에 할당하였다.
tree_reg = make_pipeline(preprocessing, DecisionTreeRegressor())
tree_rmses = - cross_val_score(tree_reg, housing, housing_labels,
scoring="neg_root_mean_squared_error",
cv=10)아래 표는 10-겹 교차 검증을 통해 얻은 세 가지 모델의 성능(평균 RMSE)을 요약한다.
| 모델명 | 교차 검증 평균 RMSE | 성능 평가 요약 |
|---|---|---|
| 선형 회귀 모델 | 약 7만 | 세 모델 중 가장 높은(나쁜) 오차를 보임 |
| 결정트리 회귀 모델 | 약 6만7천 | 선형 회귀 모델보다 약간 낫지만 여전히 꽤 높은 오차를 보임 |
| 랜덤 포레스트 회귀 모델 | 약 4만7천 | 훈련이 다소 오래 걸리지만 세 모델 중 성능이 가장 뛰어남 |
2.10모델 미세 조정¶
지금까지 살펴본 모델 중에서 랜덤 포레스트 회귀 모델의 성능이 가장 좋았다. 이렇게 가능성이 높은 모델을 찾은 다음엔, 모델의 세부 설정(하이퍼파라미터)을 조정해 성능을 최대한 끌어올릴 수 있다.
먼저 하이퍼파라미터의 개념을 간단히 짚어본 뒤, 선택한 모델의 하이퍼파라미터를 미세 조정fine-tuning하는 데 가장 일반적으로 사용되는 다음 두 가지 기법을 소개한다.
그리드 탐색
랜덤 탐색
2.10.1모델 하이퍼파라미터¶
머신러닝 모델의 하이퍼파라미터hyperparameter는 모델 객체를 생성(지정)할 때 사용자가 직접 지정하는 모델 설정 인자를 가리킨다. 지정된 하이퍼파라미터는 모델의 구조나 훈련 방식을 제어하는 역할을 한다.
지금까지 사용한 세 모델이 사용하는 하이퍼파라미터는 다음과 같다.
SimpleImputer의 결측치 대체 전략(strategy='median'등)결정 트리의 최대 깊이(
max_depth)랜덤 포레스트의 앙상블 트리 개수(
n_estimators)
이외에 다수의 하이퍼파라미터를 이용해서 각 모델을 설정할 수 있는데, 사이킷런이 제공하는 모든 모델은 합리적인 기본 하이퍼파라미터로 초기화되어 있다.
예를 들어, RandomForestRegressor() 방식으로 정의한
랜덤 포레스트 회귀 모델에 사용된 기본 하이퍼파라미터 조합은 다음과 같다.
RandomForestRegressor(
n_estimators=100,
*,
criterion='squared_error',
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.0,
max_features=1.0,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
bootstrap=True,
oob_score=False,
n_jobs=None,
random_state=None,
verbose=0,
warm_start=False,
ccp_alpha=0.0,
max_samples=None,
monotonic_cst=None)사이킷런이 제공하는 변환기 또는 모델의 하이퍼파라미터는 일반적으로 객체를 생성할 때,
즉 클래스 생성자인 __init__() 메서드가 호출될 때의 인자로 전달된다.
앞서 정의한 ClusterSimilarity 클래스의 생성자의 헤더가 다음과 같다.
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따라서 ClusterSimilarity() 방식으로 군집 분류 모델을 정의하면
다음과 같이 기본 하이퍼파라미터가 지정된 모델 선언으로 처리된다.
ClusterSimilarity(
n_clusters=10,
gamma=1.0,
random_state=None)지금까지 사용된 모델은 모두 기본 키워드 인자를 사용하였다. 곧이어 소개할 그리드 탐색과 랜덤 탐색 기법을 적용하여 주어진 데이터셋에 대한 보다 적절한 하이퍼파라미터 조합을 찾는 방법을 설명한다.
2.10.2모델 파라미터¶
프로그래밍 분야에서 파라미터parameter는 여러 의미로 사용된다. 앞서 설명한 하이퍼파라미터 이외에, 예를 들어 함수의 인자를 받는 기능을 수행하는 함수 파라미터가 대표적이다.
반면에 머신러닝 모델과 관련해서는 또다른 종류의 파라미터가 사용된다. 바로 변환기 또는 예측기가 데이터를 변환하거나 훈련하는 과정에서 데이터로부터 직접 학습하여 내부적으로 가지게 되는 값들이다.
변환기가 학습하는 파라미터 예제
StandardScaler: 특성별 평균값과 표준 편차SimpleImputer: 특성별 중앙값, 평균값, 최빈값OneHotEncoder: 범주형 변수의 고유한 범주(카테고리) 목록 (‘1H OCEAN’, ‘INLAND’ 등등)ClusterSimilarity: 군집 중심의 위도와 경도
예측기가 학습하는 파라미터 예제
선형 회귀 모델: 특성에 곱해지는 기울기와 절편(편향)
결정 트리: 노드 분할에 사용되는 특성과 분할 임계값
랜덤 포레스트: 개별 결정 트리들의 파라미터
사이킷런이 제공하는 변환기와 예측기는 파라미터를 fit() 메서드가 실행되는 동안 데이터로부터 학습되어
객체 자체에 속성으로 저장된다.
속성 이름은 mean_, coef_, intercept_, cluster_centers_ 등 밑줄(언더스코어)로 끝난다.
2.10.3그리드 탐색¶
그리드 탐색grid search은 지정된 값들의 모든 조합에 대해 교차 검증을 진행하여 최적의 모델을 위한 하이퍼파라미터 조합을 찾는 기법이다.
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)그리드 탐색 훈련 횟수
지정된 모델의 최적의 하이퍼파라미터를 찾기 위한 그리드 탐색 과정동안 진행되는 모델의 훈련 횟수는 다음과 같이 계산된다.
하이퍼파라미터 조합 탐색 경우의 수
총 (3x3 + 2x3 = 15) 가지의 하이퍼파라이터의 조합 탐색
(군집수 3 가지) * (최대특성수 3 가지) + (군집수 2 가지) * (최대특성수 3 가지)
모델 훈련 횟수
매 하이퍼파라미터 조합에 대해 3-겹 교차 검증(
cv=3) 진행따라서 모델 훈련을 총 45(=15x3)번 진행
그리드 탐색 결과
학습이 완료된 그리드 탐색 객체는 찾아낸 최적의 하이퍼파라미터와 해당 하이퍼파라미터로 설정된 최적의 모델, 그리고 훈련 과정중에 얻어진 모델의 성능을 모두 객체 내부에 아래 언급된 속성으로 저장한다.
grid_search.best_params_속성그리드 탐색을 통해 찾아낸 최적의 하이퍼파라미터 조합 저장
grid_search.best_estimator_속성그리드 탐색을 통해 찾아낸 최적의 모델 저장
grid_search.cv_results_속성최고 성능 모델의 성능(RMSE 등) 저장
2.10.4랜덤 탐색¶
그리드 탐색은 적은 수의 하이퍼파라미터 조합을 실험해볼 때만 유용하다. 반면에 하이퍼파라미터 탐색 공간이 커서 조합 경우의 수가 많아지면 훈련 시간이 너무 올래 걸려 활용하기 어렵다. 이런 경우 랜덤 탐색randomized search이 보다 효율적으로 최적의 하이퍼파라미터 조합을 찾아낼 수 있다.
RandomizedSearchCV 클래스
아래 코드는 랜덤 포레스트 모델을 대상으로 랜덤 탐색을 실행한다.
# 하이퍼파라미터 탐색 공간 지정
param_distribs = {
'preprocessing__geo__n_clusters': randint(low=3, high=50),
'random_forest__max_features': randint(low=2, high=20)
}
# 10개의 하이퍼파라미터 무작위 선택, 3-겹 교차 검증 활용
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)랜덤 탐색 훈련 횟수
지정된 모델의 최적의 하이퍼파라미터를 찾기 위한 랜덤 탐색 과정동안 진행되는 모델의 훈련 횟수는 다음과 같이 계산된다.
하이퍼파라미터 조합
언급된 두 하이퍼파라미터 각각에 대해 지정된 범위 내에서 임의의 값 지정
모델 훈련 횟수
총 10번(
n_iter=10) 무작위 선정 진행매 하이퍼파라미터 조합에 대해 3-겹 교차 검증(
cv=3) 진행따라 모델 훈련을 총 30(=10x30)번 진행
훈련된 랜덤 탐색 객체 속성
훈련이 완료된 랜덤 탐색 또한 동일한 방식으로 최적의 모델과 최적의 하이퍼파라미터 조합, 훈련 과정동안의 평가 성능을 객체 속성으로 저장한다.
2.11최적 모델 활용 및 평가¶
모델 미세 조정을 통해 구해진 최적의 모델을 평가, 활용, 저장하는 방법을 간단한 예제를 이용하여 소개한다.
2.11.1테스트셋 활용¶
애초에 주언진 캘리포니아 주택가격 데이터셋을 훈련셋과 테스트셋으로 8대 2의 비율로 계층 샘플링 방식으로 분리하여 지금까지 훈련셋만 활용하였다. 반면에 테스트셋은 지금까지의 훈련 과정에 전혀 노출되지 않았다.
이제 더 이상의 훈련을 진행할 필요가 없을 정도로 훈련된 모델의 성능에 만족한다고 가정하자. 다음 할 일은 모델을 실전에 투입했을 때의 모델 성능 예측하기이며, 여기에 테스트셋을 활용한다.
아래 코드는 랜덤 탐색으로 확인된 최적의 모델의 테스트셋에 대한 RMSE를 계산한다. 테스트셋 또한 입력 데이터셋과 타깃셋으로 먼저 구분한 다음에 모델 예측에 활용됨에 주의한다.
# 최종 모델
final_model = rnd_search.best_estimator_
# 테스트셋 활용 평가: 입력 데이터셋과 타깃셋 구분
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()
# RMSE 계산
final_predictions = final_model.predict(X_test)
final_rmse = mean_squared_error(y_test, final_predictions, squared=False)2.11.2모델 기타 활용법¶
머신러닝 모델은 단순히 예측을 위해서만 사용되지는 않는다. 모델 종류에 따라 예측값 계산과 함께 다른 기능을 제공하기도 한다.
예를 들어, 훈련이 잘 진행된 랜덤 포레스트 모델은 입력 데이터셋의 각 특성이 모델이 예측값을 계산할 때 얼마나 많이 기여하는가를 특성 중요도라는 기준으로 훈련 과정중에 평가한다.
캘리포니아 주택가격 예측을 위해 최적화된 랜덤 포레스트 모델은
feature_importances_ 속성에 특성별 중요도를 저장해 두며,
확인 결과 ‘로그 변환된 중위소득’(log__median_income) 특성의 중요도가 가장 높다. 그 다음으로는 해안 근접도 특성 중에서 특히 ‘내륙’(INLAND) 특성의 중요도가 높다.
특성 중요도는 전처리 변환기와 아무 상관이 없으며, 랜덤 포레스트 모델 등 일부 모델에서만 훈련중에 함께 저장된다.
아래 코드는 final_model에서 랜덤 포레스트 모델의 이름이 "random_forest"로 지정되었기에 해당 이름을 이용하여 인덱싱 한 다음에 특성 중요도를 확인한다.
final_model["random_forest"].feature_importances_2.11.3모델 저장¶
최적의 모델을 훈련시키는 과정이 매우 길 수 있다. 따라서 한 번 훈련된 좋은 모델은 파일로 저장해 놓아야 한다. 그러면 모델을 활용하고자 할 때 저장된 파일을 모델로 불러와서 훈련 없이 바로 활용할 수 있다. 또한 새롭게 훈련시킨 모델이 적절하지 않다고 판단되어 이전 버전의 모델로 되돌려야 하는 상황이 발생할 수도 있기에 잘 훈련된 모델의 저장은 필수적이다.
모델의 저장과 불러오기는 각각 joblib 모듈의
dump() 함수와 load() 함수를 활용한다.
저장하기
import joblib joblib.dump(final_model, "my_california_housing_model.pkl")불러오기와 활용
final_model_reloaded = joblib.load("my_california_housing_model.pkl") final_model_reloaded.predict(X_test)
2.12연습문제¶
(1) (코드 워크아웃) 머신러닝 프로젝트 내용을 학습하라.
(2) total_bedrooms에 포함된 결측치를 다르게 처리할 경우 모델의 성능에 차이가 발생하는지 여부룰 확인하라.
가. 결측치를 포함하는 구역 샘플 삭제
housing.dropna(subset=["total_bedrooms"], inplace=True)나. 결측치가 포함된 특성 자체 제거
housing.drop("total_bedrooms", axis=1, inplace=True)(3) 해안 근접도 특성에 대해 원-핫 인코딩 대신 OrdinalEncoder 변환기 객체를 적용할 경우 훈련된 모델의 성능이 어떻게 달라지며, 성능의 차이에 대한 근거를 추정하라.
housing_cat = housing[["ocean_proximity"]]
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)(4) 아래 코드는 preprocessing 변환기 파이프라인과 랜덤 포레스트 회귀 모델을 하나로 묶은 모델을 지정한다.
full_pipeline = Pipeline([
("preprocessing", preprocessing),
("random_forest", RandomForestRegressor(random_state=42)),
])전처리 변환기와 예측기 모델을 하나로 묶은 파이프라인 머신러닝 모델의 장점은 다음과 같다.
코드 간소화: 전처리와 훈련 과정을 하나의 객체로 묶어 코드가 깔끔해지고 관리가 쉬워짐.
데이터 누설 방지: 교차 검증 시 각 폴드에 맞게 전처리가 독립적으로 수행되어 검증 데이터가 훈련에 반영되는 것을 막음
모델 미세 조정 용이: 예측기뿐만 아니라 변환기의 특성까지 한 번에 고려하며 교차 검증 진행 가능 가능
반면에 몇 가지 단점을 갖는다.
중간 전처리 결과(데이터)를 직접 눈으로 확인하기 어려움
가장 큰 단점임.
원본 데이터가 파이프라인의
fit_predict를 통과하면 바로 최종 예측값이 나와버리기 때문에, 중간에 원-핫 인코딩이 잘 되었는지, 스케일링은 정상적으로 되었는지 중간 데이터프레임 형태를 디버깅하거나 시각화하기가 까다로움이를 보려면 결국 파이프라인을 쪼개서
transform만 따로 실행해봐야 함.
하이퍼파라미터 튜닝 시 이름이 길고 복잡해짐
GridSearchCV등에서 모델 미세 조정할 때, 파라미터 이름을 지정하는 방식이 매우 길어짐.(예: 모델만 있을 때)
max_features(파이프라인 결합 시)
random_forest__max_features,preprocessing__geo__n_clusters같이단계이름__파라미터이름형태의 이중 밑줄(__) 문법을 사용해야 해서 코드가 길어지고 오타가 나기 쉬움.
매우 큰 데이터셋에서 튜닝 시 반복 연산(시간 효율성 저하)
교차 검증이나 그리드 서치를 돌릴 때, 파이프라인 전체를 통째로 넣으면 모델의 파라미터만 바꾸면서 실험할 때도 무거운 앞단 전처리(예: KMeans 클러스터링, 특성 추가 등)를 반복해서 매번 다시 계산하게 될 수 있음.
단, 사이킷런 파이프라인의
memory인자를 사용하여 중간 변환 결과를 캐싱(저장)하면 이 문제는 해결할 수 있음.
예측 결과 분석(변수 중요도 등) 추출의 번거로움
RandomForest나XGBoost같은 트리를 학습시킨 후 “어떤 특성이 가장 중요했지?”(feature_importances_)를 물어보려면, 모델을 파이프라인 껍질 안에서 꺼내와야 함.더불어, 전처리 과정에서 새로 생겨난 컬럼 이름들(더미 변수 등)까지 매칭시켜주려면
pipeline.named_steps['전처리'].get_feature_names_out()같은 복잡한 코드를 추가로 작성해야 함.
이제 preprocessing과 예측기를 분리하여 훈련을 진행하는 코드를 작성하여,
앞서 언급한 full_pipeline의 장단점을 확인하라.