02_End-to-End_Machine_Learning_Project(final).ipynb

책: O'REILLY의 Hands-On Machine Learning with Scikit-Learn & TensorFlow

URL: http://shop.oreilly.com/product/0636920052289.do

본문에 사용되는 사진들은 모두 위의 책에서 발췌

소스코드: https://github.com/ageron/handson-ml

틀린 내용이 있으면 언제든지 댓글 달아주세요! (오역은 어느정도 이해해주시길)


chapter2. End-to-End Machine Learning Project

 이번 장에서는 머신러닝의 프로젝트의 처음에서 끝까지 살펴보고자 한다. 주요 단계로는 아래와 같다.

  1. Look at the big picture.
  2. Get the data.
  3. Discover and visualize the data to gain insights.
  4. Prepare the data for Machine Learning algorithms.
  5. Select a model and train in.
  6. Fine-tune your model.
  7. Present your solution.
  8. Launch, monitor, and maintain your system.

Working with Real Data

 머신러닝을 공부하는데 있어 가장 좋은 방법은, 인공적으로 만든 데이터 셋이 아닌 실제 데이터를 가지고 실험하는 것이다. 우리가 사용할 수 있는 데이터는 상당히 많다. 

 이번 2장에서는 캘리포니아 주택 가격에 대한 데이터를 사용할 것이다. 해당 데이터는 1990년 캘리포니아 인구조사로부터 기반한다. (좀 오래 된 데이터이긴 하다..) 하지만 저자는 해당 데이터가 학습을 위해 많은 양을 포함하고 있고, feature에 대해 새로 추가하고 삭제하고 하는 기법을 사용한다고는 한다.

 

1. Look at the Big Picture

 Welcome to Machine Learning Housing Corporation!

 첫 번째 과제로는 캘리포니아 인구조사 데이터를 가지고 캘리포니아의 주택 가격의 모델을 만드는 것이다.

데이터로는 인구, 수입의 중간 값(median), 주택 가격의 중간 값(median), 그리고 캘리포니아의 지구(구역) 등이 측정 항목으로 포함되어 있다.

여기에서 지구(구역)은 'districts'로 약 600명에서 3,000명의 인구로 구성된 구역이다.

 만들어야하는 모델은 주어진 모든 측정 항목으로부터, 다른 districts의 주택 가격의 중간 값(median)을 예측할 수 있어야 된다.


1) Frame the Problem

 상사에게(지금 현재 Machine Learning Housing Corporation에 들어

와있다...) 가장 먼저 해야할 질문으로는 사업 목적에 대해 정확히 파악하는 것이다. 단순 모델을 만드는 것이 목적은 아닐 것이다. 어떻게 이 모델을 사용함으로써 이익을 추구할 수 있을까? 이 것은 상당히 중요한데 그 이유로는, 어떻게 문제화 할 것이며, 무슨 알고리즘을 선택할 것인지, 또한 모델을 평가할 때 무슨 성능 측정을 할 것인지 등에 대해 결정 할 수 있기 때문이다.

 아래의 그림(machine learning pipeline)과 같이, 주택 가격을 예측하여, 투자 할 가치가 있는지에 대해 평가 할 것이다.

 Pipelines

데이터 처리 컴포넌트의 시퀀스를 data pipeline 이라고 부른다. pipeline은 머신러닝 시스템에서 매우 일반적으로 사용되는데, 많은 데이터를 조작하고, 변환해야 되기 때문이다.

 컴포넌트는 일반적으로 비동기(asynchronously)로 실행된다. 각각의 컴포넌트들은 많은 양의 데이터를 가져와서 처리하고, 다른 데이터 스토어에 결과 값을 내보낸다. 그 후, pipeline의 다음 컴포넌트에서 이 데이터를 가져와서, 자신의 output에 내보내는 식이다. 

 각각의 컴포넌트는 독립적으로 수행되는데, 컴포넌트간의 인터페이스는 단순한 데이터 스토어다. 이를 통해, 시스템을 매우 쉽게 파악할 수 있으며, 여러 팀이 서로 다른 컴포넌트에 집중할 수 있게 된다. 

 그 다음 상사에게 물어 볼 질문으로는, 현재 솔루션(있을 시에)에 대해 묻는 것이다. 이는 문제를 해결함에 있어 통찰력을 줄 수 있다. 

 상사는 주택 가격을 측정함에 있어, 전문가에 의해 수동적으로 측정 되고 있다고 한다. 구역에 대한 최신 정보를 수집하고 있으며, 만약 주택 가격의 중간 값을 얻을 수 없을 경우, 복잡한 규칙을 사용하여 측정하고 있다고 한다. 이러한 방법은, 시간과 비용이 많이 소모되며, 측정 값도 좋지 않다. 

  이제 시스템을 설계해보자! 첫 째로, 틀을 만들어야 되는데 이것은 지도/비지도/강화학습 중 어떤 것이 적합할까? 또한, 분류, 회귀, 아니면 다른 것일까? 또한 batch 학습이 적합할까, 아니면 온라인 학습이 적합할까? 진행하기 전에 한번 생각해보는 시간을 가져보자.

 이것은 전형적인 지도학습의 문제이다. 또한, 이것은 주택 가격에 대한 측정이므로 회귀 문제가 적합하며, 더욱 정확하게는 multivariate 회귀 문제라고 할 수 있다. 그 이유는 다수의 feature를 사용하기 때문이다. 마지막으로 이 데이터는 연속적인 데이터가 들어오는 시스템이 아니기 때문에, 데이터가 급격하게 변하는 것을 조정할 필요가 없다. 그렇기 때문에 해당 시스템은 batch 학습이 적합하다.


2) Select a Performance Measure

 다음 단계로는 성능 측정(performance measure)을 선택하는 것이다. 일반적인 회귀 문제의 성능 측정으로는 평균 제곱근 편차(Root Mean Square Error, RMSE) 가 있다.

 

Notations

머신러닝에서 매우 일반적으로 사용되는 notations

  • m: 데이터(instance)의 수
  • x^(i): 데이터 셋에서 i번째의 모든 feature 값들의 벡터 (단, y^(i)값인 라벨은 제외) 
     예를 들어, 위도:33.91, 경도: -118.29, 중간 수입: 500만원 일 때, 주택 가격의 중간 값이 5억이라고 가정해보자.
     이 때, x^(1) = [33.91, -118.29, 500]^T(전치해서 벡터를 뜻함), y^(1) = 1 이 된다.
  • X: 데이터 셋의 모든 feature의 행렬
     x^1, X^2..... 들에 대해 행렬로 만든 것
  • h: 예측 함수, hypothesis라고 부름. 
     예를 들어, x^(i) 데이터의 feature 벡터가 주어졌을 때, 결과 값으로 예측 값인 y-hat^(i) =  h(x^(i))로 표현
  • RMSE(X,h): 가설 h를 이용하여 데이터를 측정하기 위한 cost 함수
    별거아닌데 작성하기가 어렵다..

 일반적으로 RMSE가 회귀 작업을 위한 성능 측정에 대해 선호하는 방법이지만, 일부 상황에 대해서는 다른 방법이 좋을 수 있다. 예를 들어, 많은 이상치 districts가 있다고 가정해보자. 이러한 경우에는, 평균 절대값 오차(Mean Absolute Error)가 이용 될 수 있다.

 RMSE와 MAE는 두 벡터 사이의 거리를 측정하는 방법이다. 

 

3) Check the Assumptions

 가설을 확인할 것. 이는 심각한 문제를 초기에 해결 할 수 있음. 예를 들어, 예측한 가격에 대해 실제 값이 아닌 "싸다, 보통이다, 비싸다"라고 카테고리화 시켜야 되는 문제였다면? 이는 회귀 작업이 아닌 분류 작업으로 바꿔야 될 필요가 있다. 다행히도, downstream system 팀원들에게 물어 본 결과, 카테고리가 아닌 실제 값이라고 한다! 다음 단계로 넘어가자!


2. Get the Data

 이 책은 jupyter notebook으로 실습을 한다. 본 포스팅에는 jupyter 설치 방법은 제외한다.


1) Download the Data

 데이터 fetch에 대해 소개한다.

데이터는 저장소로부터 불러오게끔 되어있으며, 사용하고 있는 머신에 다양성을 두기 위해 os를 import 하여 처리하고 있다. 

로컬 내 디렉토리가 없을 경우, 새로 생성된다. 다운 받는 파일이 tar 형식으로 압축되어 있기 때문에, tar파일에 대한 압축 해제 코드도 포함되어 있다.


 다음으로는 pandas를 통해, 데이터를 불러 올 수 있다. 함수를 정의해 놓음으로써, 편리하게 재 사용 할 수 있다.

위에서 tar파일을 압축 해제하면 csv파일이 생성 된다. 이 csv파일을 불러오는 것이다.

파일을 로컬 내 저장하고, 압축을 푸는 함수는 fetch_housing_data() 를 통해 호출 할 수 있고, 데이터를 pandas로 불러오는 함수는 load_housing_data() 를 통해 진행 할 수 있다.


2) Take a Quick Look at the Data Structure 

 jupyter notebook을 이용하여, 불러 온 화면은 아래와 같다.

총 20,640개의 데이터가 있으며, 10개의 컬럼, 즉 feature를 가지고 있다. 

주목해야 할 점은, ocean_proximity feature를 제외한 다른 feature들은 float64의 데이터 형식으로 되어있지만, ocean_proximity의 경우 string값으로 되어 있다는 점이다. 또한, total_bedrooms의 경우 다른 feature들은 20,640의 데이터를 가지고 있는 반면, 20,433개로 207개의 데이터가 손실이 있다. 이는 값이 들어 있지 않을 때, 처리되지 않기 때문이다. describe() 함수를 호출하면, 각각의 feature 들에 대해 개수나, 평균, 표준편차 등을 확인 할 수 있다.

 위의 ocean_proximity의 경우 string으로 되어 있다고 하였다. 이는 무엇을 의미하는 것일까? 

이것은 아마 categorical 속성이라는 것을 알 수 있다. (연속된 데이터로 이루어진 것이 아니기 때문에 regression으로 생각하지는 않을 것이다)

value_counts() 함수를 사용하여 ocean_proximity의 값들을 확인해 볼 수 있다. 5개의 값으로 구성되어 있음을 알 수 있다.

 데이터의 형태를 빠르게 확인해 볼 수 있는 히스토그램을 그려보자. 

 위 히스토그램에서 눈여겨볼 만한 사항으로는 median_income부분이 미국 달러로 표현되지 않았다는 것이다. 

또한 housing_median_age와 median_house_value 모두 상한 선을 가지고 있다는 것이다. 

마지막으로는 꼬리가 길다는 것이다. 이것은 몇몇 머신러닝 알고리즘에 있어 패턴을 인식하는 데 있어 어려움을 줄 수 있다.

추후 이러한 속성들을 종 모양의 분포(정규분포겠쥬?)를 가질 수 있도록 변형을 할 것이다.


 3) Create a Test Set 

 테스트 셋을 만드는 것은 이론적으로 매우 간단하다. 단순히 몇 개의 instance들을 랜덤으로 추출하면 된다. 일반적으로 테스트 셋은 전체 셋의 20%정도를 추출한다.

위의 방법은 잘 동작하지만, 문제가 있다. 만약 프로그램을 다시 돌린다면, 다른 테스트 셋이 생성된다.. 

이러한 문제를 해결하기 위한 방법 중 하나는 처음 돌렸을 때의 데이터를 저장하고, 이를 다시 불러오는 방법이다.

다른 방법으로는 seed 값을 설정해주는 것이다. 즉 np.ramdom.permutation을 호출하기 전에 seed값을 설정함으로써, 항상 같은 shuffled된 값들을 생성하는 방법이다.

 

 그러나 위의 두가지 방법 모두 새로운 데이터셋을 업데이트 하고 fetch 할 때 문제가 발생한다. 

이러한 문제를 해결하기 위한 방법으로는 각각의 인스턴스 식별자(identifier)를 사용하여 해당 인스턴스가 테스트 셋에 포함되어야 하는지에 대한 여부를 결정하는 것이다. 예를 들어, 각 인스턴스 식별자의 hash값을 계산한 후, hash의 마지막 값만 유지시킨다. 그 다음 51과 같거나 작을 경우 (256의 20%) 테스트 셋에 넣는다. 이러한 방식을 사용하면 데이터 셋을 새로 고치더라도 일관되게 유지 된다. 새로운 테스트 셋에는 새로운 인스턴스의 20%가 포함되겠지만, 이전 트레이닝에 사용된 셋의 어떠한 인스턴스들도 포함되지 않을 것이다.

 위의 방식으로 진행하기에는 아쉽게도 housing dataset에는 식별할 수 있는 unique한 값이 없다. 그렇기 때문에 간단한 방법으로 row index 번호를 unique한 식별자로 사용하고자 한다.

 아래의 그림을 보면 맨 왼쪽 컬럼에 index 열이 추가되었음을 확인할 수 있다. 이는 다른 행과 겹치지 않기 때문에 unique하다.

 train data와 test data로 구분하는 것은 아래와 같다.


  파이썬에서 머신러닝에 사용하는 라이브러리 중 가장 유명한 scikit-learn에도 위의 데이터를 나누는 함수를 제공한다.

가장 간단한 것으로는 train_test_split이 있다. 이것은 위의 split_train_test와 매우 유사하다. 

짧기 때문에 본문에 표시하고자 한다.

1
2
3
4
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

#https://colorscripter.com/
cs

끝이다..... 뭐 처음부터 이런방식대로 사용하는건 편리하겠지만, 약간의 이해를 하기 위해서 훑어봤다라고 생각하면 좋지 않을까싶다. 그런데 이러한 라이브러리를 사용함에 있어, 필요한 부분은 버전업이 되면서 함수들이 달라질 수 있으므로 버전과 함수명을 체크하는 것이다. (예를 들어 scikit-learn의 17버전에는 train_test_split이라는 함수를 model_selection 부분에서 찾을 수 없었지만, 19버전에서는 사용할 수 있었다.)

 전문가들이 중간 주택 가격을 예측하기 위해서는 중간 소득이 중요하다 가정해보자. 중간 소득 히스토그램을 확인해보자.

 대부분의 값들이 2에서부터 5까지 밀집되어있다. 또한 몇몇은 6을 초과하고 있다. 각 데이터 집합에 편향되지 않은 인스턴스들을 갖는 것이 중요하다.


 아래의 그림은 중간 소득에 1.5로 나눈 값을 반올림 처리하고(소득 카테고리를 제한하기 위해), 5보다 큰 값에 대해서는 5로 합치는 작업을 수행한다.

 

 다음으로는 소득 카테고리 기반으로 계층화(stratified)된 샘플링을 수행할 준비가 됐다. Scikit-learn의 StratifiedShuffleSplit 클래스를 사용할 수 있다.

 

 이제 원 상태로 돌아가기 위해 income_cat 속성을 삭제하여야 한다.



3. Discover and Visualize the Data to Gain Insights

 지금까지는 전반적인 데이터의 구조와 테스트셋의 구성에 대해 살펴보았다. 이제 우리의 목표는 조금 더 깊게 살펴보는 것이다.


1) Visualizing Geographical Data

 우리의 housing data는 경도와 위도를 가지고 있기 때문에, scatter 그래프는 좋은 아이디어로 사용 될 수 있다.

 좌측의 그래프의 경우 구분이 잘 되지 않아, 오른쪽에 alpha 값을 주었더니 시각적으로 보기 편하다. 밀집되어 있는 부분은 미국의 LA나 San Diego 뭐 이런 데 근처라고 한다. (한국의 지도도 이렇게 나오면 어딘지 잘 모르겠는데, 미국은 .. 그냥 그러려니)


 이제 주택 가격에 대해 살펴보고자 한다. 

 cmap에 미리 정의된 것들은 아래 보이는 것처럼 상당히 많다.
(가끔 이름이 생각이 안날 때, 일부러 틀리게 적고 확인하는 꼼수를... )

jet는 파랑(낮은 값)색에서 빨강(높은 값)의 범위를 가지는 color map이다.

 위의 이미지에서 볼 수 있듯이, 주택 가격은 인구밀도와, 지역과 많은 관련성(예를 들면, ocean과의 밀접)이 있다는 것을 확인할 수 있다. 이것은 클러스터링 알고리즘을 사용하여 주요 클러스터를 감지하고, 클러스터 센터의 근접성을 측정하는 새로운 feature를 추가하는 데 있어 유용하게 적용 될 수 있다.


2) Looking for Correlations

 dataset이 매우 크지 않기 때문에, corr() 함수를 사용하여 모든 속성들의 pair 사이에 표쥰 상관 계수를 쉽게 계산할 수 있다.

그리고 각 feature들이 얼마나 주택 중간 가격(median house value)과의 상관 관계가 있는지 확인할 수 있다.


 상관 계수의 범위는 -1에서부터 1까지이다. 1과 가까울 수록 강한 양의 상관관계를 표현하며, -1과 가까울수록 강한 음의 상관관계를 표현한다. 0에 가까울수록 두 데이터간의 상관관계는 적다. 아래 그림을 보면, 이해하기 쉽다.


 속성들 간의 상관 관계를 체크하기 위한 다른 방법으로는 pandas 의 scatter_matrix 함수를 사용하는 것이다. 

모든 9개의 feature들은 각각에 대해 상관 분석을 하기 때문에, 많은 양의 그래프가 출력된다. 그렇기 때문에 위의 속성 중 상위 4개에 대해서만 표현하기로 하자.

 대각선에 있는 히스토그램 그래프들을 보자. 만약 pandas가 각각의 변수를 그 자체로 plotted 하게 되면, 직선으로 가득 차게되어 유용하지 않을 것이다. 그렇기 때문에, pandas는 각각의 속성에 대해 히스토그램으로 표현하고 있다.

위에서 주택가격과 상관 관계가 가장 높았던 것은 중간 소득(median income)이었다. 이는 1행 2열의 산점도인데 좀 더 살펴보자.

 아래의 산점도를 살펴보면 두 가지를 확인해 볼 수 있다.

첫 째로, 상관 관계가 상당히 강하다. 점들이 분산되어 있지 않고, 양의 상관 관계임을 확인할 수 있다.

둘 째로, $500,000 이상의 값들은 $500,000으로 맞춰진다. 그러나 이 산점도는 $450,000, $350,000, $280,000 등에서도 수평선을 확인할 수 있다. 알고리즘이 이러한 데이터의 이상점(quirks)을 학습하고 재현하는 것을 보호하기 위해, 해당 districts를 제거할 수 있다. 


3) Experimenting with Attribute Combinations

 머신러닝 알고리즘에 대한 데이터를 실제로 진행할 때, 다양한 속성들에 대해 조합을 시도해 볼 수 있다.

예를 들어, 총 districts에 있는 방의 총 개수는, 실제로 얼마나 많은 가구들이 존재하는지 모른다면 불필요한 정보일 수 있다.

실제로 원하는 것은 가구 당 방의 갯수이다.

 마찬가지로, 총 침실 수 자체만으로는 유용한 정보를 도출할 수 없다. 이 때 필요한 것은, 방의 개수와 비교하는 것이다. 

그리고 가구당 인구 또한 흥미로운 조합의 속성일 수 있다. 다음과 같이 새로운 속성을 만들어보자.


 나쁘지 않았다. 새롭게 추가된 방 개수당 화장실 수 속성은 총 방의 개수나 침술 수보다 더 관련이 높음을 알 수 있다. 

해석해보자면 침실의 낮은 비율이 집 값이 비싼거와 연관이 있고, 가구당 방의 개수는 districts의 총 방의 개수보다 더 관련이 있다. 

4. Prepare the Data for Machine Learning Algorithms

 이제 머신러닝 알고리즘을 위해 데이터를 준비하는 시간이다. 

처음으로는 training set에 대해 깨끗한 상태로 되돌려야된다.

1
2
3
4
5
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
 
#https://colorscripter.com
 
cs

1) Data Cleaning

 대부분의 머신러닝 알고리즘은 손실된 feature값에 대해 잘 작동하지 않는다. 그렇기 때문에 이를 처리하기 위한 몇 가지 기능을 만들어야된다. 우선 2.2)에서 언급했던 total_bedrooms을 떠올려보자. 분명 20,640에서 207개가 손실된 20,433개만 가지고 있었다. 이를 수정해보자. 이를 위해 3가지 옵션이 있다.

  • Get rid of the corresponding districts.
  • Get rid of the whole attribute.
  • Set the values to some value (zero, the mean, the median, etc).

 DataFrame's의 dropna(), drop(), 그리고 fillna() 함수를 사용하면 쉽게 해결할 수 있다.

이름에서 알 수 있듯이, dropna는 nan 값들이 있는 데이터를 삭제 하는 방법

drop은 전체 속성의 값, 즉 total_bedrooms 열을 삭제, 마지막으로 fillna는 nan값들을 특정 선택하는 데이터로 채우는 것이다.

1
2
3
4
5
6
7
8
#option 1
housing.dropna(subset = ["total_bedrooms"])
#option 2
housing.drop("total_bedrooms", axis=1)    #여기에서 axis=1은 열을 뜻한다.
#option 3
housing["total_bedrooms"].fillna(median, inplace=True)    #inplace=True를 해줘야 데이터가 갱신된다.
 
#https://colorscripter.com/
cs

 

 만약 option 3을 선택한다면, training set의 중간 값을 계산해줘야 되며, training set의 빈 칸에 중간 값으로 채워줘야 된다.

Scikit-learn에서는 누락 된 값을 처리 할 수 있는 Imputer라는 편리한 클래스를 제공한다. 

먼저 Imputer 인스턴스를 생성하고, 각 속성의 누락 된 값을 해당 속성의 중앙값으로 대체하도록 지정해주면 된다.

1
2
3
4
from sklearn.preprocessing import Imputer 
imputer = Imputer(strategy ="median")
 
#https://colorscripter.com/
cs

 2.2)에서 언급했던 ocean_proximity가 어떤 특성이 있었는지 기억나는가. 그렇다. ocean_proximity는 non numeric으로 이루어져 있다. 중앙 값은 숫자 속성에서만 계산 될 수 있기 때문에 텍스트 속성 없이 데이터 사본을 만들어야 된다.

1
2
3
4
#숫자로만 이루어진 housing_num이라는 별도의 사본을 만듦
housing_num = housing.drop("ocean_proximity", axis = 1)
 
#https://colorscripter.com/
cs


 이제 fit() 함수를 사용하여 Imputer 인스턴스를 training data에 맞출 수 있다.

1
2
3
imputer.fit(housing_num)
 
#https://colorscripter.com/
cs


 Imputer는 단순히 각 속성의 중앙 값을 계산하고 그 결과를 statistics_ 인스턴스 변수에 저장한다. total_bedrooms 속성에만 값이 누락 되었었지만, 시스템이 작동 한 후에 새 데이터에 누락 된 값이 없는지에 대해 확신할 수 없기 때문에 모든 속성에 적용하는 것이 더 안전하다.

imputer.statistics_에 저장 된 값과 속성들의 중간값을 계산 한 것과 같음을 확인할 수 있다.


 

 이제 학습된 중앙값으로 누락된 값들을 대체함으로써 트레이닝 셋을 변환하기 위해 "훈련 된" imputer 를 이용할 수 있다.

결과물은 변환 된 feature 들을 포함한 Numpy 배열이다. 

 만약 pandas DataFrame으로 다시 되돌리고 싶다면 아래와 같다.


2) Handling Text and Categorical Attributes

 좀 전에 categorical 속성인 ocean_proximity를 삭제했었다. 그 이유는 텍스트로 구성 되어 있어 중간 값을 계산 할 수 없었기 때문이다. 많은 머신러닝 알고리즘은 숫자로 처리하는 것을 선호한다. 그럼 이러한 텍스트로 이루어진 것들은 사용할 수 없을까?

정답은 당연히 아니다. 이제 이 텍스트로 구성된 것을 숫자로 변환하는 작업을 해보자.

 Scikit-Learn에는 이러한 것들을 처리하기 위해 LabelEncoder 함수를 제공한다.

인코딩 된 값을 확인해보면 3,3,3....1,1,1 이라는 숫자를 확인 할 수 있다. 이는 1H OCEAN은 0, INLAND는 1... ISLAND는 4이다.


 위의 인코더의 문제점은, 머신러닝 알고리즘이 두 개의 근접한 값들이 거리가 있는 두 개의 값들보다 더 유사하다고 가정한다는 것이다. 이는 예를 들어 범주 0과 4가 범주 0과 1보다 더 유사할 수 있는 문제가 발생한다.

이러한 문제점을 해결하기 위한 방법으로 바이너리 속성을 부여하는 것이다. 이를테면 1H OCEAN이 1일 때 나머지 속성들은 0을 주는 방식이다. 이러한 방법을 one-hot encoding이라고 부른다.

즉, 1H OCEAN, INLAND, NEAR OCEAN, NEAR BAY, ISLAND 라는 5개의 각기 다른 값이 있을 때,  1H OCEAN이 1이면 10000,

NEAR OCEAN이 1이면 00100 식으로, 1은 (hot), 다른 것들은 0 (cold)가 되는 인코딩 방법이다. 하나만 hot이자나~~


 Scikit-learn은 integer categorical 값을 one-hot 벡터로 변환하는 OnehotEncoder를 제공한다. 주의해야 할 점은 fit_transform()은 2차원 배열을 기대하지만, housing_cat_encoded는 1차원 배열이라는 것을 주의해라. 그래서 우리는 재구성이 필요하다.


 결과물이 왜 numpy 배열이 아닌 sparce 행렬일까??? category가 수천 가지인 경우 매우 유용하게 적용 된다. one-hot 인코딩 후 수천 개의 열을 가진 행렬이 생성 된다. 이 행렬은 하나의 열인 1을 제외하고는 모두 0으로 채워진다. 이 0을 저장하기 위해 많은 메모리를 사용하는 것은 상당히 낭비가 아니겠는가. 그런데 이 sparce 행렬은 0이 아닌 요소의 위치만 저장한다. 
참 좋은 기능이다!


Scikit-learn에는 텍스트를 integer categories로 바꾸고 이 integer categories를 one-hot vectors로 바꿔주는 클래스 또한 제공함: LabelBinarizer


3) Custom Transformers

 Scikit-learn이 물론 유용한 transformers를 제공하고 있지만 custom 해야 할 필요가 있음.

Scikit-learn은 클래스를 생성하고 3개의 함수를 구현하는 것이 전부임
(3개의 함수란 fit(): self를 반환, transform(), 그리고 fit_transform()

 여기에서 transformer 함수는 하나의 하이퍼 파라미터로 add_bedrooms_per_room이 기본적으로  true로 설정되어 있다. 이러한 하이퍼 파라미터를 사용하면 해당 속성을 추가하여 머신러닝 알고리즘에 도움이 되는지에 대한 여부를 쉽게 확인할 수 있다.


4) Feature Scaling

 데이터를 적용하기 위해 가장 중요한 변형 방법 중 하나는 feature scaling이다. 특이한 경우를 제외하고는 대부분의 머신러닝 알고리즘은 매우 다른 scale에 대해 정상적으로 동작하지 않는다. housing data에 대해 상이한 scale을 확인해보자.

 total_rooms의 경우, 6에서부터 39,320 까지의 범위를 가진다. 이에 반해, median_income의 경우 0.5에서부터 15의 범위를 가진다.


 이러한 문제를 해결하기 위한 방법으로는 min-max scaling과 standardization이 있다.

  • Min-max scaling(많은 사람들은 정규화(normarlization)라고도 부른다)
    - 0에서부터 1까지의 범위로 만드는 방법으로 간단하다. 주어진 값에서 최소값을 빼주고, 최대값에서 최소값을 뺀 값을 나눠주면 된다.

    Scikit-learn에서는 MinMaxScaler를 제공한다. 한가지 재밌는 사항으로는 0-1의 범위를 원하지 않을 때, feature_range라는 하이퍼 파라미터를 제공해주기 때문에 범위를 설정해줄 수 있다.

  • Standardization
    - 위의 정규화 방식과 꽤 다르다. 먼저 평균 값을 빼주고(그렇기 떄문에 standardization값은 항상 평균값이 0이다), 분산(variance)로 나눠준다. 정규화와 달리 standardization은 특정 범위로 한정되지 않는다. 이 점은 몇몇 알고리즘에 문제가 될 수 있는데 그 중 하나는 neural network의 경우 input 값이 0에서부터 1까지의 값을 집어넣기 때문에 적합하지 않을 수 있다.
    하지만 장점으로는 이상점(outlier)에 덜 영향을 받는다는 것이다. 예를 들어, district의 중간 소득 값을 실수로 100이라고 가정해보자. Min-max scaling의 경우, 0-15의 범위로부터 0-0.15로 모든 다른 값들을 망칠 수 있다.
    Scikit-learn에서는 StandardScaler를 제공한다.

5) Transformation Pipelines

 데이터 변환에 있어, 많은 일련의 순서로 실행된다는 것을 확인해 볼 수 있었다. Scikit-learn에서는 Pipeline 클래스를 제공함으로써 이러한 변환에 있어 도움을 준다. 

 Pipeline 생성자는 이름과 추정자(estimator)가 쌍(pair)인 리스트로 구성되어 있다. 마지막 추정자를 제외하고는 반드시 transformers 를 가져야 한다. (즉, fit_transform() 함수를 가져야만 한다.)

 이름은 원하는 대로 지정이 가능하다. (두개의 밑줄 "__"을 포함하지 않는다면)

 pipeline's fit() 함수를 호출하면, 각 호출의 출력 값을 매개 변수로 전달하여 최종 추정량에 도달할 때까지 fit_transform() 함수를 순차적으로 호출하게 된다. 

 pipeline은 최종 추정자로써 동일한 함수를 제공한다. 예를 들어 마지막 추정자는 transformer인 StandardScaler다. 그렇기 때문에 pipeline은 모든 데이터 변환에 순차적으로 적용하는 transform() 함수를 가진다. (또한, fit()과 함께 transform()을 호출하는 대신에 사용했던 fit_transform() 함수를 가진다) 

 

 이제 숫자 열(numerical columns)을 numpy 배열에 수동적으로 추출하는 대신, pandas 데이터 프레임을 이용하여 직접 pipeline에 넣는다면 좋을 것이다. Scikit-learn에는 pandas 데이터 프레임을 다루는 방법은 없지만, 우리는 이 작업을 위해 custom transformer를 만들 수 있다.

 우리의 DataFrameSelector 함수는 원하는 속성을 선택하고, 나머지 부분은 삭제하며, Dataframe을 numpy 배열로 변환한다.

이를 통해, 우리는 쉽게 pandas 데이터 프레임을 이용하여 numerical values 만을 다룰 수 있는 pipeline을 작성할 수 있다.

pipeline은 숫자 속성을 가진 것만 선택할 수 있는 DataFrameSelecor로 시작하고, 이전에 논의한 전처리 단계를 거쳐야 된다. 또한 DataFrameSelecor를 사용하여 categorical 속성을 선택한 다음 LabelBinarizer을 적용하여 Categorical 속성에 대한 다른 pipeline을 쉽게 작성할 수 있다.

 이 두개의 pipeline을 하나로 합칠 수는 없을까?

당연히 존재한다. Scikit-learn에는 FeatureUnion 클래스를 제공한다. 리스트 형식의 transformer를 보내면 transform() 함수가 호출된다. 이 때 각각의 transformer가 병렬적으로 실행되며, 결과 값을 기다린다. 그 다음 각각의 transformer가 fit() 함수를 호출하면 최종 fit() 함수가 호출되어 결과를 합친다.


5. Select and Train a Model

 아... 여기까지는 잘됐다. 근데 모델 학습을 위한 housing_prepared에서 에러가 난다.

( TypeError: fit_transform() take 2 positional arguments but 3 were given)


 이 부분은 좀 더 확인이 필요할 듯.... 뒷부분은 학습데이터가 없기 때문에 진행 못함

상당히 찜찜해서 빠른 시일내에 확인해서 업데이트 예정 (9월 4일)


 해결했다. https://github.com/ageron/handson-ml/issues/75 이슈사항에 이미 등록되어 있었다.

아래 LabelBinarizerPipelineFriendly 클래스를 생성해준다.

다음으로는 cat_pipeline쪽에 기존의 LabelBinarizerPipeline부분을 LabelBinarizerPipelineFriendly로 대체해준다.


 이제 정상적으로 데이터를 뽑아보자. 이 화면을 그렇게 보고싶었다.........


1) Training and Evaluating on the Training Set

 이제 원없이 모델을 돌려보자. 첫 번째로는 선형 회귀 모델을 사용하여 학습시켜보자.

  예상치가 정확하지는 않지만(예를 들어, 첫 예측인 40%정도밖에 안됨) 작동은 된다. 

이제 평균제곱근오차(Root Mean Square Error, RMSE)를 측정하기 위해 Scikit-learn의 mean_squared_error 함수를 사용해보자.

 아무것도 안한 것보다는 좀 나아졌다. 그러나 좋은 점수는 아니다. 대부분의 districts의 중간 주택 가격이 $120,000에서 $265,000 이기 때문에 예측 에러가 $68,628이라는 숫자는 만족스럽지 못하다. 이러한 예는 학습 데이터의 underfitting 문제다. 

이러한 underfitting 문제가 발생했다는 것은 좋은 예측을 위한 충분한 정보가 제공되지 않거나, 모델이 강력하지 않다는 것을 의미한다. 이러한 underfitting을 해결하기 위한 방법으로는 보다 강력한 모델을 선택하거나, 더 좋은 feature들로 학습 알고리즘을 사용하거나, 모델의 제약을 줄이는 것이다. 

 이 모델은 정규화(regularized)되지 않았기 때문에 모델의 제약을 줄이는 것은 제외된다. 
(본문에는 없지만, 간단하게 얘기하면 정규화 즉 규제를 할 때, 알파값을 설정하여 모델의 규제를 완화시키거나 강화시킴으로써 언더피팅과 오버피팅 문제를 해결 하는 방법이 있음)

 

 더 많은 기능을 추가 할 수 있지만 우선 더 복잡한 모델을 사용해보자.

이번에는 의사 결정 ㄴ 회귀(Decision Tree Regressor)로 학습시켜보자. 이것은 강력한 모델로, 데이터에서 복잡한 비선형 관계를 찾을 수 있는 모델이다. (6장에서 더 자세히 살펴 볼 것이다.)

 0이란다. 딱봐도 이건 잘못되었다는 것을 알 수 있을것이다. 이러한 것을 지나치게 과적합되었다고해서 overfitting 이라 한다.

어떻게 확신할 수 있을까? 앞에서 살펴봤듯이, 확신 할 수 있는 모델이 생성되기 전까지 테스트 데이터를 건드리는 것은 원하지 않을 것이다. 이 때, 학습용 데이터의 일부를 훈련에 사용하고, 그 중 일부를 모델 검증용으로 사용하게 된다. 

즉, 학습용+테스트용 -> 학습용+검증용+테스트용 으로 된다는 것이다. 물론 학습용=학습용일부+검증용 이다.


2) Better Evaluation Using Cross-Validation

 의사 결정 나무 모델을 평가하는 방법 중 하나는, train_test_split 함수를 사용하여, 학습용 셋을 학습용 셋과 검증용 셋으로 분할 한 후, 분할 된 학습용 셋으로 모델을 학습시키고, 검증용 셋으로 모델을 평가하는 방법이 있다.

즉, train_test_split 함수 사용 -> 학습용셋을 학습용과 검증용으로 분할 -> 학습용으로 모델 학습 -> 검증용으로 모델 평가

위의 방법은 어렵지 않고 상당히 잘 작동한다.


 훌륭한 대안으로는 Scikit-learn의 cross-validation feature를 사용하는 것이다. 아래는 K-fold cross-validation 을 수행하는 그림이다. 이것은 무작위로 fold라고 불리는 10개의 subset으로 학습 데이터 셋을 분리한다. 그 다음 의사결정나무(decision tree) 모델로 10번 훈련시키고 평가 한다. 매 번 평가를 위해 다른 fold를 골라내고 또 다른 9개의 fold를 훈련시킨다.

그 결과는 10개의 평가점수를 포함한 배열로 표현된다.

이제 결정트리가 그 전 만큼 좋아 보이지는 않는다. 실제로 선형 회귀 모델보다 안좋아보인다. 교차 검증을 하면 모델의 성능 측정 뿐만 아니라 추정치의 정확성(즉, 표준편차) 또한 측정 할 수 있다는 것을 기억하자. 결정 트리는 +-2,728의 편차를 갖는, 약 71,265의 점수를 얻었다. 

 하나의 검증 셋만을 사용한다면, 이런 정보를 얻을 수 없다. 하지만 교차 검증은 여러 번 모델을 훈련시키는 비용이 발생하기 때문에 항상 가능한 것은 아니다.

  똑같은 방식으로 선형 회귀 모델에도 적용해보자.


 다음으로는 랜덤포레스트(Random Forest)를 실행해보자. 랜덤포레스트는 기본적으로 조금씩 다른 여러 결정 트리의 묶음이다. 랜덤 포레스트의 아이디어는 서로 다른 방향으로 과대적합된 트리를 많이 만들어내면 그 결과를 평균냄으로써 과대적합된 양을 줄일 수 있다는 것이다. 이렇게 하면 트리 모델의 예측 성능은 유지되면서 과대적합이 줄어드는 것이 수학적으로 증명 되었다.

랜덤 포레스트는 7장에서 더욱 자세하게 살펴 볼 것이다. 

 훨씬 더 좋은 결과를 얻었다. 그래도 아직 overfitting 되어 있다. 여기까지만 일단 진행하자.


Tip

파이썬의 pickle 모듈이나, sklearn.externals.joblib을 통해, Scikit-learn 모델을 쉽게 저장할 수 있다.


1
2
3
4
5
6
7
from sklearn.externals import joblib
 
joblib.dump(my_model, "my_model.pkl")
#나중에 불러 올 때
my_model_loaded = joblib.load("my_model.pkl"
 
#https://colorscripter.com/
cs


6. Fine-Tune Your Model

추후 진행 예정


+ Recent posts