ML 세션 - Week5.트리 알고리즘

» AI

로지스틱 회귀로 와인 분류하기

  •   import pandas as pd
    
      wine = pd.read_csv('https://bit.ly/wine_csv_data')
      wine.info() # 누락된 값 확인
      """
      wine.info()
      <class 'pandas.core.frame.DataFrame'>
      RangeIndex: 6497 entries, 0 to 6496
      Data columns (total 4 columns):
      #   Column   Non-Null Count  Dtype  
      ---  ------   --------------  -----  
      0   alcohol  6497 non-null   float64
      1   sugar    6497 non-null   float64
      2   pH       6497 non-null   float64
      3   class    6497 non-null   float64
      dtypes: float64(4)
      memory usage: 203.2 KB
      """ # 누락된 값 없음 -> 있으면 그 데이터 버리거나 평균값으로 채우기
      wine.describe() # class 0=레드와인, 1=화이트와인
    
  • alt text
  • 알코올 도수와 당도, pH 값의 스케일이 다름 -> 표준화
  •   data = wine[['alcohol', 'sugar', 'pH']].to_numpy() # 데이터프레임 넘파이 배열로 바꾸기
      target = wine['class'].to_numpy()
      # 훈련세트 테스트세트 나누기
      from sklearn.model_selection import train_test_split
      train_input, test_input, train_target, test_target = train_test_split(
          data, target, test_size=0.2, random_state=42) # 테스트 세트 20%로
    
      from sklearn.preprocessing import StandardScaler
      ss = StandardScaler()
      ss.fit(train_input)
      train_scaled = ss.transform(train_input) # 훈련세트 전처리
      test_scaled = ss.transform(test_input) # 테스트 세트 변환
    
      from sklearn.linear_model import LogisticRegression # 로지스틱 회귀 모델 훈련
      lr = LogisticRegression()
      lr.fit(train_scaled, train_target)
      print(lr.score(train_scaled, train_target)) # 0.7808350971714451
      print(lr.score(test_scaled, test_target)) # 0.7776923076923077
      # 과소적합 가능성 있음
    
  • 회귀모델은 가시적으로 한 눈에 파악하기 어려움 -> 결정트리 이용

결정트리

  • 스무고개와 같다
  • 질문을 하나씩 던져서 정답을 맞춰가는 것
  • 사이킷런의 DecisionTreeClassifier 클래스 사용
  •   from sklearn.tree import DecisionTreeClassifier
    
      dt = DecisionTreeClassifier(random_state=42)
      dt.fit(train_scaled, train_target)
    
      print(dt.score(train_scaled, train_target)) # 0.996921300750433
      print(dt.score(test_scaled, test_target)) # 0.8592307692307692
      # 과대적합 보임
    
      import matplotlib.pyplot as plt
      from sklearn.tree import plot_tree
    
      plt.figure(figsize=(10,7))
      plot_tree(dt)
      plt.show()
    
  • alt text
  • 결정트리는 위에서 아래로 자람
  • 맨 위 노드를 루트노드, 맨 아래 노드를 리프 노드
  •   plt.figure(figsize=(10,7))
      plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH']) 
      # max_depth로 트리 깊이 제한해서 출력 1-> 루트노드 제외하고 하나 노드 더
      # filled 매개변수로 노드 색 다르게
      # feature_names로 특성의 이름 전달해서 나누는 기준 확인
      plt.show()
    
  • alt text
    • 당도가 -0.239 이하면 왼쪽 노드, 아니면 오른쪽 노드
    • value 값을 보면 대부분의 화이트와인 샘플 오른쪽 노드로 이동 -> 색 진해짐
    • 만약 여기서 트리 성장을 멈출 경우 왼오 둘다 양성 클래스로 예측
    • gini란 지니 불순도를 의미
      • \[지니 불순도 = 1-(음성 클래스 비율^2 + 양성 클래스 비율^2)\]
      • 어떤 노드의 두 클래스 비율이 1:1이라면 지니불순도는 0.5로 가장 커짐
      • 노드에 하나의 클래스만 있다면 지니불순도는 0이 되어 순수 노드가 됨
  • 결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리 성장
    • 부모의 불순도 - (왼쪽 노드 샘플 수 / 부모의 샘플 수) * 왼쪽 노드 불순도 - (오른쪽 노드 샘플 수 / 부모의 샘플 수) * 오른쪽 노드 불순도
  • 이러한 차이를 정보 이득(Information Gain)이라 함
  • DecisionTreeClassifier 클래스에선 criterion=’entropy’로 엔트로피 불순도 사용
    • \[엔트로피 불순도 = - 음성 클래스 비율\times \log_2 (음성 클래스 비율) - 양성 클래스 비율\times \log_2 (양성 클래스 비율)\]

가지치기

  • 트리의 최대 깊이(max_depth) 지정
  •   dt = DecisionTreeClassifier(max_depth=3, random_state=42)
      dt.fit(train_scaled, train_target)
      print(dt.score(train_scaled, train_target)) # 0.8454877814123533
      print(dt.score(test_scaled, test_target)) # 0.8415384615384616
    
      plt.figure(figsize=(20,15))
      plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
      plt.show()
    
  • alt text
    • 왼쪽에서 세 번째에 있는 노드만 음성 클래스가 더 많음 -> 이 노드에 도착해야 레드와인으로 예측
      • 당도는 -0.802이하여야 하고 알코올 두소는 0.454 이하인 것이 레드와인
  • 특성값의 스케일은 결정 크리 알고리즘에 아무런 영향을 미치지 않아서 표준화 전처리를 할 필요가 없음
  •   dt = DecisionTreeClassifier(max_depth=3, random_state=42)
      dt.fit(train_input, train_target) # 전처리 하기 전 훈련세트 테스트세트로 훈련
      print(dt.score(train_input, train_target)) # 0.8454877814123533
      print(dt.score(test_input, test_target)) # 0.8415384615384616 -> 전처리 한 후랑 결과 같음
    
      plt.figure(figsize=(20,15))
      plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
      plt.show()
    
  • alt text
    • 당도는 1.625이하여야 하고 알코올 두소는 11.025 이하인 것이 레드와인
  •   print(dt.feature_importances_) # 특성 중요도 확인
      # [0.12345626 0.86862934 0.0079144 ] 당도가 0.87정도로 중요도 가장 높음
    

검증세트

  • 테스트세트를 사용하지 않고 과대적합 과소적합 판단하기 위해 사용
  • alt text
  • 훈련세트로 훈련하고 검증 세트로 모델 평가 -> 가장 좋은 모델 선택 후 훈련세트랑 검증세트 합쳐서 훈련 후 테스트세트로 평가
  •   sub_input, val_input, sub_target, val_target = train_test_split(
          train_input, train_target, test_size=0.2, random_state=42)
      print(sub_input.shape, val_input.shape) # (4157, 3) (1040, 3) 훈련세트 4157개, 검증세트 1040개
    
      from sklearn.tree import DecisionTreeClassifier
      dt = DecisionTreeClassifier(random_state=42)
      dt.fit(sub_input, sub_target) #훈련세트로 훈련
      print(dt.score(sub_input, sub_target)) # 0.9971133028626413
      print(dt.score(val_input, val_target)) # 0.864423076923077 -> 과대적합 보임
    

교차 검증

  • 검증 세트를 뗴어 내어 평가하는 과정 반복 후 이 점수를 평균해 최종 검증 점수를 얻음
  • alt text
  • k-폴드 교차 검증(주로 5나 10)
  • 사이킷런 cross_validate() 사용(기본적으로 5-폴드 교차 검증 수행)
  •   from sklearn.model_selection import cross_validate
      scores = cross_validate(dt, train_input, train_target)
      print(scores)
      """
      {'fit_time': array([0.00931716, 0.00749564, 0.00773239, 0.00731683, 0.00710797]), 
      'score_time': array([0.00109315, 0.00111032, 0.00101209, 0.00106931, 0.00115085]), 
      'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
      """ # 훈련하는 시간, 검증하는 시간, 검증 폴드의 교차검증 최종 평균 점수
    
  • 훈련 세트 섞으려면 분할기 지정 필요(train_test_split으로 섞은 후 사용했기에 지금은 필요 x)
    • 회귀일 경우 기본적으로 KFold, 분류일 경우 StratifiedKFold 사용
    •   from sklearn.model_selection import StratifiedKFold
        scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
        splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42) # n_splits매개변수는 몇 폴드 교차검증을 할 지 결정
        scores = cross_validate(dt, train_input, train_target, cv=splitter)
      

하이퍼파라미터 튜닝

  • 하이퍼파라미터란 사용자 지정 파라미터
  • 라이브러리가 제공하는 기본값을 그대로 사용해 훈련 후 검증 세트점수나 교차 검증을 통해 매개변수를 조금씩 변화
  • 사이킷런에서 제공하는 GridSearchCV 사용
    • 하이퍼파라미터 탐색과 교차 검증 한 번에 수행
    •   from sklearn.model_selection import GridSearchCV
        params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
      
        gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1) # 결정트리 클래스 객체생성 후 그리드 서치 객체로 바로 전달
      
        gs.fit(train_input, train_target) # 결정 트리 모델 min_impurity_decrease 값을 바꿔가며 5번 실행
        # cv매개변수 기본값이 5라 min_impurity_decrease 값마다 5-폴드 교차 검증 수행 -> 5 * 5 = 25개의 모델 훈련
        # n_jobs매개변수로 병렬 실행에 사용할 CPU 코어 수 지정(기본값 1, -1로 지정 시 모든 코어 사용)
      
    • 훈련이 끝나면 모든 모델 중 검증 점수가 가장 높은 모델의 매개변수 조합으로 자동으로 모델 다시 훈련
    •   dt = gs.best_estimator_ # 검증 점수 가장 높은 모델의 매개변수 조합
        print(dt.score(train_input, train_target)) # 0.9615162593804117
      
        print(gs.best_params_) # 그리드 서치로 찾은 최적의 매개변수
        # {'min_impurity_decrease': 0.0001}
      
        print(gs.cv_results_['mean_test_score']) # 각 매개변수에서 수행한 교차 검증의 평균 점수
        # [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
      
        best_index = np.argmax(gs.cv_results_['mean_test_score']) # 가장 큰 값의 인덱스 추출
        print(gs.cv_results_['params'][best_index]) # 인덱스 이용해 params 키에 저장된 매개변수 출력
        # {'min_impurity_decrease': 0.0001}
      
    •   params = {'min_impurity_decrease': np.arange(0.0001, 0.001, 0.0001),
          'max_depth': range(5, 20, 1),
          'min_samples_split': range(2, 100, 10)
        }
        # arrange() 함수는 첫 번째 매개변수 값에서 시작해 두 번째 매개변수에 도달할 때까지 세 번째 매개변수를 계속 더한 배열을 만듦
        # 0.0001에서 시작해서 0.001이 될 때까지 0.0001을 계속 더함 -> 두 번째 매개변수는 포함되지 않으므로 배열의 원소는 총 9개
      
        # range()는 arrange()와 비슷하지만 정수만 사용 가능
        # max_depth는 5에서 20까지 1씩 증가 -> 15개
        # min_samples_split은 2에서 100까지 10씩 증가 -> 10개
      
        # 교차 검증 횟수 9 * 15 * 10 = 1350번 * 5-폴드 교차 검증 -> 6750번
      
        gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
        gs.fit(train_input, train_target)
        print(gs.best_params_)
        # {'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}
        print(np.max(gs.cv_results_['mean_test_score'])) # 0.8683865773302731
      

랜덤 서치

  • 매개변수 값의 범위나 간격 정하기
  • 매개변수를 샘플링할 수 있는 확률 분포 객체 전달
  •   from scipy.stats import uniform, randint # 수치 계산 전용 라이브러리
      # uniform은 실숫값을 뽑고 randint는 정숫값 뽑음
      rgen = randint(0, 10)
      rgen.rvs(10) # array([4, 7, 6, 8, 9, 3, 8, 3, 1, 4])
    
      ugen = uniform(0, 1)
      ugen.rvs(10)
      # array([0.07156624, 0.51330724, 0.78244744, 0.14237963, 0.05055468, 0.13124955, 0.15801332, 0.99110938, 0.08459786, 0.92447632])
    
  • 샘플링 횟수는 시스템 자원이 허락하는 한 최대한 크게
  •   params = {'min_impurity_decrease': uniform(0.0001, 0.001),
              'max_depth': randint(20, 50),
              'min_samples_split': randint(2, 25),
              'min_samples_leaf': randint(1, 25),
              } 
              # 'min_impurity_decrease'는 0.0001에서 0.001 사이 실숫값 샘플링
              # 'max_depth'는 20에서 50 사이 정수
              # 'min_samples_split'는 2에서 25 사이 정수
              # 'min_samples_leaf': 어떤 노드가 분할하여 만들어질 자식 노드의 샘플 수가 이 값보다 작을 경우 분할하지 않음
              # 'min_samples_leaf'는 1에서 25 사이 정수
    
  •   from sklearn.model_selection import RandomizedSearchCV
    
      gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params,
                              n_iter=100, n_jobs=-1, random_state=42) # n_iter로 샘플링 횟수 지정
      gs.fit(train_input, train_target)
    
      print(gs.best_params_) # 최적의 매개변수 조합
      # {'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}
    
      print(np.max(gs.cv_results_['mean_test_score'])) # 최고의 교차 검증 점수
      # 0.8695428296438884
    
      dt = gs.best_estimator_
      print(dt.score(test_input, test_target)) # 최적 모델로 테스트세트 성능 확인 -> 0.86
    

정형 데이터와 비정형 데이터

  • 정형 데이터란 어떤 구조로 되어있는 데이터
    • csv, 데이터베이스, 엑셀에 저장하기 쉬움
    • 앙상블 학습 알고리즘 best
      • 결정트리를 기반으로 만들어짐
  • 비정형 데이터란 데이터베이스나 엑셀로 표현하기 어려운 데이터
    • 신경망 알고리즘 사용

랜덤 포레스트

  • 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만듦
  • alt text
  • 훈련 데이터를 랜덤하게 만듦
    • 부트스트랩 샘플
      • 중복된 샘플 뽑을 수 있음
      • 훈련 세트와 크기가 같음
      • alt text
  • 노드 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 최선의 분할을 찾음
    • RandomForestClassifier은 기본적으로 전체 특성 개수의 제곱근만큼의 특성 선택
    • alt text
  • 기본적으로 100개의 결정 트리를 이렇게 훈련
  • 분류일 때는 각 트리의 클래스 별 확률을 평균해 가장 높은 확률을 가진 클래스 예측으로 삼음
  • 회귀일 때는 각 트리의 예측을 평균
  • 랜덤하게 선택한 샘플과 특성을 사용하기 때문에 과대적합 방지, 검증 세트와 테스트세트에서 안정적인 성능 얻음
  •   from sklearn.model_selection import cross_validate
      from sklearn.ensemble import RandomForestClassifier
      rf = RandomForestClassifier(n_jobs=-1, random_state=42) 
      scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
      print(np.mean(scores['train_score']),np.mean(scores['test_score']))
      # 0.9973541965122431 0.8905151032797809
    
  •   rf.fit(train_input, train_target)
      print(rf.feature_importances_) # 특성 중요도 계산
      # [0.23167441 0.50039841 0.26792718]
    
  • 특성의 일부를 랜덤 선택해 결정 트리를 훈련하기 때문에 한 특성에 과도하게 집중하지 않고 많은 특성이 훈련에 기여할 기회 얻음 -> 과대적합 줄이고 일반화 성능 높임
  • RandomForestClassifier에는 자체적으로 모델 평가해 점수 얻는 기능 있음
    • OOB 샘플이라고 부트스트랩 샘플에 포함되지 않고 남는 샘플 사용해 평가
    •   rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
        rf.fit(train_input, train_target)
        print(rf.oob_score_) # 0.8934000384837406
      

엑스트라 트리

  • 랜덤 포레스트와 유사
    • 기본적으로 100개의 결정 트리 훈련
    • 결정 트리가 제공하는 매개변수 대부분 지원
    • 일부 특성 랜덤하게 선택해 노드 분할하는 데 사용
  • 랜덤 포레스트와의 차이
    • 부트스트랩 샘플 사용 x
    • 노드를 무작위로 분할
    • 계산 속도가 빠름
    • 무작위성이 더 커서 랜덤 포레스트보다 더 많은 결정트리 훈련
  • splitter=’random’
  • 사이킷런의 ExtraTreesClassifier 사용(회귀는 ExtraTreesRegressor)
  •   from sklearn.ensemble import ExtraTreesClassifier
      et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
      scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
    

그레이디언트 부스팅

  • 깊이가 얕은 결정트리 사용해 이전 트리의 오차를 보완하는 방식으로 앙상블
    • 손실함수의 낮은 곳으로 천천히 이동하기 위해
  • 사이킷런의 GradientBoostingClassifier(회귀는 GradientBoostingRegressor)
    • 기본적으로 깊이가 3인 결정 트리 100개 사용
    • 과대적합에 강하고 높은 일반화 성능 가짐
  • 경사하강법 사용해 트리 앙상블에 추가
  • 분류에서는 로지스틱 손실 함수, 회귀에선 평균제곱오차함수 사용
    • 학습률 매개변수로 속도 조절 (기본값 0.1)
  •   from sklearn.ensemble import GradientBoostingClassifier
    
      gb = GradientBoostingClassifier(random_state=42)
      scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
      print(np.mean(scores['train_score']), np.mean(scores['test_score'])) 
      # 0.8881086892152563 0.8720430147331015
    
      gb.fit(train_input, train_target)
      print(gb.feature_importances_) # [0.15872278 0.68010884 0.16116839]
      # 랜덤 포레스트보다 일부 특성에 더 집중
    
  • subsample 매개변수의 기본값은 1(전체 훈련 세트 사용)
    • 1보다 작으면 전체 훈련 세트의 일부만 사용
    • 경사하강법 단계마다 일부 샘플을 랜덤하게 선택해 진행하는 확률적 경사 하강법이나 미니배치 경사 하강법이랑 비슷
  • 랜덤 포레스보다 조금 더 높은 성능
  • 순서대로 트리를 추가해서 훈련 속도가 느림
  • n_jobs 매개변수 없음

히스토그램 기반 그레이디언트 부스팅

  • 그레이디언트 부스팅 속도와 성능 개선
  • 정형 데이터를 다루는 알고리즘
  • 입력 특성을 256개의 구간으로 나눔
    • 노드 분할 시 최적의 분할을 매우 빠르게 찾음
  • 누락 특성이 있어도 전처리 필요 없음(알아서 해줌)
  • 사이킷런의 HistGradientBoostingClassifier 사용
    • 기본 매개변수에서 안정적인 성능
    • max_iter로 부스팅 반복 횟수 지정
    •   from sklearn.ensemble import HistGradientBoostingClassifier
        hgb = HistGradientBoostingClassifier(random_state=42)
        scores = cross_validate(hgb, train_input, train_target, return_train_score=True, n_jobs=-1)
        print(np.mean(scores['train_score']), np.mean(scores['test_score'])) # 0.9321723946453317 0.8801241948619236
      
        hgb.fit(train_input, train_target)
        print(rf,feature_importances_) # [0.23167441 0.50039841 0.26792718]
        # 다양한 특성 골고루 잘 평가
        hgb.score(test_input, test_target) # 0.872307692307692 
      
  • XGBoost 라이브러리도 good
    • 다양한 부스팅 알고리즘 지원
    • tree_method 매개변수 hist로 지정하면 히스토그램 기반 그레이디언트 부스팅 사용 가능
    •   from xgboost import XGBClassifier
        xgb = XGBClassifier(tree_method='hist', random_state=42)
        scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)
        print(np.mean(scores['train_score']), np.mean(scores['test_score'])) # 0.9555033709953124 0.8799326275264677
      
  • 마이크로소프트의 LightGBM 라이브러리
    •   from lightgbm import LGBMClassifier
        lgb = LGBMClassifier(random_state=42)
        scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)
        print(np.mean(scores['train_score']), np.mean(scores['test_score'])) # 0.935828414851749 0.8801251203079884