티스토리 뷰

728x90

from: https://www.datanami.com

머신러닝 파이프라인

머신러닝 수행 과정을 정형화하는 파이프라인을 구축해본다. 일반적으로 머신러닝은 대략 아래와 같은 순서로 진행한다.

더보기

 

1. 큰 그림 보기

 

2. 데이터 가져오기

 

3. 데이터 탐색과 시각화

 

4. 데이터 준비

 

5. 모델 선택과 훈련

 

6. 모델 세부튜닝

 

 

1. 큰 그림 보기

문제 정의부터 시작해야한다. 해결하려는 문제가 어떤 종류의 문제인가?

지도 학습으로 해결할 수 있는 문제인가?(데이터에 예측하려고하는 레이블, 타깃값이 있는가) 비지도 학습 혹은 강화 학습?

또한, 예를 들어 주식 가격(삼성전자의 종가라고 하자.)을 예측하고 싶다면,

내일 삼성전자의 종가가 오늘 종가에 비해 오를지 혹은 떨어질지를 예측하고 싶은가?(분류)

아니면 정확한 종가를 예측하고 싶은가?(회귀)

데이터를 실시간으로 수집해 모델을 학습시키고 싶은가?(온라인 학습)

아니면 오프라인에서 한 번에 학습시키고 예측에 사용하고 싶은가?(배치 학습)

 

이후 성능 측정 지표를 선택해야 한다. 예를 들어 회귀 문제면 모델의 오류가 얼마나 큰 지 측정하기 위해 대표적으로 Euclidean norm, L2 norm으로도 알려져 있는 RMSE(Root Mean Squared Error)를 사용한다. 이상치가 많다면 MAE(Mean Absolute Error)을 사용할 것을 고려해야 한다.

 

2. 데이터 가져오기

이후 데이터를 가져와야 한다. 이 장에서는 앞으로 캘리포니아 주택 가격 데이터(클릭시 바로 다운로드)를 사용할 것이다. 데이터를 가져올 때 구하려는 문제에 부합하는 데이터 셋을 가져와야 한다. 이렇게 매번 직접 찾아서 다운로드받고, 압축을 풀고, 파이썬에서 불러들이는 수작업을 해도 상관없지만, 번거롭다고 느끼거나 실시간으로 데이터를 처리해야할 필요가 있는 경우 아래 코드와 비슷하게 함수를 작성하는 것이 편할 것이다.

 

아래 코드는 데이터를 읽어들이는 함수이다. 단순히 경로에서 housing.csv 데이터 셋을 판다스 데이터프레임으로 읽어들인다.

 

데이터를 읽어들였으면 head() 메서드로 각 특성의 데이터를 첫 5행만 출력해서 대략적으로 살펴본다.

 

info() 메서드는 총 데이터가 얼마나 있는지, null값이 포함되어 있는지, 각 특성의 데이터 타입은 무엇인지 출력한다. 특성 중 유일하게 ocean_proximity만 object 타입인 것을 확인할 수 있다.

 

describe() 메서드로 수치형 특성의 요약 정보를 확인한다. count는 null을 제외한 행의 개수, 여기서 std는 표준 편차, 25%, 50%, 75%는 사분위수로 25%가 의미하는 바는 아래 표에서 housing_median_age 값의 하위 25%가 18 이하라는 것이다.

 

아래와 같이 데이터셋.hist() 메서드를 이용하면 전체 특성에 대한 각각의 히스토그램을 출력할 수 있다. 위 표들 처럼 수치를 나열하는 것보다는 아래 그래프처럼 시각화하는 것이 한 눈에 알아보기 더 쉬울 것이다.

 

주의 사항은 데이터가 전처리되어있는지 확인해야 한다는 것이다. 예를 들어 위에서 median_income을 보면 수평축에 0, 2, 4, ... 식으로 표기되어 있는 것을 볼 수 있다. 2 달러, 4 달러가 아닌 2만 달러, 4만 달러를 의미한다. 그래서 데이터 셋을 가져올 때 해당 사이트 등에서description 같은 데이터 셋을 설명하는 부분을 잘 살펴보아야한다. 또한 최솟값과 최댓값이 한정되어 있는지 확인한다.

위에서 median_house_value를 보면 500000달러 값이 차지하는 비율이 높은데, 이는 최대값이 한정되어 있다는 것을 뜻한다. 잘 확인해야한다. 또한 특성 스케일이 서로 많이 다르거나 히스토그램의 꼬리가 두꺼운지를 확인하여 전처리 작업을 해야한다. 이들의 경우 아래 특성 스케일링 부분에서 살펴본다.

 

이후 아래와 같이 test set를 생성한다. test set는 모델을 train set에서 학습시킨 후 최종적으로 테스트하기 위해 사용한다. (데이터 스누핑 편향 조심)

 

test set를 만들때는 샘플링 편향을 피하기 위해 계층적 샘플링을 하는 것이 바람직하다. test set를 만들 때 train set의 특성의 비율을 맞춰주기 위함이다. 

 

예를 들어 median_income의 데이터 대부분은 1.5에서 6에 분포해있다. 이를 여러 계층으로 나누어 각 계층이 test set에도 train set 에서의 계층 처럼 각 계층 내의 데이터를 잘 표현하도록 한다. 먼저 아래와 같이 총 5개의 계층으로 나눈다.

 

이후 사이킷런의 StratifiedShuffleSplit 메서드를 아래와 같이 사용한다.

 

strat_test_set의 median_income 특성의 데이터 분포를 보면 아래의 housing의 그것과 데이터 분포가 매우 비슷함을 알 수 있다. 만약 위에서처럼 train_test_split으로 랜덤 샘플링으로 test set을 구성하면 해당 특성의 데이터 분포가 train set의 그것과 꽤 차이가 난다.(말 그대로 랜덤 샘플링이기에 많이 차이날수도, 적게 차이날수도 있다.)

 

3. 데이터 이해를 위한 탐색과 시각화

데이터 과학 교과서를 보면 가장 중요하게 다루는 부분이 데이터 탐색이다. 탐색적 데이터 분석이나 EDA(Exploratory Data Analysis)라고 부르는데 개념 자체는 단순하나(말 그대로 '탐색적' 데이터 분석. 시각화 등을 통해 '눈'으로 패턴을 인식해 통찰을 얻는 식이다.) 그 방법이 개인에 따라 무궁무진하다. 도메인 지식이 매우 중요하게 작용한다.(부동산 중개업자가 머신러닝을 배워서 주택 데이터를 다룬다고 상상해보자. 또는 생물학자가 단백질 분자 구조를 머신러닝 알고리즘으로 예측한다든가. 실제로 캐글에 가보면 컴퓨터공학과나 통계학과 학부생이 아니라 각 도메인의 경력자들이 그에 맞는 대회에서 우승하는 경우가 잦다고 한다.)

 

 

캘리포니아 주택 데이터처럼 지리 정보 특성이 있다면 산점도로 만들어 시각화해보는 것도 좋은 방법이다. 이를 통해 주택 가격이 인구밀도나 지역과 연관있다는 것을 확인할 수 있다. 아래의 산점도는 캘리포니아 해안선에 따라 많은 인구가 분포해 있고, 그 구역의 주택 가격이 높은 것을 확인할 수 있다.(왼쪽 아래 부분이 바다, 오른쪽 위 부분이 내륙, 그 사이의 경계 부분이 해안가)

 

상관관계 조사

특성 간 상관관계를 조사해본다. 사이킷런의 corr()를 이용하면 표준 상관계수를 바로 구할 수 있다.

 

예측하고자 하는 median_house_value와의 특성 간 중요도를 출력한다. median_income이 가장 상관계수가 높다.

 

주의할 것은 상관계수는 기울기와 무관하다는 것과 corr() 메서드는 선형적 상관관계만 조사한다는 것이다.(x가 증가하면 y가 증가/감소). 비선형적 관계는 조사하지 않는다.(x가 0에 가까워지면 y가 증가/감소). 아래와 같은 비선형적 상관관계는 조사하지 않는다.

 

 

위 처럼 수치화하는 것 외에 특성 간 산점도를 사이킷런의 scatter_matrix() 메서드로 출력해볼 수 있다. 특성 간 상관관계 비교니까 총 특성의 수 * 총 특성의 수만큼 출력할 수 있으나 아래처럼 4개끼리만 조사해본다.(총 16개). 여기서 대각선은 산점도가 아니라 히스토그램인데 자기 자신과의 상관관계는 1이기 때문에 출력할 의미가 없다. 따라서 그냥 각 특성의 히스토그램으로 대신 출력한다.

 

특성끼리 조합해 더 나은 특성을 만들어볼 수 있다. 이 부분이 도메인 지식을 요구하는 부분이다. 아래같은 가구당 방 개수, 총 방 개수당 침실 개수, 가구당 인원같은 간단한 특성 조합은 시간을 들인다면 충분히 생각해낼 수 있을 것이다.

 

특성 조합 추가 후 상관계수를 출력해본다. 추가한 특성 중 rooms_per_household가 median_income 다음으로 타깃과의 상관관계가 높다.

 

4. 머신러닝 알고리즘을 위한 데이터 준비

먼저 레이블과 그 외 데이터를 아래와 같이 분리한다.

 

이후 데이터 정제 과정이 필요하다. 위에서 봤듯이 결측치(null값)가 있는 특성이 있었다. 이 값이 포함된 행 전체를 삭제하거나(옵션1), 해당 특성 자체를 삭제하거나(옵션2), 해당 특성의 평균값을 계산해 채워넣을 수 있다.(혹은 0이나 중앙값 등등)

 

옵션1

 

옵션2

 

옵션3(평균값이라면 그 값을 따로 저장해놓아야 test set이나 이후 새로운 데이터에도 같은 값을 적용할 수 있다.)

 

결측치를 다룬 이후에는 특성 중 유일한 텍스트 타입 특성 값을 가지고 있는 ocean_proximity 특성을 다룬다. 먼저 해당 텍스트 특성이 중 범주형(Category)인지 다른 타입인지 확인한다.

 

10개만 출력해보았더니 똑같은 텍스트 갑이 반복된다. 범주형인 것을 확인할 수 있다.

 

대부분의 머신러닝 알고리즘은 숫자형 데이터를 다루므로 숫자로 변환한다.

 

OrdinalEncoder() 혹은 Series.factorize() 메서드를 이용한다.

 

이 방법은 문제가 있다. 만약 bad, good 같이 범주에 순서가 있다면 문제가 없다. 그러나 이 방법은 범주 간 연관성을 왜곡할 가능성이 있다. 즉 실제로 <1H OCEAN과 NEAR OCEAN 값이 비슷함에도 불구하고 각각 숫자로 변환되어 0과 4로 표기된다. 이 둘은 0과 1보다 차이가 크다. 이 차이가 알고리즘의 학습에 영향을 준다.

 

따라서 범주 별 이진 특성으로 해결한다. 원 핫 인코딩으로 불린다.(보통 이 방법을 많이 사용한다.)

사이킷런의 OneHotEncoder() 메서드를 이용한다. 반환값은 희소행렬(희소 행렬의 종류는 여러가지가 있지만. 이 메서드는 0이 아닌 원소의 위치만 저장하는 방식으로 메모리 낭비를 줄인다.)이다. 밀집 행렬은 특성 값이 많을 수록 불필요하게 채워야하는 0값이 많아지므로 메모리 낭비가 커진다.

 

또한 범주의 종류가 많다면 수 많은 입력 특성 생긴다. 이는 모델이 각 데이터마다 처리해야할 연산이 많아진다는 것을 의미한다. 학습 속도가 저하되는 것이다. 그래서 대안으로 숫자형 특성으로 대체하거나(예를 들어 국가 코드(텍스트 타입)가 특성으로 존재한다면 이를 GDP(숫자 타입)으로 바꾼다.) 표현 학습(Representation learning)으로 범주의 표현을 학습해 벡터로 표현하는 방법이 있다.

 

아래와 같이 나만의 변환기를 파이썬 클래스로 만들어 특성 조합을 추가해볼 수 있다.(물론 위에서 처럼 수작업으로 해도 된다.)

 

특성 스케일링

트리 기반 알고리즘 외 다른 알고리즘은 특성의 스케일이 많이 다르면 잘 학습되지 않는다. 보통 Min-Max 스케일링이나 표준화로 특성 스케일링으로 해결한다.

 

Min-Max 스케일링

가장 간단하다. 정규화(normalization)라고 불리며. 각 데이터에서 최솟값을 빼고, 그 결과에 최댓값과 최솟값의 차이로 나눠서 구한다. 혹은 간단히 사이킷런의 MinMaxScaler() 메서드를 사용한다.

 

표준화(standardization)

평균을 뺀 후(그래서 평균이 0이 됨) 표준 편차로 나누어 분포의 분산이 1이 되도록 한다. 상한과 하한이 없어 어떤 알고리즘에서는 문제 생길 수 있다(예를 들어 신경망 알고리즘은 입력 특성으로 0부터 1사이의 값을 기대한다.) Min-Max 스케일링에 비해 이상치 영향을 덜 받는다. 사이킷런에서는 간단히 StandardScaler() 메서드를 사용한다.

 

변환 파이프라인

아래와 같이 사이킷런의 Pipeline() 메서드를 이용해 깔끔하게 여러 변환기(Transformer)와 추정기(Estimator)들을 적용할 수 있다.

 

위 메서드는 오로지 수치형 특성에 대해서만 적용할 수 있다. 만약 범주형 특성 데이터도 함께 다루고 싶다면 사이킷런의 ColumnTransformer() 메서드를 사용한다. 이 메서드는 각 특성에 맞게 추정기와 변환기를 적용해준다. 각 추정기와 변환기는 위에서 정의했다.

 

위에서 봤듯이 OneHotEncoder는 희소행렬을 반환한다. 이에 반해 위의 수치형 특성을 전처리한 결과는 밀집행렬이 반환된다 .이렇게 희소행렬과 밀집행렬이 섞여있다면 ColumnTransformer가 최종 행렬의 밀집 정도를 알아서 추정해 임계값(기본값은 0.3)보다 낮으면 희소행렬 을 반환한다.

 

 

5. 모델 선택과 훈련

아래 코드는 train set에서 선형회귀 모델을 훈련시킨다.

 

아래 코드는 모델 훈련 후 예측을 진행한다. (train set에서 훈련하고 같은 train set에서 예측)

 

실제 타깃값과 각각 비교해보면 첫번째 결과는 꽤 차이가 난다. 잘 학습하지 못한 것이다.

 

전체 train set에 대한 모델의 RMSE를 측정해본다.

 

타깃 데이터가 대부분 120000~250000에 분포해 있다는 것을 감안하면 위 오차는 큰 편이다. 만약 타깃 데이터 대부분이 1억 대라고 상상해보면 위의 68000 정도의 오차는 크지 않다고 할 수 있다.

 

교차 검증을 사용한 평가

먼저 결정 트리 알고리즘을 학습시킨다.

 

이후 RMSE를 측정해보았더니 0이 나왔다. 즉 train set을 완벽하게 학습한 것이다.(과대적합) 이는 사실 바람직한 결과가 아니다. 이렇게 완벽하게 학습한 것과 그렇지 못한 모델로 새로운 데이터에 대해 예측해보면(이를 테면 test set) 후자가 일반적으로 더 좋은 성능을 보인다. 즉 train set을 완벽하게 학습하는 것은 실제로는 옳게 학습한 것이 아니다. 비유하자면 결정 트리는 train set을 완벽하게 암기한 것이다. 이 암기 내용만 가지고 새로운 데이터에 적용하려고 하니 더 성능이 나쁜 것이다. 또는 귀납의 오류를 범한 것이기도 하다. 매번 '하얀' 백조만 관찰한 후 모든 백조가 '희다'고 결론내릴 수 없다. 좀 더 내려가다보면 결정 트리가 '검은' 백조를 마주친 후 바로 무너져 내리는 모습을 볼 수 있다.

 

이제 교차 검증을 진행한다. 교차 검증은 train set을 여러 개의 fold(부분집합)로 나누고 모델을 여러 fold에서 훈련시킨 후 fold 하나로 검증하는 것을 반복한다. 예를 들어, train set을 3개의 fold를 나눈 경우를 생각해보자. 1번 fold, 2번 fold, 3번 fold로 말이다. 짧게 각각 번호만 표기해서 설명을 이어나간다. 즉 먼저 모델을 1과 2로 훈련시킨후 3으로 검증한 후 결과 저장, 1과 3으로 훈련, 2로 검증, 결과 저장, 이런 식으로 반복한다. 아래는 cv=10 즉 10개의 fold로 나누고 검증한다. 

 

* cross_val_score() 메서드는 scoring 매개변수에 비용이 아닌 효용 함수를 기대한다. 따라서 score에 비용 함수인 RMSE에 -1을 곱해 효용함수로 바꾸고 다시 -1을 곱해 원래의 우리가 기대하는 RMSE로 출력되도록 한다.

 

총 10개의 결과를(예를 들어 아래의 점수: [70194.33680785]가 첫번째 검증 결과다.) 확인할 수 있다.

평균과 표준 편차는 이 10개의 결과 값들로 구한 것이다. 

 

6. 모델 세부 튜닝

여태까지 일련의 과정을 거쳐 가능성 있는 모델 몇 개를 추렸다고 가정한다.(예를 들어 LinearRegression, DecisionTreeClassifier 등으로 RMSE 구한 후 이 값이 작은 모델들을 선별했다고 가정한다.)

이후 세부 튜닝을 통해 각 모델의 가장 좋은 하이퍼파라미터를 구해본다.(예를 들어 결정 트리라면 트리 깊이)

 

그리드 탐색

사이킷런의 GridSearchCV()를 사용한다. 파이썬 자료구조인 리스트([])로 탐색할 하이퍼파라미터와 시도해볼 값을 지정한다. GridSearchCV는 이들의 모든 조합을 탐색하며 RMSE를 구한다.

 

위 과정을 통해 최상의 하이퍼파라미터를 구한 결과이다. max_features는 랜덤 포레스트의 각 결정 트리 학습에 사용할 최대 특성의 개수이다. n_estimators는 총 결정 트리의 개수이다. 

 

* 랜덤 포레스트는 결정 트리의 앙상블이다. (앙상블: 그룹. 여러 결정 트리 모델을 학습시키고 각 트리의 예측 결과를 취합해 하나의 최종 결과를 출력한다. 예를 들어 다수결로. 그러면 결정 트리의 개수가 짝수면 안 되지 않냐고 할 수 있다. 맞다. 실제로는 다수결보다는 좀 다른 방식으로 결과를 예측한다. 여기의 첫 단락과 그 아래 Bagging 단락 설명을 보세요.)

 

랜덤 탐색

사이킷런의 RandomizedSearchCV() 메서드를 사용한다. 아래의 param_distribs 딕셔너리로 지정한 randint 범위 내에서 무작위로 값을 선택해 n_iter로 지정한 횟수만큼 반복한다. GridSearchCV는 탐색해야할 하이퍼파라미터 조합이 지나치게 크다면 시간이 오래 걸린다는 단점이 있기 때문에 특성의 조합이 많고 충분한 컴퓨팅 성능, 시간이 없을 때 사용한다.

 

앙상블 방법

위의 랜덤 포레스트 처럼 결정 트리 같은 모델 여러 개를 연결해 더 나은 예측 결과를 얻기 위해 사용한다. 앙상블 방법은 단일 모델보다 더 나은 결과를 내기에 자주 사용된다.

 

최상의 모델 분석

시간을 들여 모델을 분석하면 좋은 통찰을 얻는 경우가 있다. 예를 들어 랜덤 포레스트는 각 특성의 중요도를 알려준다. 이를 통해 불필요한 특성을 제거하거나 이후 여러 특성 조합이나 이상치 제거 등의 방법으로 모델 성능을 더 끌어올릴 수 있다.

 

테스트 세트로 시스템 평가

모델과 그 최상의 하이퍼파라미터를 선정한 후 test set에서 평가를 진행한다. 

 

바로 위 코드에서 test set에서의 RMSE를 구해보았다. 이 추정 결과가 얼마나 신뢰할만한 것인가?

신뢰 구간을 구해 추정값의 정확도를 구해본다.

 

scipy의 stats() 메서드를 통해 아래와 같이 95% 신뢰구간에서 추정해본다.

 

 

 

 

 

공개 데이터 저장소 모음 사이트들: 

https://goo.gl/SJHN2k

https://homl.info/10

http://reddit.com/datasets

 

댓글