ML 세션 - Week5.트리 알고리즘
Aug 20, 2024
»
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=화이트와인 
- 알코올 도수와 당도, 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() 
- 결정트리는 위에서 아래로 자람
- 맨 위 노드를 루트노드, 맨 아래 노드를 리프 노드
-
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()
- 당도가 -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()
- 왼쪽에서 세 번째에 있는 노드만 음성 클래스가 더 많음 -> 이 노드에 도착해야 레드와인으로 예측
- 당도는 -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()
- 당도는 1.625이하여야 하고 알코올 두소는 11.025 이하인 것이 레드와인
-
print(dt.feature_importances_) # 특성 중요도 확인 # [0.12345626 0.86862934 0.0079144 ] 당도가 0.87정도로 중요도 가장 높음
검증세트
- 테스트세트를 사용하지 않고 과대적합 과소적합 판단하기 위해 사용

- 훈련세트로 훈련하고 검증 세트로 모델 평가 -> 가장 좋은 모델 선택 후 훈련세트랑 검증세트 합쳐서 훈련 후 테스트세트로 평가
-
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 -> 과대적합 보임
교차 검증
- 검증 세트를 뗴어 내어 평가하는 과정 반복 후 이 점수를 평균해 최종 검증 점수를 얻음

- 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
- 결정트리를 기반으로 만들어짐
- 비정형 데이터란 데이터베이스나 엑셀로 표현하기 어려운 데이터
- 신경망 알고리즘 사용
랜덤 포레스트
- 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만듦

- 훈련 데이터를 랜덤하게 만듦
- 부트스트랩 샘플
- 중복된 샘플 뽑을 수 있음
- 훈련 세트와 크기가 같음

- 부트스트랩 샘플
- 노드 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 최선의 분할을 찾음
- RandomForestClassifier은 기본적으로 전체 특성 개수의 제곱근만큼의 특성 선택

- 기본적으로 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
-