내맘대로 코딩

[ASAC] ML 머신러닝 - K-fold, KNN, RF(Random Forest), RGS, GS 본문

ASAC 빅데이터 분석가/ML

[ASAC] ML 머신러닝 - K-fold, KNN, RF(Random Forest), RGS, GS

Yoonjung 2024. 5. 9. 08:56

 

데이터 셋 분리 (train / test) - train_test_split 모듈

# train 데이터를 가지고... 바로 kaggle 의 test를 풀기에는.... 
# 좀 그래서... 
# ==> 자체적으로 train, test를 나눠서 하려고 함! train을 가지고! 
# FM : train (train / validation), test 
# validation : 내가 정답을 알고 있음!--> 자체 평가가 가능!! 
# test : 내가 정답을 가지고 있지 않음! --> kaggle에 업로드 해야만 확인!!
# ==> 데이터 셋을 뭔가 분리할 떄 : train_test_split 모듈! 
from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(X_titanic, #처리가 된 문제지
                                                  y_titanic, # 처리가 된 정답지 : 인코딩! 
                                                  test_size=0.2, # 얼마나 셋을 분리할지 결정하는 비율 (평가셋이 많아지면,학습데이터가 적어질 수 있으므로 개수를 잘 고려하기)
                                                  random_state=1234, # 재현성을 위해서.. 공통된 기준 
                                                  ### 정답지의 비율을 바탕으로 셋을 분리해줘! 
                                                  # --> 최대한 원본을 유지하면서 자체 평가 셋을 구성할 때 
                                                  stratify = y_titanic
                                                  ) 
  • 비율 고려
y_train.value_counts(normalize=True)

=> Survived
0    0.616573
1    0.383427
Name: proportion, dtype: float64
y_val.value_counts(normalize=True)

=> Survived
0    0.614525
1    0.385475
Name: proportion, dtype: float64

# --> y_titanic 의 원본 비율을 유지하면서... 
#     학습용과 평가용도 비율을 고려해서 샘플링을 함! 

y_titanic.value_counts(normalize=True)
=> Survived
0    0.616162
1    0.383838
Name: proportion, dtype: float64

 

K-fold

  • 기본 기능 : Training Data Set을 다시 K개로 분할을 하여서, 이것들을 돌려가면서 검증하는 것!
# train 데이터를 가지고... 
# HPT을 위해서 여러 모델을 할 떄 동일한 평가 기준을 적용하기 위해서 
# k-fold 의 구성을 재현성을 가지고 동일한 셋으로 평가
# ==> Random_sample 을 재현성을 구현하기 위해서 세팅!! 
  • K-fold 유형
# 1) 일반적인 k-fold 
from sklearn.model_selection import KFold

kfold = KFold(n_splits=5, random_state=1234, shuffle=True ) # 5개로 분리, 
# --> 내가 언제 어디서라도 cv = 5 대신에 cv=kflod를 부르면 
#     train이 동일한 상태라면.. 동일한 샘플들이 동일한 셋으로
# 2) 비율을 유지하는 strafied k-fold 
from sklearn.model_selection import StratifiedKFold

str_kflod= StratifiedKFold(n_splits=5, random_state=1234, shuffle=True )
# 3) 복원추출을 사용하는 repeated k-fold
# --> 샘플은 적어서 .. 일반적인 k-fold를 하면.. 셋당 샘플이 적어져서 
#     학습이 제대로 안 될 수 있다!! 
# --> 복원추출을 활용해서.. 데이터의 수를 펌핑!!! 
# --> 굳이 데이터가 많은 경우에서는 사용을 하지는 않음..... 
# ==> 데이터의 수가 좀 부족한데... k-fold를 사용하고 싶을 때 주로 쓰임
from sklearn.model_selection import RepeatedKFold

repeat_kfold = RepeatedKFold(n_splits=5, n_repeats=10, random_state=1234) # n_repeats : 몇번 복원추출할지 
# 4) 비율 유지 & 반복 repeated strafied k-fold 
from sklearn.model_selection import RepeatedStratifiedKFold

rskold = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=1234)
# --> 위의 목적은 성능 비교를 위해서 cv=5 하면.. 돌릴 때 마다 셋의 구성이 달라져서 비교가 애매
# ==> 재현성을 위해서 & 공통 비교 셋을 구성 
#     cv = kfold
# ++ 데이터셋만 동일하면 동일한 것들로 구성이 됨! 

 

  •  모델의 성능체크
# 앞으로 할 과정은 
# 1) 개별 모델에 대한 대략적인 성능 체크 : 기준이 되는 Baseline 체크 
# 2) 개별 모델에 대한 성능을 끌어올려야 함 HPT 
#   ==> RandomGridSerchCV : 크게 추리는 것 (좋은 후보군을 추림)
#   ==> GridSerchCV : 좀 더 디테일하게 탐색!! 
#   최근) Bayseian opt --> GridSearchCV 최적을 찾기도 함... 
# +++ **Bayseian opt 이 훨씬 효율적 => Bayseian에서 큰 범위로 세팅! 
# +++ 여러 플랫폼이 존재를 함
# 3) 실제 모의 평가를 진행해서 모델의 성능 체크!! --> val
# --> 개별 모델에 대한 성능 최적화가 잘 되었는지 체크! 
# --> 만약에... 성능이 별로인 경우 : 다시 2-3번 돌아가면서 체크... 
# ==> 최적의 f를 디테일하게 선택!!! (최적의 성능을 끌어내겠다)
# ----> knn_best, rf_best, xgboost_best etc... 
# 4) 최종 모델을 선택하는 과정!! 
# --> 4-1) 시도한 여러 모델 중에서 제일 best를 선정!! 
# --> 4-2) 했던 여러 모델을 종합해서 voting 으로 최종!! 
#          (Hard voting, Soft voting)
#          ++ Many & Diversity 

#     4-3) Stacking (대회 한정) 다시 재학습!! 
#     etc.............

# ++++ 옵션) 주어진 셋을 컬럼/셋을 랜덤 샘플링을 하거나, 
#            주요 변수 중심으로 셋을 Bootstraping + 여러 모델 + 최종 결론 
# +++ 다양한 시도적인 방법들이 대회에서는 있음! 
# (일반적인 논문에서 쓰는 스타일 FM, 
# -- 실제 현업 ( 그 중간쯤 입장.... 성능이 더 좋아야 할 수 도 있고, 해석이 더 중요할 수도 있음  + new)
# 대회에서는 뭐라도 다 사용을 함 : 제약은 없음)

# 5) --> 진짜 최종 실전 문제를 풀어야 함 test.csv를 풀어야 함 
# +++ kaggle 에 직접 업로드 
# 주의사항 : train 과 동일한 전처리!!! 
#           ** 기준 : train 셋의 기준으로... 
#           ** test 셋에서 unseen data 처리!! (인코딩)
# 모델을 학습하는 과정에서 필요한 모듈 
# 1) base-line : cross-val, 통으로 할 수도 있고... 
from sklearn.model_selection import cross_val_score

# 2) 평가 : 채점기 --> 출제자의 의도에 맞는 평가 방식으로 채점... 
#   ==> 분류의 정확성 : accuracy 를 평가지표로 사용하겠다!! 
#   ++ 목적에 따라서 다양하게 존재를 함 
#   참고) sklearn의 metrics 참조하여 평가지표를 쓰면 됨 

from sklearn.metrics import accuracy_score

# 3) HPT 을 통해 f 성능 극대화 (시간적인 부분이 많이 소요되는 과정!)
# ==> 직접 다 체크 : gridsearch + CV 
# ==> 랜덤하게 조합을 통해서 하자 : RandomGridSearch + CV 
#     (원래 원본 논문은 분포를 이용해서 랜덤값을 찾는데... 이제는 내가 주어진 값들의 조합에서만 랜덤하게 테스트함)
#     --> 주로 optuna (Bayesian opt) 로 사용 : *단 외부 패키지임 

from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
# ++ 왜 GS/RGS 아직까지 쓰는지? 
# optuna 같은 외부 패키지 같은 경우는 HPT를 하는 것과 최종 모델은 다시 학습시켜야함! 
# Sklearn GS/RGS 찾는 과정에서 제일 좋았던 모델이 저장이 되어서 
# 좋은 파라미터로 재학습을 하지 않고, 불러다 쓰면 됨!! 

 

1번 f(x)의 후보 : knn 모델

# 1번 f(x)의 후보 : knn 모델 
# 매뉴얼을 보고 진행필요 
# <https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html>
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_jobs = -1 )
# 아무 세팅을 안하는 기본 모델을 가지고 baseline을 체크... 
  • Baseline 체크
scores = cross_val_score( knn,
                         X_train, y_train,
                          cv = kfold, scoring="accuracy")
scores

#array([0.72727273, 0.70629371, 0.65492958, 0.68309859, 0.78169014])
for iter_count, acc in enumerate(scores):
    print(f"KNN의 {iter_count}시도 acc:{acc}")
print("KNN의 모델의 Acc Mean:", scores.mean())
print("KNN의 모델의 Acc std:", scores.std())

KNN의 0시도 acc:0.7272727272727273
KNN의 1시도 acc:0.7062937062937062
KNN의 2시도 acc:0.6549295774647887
KNN의 3시도 acc:0.6830985915492958
KNN의 4시도 acc:0.7816901408450704
KNN의 모델의 Acc Mean: 0.7106569486851178
KNN의 모델의 Acc std: 0.042908832008865715
# ---> 주어진 데이터와 전처리에 한해서...
#      대충 knn 을 돌려보면 대략 0.71정도는 나오겠구나(acc)
# --> 이제 할 일은 : KNN 더 디테일한 파라미터들을 세팅을 통해서 
#     성능이 0.71 보다는 좋게 나오는 것을 찾아보자!! 
#     + 0.78 정도는 넘으면 좋지 않을까.... 
# ==> HPT 에 Baseline 
# 참고/질문사항 정리) 
# random_state : 1234그냥 정의한 것임 (아무의미 없음) 학습시킬 떄 그때끄때 셋이 다르기 떄문에 
#                셋을 고정하기 위해서 번호로 정의해주는 것 
# -> 나중에 어떤 다른 사람이 학습할 때 1234로 학습하라고 하면, 그 셋으로 학습할 수 있음 / 재현성을 유지할 수 있음 
# 1234를 하면 뭐를 할지 정해짐 
  • HPT - RGS/GS
# HPT 에 사용할 수 있는 수단들....
# RGS/GS --> 매번 시행이 독립적!! 
# Bayesian --> 과거 시행의 결과를 활용해서 next!!! 
  • RGS
# RGS : 1) 테스트할 파라미터의 조합들 중에서, 임의로 선택! 
# ==> 테스트할 종류가 엄청 많을 때 사용이 가능함! 
# ==> 범위를 좀 좁힐 때 사용할 수 있음 
# 참고) scikitlearn에서 변질이 되었음... 

# ==> 내가 직접 테스트할 파라미터들을 세팅!! + 매뉴얼!! (매뉴얼에 맞춰서 하기!)
parameters = {
    # test 할 값들을 나열 : 직접 하거나 / range / 수학함수 math / 리스트 컴프리핸션
    "n_neighbors" : [1,3,5,7,9, 11, 13, 15, 17, 19], # 10개 
    "algorithm" : ["auto", "ball_tree", "kd_tree"], # 3개 
    # => 30개 테스트 
}

# RGS 
# 1) 사용할 모델 : HW 성능 관련된 것은 기본으로 설정! 
knn = KNeighborsClassifier(n_jobs = -1)
# 2) 몇번을 탐색할 것인가!! 
n_iter = 10 # 10개만 샘플링해서 찾아보겠다! (테스트할 파라미터 조합보다 경우의 수가 작으면 에러남)
# 3) RGSCV 세팅 : 마치 모델!! 
knn_kf_rgs = RandomizedSearchCV(knn, # 모델, 
                                # 테스트할 파라미터들.. dict로 테스트할 파라미터 조합들을 넘겨주면 됨
                                param_distributions = parameters, 
                                cv = kfold, # 5개로 분할해서 테스트
                                scoring = "accuracy",
                                n_jobs = -1, 
                                random_state = 1234, 
                                n_iter = n_iter)

# 4) 실제 데이터를 밀어넣어서 학습 
knn_kf_rgs.fit(X_train, y_train)

  • RGS 결과
# RGSCV에 실제 수행 결과 : 로그표 
knn_kf_rgs.cv_results_

# 30가지 조합에서 10가지만 표기 params : [{'n_neighbors': 15, 'algorithm': 'auto'} ~~~~~ 

 

# 했던 조합들 중에서 제일 좋았던 조합은 뭐냐??
# --> 선정 기준은 오로지 cv에 대한 평균 성능 지표의 순서!
knn_kf_rgs.best_params_

-> {'n_neighbors': 9, 'algorithm': 'auto'}

# 했던 조합들 중에서 제일 좋은 조합의 평균 성능??
knn_kf_rgs.best_score_

--> 0.7191273515217178

  • 실제 val로 자체 평가 및 채점
# --> 이게 괜찮네 라고 판단을 하면... 
# 실제 val으로 자체 평가!!!
knn_kf_rgs_best = knn_kf_rgs.best_estimator_ # 다시 재학습을 할 필요가 없는 부분!
# val에서 대한 문제를 풀어보자! 
knn_kf_rgs_ypred = knn_kf_rgs_best.predict(X_val)
# val 에 대한 채점 
knn_kf_rgs_acc = accuracy_score(y_val, knn_kf_rgs_ypred)
print("KNN Fkold RGS Acc:", knn_kf_rgs_acc)

-> KNN Fkold RGS Acc: 0.6871508379888268
  • GS (좀 더 디테일하게 탐색)
### --> RGS에서 제일 좋다고 하는 조합을 봤더니... 
#      좀더 디테일하게 탐색을 하고 싶다!! : GS 

# 가정 : RGS / Bayseian opt  찾은 후보들 중에서 디테일하게 탐색... 

# 1) 테스트할 파라미터 조합 : 몇 가지로 좀 줄여서... 
parameters = {
    "n_neighbors" : [7, 9, 11, 13] #overfit, 기준, underfit 2개 을 선택 

}
# 2) 사용할 모델을 선택 
knn = KNeighborsClassifier(n_jobs = -1)
# 3) GSCV : 내가 생각한 파라미터 조합은 다 수행! 
knn_kf_gs = GridSearchCV(knn,
                         param_grid = parameters, 
                         cv = kfold,
                         scoring = "accuracy",
                         n_jobs = -1)
# 4) 실제 데이터를 밀어넣어서 수행
knn_kf_gs.fit(X_train, y_train)

--> knn_kf_gs.cv_results_
  • GS 결과
knn_kf_gs.best_params_
--> {'n_neighbors': 9}

knn_kf_gs.best_score_
--> 0.7191273515217178

# ==> 아까 RGS로 찾은 n_neighbors = 9 가 제일 최적이구나!! 라고 결론을 지을 수 있음!
# KNN의 최종 best 모델은 knn_kf_rgs_best_ 
# ==> 기본 설정 + n=9 였을 떄임!!

 

2번 F(x)의 후보 모델 : RF (Random Forest)

# F(x)의 후보 모델2 : RF
# ref) <https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html>

from sklearn.ensemble import RandomForestClassifier
  • Baseline 체크
# RF의 BaseLine을 체크 

rf = RandomForestClassifier(n_jobs=-1, random_state=1234 )
scores = cross_val_score( rf,
                         X_train, y_train,
                          cv = kfold, scoring="accuracy")
scores

--> array([0.81118881, 0.84615385, 0.78873239, 0.76056338, 0.83098592])
for iter_count, acc in enumerate(scores):
    print(f"RF의 {iter_count}시도 acc:{acc}")
print("RF의 모델의 Acc Mean:", scores.mean())
print("RF의 모델의 Acc std:", scores.std())

--> 
RF의 0시도 acc:0.8111888111888111
RF의 1시도 acc:0.8461538461538461
RF의 2시도 acc:0.7887323943661971
RF의 3시도 acc:0.7605633802816901
RF의 4시도 acc:0.8309859154929577
RF의 모델의 Acc Mean: 0.8075248694967005
RF의 모델의 Acc std: 0.030379537772527453
  • HPT - RGS/GS
# RF으로 한다면... 대략 0.80 ~ 0.81 정도를 기준으로 
# --> 이 값보다 높은 성능이 나오면 좋을 디테일할 파라미터값을 찾아보자 HPT 
  • RGS
# RGS 
# 1) 모델 
rf = RandomForestClassifier(n_jobs=-1, random_state=1234 )
# 2) 변경하고자 하는 파라미터들의 조합 세팅 : dict 
parameters = {
    # RF : Bagging --> 데이터 증강 & Many 
    # 제일 중요한 파라미터 : 몇 개의 모델의 의견을 종합
    # --> 너무 큰 수를 사용하면 시간이 오래걸림!! 
    "n_estimators" : [10, 30, 50, 70, 100, 200, 300, 500, 1000, 2000],
    # 사용할 컬럼의 수 : 주어진 컬럼은 8개 이기 때문에 -> 테스트 3-7 
    "max_features" : [3,4,5,6,7],
    # 구체적인 개별 모델 : dt --> overfit
    # ==> 사용하는 데이터의 수와 컬럼의 수 
    "max_depth" : [2,3,4,5,6,7,8,10,20], 
    "min_samples_split" : [1,3,5,7,9]
    # ==> 개별 모델을 overfit 하게 되면... 
    #     Bias는 줄어들고, variance는 클 수 있음... -> 적당히 처리하며 컨트롤해야함!
}

# 3) RGS 에 얼마나 샘플링을 해서 시도할지 
n_iter = 20 
# 4) RGS -> 세팅 
rf_kfold_rgs = RandomizedSearchCV(rf,
                               param_distributions = parameters,
                               cv = kfold,
                               scoring = "accuracy", #평가지표 설정 
                               n_jobs = -1, 
                               random_state = 1234,
                               n_iter = n_iter)
# 5) 실제 데이터로 점검 
rf_kfold_rgs.fit(X_train, y_train)
# 참고) 중간 진행 상황이런 부분 볼 떄 verbose, verbosity etc 
  • RGS 결과
rf_kfold_rgs.best_score_
--> 0.8299911356249385

rf_kfold_rgs.best_params_
--> {'n_estimators': 50, 'min_samples_split': 7, 'max_features': 6, 'max_depth': 6}
  • 실제 val로 자체 평가 및 채점 (중간 평가)
# RGS 실제 어느정도인지 중간 평가 val 
rf_kf_rgs_best = rf_kfold_rgs.best_estimator_
rf_kf_rgs_ypred = rf_kf_rgs_best.predict(X_val)
rf_kf_rgs_acc = accuracy_score( y_val, rf_kf_rgs_ypred)
rf_kf_rgs_acc

--> 0.8324022346368715

X_val.shape 
--> (179, 8)

# --> Val의 셋의 숫자가 적어서... 몇 개로 성능이 제대로 나오지 않을 수 있음 
# ==> test 로 실제 적용을 합니다. 
# 현실적으로는 val, test 많은 양의 데이터로 평가를 합니다. 
# kaggle train 100, test 100 + 숨겨진 셋... 
  • GS
# 혹시 조금 더 좋은 파라미터 값은 없을까? 
# --> RGS에서 찾은 best 주변을 좀 더 디테일하게 탐색 : GS 

# rgs로 할 때, 
rf_kfold_rgs.best_params_
--> {'n_estimators': 50, 'min_samples_split': 7, 'max_features': 6, 'max_depth': 6}
rf = RandomForestClassifier(n_jobs=-1, random_state=1234 )
parameters = {
    "n_estimators" : [50, 100, 500],
    "max_features" : [6, 5, 7], # 6을 중심으로 5,7 
    "max_depth" : [6, 5,7], 
    # "min_samples_split" : [7, 6, 8]
} # --> 3*3*3 = 27개 시도 & cv=5 ==> 135개가 돌아가게 됨

rf_kf_gs = GridSearchCV(rf, 
                        param_grid = parameters,
                        cv=kfold, scoring = "accuracy", n_jobs=-1)
rf_kf_gs.fit(X_train, y_train)
  • GS 결과
rf_kf_gs.best_score_
--> 0.8313798877179159

rf_kf_gs.best_params_
--> {'max_depth': 7, 'max_features': 6, 'n_estimators': 50}
  • 실제 val로 자체 평가 및 채점
# val으로 다시 체크해보기!! 
rf_kf_gs_best = rf_kf_gs.best_estimator_
rf_kf_gs_ypred = rf_kf_gs_best.predict(X_val)
rf_kf_gs_acc = accuracy_score(y_val, rf_kf_gs_ypred)
rf_kf_gs_acc

 

—> 랜덤포레스트로 하는 걸로 할지, 그리드 서치로 할지 최종 선택 필요

 

모델 저장 및 불러오기

# 팀 프로젝트들을 하다보면...., 
# 실제 활용을 할 때에는 역할별로 맡은 모델을 작성하고, 공유!!! 
## --> 내가 만든 모델에 대한 저장 & 불러오는 과정!! 
## model save, model load 
## --> DL같은 경우도 모델에 대한 저장/불러오는 것은 중요!!! 
  • Joblib 패키지 사용하여 모델 저장 및 불러오기
# anaconda를 사용해서 하면 기본 패키지 중에 joblib 
import joblib # 이걸 기준으로 해야 문제가 없음 

# 1) 모델을 저장 : joblib.dump(어떤 모델, 어디다가 저장할지) 
# -- 어떤 모델을 세이브 할건지 지정하고, 어디다 저장할지 기록
# 참고) 파이썬에서는 주로 모델을 저장하는 파일 양식 pickle 타입 
#       ~~ .pkl 
# 참고) 모델이 큰 경우는 몇 백메가 ~ 몇 십 기가 ~~ 이상.... 
joblib.dump(rf_kf_gs_best, "rf_kf_gs_best.pkl") #피클파일로 세이브

# 2) 받은 모델을 불러와서 사용 : joblib.load(불러올 모델 파일 경로)
model_path = '/content/rf_kf_gs_best.pkl' 
rf_backup = joblib.load(model_path)
rf_backup

# ==> 체크 
(rf_kf_gs_best.predict(X_val) != rf_backup.predict(X_val)).sum()
--> 0
  • pickle 패키지 사용하여 모델 저장 및 불러오기
# 참고) 조원분들의 각기 머신들의 사양이 다 다름!! 
#      ==> OS에 차이가 발생을 함..... 모델 공유에 문제가 있을 수 있음... 
# 체크사항 1) scikit-learn 버전을 맞춰야 함! 
#            --> 안 맞으면 경고가 발생할 수도 있음! 
# 체크사항 2) 명확하게 pickle 패키지를 불러서 사용을 해야함!!! 
#          ==> joblib가 아니라, pickle 패키지를 불러서 사용 
# 체크사항 3) 불러오거나 저장하거나 할 때 : python의 파일 입출 IO open() 함수 사용필요

# 서로 다른 OS 상 모델 저장) 
import pickle 
file_name = "my_model.pkl"
pickle.dump(rf_kf_gs_best, open(file_name, "wb"))

# 서로 다른 OS 상 모델을 받을 때) 
import pickle
path = "/content/my_model.pkl"
new_model = pickle.load(open(path, "rb"))