ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • chapter2. 머신러닝 프로젝트 처음부터 끝까지
    공부/Hands-On Machine Learning 2022. 11. 2. 16:01
    728x90

    2.1 실제 데이터로 작업하기

    • 유명한 공개 데이터 저장소
      • UC 얼바인 머신러닝 저장소(http://archive.ics.uci.edu/ml)
      • 캐글 데이터셋(http://www.kaggle.com/datasets)
      • 아마존 AWS 데이터셋(http://registry.opendata.aws)
    • 메타 포털(공개 데이터 저장소가 나열되어 있음)
      • 데이터 포털(http://dataportals.org)
      • 오픈 데이터 모니터(http://opendatamonitor.eu)
      • 퀸들(http://quandl.com)
    • 인기 있는 공개 데이터 저장소가 나열되어 있는 다른 페이지
      • 위키백과 머신러닝 데이터셋 목록(https://goo.gl/SJHN2K)
      • Quora.com(https://homl.info/10)
      • 데이터 세브레딧(http://www.reddit.com/r/datasets)

    2.2 큰 그림 보기

    맨 처음 할 일은 캘리포니아 인구조사 데이터를 사용해 캘리포니아의 주택 가격 모델을 만드는 것

    데이터는 캘리보니아의 블록 그룹마다 인구, 중간소득, 중간주택가격 등을 담고있음

    이 데이터로 모델을 학습시켜서 다른 측정 데이터가 주어졌을 때 구역의 중간 주택가격을 예측해야함

    2.2.1 문제 정의

    1. 회사에서 이 모델을 어떻게 사용해 이익을 얻으려고 하는지 알아야함

    목적을 아는 것은 문제를 어떻게 구성할지, 어떤 알고리즘을 선택할지, 모델 평가에 어떤 성능 지표를 사용할지, 모델 튜닝을 위해 얼마나 노력을 투여할지 결정하기 때문에 아주 중요

    2. 현재 솔루션은 어떻게 구성되어 있는지 알아야함

    현재 상황은 문제 해결 방법에 대한 정보는 물론이고 참고 성능으로도 사용가능

    3. 문제를 정의해야함

    1. 레이블된 훈련 샘플이 있으니 지도학습 작업
    2. 값을 예측해야하므로 회귀 문제(예측에 사용할 특성이 여러개이므로 다중 회귀문제)
    3. 각 구역마다 하나의 값을 예측하므로 단변량 회귀 문제
    4. 구역마다 여러 값을 예측한다면 다변량 회귀
    5. 이 시스템으로 들어오는 데이터에 연속적인 흐름이 없으므로 빠르게 변하는 데이터에 적응하지 않아도 되고 데이터가 메모리에 들알만큼 충분히 작으므로 배치학습이 적절

    2.2.2 성능 측정 지표 선택

    성능 측정 지표를 선택

    회귀 문제의 전형적인 성능 지표는 평균 제곱근오차(root mean square error, RMSE)

    ->오차가 커질수록 이 값은 더욱 커지므로 예측에 얼마나 많은 오류가 있는지 가늠하게 해줌

    더보기

    n은 RMSE를 측정할 데이터셋에 있는 샘플 수

    ex)2000개의 구역의 검증 세트에 대해 RMSE를 평가한다면 n=2000

    yi는 하나의 샘플 특성 벡터를 받아 그 샘플에 대한 예측값을 출력한 것(=가설,hypothesis)

    ŷi는 해당 레이블 (해당 샘플의 기대 출력값)

    2.2.3 가정검사

    카테고리가 아닌 실제 가격을 사용함(회귀->분류)

    2.3 데이터 가져오기

    파이썬 설치 후 머신러닝코드와 데이터셋을 저장할 작업 디렉터리를 만듦

    2.3.1 작업환경 만들기

    import os
    
    ML_PATH="./sample_data/ml"  #작업 디렉터리 경로
    os.mkdir(ML_PATH)           #작업 디렉터리 생성
    
    !pip --version    #pip libarary 버전 확인
    
    !pip install --upgrade colab matplotlib numpy pandas scipy scikit-learn #필요한 라이브러리 설치
    #만약 주피터 노트북에서 한다면 colab대신 jupyter를 넣으면 됨

    2.3.2 데이터 다운로드

    import tarfile
    import urllib
    
    DOWNLOAD_ROOT="http://raw.githubusercontent.com/ageron/handson-ml2/master/"
    HOUSING_PATH=os.path.join("datasets","housing")
    HOUSING_URL=DOWNLOAD_ROOT+"datasets/housing/housing.tgz"
    
    def fetch_housing_data(housing_url=HOUSING_URL,housing_path=HOUSING_PATH):
      os.makedirs(housing_path, exist_ok=True)
      tgz_path=os.path.join(housing_path, "housing.tgz")
      urllib.request.urletrieve(housing_url, tgz_path)
      housing_tgz=tarfile.open(tgz_path)
      housing_tgz.extractall(path=housing_path)
      housing_tgz.close()
    '''
    아래 함수를 호출하면 현재 작업공간에 datasets/housing디렉터리를 만들고
    housing.tgz파일을 내려받고 같은 디렉터리에 압축을 풀어 housing.csv파일 생성
    '''
    fetch_housing_data()
    import pandas as pd
    
    #판다스를 이용해 데이터를 읽어들이는 함수
    def load_housing_data(housing_path=HOUSING_PATH):
      csv_path=os.path.join(housing_path,"housing.csv")
      return pd.read_csv(csv_path)

    2.3.3 데이터 구조 훑어보기

    housing=load_housing_data()
    housing.head()				#DateFrame의 head() : 처음 다섯 행 확인

    housing데이터세트의 첫 다섯행

    특성 : longitude, latitude, housing_median_age, totle_rooms. total_bedrooms, population, households, median_income, median_house_value, ocean_proximity

    housing.info()	#데이터에 대한 간략한 설명과 전체 행수, 
    #특히 각 특성의 데이터 타입과 널이 아닌 값의 개수를 확인하는데 유용

    데이터셋에 20,640개의 샘플이 들어있음
    totle_bedrooms 특성은 20,443개만 널값이 아님->207개의 구역은 이 특성을 가지고 있지 않다
    ocean_proximity특성만 빼고 모든 특성이 숫자형
    ocean_proximity필드의 데이터 타입은 object이므로 파이썬 객체도 될 수 있지만 csv파일로 읽어들였기 때문에 텍스트특성일 것
    head()를 사용했을떄 ocaen_proximity 열의값이 반복되는 것으로 보아 아마 범주형일 것
    #어떤 카테고리가 있고 각 카테고리마다 얼마나 많은 구역이 있는지 value_counts()로 확인
    #ocaen_proximity에선 어떤 범주형이 있는 지 확인
    housing['ocean_proximity'].value_counts()

    #.describe() 숫자형 특성의 요약정보를 보여줌
    housing.describe()

    %matplotlib inline
    import matplotlib
    import matplotlib.pyplot as plt
    
    #데이터의 형태를 빠르게 검토하는 다른 방법은 각 숫자형 특성을 히스토그램으로 그려보는 것
    #히스토그램은 주어진 값의 범위(수평축)에 속한 샘플수(수직축)을 나타냄
    housing.hist(bins=50, figsize=(20,15))
    plt.show()

    위의 히스토그램에서 몇 가지 사항을 확인할 수 있음

    1. 중간 소득(median income)특성이 US달러로 표현되어있지 않은  것 같음 데이터를 취합한 팀에 확인해보니 스케일을 조정하고 상한이 15,하한이 0.5가 되도록 만들었다고 함.대략 수만 달러를 나타냄, 머신러닝에서는 전처리된 데이터를 다루는 경우가 흔하고 이것이 문제가 되지는 않지만 데이터가 어떻게 계산된 것인지 반드시 이해하고 있어야함
    2. 중간 주택 연도(housing median age)와 중간 주택 가격(median house value) 역시 최댓값과 최솟값을 한정했음.중간 주택가격의 경우 타깃 속성(레이블)으로 사용되기 때문에 심각한 문제가 될 수 있음. 가격이 한곗값을 넘어가지 않도록 머신러닝 알고리즘이 학습될지도 모름. 이것이 무제가 될지 안 될지는 클라이언트 팀(이 시스템의 출력을 사용할 팀)과 함께 검토하는 것이 좋음. 만약 그 팀에서 $500,000를 넘어가더라도 정확한 예측값이 필요하다고 한다면 우리가 선택할 수 있는 방법은 두가지
      1. 한곗값 밖의 구역에 대한 정확한 레이블 구함
      2. 훈련세트에서 이런 구역을 제거($500,000가 넘는 값에 대한 예측은 평가 결과 나쁠 것이므로 테스트에서도 제거)
    3. 특성들의 스케일이 서로 많이 다름. 특성 스케일링에 대해서는 이 장의 뒷부분에서 살펴보겠음
    4. 많은 히스토그램의 꼬리가 두껍습니다. 가운데에서 왼쪽보다 오른쪽으로 더 멀리 뻗어있음. 이런형태는 일부 머신러닝 알고리즘에서 패턴을 찾기 어렵게 함. 나중에 이런 특성들을 좀 더 종 모양의 분포가 되도록 변형시킬 것

    2.3.4 테스트 세트 만들기

    아직 데이터를 잠시 살펴봤을 뿐이고 어떤 알고리즘을 사용할지 정하기 전에 전체 데이터를 자세히 파악해야하지않을까?

    ->사실 맞음, 하지만 우리 뇌는 매우 과대적합되기 쉬운 엄청난 패턴 감지 시스템이므로 만약 테스트 세트를 들여다본다면 테스트테스트세트에서 겉으로 드러난 어떤 패턴에 속아 특정 머신러닝을 모델을 선택하게 될지도 모름. 이 테스트 세트로 일반화 오차를 추정하면 매우 긍정적인 추정이 되며 시스템을 론칭했을 때 기대한 성능이 나오지 않을 것 

    =>데이터 스누핑(data snooping)

     

    테스트세트를 생성하는 일은 무작위로 어떤 샘플을 선택해서 데이터셋의 20%정도를 떼어놓으면 됨

    import numpy as np
    
    def split_train_test(data,test_ratio):
      shuffled_indices=np.random.permutation(len(data))
      test_set_size=int(len(data)*test_ratio)
      test_indices=shuffled_indices[:test_set_size]
      train_indices=shuffled_indices[test_set_size:]
      return data.iloc[train_indices],data.iloc[test_indices]

    이 방법도 괜찮지만 완벽하진 않음

    프로그램을 다시 실행하면 다른 테스트 세트가 생성되므로 여러번 계속하면 우리는 전체 데이터셋을 보는 셈이므로 이런 상황은 피해야함

     

    한 가지 해결책은 처음 실행에서 테스트 세트를 저장하고 다음번 실행에서 이를 불러들이는 것

    다른 해결책은 항상 같은 난수 인덱스가 생성되도록 np.random.permutation()을 호출하기 전에 난수 발생기의 초깃값을 지정하는 것(ex.np.random.seed(42))

     

    하지만 위의 두가지 해법 모두 다음번에 업데이트된 데이터셋을 사용하려면 문제가 됨

    데이터셋을 업데이트한 후에도 안정적인 훈련/테스트 분할을 위한 일반적인 해결책은 샘플의 식별자를 사용하여 테스트세트로 보낼지 말지 정하는 것(샘플이 고유하고 변경 불가능한 식별자를 가지고 있다고 가정)

    예를 들어 각 샘플마다 식별자의 해시값을 계산하여 해시 최대값의 20%보다 작거나 같은 샘플만 테스트 세트로 보낼 수 있음.이렇게 하면 여러번 반복 실행되면서 데이터셋이 갱신되더라도 테스트 세트가 동일하게 유지됨.새로운 테스트 세트는 새 샘플의 20%를 갖게 되지만 이전에 훈련 세트에 있던 샘플은 포함시키지 않을 것

    from zlib import crc32
    
    def test_set_check(identifier, test_ratio):
      return crc32(np.int64(identifier))&0xffffffff<test_ratio*2**32
    
    def split_train_test_by_id(data, test_ratio, id_column):
      ids=data[id_column]
      in_test_set=ids.apply(lambda id:test_set_check(id, test_ratio))
      return data.loc[~in_test_set],data.loc[in_test_set]
    #주택 데이터셋에는 식별자 칼럼이 없음. 대신 행의 인덱스를 ID로 사용하면 간단히 해결
    housing_with_id=housing.reset_index()
    train_set, test_set=split_train_test_by_id(housing_with_id,0.2,'index')
    #행의 인덱스를 고유 식별자로 사용할 때 새 데이터는 데이터셋의 끝에 추가되어야하고 어떤 행도 삭제되지 않아야함
    #이것이 불가능할 땐 고유식별자를 만드는 데 안전한 특성을 사용해야함 ex)구역의 위도,경도는 몇백년후에도 안정적->id로 만들수 있음
    housing_with_id['id']=housing['longitude']*1000+housing['latitude']
    train_set,test_set=split_train_test_by_id(housing_with_id,0.2,'id')
    '''
    사이킷런은 데이터셋을 여러 서브셋으로 나누는 다양한 방법을 제공
    가장 간단한 함수 : train_test_split 
    split_train_test와 아주 비슷하지만 두 가지 특징이 더 있음
    앞서 설명한 난수 초깃값을 지정할 수 있는 random_state 매개변수가 있음
    두번째로 행의 개수가 같은 여러개의 데이터셋을 넘겨서 같ㅇ느 인덱스를 기반으로 나눌 수 있음
    ex)데이터프레임이 레이블에 따라 여러개로 나뉘어 있을 때 아주 유용
    '''
    from sklearn.model_selection import train_test_split
    
    train_set, test_set=train_test_split(housing, test_size=0.2, random_state=42)

    지금까지는 순수한 무작위 샘플링 방식을 보았음

    데이터셋이 충분히 크다면 일반적으로 괜찮지만 그렇지 않다면 샘플링 편향이 생길 가능성이 큼

    계층적 샘플링 ex)전체인구를 대표할 수 있는 1000명을 선택하는데 미국 인구의 51.3%가 여성, 48.7%이 남성이면

    513명이 여자 487명이 남자여야 함 즉, 전체 인구는 계층이라는 동질의 그룹으로 나뉘고 테스트 세트가 전체 인구를 대표하도록 각 계층에서 올반른 수의 샘플을 추출.완전한 무작위 샘플링을 사용하여 설문을 질행하면 결과가 크게 편향

     

    중간 소득이 중간 주택 가격을 예측하는 데 매우 중요하다고 가정

    이 경우 테스트 세트가 전체 데이터셋에 있는 여러 소득 카테고리를 잘 대표해야함

    중간 소득이 연속적인 숫자형특성이므로 소득에 대한 카테고리 특성을 만들어야함

    중간 소득의 히스토그램을 좀 더 자세히 보면 대부분 1.5~6에 보여있지만 일부는 6을 넘기기도 함

    계층별로 데이터셋에 충분한 샘플 수가 있어야함 그렇지 않으면 편향이 발생할 것

    너무 많은 계층으로 나눈면 안 된다는 뜻이고 각 계층이 충분히 커야한다는 것

    #pd.cut()함수를 이용하여 카테고리를 5개를 가진 소득 카테고리 특성을 만듦
    housing['income_cat']=pd.cut(housing['median_income'],
                                 bins=[0.,1.5,3.0,4.5,6.,np.inf],
                                 labels=[1,2,3,4,5])
    housing['income_cat'].hist()

    이제 소득 카테고리를 기반으로 계층 샘플링 할 준비가 되었음

    사이킷런의 Startified ShuffleSplit을 사용할 수 있음

    #StartifiedShuffleSplit는 StartfiedKFold의 계층 샘플링과 ShuffleSplit의 랜덤 샘플링을 합친것
    #test_size와 train_size매개변수의 합을 1잏로 지정할 수도 있음
    from sklearn.model_selection import StratifiedShuffleSplit
    
    split=StratifiedShuffleSplit(n_splits=1,test_size=0.2, random_state=42)
    for train_index, test_index in split.split(housing, housing['income_cat']):
      strat_train_set=housing.loc[train_index]
      strat_test_set=housing.loc[test_index]
    #테스트세트에서 소득 카테고리의 비율을 살펴보겠음
    strat_test_set['income_cat'].value_counts()/len(strat_test_set)

    income_cat특성을 삭제해서 데이터를 원래상태로 되돌리겠음

    for set_ in(strat_train_set, strat_test_set):
      set_.drop('income_cat',axis=1, inplace=True)

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

    먼저 테스트 세트를 떼어놓았는지 확인하고 훈련 세트에 대해서만 탐색

    훈련세트가 매우 크면 조작을 간단하고 빠르게 하기 위해 탐색을 위한 세트를 별도로 샘플링할수도 있음

    예제는 크기가 작으므로 훈련세트 전체를 사용하겠음

    #그 전에 훈련 세트를 손상시키지 않기 위해 복사본을 만들어 사용
    housing=strat_train_set.copy()

    2.4.1 지리적 데이터 시각화

    더보기

    #지리정보(위도,경도)가 있으니 모든 구역을 산점도로 만들어 데이터를 시각화하는 것은 좋은생각

    housing.plot(kind='scatter',x='longitude',y='latitude')

    캘리포니아 지역을 잘 나타내지만 어떤 특별한 패턴을 찾기는 힘듦

    aplha 옵션을 0.1로 주면 데이터 포인트가 밀집된 영역을 잘 보여줌

    housing.plot(kind='scatter',x='longitude',y='latitude',alpha=0.1)

    베어 에어리아(Bay Area), 로스앤젤레스 근처, 샌디에고 같이 밀집된 지역이 눈에 잘 띄고 센트럴 밸리, 특히 새크라멘트와 프레즈노 근처를 따라 밀집된 지역이 긴 띠를 이루고 있음

    우리 뇌는 그림에서 패턴을 잘 인식해내지만 더 두드러진 패턴을 보려면 매개변수를 다양하게 조절

    주택가격을 나타내보겠음. 원의 반지름은 구역의 인구를 나타내고(매개변수 s),색상은 가격을 나타냄(매개변수 c)

    미리 정의된 컬러 맵 중 파란색(낮은가격)에서 빨간색으로(높은가격)까지 범위를 가지는 jet을 사용(매개변수 cmap)

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

    역시 주택가격은 지역(ex.바다와 인접한 곳)과 인구밀도에 관련이 매우 크다는 사실을 알 수 있음

    군집 알고리즘을 사용해 주요 군집을 찾고 군집의 중심까지의 거리를 재는 특성을 추가할 수 있음

    해안 근접성 특성이 유용할 수도 있지만 북부 캘포니아 기역의 해안가는 주택가격이 그리 높지 않아 간단한 규칙이 적용되기 어려움

    2.4.2 상관관계 조사

    데이터셋이 너무 크지 않으므로 모든 특성가의 표준 상관계수(피어슨의 r)를 corr()메서드를 이용해 쉽게 계산가능

    corr_matrix=housing.corr()
    #중간 주택가격과 다른 특성 사이의 상관관계 크기가 얼마나 되는지 살펴봄
    corr_matrix['median_house_value'].sort_values(ascending=False)

    1에 가까우면 강한 양의 상관관계를 가진다는 뜻/ -1에 가까우면 강한 음의 상관관계를 가진다는 뜻/0에 가까우면 관계X

    특성 사이에 상관관계를 확인하는 다른 방법은 숫자형 특성 사이엥 산점도를 그려주는 판다스의 scatter_matrix함수사용

    여기서는 숫자형 특성이 11개이므로 총 11^2인 121개의 그래프가 되어 너무 많기 때문에 중간 주택가격과 상관관계가 높아보이는 특성 몇개만 살펴보겠음

    from pandas.plotting import scatter_matrix
    
    attributes=['median_house_value','median_income','total_rooms','housing_median_age']
    scatter_matrix(housing[attributes],figsize=(12,8))

    대각선 방향은 자기 자신의 변수와 관련된 거라 필요X

    중간 주택 가격을 예측하는데 가장 유용할 것 같은 특성은 중간 소득이므로 자세히 살펴보겠음

    1. 상관관계가 매우 강함, 포인트들이 너무 널이 퍼져 있지 않음
    2. 앞서 본 가격 제한값 $500,000이 거의 직선에 가까운 형태를 보여줌, $450,000근처와 $350,000,$280,000에도 있고 그 아래도 조금 더 보임, 알고리즘이 이런 이상한 형태를 학습하지 않도록 해당구역을 제거하는 것이 좋음

    2.4.3 특성조합으로 실험

    머신 러닝 알고리즘에 주입하기 전, 정제해야할 이상한 데이터를 확인했고, 특성 사이에서 흥미로운 상관관계도 발견

    어떤 특성은 꼬리가 두꺼운 분포라서 데이터를 변경해야 할 것(ex.로그 스케일)

    머신러닝 알고리즘용 데이터를 준비하기 전에 마지막으로 해볼 수 있는 것은 여러 특성의 조합을 시도해보는 것

    ex)특정 구역의 방 개수는 얼마나 많은 가구수가 있는지 모른다면 그다지 유용하지 않음, 하지만 가구당 방 개수라면?

    ex)전체 침실 개수도 그 자체로는 유용하지 않음, 방 개수랑 비교하는게 나음

    ex)가구당 인원도 흥미로운 특성 조합일 것 같음

    #위의 예시
    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)

    새로운 bedrooms_per_room특성은 전체 방개수나 침실개수보다 중간 주택가격과의 상관관계가 높다

    ->당연히 침실/방의 비율이 낮은 집은 더 비싼 경향이 있음

    가구당 방 개수도 구역 내 전체 방 개수보다 더 유용

    ->당연히 더 큰집이 비쌈

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

    머신러닝 알고리즘을 위해 데이터 준비해야할 차례

    수동으로 하는 대신 함수로 만들어 자동화해야하는 이유

    1. 어떤 데이터셋에 대해서도 데이터 변환을 손쉽게 반복가능(ex.다음번에 새로운 데이터셋을 사용할 때)
    2. 향후 프로젝트에 사용할 수 있는 변환 라이브러리를 점진적으로 구축
    3. 실제 시스템에서 알고리즘에 새 데이터를 주입하기 전 변화시키는데 함수 사용가능
    4. 여러가지 데이터 변환을 쉽게 시도해볼 수 있고 어떤 조합이 가장 좋은지 확인하는데 편리

    먼저 원래 훈련 세트로 복원 하고 (strat_train_set을 다시 한번 복사) 예측변수와 타깃값에 같은 변형을 적용하지 않기 위해 예측변수와 레이블을 분리하겠음(drop()은 데이터 복사본을 만들면 strat_train_set에는 영향을 주지 않음)

    housing=strat_train_set.drop('median_house_value',axis=1)
    housing_labels=strat_train_set['median_house_value'].copy()

    2.5.1 데이터 정제

    대부분의 머신러닝 알고리즘은 누락된 특성을 다루지 못하므로 이를 처리할 수 있는 함수를 몇 개 만들겠음

    앞서 total_bedrooms 특성에 값이 없는 경우를 보았는데 이를 고쳐보겠음

    • 방법1 : 해당 구역 제거
    • 방법2 : 전체 특성 삭제
    • 방법3 : 어떤 값으로 채움(ex.0, 평균, 중간값 등)

    데이터 프레임의 drapna(), drop(),fillna()메서드를 이용해 이런 작업을 간단하게 처리함

    housing.dropna(subset=['total_bedrooms'])   #방법1
    housing.drop('total_bedrooms', axis=1)      #방법2
    median=housing['total_bedrooms'].median()   #방법3
    housing['total_bedrooms'].fillna(median, inplace=True)

    방법3을 선택하면 훈련 세트에서 중간값을 계산하고 누락된 값을 이 값으로 채워 넣어야함

    계산한 중간값을 저장하는 것을 잊지 말기

    나중에 시스템을 평가할 때 테스트세트에 있는 누락된 값과 시스템이 실제 운영될 때 새로운 데이터에 있는 누락된 값을 채워넣는데도 필요하기 때문

     

    사이킷런의 SimpleImputer는 누락된 값을 손쉰게 다루도록 해줌

    누락된 값을 특성의 중간값으로 대체한다고 지정하여 SimpleImputer의 객체를 생성

    from sklearn.impute import SimpleImputer
    
    imputer=SimpleImputer(strategy='median')

    중간값이 수치형 특성에만 계산될 수 있기 때문에 텍스트 특성인 ocean_proximity를 제외한 데이터 복사본 생성

    housing_num=housing.drop('ocean_proximity',axis=1)

    이제 imputer 객체의 fit()메서드를 사용해 훈련 데이터에 적용가능

    imputer.fit(housing_num)
    imputer.statistics_

    housing_num.median().values

    아재 학습된 imputer객체를 사용해 훈련 세트에 누락된 값을 학습한 중간값으로 바꿀 수 있음

    X=imputer.transform(housing_num)

    이 결과는 변형된 특성들이 들어 있는 평범한 넘파이 배열, 이를 다시 판다스 데이터 프레임으로 되돌릴 수 있음 

    housing_tr=pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
    더보기

    사이킷런의 설계 철학

    • 일관성 : 모든 객체가 일관되고 단순한 인터페이스를 공유
      • 추정기(estimator) : 데이터셋을 기반으로 일련의 모델 파라미터들을 추정하는 객체(ex.imputer객체는 추정기) 추정 자체는 fit()메서드에 의해 수행되고 하나의 매개변수로 하나의 데이터셋만 전달(지도학습알고리즘에서는 매개변수가 2개로 두번째 데이터셋은 레이블을 담고 있음) 추정과정에서 필요한 다른 매개변수들은 모두 하이퍼파라미터로 간주되고(ex.imputer의 strategy), 인스턴스 변수로 저장
      • 변환기(transformer) : 데이터셋을 변환하는 추정기를 변화기라고 함. 여기서도 API는 매우 단순. 변환은 데이터셋을 매개변수로 전달받은 transform()메서드가 수행.그리고 변환된 데이터셋을 반환함. 이런 변환은 일반적으로 imputer의 경우와 같이 학습된 모델 파라미터에 의해 결정. 모든 변환기는 fit()과 transform()을 연달아 호출하는 것과 동일한 fit_transform()메서드도 가지고 있음
      • 예측지(predictor)일부 추정기는 주어진 데이터셋에 대해 예측을 만들 수 있음 예를 들어 앞장에 나온 LinearRegression모델이 예측기. 어떤 나라의 1인당 GDP가 주어질 때 삶의 만족도를 예측. 예측기의 predict()메서드는 새로운 데이터셋을 받아 이에 사응하는 예측값을 반환. 또한 테스트세트(지도 학습 알고리즘이라면 레이블도 함께)를 이용해 예측의 품질을 측정하는 score()메서드를 가짐
    • 검사기능 : 모든 추정기의 하이퍼파라미터는 공개 인스턴스 변수로 직접 접근할 수 있고(ex.imputer.strategy)모든 추정기의 학습된 모델 파라미터도 접미사로 밑줄을붙여서 공개 인스턴스 변수로 제공(ex.imputer.statistics)
    • 클래스 남용 방지 : 데이터셋을 별도의 클래스가 아니라 넘파이 배열이나 사이파이 최소 행렬로 표현. 하이퍼파라미터는 보통의 파이썬 문자열이나 숫자
    • 조합성 : 기존의 구성요소를 최대한 재사용. 앞으로 보겠지만 예를 들어 여러 변환기를 연결한 다음 마지막에 추정기 하나를 배치한 pipeline추정기를 쉽게 만들 수 있음
    • 합리적인 기본 값 : 사이킷런은 일단 돌아가는 기본 시스템을 빠르게 만들 수 있도록대부분의 매개변수에 합리적인 기본값을 지정해두었음

    2.5.2 텍스트와 범주형 특성 다루기

    housing_cat=housing[['ocean_proximity']]
    housing_cat.head(10)

    가능한 값을 제한된 개수로 나열한 것이고 각 값은 카테고리를 나타냄 따라서 이 특성은 범주형 특성

    대부분의 머신러닝 알고리즘은 숫자를 다루므로 이 카테고리를 텍스트에서 숫자로 변환하겠음

    이를 위해 사이킷런의 OrdinalEncoder 클래스를 사용

    from sklearn.preprocessing import OrdinalEncoder
    ordinal_encoder=OrdinalEncoder()
    housing_cat_encoded=ordinal_encoder.fit_transform(housing_cat)
    housing_cat_encoded[:10]

    categories_ 인스턴스 변수를 사용해 카테고리 목록을 얻을 수 있음

    범주형 특성마다 카테고리들의 ID배열을 담은 리스트 반환

    ordinal_encoder.categories_

    원-핫 인코딩을 사용하여 숫자형으로 바꾸기

    새로운 특성을 더미(dummy)특성이라고 부르기도 함

    사이킷런은 범주의 값을 원-핫 벡터로 바꾸기 위한 OneHotEncoder클래스를 제공

    from sklearn.preprocessing import OneHotEncoder
    cat_encoder=OneHotEncoder()
    housing_cat_1hot=cat_encoder.fit_transform(housing_cat)
    housing_cat_1hot

    출력을 보면 넘파이 배열이 아니고 사이파이 희소행렬(SciPy sparse matrix)

    이는 수천개의 카테고리가 있는 범주형 특성일 경우 매우 효율적

    0을 모두 저장하는 것은 낭비이므로 0이 아닌 원소의 위치만 저장

    넘파이 배열로 바꾸려면 toarray()메서드 호출

    housing_cat_1hot.toarray()

    여기에서도 인코더의 categories_ 인스턴스 변수를 사용해 카테고리 리스트를 얻을 수 있음

    cat_encoder.categories_

    2.5.3 나만의 변환기

    사이킷런은 (상속이 아닌)덕 타이핑(duck typing)을 지원하므로 fit(self를 반환), transform(),fit_transform()메서드를 구현한 파이썬 클래스를 만들면 됨

    더보기

    덕 타이핑(duck typing)?

    상속이나 인터페이스 구현이 아니라 객체의 속성이나 메서드가 객체의 유형을 결정하는 방식

    마지막 메서드는 TranformerMixin을 상속하면 자동으로 생성

    BaseEstimator를 상속하면(그리고 생성자에 *args, **kargs를 사용하지 않으면) 하이퍼파라미터 뉴팅에 필요한 두 메서드(get_params()와  set_parmas())를 추가로 얻게 됨

    아래는 앞서 이야기한 조합 특성을 추가하는 간단한 변환기

    from sklearn.base import BaseEstimator, TransformerMixin
    
    rooms_ix, bedrooms_ix, population_ix, households_ix=3,4,5,6
    
    class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
      def __init__(self, add_bedrooms_per_room=True):
        self.add_bedrooms_per_room=add_bedrooms_per_room
      def fit(self, X, y=None):
        return self
      def transform(self, X):
        rooms_per_household=X[:,rooms_ix]/X[:,households_ix]
        population_per_household=X[:,population_ix]/X[:,households_ix]
        if self.add_bedrooms_per_room:
          bedrooms_per_room=X[:,bedrooms_ix]/X[:,rooms_ix]
          return np.c_[X,rooms_per_household, population_per_household,bedrooms_per_room]
        else:
          return np.c_[X, rooms_per_household, population_per_household]
    
    attr_adder=CombinedAttributesAdder(add_bedrooms_per_room=False)
    housing_extra_attribs=attr_adder.transform(housing.values)

    2.5.4 특성 스케일링

    데이터에 적용할 가장 중요한 변환 중 하나가 특성 스케일링(featrue scaling)

    머신러닝 알고리즘은 입력 숫자 특성들의 스케일이 많이 다르면 잘 작동하지 않음

    ex)주택가격 데이터,전체 방 개수의 범위는 6~39,320인 반면 중간 소득의 범위는 0~15

     

    모든 특성의 범위를 같도록 만들어주는 방법

    • min-max 스케일링(=정규화, normalization)
      • 가장 간단
      • 0~1 범위에 들도록 값을 이동하고 스케일을 조정하면 됨
      • 데이터에서 최솟값을 뺀 후 최댓값과 최솟값의 차이로 나누면 됨
      • 사이킷런에는 이에 해당하는 MinMaxScaler를 제공, 0~1범위를 원하지 않는다면 feature_range매개변수로 범위 변경 가능
    • 표준화(standardization)
      • 먼저 평균을 뺀 후(그래서 표준화를 하면 항상 평균이 0) 표준편차로 나누어 결과 분포의 분산이 1이 되도록
      • min-max스케일링과는 달리 표준화는 범위의 상한과 하한이 없어 어떤 알고리즘에서는 문제가 될수도(ex.신경망)
      • 표준화는 이상치에 영향을 덜 받음
      • 사이킷런에는 표준화를 위해 StandardScaler변환기가 있음
    더보기

    모든 변환기에서 스케일링은 전체 데이터가 아니고 훈련 데이터에 대해서만 fit()메서드를 적용, 그 후 훈련 세트와 테스트 세트,새로운 데이터에 대해 transform()메서드 사용

    2.5.5 변환 파이프라인

    사이킷런에는 연속된 변환을 순서대로 처리할 수 있도록 도와주는 Pipeline클래스가 있음

    #다음은 숫자 특성을 처리하는 간단한 파이프라인
    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_transformer(housing)

    2.6 모델 선택과 훈련

    2.6.1 훈련 세트에서 훈련하고 평가하기

    선형회귀모델을 훈련

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

    아주 정확한 예측은 아니나 작동은 함

    사이킷런의 mean_square_error함수를 사용해 정제 훈련세트에대한 이 회귀모델의 RMSE를 측정해보겠음

    from sklearn.metrics import mean_squared_error
    housing_prediction=lin_reg.predict(housing_prepared)
    lin_mse=mean_squared_error(housing_labels, housing_prediction)
    lin_rmse=np.sqrt(lin_mse)
    lin_rmse

    대부분 구역의 중간 주택가격은 120,000~265,000인 것을 감안하면 매우 만족스럽지 않은 결과

    모델이 훈련 데이터에 과소적합된 사례

    ->특성들이 좋은 예측을 만들만큼 충분한 정보를 제공하지 못했거나 모델이 충분히 강력하지 못하다는 사실

    ->과소적합을 해결하려면 더 강력한 모델을 선택하거나 훈련 알고리즘에 더 좋은 특성을 주입하거나 모델의 규제를 감소

    일단 더 복잡한 모델을 적용해보기

    from sklearn.tree import DecisionTreeRegressor
    
    tree_reg=DecisionTreeRegressor()
    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

    충격!

    오차가 전혀 없다니 말이 안 된다 이는 모델이 데이터에 너무 심하게 과대적합된 것으로 보임

    2.6.2 교차검증을 사용한 평가

    결정 트리 모델을 평가하는 방법으로 train_test_split함수를 사용하여 훈련세트/검증세트로 나누고 훈련시키고 검증

    훌륭한 대안으로 사이킷런의 k-fold cross-validation이 있음

    훈련세트를 fold라 불리는 10개의 서브셋으로 무작위로 분할

    결정트리모델을 10번 훈련하고 평가하는데 매번 다른 폴드를 선택해 평가에 사용, 나머지 9개는 훈련에 사용

    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)
    def display_scores(scores):
      print("점수:",scores)
      print("평균:",scores.mean())
      print("표준편차:",scores.std())
    
    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)
    display_scores(lin_rmse_scores)

    확실히 결정트리 모델이 과대적합되어 선형 회귀모델보다 성능이 나쁨

    마지막으로 RandomForestRegressor 모델을 한 번 더 시도

    from sklearn.ensemble import RandomForestRegressor
    forest_reg=RandomForestRegressor()
    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_scores=cross_val_score(forest_reg, housing_prepared, housing_labels,
                               scoring="neg_mean_squared_error",cv=10)
    
    forest_rmse_scores=np.sqrt(-forest_scores)
    display_scores(forest_rmse_scores)

    앞서 본 모델들보다는 훨씬 훌륭해보이지만 훈련세트에 대한 점수가 검증 세트에 대한 점수보다 훨씬 낮으므로 이보델도 여전히 과대적합되어있다는 것

    과대적합을 해결하는 방법은 모델을 간단히 하거나 제한(규제)하거나 더 많은 훈련 데이터를 모으는 것

    2.7 모델 세부 튜닝

    2.7.1 그리드 탐색

    가장 단순한 방법은 만족할 만한 하이퍼파라미터 조합을 찾을떄까지 수동으로 하이퍼파라미터를 조정하는 것

    ->하지만 매우 지루하고 많은 경우를 탐색하기에 시간이 부족할수도 있음

    대신 사이킷런의 GridSearchCV를 사용하는 것이 좋음

    탐색하고자 하는 하이퍼파라미터와 시도해볼 값을 지정하기만 하면 됨

    가능한 모든 하이퍼파라미터 조합에 대해 교차 검증을 사용해 평가하게 됨

    #예를 들어 RandomForestRegressor에 대한 최적의 하이퍼 파라미터 조합 탐색
    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()
    
    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)
    grid_search.best_params_

    최적의 추정기에 직접 접근할수도 있음

    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)

    2.7.2 랜덤 탐색

    그리드 탐색 방법은 이전 예제와 같이 비교적 적은 수의 조합을 탐구할 때 괜찮음

    하이퍼파라미터 탐색공간이 커지면 RandomizedSearchCV를 사용하는 편이 더 좋음

    RamdomizedSearchCV는 GrieSearchCV와 거의 같은 방식으로 사용하지만 가능한 모든 조합을 시도하는 대신 각 반복마다 하이퍼파라미터에 임의의 수를 대입하여 지정한 횟수만큼 평가

    • 랜덤 탐색을 n회 반복하도록 실행하면 하이퍼파라미터마다 각기 다른 n개의 값을 탐색
    • 단순히 반복 횟수를 조절하는 것만으로도 자원 제어

    2.7.3 앙상블 방법

    모델을 세밀하게 튜닝하는 방법은 최상의 모델을 연결해보는 것

    모델의 그룹이 최상의 단일 모델보다 더 나은 성능을 발휘할 때가 많음

    개개의 모델이 각기 다른 형태의 오차를 만들 때 그럼

    2.7.4 최상의 모델과 오차분석

    최상의 모델을 분석하면 문제에 대한 좋은 통찰을 얻을 수 있음

    RandomForestRegressor가 정확한 예측을 만들기 위한 각 특성의 상대적 중요도를 알려주는 코드

    feature_importances=grid_search.best_estimator_.feature_importances_
    feature_importances

    extra_attribs=['rooms_per_hhold','pop_per_hhold','bedrooms_per_room']
    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)

    위와 같은 정보를 바탕으로 덜 중요한 특성들을 제외가능

    2.7.5 테스트 세트로 시스템 평가하기

    테스트 세트엥서 최종 모델을 평가할 차례

    테스트 세트에서 예측변수와 레이블을 얻은 후 full_pipeline을 사용해 데이터 변환(fit_transformer() X transform() O, 왜냐면 테스트 세트에서 훈련하면 안되므로) 테스트세트에서 최종모델 평가

    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%의 신뢰구간을 계산

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

    이런 경우가 생기더라도 테스트 세트에서 성능수치를 좋게 하려고 하이퍼파라미터를 튜닝하려해선 안됨

    ->이렇게 해서 향상된 성능은 새로운 데이터에 일반화되기 어려움

    2.8 론칭, 모니터링, 시스템 유지보수

     

    '공부 > Hands-On Machine Learning' 카테고리의 다른 글

    chapter4 모델 훈련  (0) 2023.01.18
    Chapter3 연습문제 스팸분류기  (0) 2023.01.16
    chapter3 연습문제 - 타이타닉  (0) 2023.01.13
    chatper3. 분류  (0) 2023.01.06
    Chapter1.한눈에 보는 머신러닝  (2) 2022.11.01

    댓글

Designed by Tistory.