잇연

[sesac LLM] day32-240216 ML 하이퍼파라미터 조정 방법, Encoding 본문

SeSac AI교육 수료(LLM 개발자 강의)/ML 머신러닝

[sesac LLM] day32-240216 ML 하이퍼파라미터 조정 방법, Encoding

잇연 2024. 2. 16. 15:46
반응형

 

오늘의 학습 목표 : 하이퍼파라미터 조정방법을 알아보자./ Label-Encoding, One-hot Encoding을 알아보자
목차 
Hyperparameter-optimization 의 개념
Hyperparameter-optimization 실습
(1) 수동튜닝
(2) GridSearchCV
(3) RandomSearchCV
(4) Optuna
(5) Raytune
Encoding

Hyperparameter Optimization의 개념

하이퍼파라미터 최적화

하이퍼파라미터 최적화(Hyperparameter Optimization)는 머신러닝 모델의 성능을 최대화하기 위해 최적의 하이퍼파라미터를 찾는 과정을 말한다. 하이퍼파라미터는 모델 학습 전에 설정되는 파라미터로, 학습 과정 자체에 의해 조정되지 않는다. 

하이퍼파라미터와 하이퍼파라미터

파라미터 : 파라미터는 학습과정에서 데이터에 의해 자동으로 결정되며 연구자가 조정할 수 없는 값

하이퍼파라미터 : 하이퍼파라미터는 머신러닝 모델을 생성할 떄 사용자(연구자)가 직접 설정하는 값으로 이를 어떻게 설정하느냐에 따라 모델의 성능이 달라진다. 딥러닝에서 중간층(hidden layer),  학습률(learning rate), 배치 크기(batch size), 에포크 수(epochs number) 등이 있다. 

 

 

Hyperparameter-optimization 실습 - 하이퍼파라미터 튜닝으로 모델 성능개선

데이터 처리는 더보기에서 확인 

더보기

1. 데이터 정보

데이터셋 출처
Pima Indians Diabetes Database | Kaggle
https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_diabetes.html
데이터 구성
Pregnancies : 임신 횟수
Glucose : 2시간 동안의 경구 포도당 내성 검사에서 혈장 포도당 농도
BloodPressure : 이완기 혈압 (mm Hg)
SkinThickness : 삼두근 피부 주름 두께 (mm), 체지방을 추정하는데 사용되는 값
Insulin : 2시간 혈청 인슐린 (mu U / ml)
BMI : 체질량 지수 (체중kg / 키(m)^2)
DiabetesPedigreeFunction : 당뇨병 혈통 기능
Age : 나이
Outcome : 768개 중에 268개의 결과 클래스 변수(0 또는 1)는 1이고 나머지는 0입니다.

당뇨병 환자에 대한 대이터이고, outcome 은 여성이 5년 이내에 당뇨병에 걸렸는지 여부를 나타내는 컬럼이므로, 'outcome' 컬럼을 모델이 예측해야 할 정답 레이블로 설정한다.

 

2. 필요한 라이브러리 로드

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

 

3. 데이터로드 

df = pd.read_csv("/경로/diabetes.csv")
df.shape
df,head(2)



4. 전처리

4-1) null값 처리하고 중앙값으로 대체

# null 값을 -> 0으로
df["Insulin_nan"] = df["Insulin"].replace(0, np.nan)

#outcome컬럼이 0일때와 1일때의 insulin_nan 값 기술통계 비교 
in_desc = df.groupby("Outcome")["Insulin_nan"].describe()
in_desc

 

in_desc.loc[0, "50%"] #0일때의 중앙값  #결과 : 102.5
in_desc.loc[1, "50%"] #1일때 중앙값  #결과 : 169.5

이 중앙값들을 대체해서 넣어야함.

df["Insulin_filled"] = df["Insulin"]

# 조건, 담을컬럼명
df.loc[(df["Insulin"]==0) & (df["Outcome"]==0), "Insulin_filled"] = in_desc.loc[0, "50%"]
df.loc[(df["Insulin"]==0) & (df["Outcome"]==1), "Insulin_filled"] = in_desc.loc[1, "50%"]

이렇게 대체해서 담아주면된다. 

 

[1] 원본에서

[2] insulin 0 값은 nan처리를 해주었고

[3] outcome을 기준으로 0과 1인 행들의 중앙값을 각각 대체해주었다. 이 세 기술통계를 비교해보자.

1) insulin컬럼 (원본)

 

2) insulin_nan (0값은 nan처리)

 

3) insulin_filled (중앙값으로 대체한 전처리 이후)

 

  1) 처음 원본은 인슐린이 0인 값을 포함해서 카운트 했기 때문에 count를 보면 전체 행 768이 다 들어갔음을 알 수 있고, 평균은 0인컬럼 68, 1인컬럼은 100이다. 중앙값은 0인컬럼이 39,1인컬럼이 0이다. 이걸 보면 outcome이 1인 행들은 인슐린이 0인 데이터가 반 이상이 된다는 것을 알 수 있다. 

  2) insulin이 0인 결측값들은 nan으로 채운 기술통계를 보자. count에서 빠져있음을 확인할 수 있다. 0값인 것들을 제거해주니까 평균은 outcome이 0인것은 130, outcome이 1인 것은 206이 되었다. 중앙값은 각각 102, 169 가 되었다. 이렇게 nan처리를 하고 제거해주기도 한다. 하지만 데이터 하나하나가 소중한 분석에서는...결측치를 중앙값이나 평균값으로 대체하곤한다. 

  3) 마지막으로 중앙값으로 대체한 insulin_filled 컬럼의 기술통계를 보자. 모든 값의 중앙값으로 대체한 것이 아니라, outcome을 기준으로 중앙값을 확인하고 대체하였다. count를 보면 전체 행이 다 들어갔음을 확인할 수 있고, 평균은 각각 117, 187, 그리고 중앙값은 102, 169가 나왔다. 

중앙값이 평균보다 낮기 때문에, 2)보다 3)에서의 평균이 더 낮아졌음을 확인할 수 있다.

 

 

4-2) 학습, 예측 데이터셋 나누기

# label_name 이라는 변수에 예측할 컬럼 이름을 담습니다.
label_name = "Outcome"

# 학습, 예측에 사용할 컬럼을 지정
# df의 컬럼을 list로 변형하고, outcome, Insulin, Insulin_nan 컬럼을 제거
# feature_names
feature_names = df.columns.tolist()
feature_names.remove(label_name)
feature_names.remove("Insulin")
feature_names.remove("Insulin_nan")
feature_names
# X, y를 만들어 줍니다.
X = df[feature_names]
y= df[label_name]
X.shape, y.shape
X와 y를 나누기



4-3) 학습, 예측 데이터셋 만들기

# train_test_split 으로 무작위로 데이터셋을 train 과 test 로 나눕니다.
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=True, stratify=y)

stratify=y는 데이터가 무작위로 섞이는데 한쪽에만 클래스가 몰아지지 않고, 이전에 있던 클래스  비율로 클래스를 유지시켜달라는 뜻

훈련 데이터와 테스트 데이터로 나눌 때 원본 데이터셋의 타겟 변수 y (outcome)의 클래스 비율을 유지하도록 도와줍니다. 이 옵션을 사용함으로써, 각 클래스의 비율이 원본 데이터셋에서와 같이 훈련 세트와 테스트 세트에 골고루 분포되도록 할 수 있습니다.

train_test_split 함수 쓸 때는 반드시 훈련데이터, 정답데이터, test_size 몇퍼센트로 자를지 작성해줘야하고, 옵션으로 랜덤성 고정할건지, 무작위로 섞을건지, stratify 를 써주면 좋다. 

X와 y를 8대 2로 나눠서 X_train, X_test, y_train, y_test 데이터셋으로 나누기



4-4) 확인

print(y_train.value_counts(normalize=True) * 100)
print(y_test.value_counts(normalize=True) * 100)
#위 아래 비율 확인

 

머신러닝 모델 생성을 위한 import

디시전트리 모델 import

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_predict

 

하이퍼파라미터 조정

(1) 수동 튜닝 

수동으로 튜닝하는 방법

더보기

하이퍼파라미터 조정 방법

  • 수동튜닝
    • 만족할만한 하이퍼파라미터들의 조합을 찾을 때까지 수동으로 조정
  • GridSearchCV()
    • 시도할 하이퍼파라미터들을 지정하면, 모든 조합에 대해 교차검증 후 가장 좋은 성능을 내는 하이퍼파라미터 조합을 찾음
    • Search Space를 잘 정의할 경우 최적해를 잘 찾아낼 수 있지만, Search Space와 Step을 잘못 설정하면 최적해를 아예 못찾을 수도 있다.
    • Space에서 정의된 모든 경우의 수를 탐색하기에 시간이 오래걸린다.
  • RandomizedSearchCV()
    • GridSearch 와 동일한 방식으로 사용하지만 모든 조합을 다 시도하지는 않고, 각 반복마다 임의의 값만 대입해 지정한 횟수만큼 평가
    • 일반적으로 Grid Search보다 효율적이지만, 최적해를 찾지 못할 수도 있다. (큰 단점이라서 최근엔 잘 안쓴다고 한다.)
  • Optuna
    • Optuna는 Grid, RandomSearch를 포함해 다양한 HyperParameter Optimization기능을 제공한다.
    • 효율적인 Tuning을 위한 SOTA Algorithm을 제공
    • Python과 쉽게 통합이 가능
    • 쉽게 Parallelization (병렬화) 할 수 있다는 장점
    • Tensorflow, Pytorch, Sklearn을 포함한 다양한 ML Framwork와 쉽게 같이 사용한다.
  • ray tune
    • Tensorflow, Pytorch, Sklearn을 포함한 모든 주요 ML모델링 프레임워크와 통합을 지원. Huggingface도 지원
    • ML workload 확장 : 분산 방식으로 데이터 처리★, 모델 훈련, 강화 학습을 위한 라이브러리 포함
    • 분산 애플리케이션 구축 : 병렬화 API를 사용하면 시스템 클러스터에서 실행하기 위해 코드를 변경할 필요없음
    • 대규모 워크로드 배포 : k8s, YARN slurm 클러스터와 호환

 

(1)-1. 최대 트리 깊이, 최대 피처 비율 list로 담기

# 트리의 깊이
max_depth_list = [2, 3, 5, 7, 9, 10]
max_depth_list

각 결정 트리 모델이 고려할 수 있는 최대 깊이를 실험하여, 모델의 성능을 최적화하기 위해 설정한다.

 

# max_features 비율
max_features_list = [0.4, 0.5, 0.6]
max_features_list

max_features_list 는 각 결정 트리 모델에서 고려할 최대 특성의 비율을 실험하여 모델의 성능을 향상시키기 위해 설정한다.

 

(1)-2 이중포문을 사용해 튜닝

# 각 max_depth와 max_features 조합에 대한 정확도를 저장할 리스트
accuracy_list = []

# max_depth_list 값을 가져와서 max_depth에 저장을 시킨다.
for max_depth in max_depth_list:
    # max_features_list 값을 가져와서 max_features에 저장을 시킨다.
    for max_features in max_features_list:
        # 정확도를 저장할 리스트 초기화
        acc_list = []
        # 새로운 결정 트리 모델 초기화
        model = DecisionTreeClassifier(max_depth=max_depth, max_features=max_features, random_state=42)
        # 교차 검증을 통한 예측값 생성
        y_pred = cross_val_predict(model, X_train, y_train, cv=3, n_jobs=-1)
        # 정확도 계산
        acc = (y_train == y_pred).mean()

        # max_depth, max_features, 정확도를 리스트에 추가
        acc_list.append(max_depth)
        acc_list.append(max_features)
        acc_list.append(acc)

        # 정확도 리스트에 추가
        accuracy_list.append(acc_list)

이 코드는 각 max_depth와 max_features 조합에 대한 결정 트리 모델을 만들고, 교차 검증을 통해 정확도를 계산하여 리스트에 저장한다. 최종적으로 각 조합에 대한 정확도 및 하이퍼파라미터를 저장하는 accuracy_list를 생성한다.

 

acc_list를 출력해보면 마지막 for문 돌렸을 때의 max_depth, max_features, acc가 담겨있고accuracy_list를 보면 for문 돌린 모든 max_depth, max_features, acc가 담겨있다.

 

(1)-3 accuracy_list로 상위 5개 하이퍼파라미터 추출 해서 최적의 하이퍼파라미터 확인하기

df_acc = pd.DataFrame(accuracy_list, columns=["max_depth", "max_features", "accuracy"])
df_acc.nlargest(5, "accuracy")

accuracy 기준으로 제일 높은 상위 5개가 추출된다. 

max_depth : 5, max_featurs :0.5, accuracy : 0.832248

 

(2) GridSearchCV

(2)-1 모델생성

model = DecisionTreeClassifier(random_state=42)
model

 

(2)-2 max_depth_list, max_feature_list 생성

이번에는 max_depth_list를 직접 리스트로 작성하는 것이 아니라 넘파이에 있는 random.randint 함수를 사용해 정수를 랜덤으로 뽑아보자. 

max_depth_list = np.random.randint(2, 7, 10)
max_depth_list = np.unique(max_depth_list)
max_depth_list
array([2, 3, 4, 5, 6])
max_feature_list = np.random.uniform(0.3, 0.7, 10)
max_feature_list
array([0.33065155, 0.44545529, 0.68530348, 0.51211622, 0.65094248,
       0.31906438, 0.56752891, 0.65424684, 0.63780351, 0.33287042])

 

(2)-3 GridSearchCV import 하고 하이퍼파라미터 담기

from sklearn.model_selection import GridSearchCV
hp = {"max_depth" : max_depth_list, "max_features":max_feature_list}

scikit-learn 패키지에서 GridSearchCV 클래스를 임포트하고, 하이퍼파라미터 그리드를 정의한다.

 

(2)-4 GridSearchCV 객체 생성하고 모델 학습시키기 

#모델 생성
clf = GridSearchCV(model, param_grid = hp, scoring="accuracy", n_jobs=-1, cv=5, verbose=2)
#모델 학습
clf.fit(X_train, y_train)

GridSearchCV 객체를 생성한다.

여기서 model은 사용할 모델을 낸다. param_grid는 탐색할 하이퍼파라미터 그리드를 지정한다. scoring은 모델의 성능을 평가하는 데 사용할 지표를 지정한다. n_jobs는 병렬 처리를 위한 작업 수를 지정한다. cv는 교차 검증을 위한 폴드(fold) 수를 나타냅니다. verbose는 그리드 서치의 진행 상황을 출력할 지 여부를 나타낸다.

 

(2)-4 best score 출력, best_estimator_출력하기

clf.best_score_

0.85829668

clf.best_score_는 모델의 성능을 나타내는 지표로, GridSearchCV가 찾은 가장 좋은 성능을 내는 최적의 모델의 평균 교차 검증 점수를 의미한다.

clf.best_estimator_

clf.best_estimator_ 결과

 

clf.best_estimator_은 GridSearchCV를 통해 찾은 최적의 모델을 나타낸다. 그리드 서치를 통해 최적의 하이퍼파라미터 조합을 찾고, 해당 조합으로 학습한 모델이 저장된다. 이 모델은 전체 학습 데이터셋에 대해 최적의 성능을 보이는 모델이다.

 

(2)-5 그리드 서치를 통해 얻은 결과를 DataFrame으로 변환하고, 테스트 세트에서의 성능 순으로 정렬

pd.DataFrame(clf.cv_results_).sort_values("rank_test_score")

 

clf.cv_results_는 GridSearchCV 객체의 속성 중 하나로, 그리드 서치를 통해 얻은 결과를 딕셔너리 형태로 저장한다. 이 결과를 pandas의 DataFrame으로 변환하고, rank_test_score 열을 기준으로 정렬한다.

 

(3) RandomSearchCV

randomSearchCV도 위 과정과 비슷하다. 빠르게 정리해보면

(3)-1 max_depth_list, max_feature_list 생성해준다.

max_depth_list = np.random.randint(2, 7, 10)
max_depth_list = np.unique(max_depth_list)

max_feature_list = np.random.uniform(0.5, 0.98, 5)
max_feature_list

max_depth_list 와 max_feature_list 만든다.

 

(3)-2 하이퍼파라미터 설정하고 모델 생성한다. 모델 학습까지 해준다.

from sklearn.model_selection import RandomizedSearchCV

param_distributions = {'max_depth': np.unique(np.random.randint(2, 7, 10)),
                       'max_features' : np.random.uniform(0.5, 0.98, 5)}

clf = RandomizedSearchCV(model, param_distributions, n_iter=5, cv=6, n_jobs=-1, verbose=2, random_state=42)
clf.fit(X_train, y_train)

 

(3)-3 best_estimator

clf.best_estimator_

Random CV가 찾은 best estimator가 출력이 된다.

 

(3)-4 랜덤 서치 결과를 DataFrame으로 변환하고 테스트 세트 성능순으로 정렬

pd.DataFrame(clf.cv_results_).nsmallest(5, "rank_test_score")

주어진 코드는 랜덤 서치를 통해 얻은 결과를 DataFrame으로 변환하고, 테스트 세트에서의 성능 순으로 정렬한다.

 

(3)-5 최적의 하이퍼파라미터로 모델 학습시키기

# 데이터를 머신러닝 모델로 학습(fit)합니다.
best_model = clf.best_estimator_
best_model.fit(X_train, y_train)

best_model = clf.best_estimator_: RandomSearchCV를 통해 찾은 최적의 모델을 변수 best_model에 할당한다.

최적의 모델인 best_model을 사용하여 학습 데이터셋 X_train y_train을 학습(fit)한다.

이렇게 하면 최적의 하이퍼파라미터로 모델 학습이 이루어진다.

 

(3)-6 최적 모델을 사용하여 머신러닝 모델로 테스트 데이터(X_test)를 예측

# 데이터를 머신러닝 모델로 예측(predict)합니다.
y_pred = best_model.predict(X_test)
y_pred[:5]

 

 

(4) Optuna

(4)-1 옵튜나 설치, 옵튜나 import

!pip install optuna
import optuna
from sklearn.metrics import accuracy_score

옵튜나는 설치가 필요하다.

 

(4)-2 목적 함수 정의와 스터디 생성,실행

옵튜나의 특징은 함수를 만들어서 옵튜나에 전달하는 것이다. 이 함수는 주로 목적 함수(objective function)로 사용되며, 옵튜나는 이 함수를 최적화하여 하이퍼파라미터 튜닝을 수행한다.

def objective(trial):  # 하이퍼파라미터 최적화를 위한 목적 함수 정의

    # 최적화할 하이퍼파라미터 설정
    max_depth = trial.suggest_int('max_depth', 1, 32)  # 1부터 32까지의 랜덤한 값을 보낸다고 요청을 보냄
    min_samples_split = trial.suggest_float('min_samples_split', 0.1, 1.0)  # 최소 샘플 분할 수를 조정
    min_samples_leaf = trial.suggest_int('min_samples_leaf', 1, 10)  # 최소 샘플 리프 수를 조정

    # 모델 생성 및 훈련
    clf = DecisionTreeClassifier(max_depth=max_depth, min_samples_split=min_samples_split, min_samples_leaf=min_samples_leaf, random_state=42)  # 의사결정트리 모델 초기화
    clf.fit(X_train, y_train)  # 학습 데이터를 사용하여 모델 훈련

    # 예측 및 평가
    pred = clf.predict(X_test)  # 테스트 데이터를 사용하여 예측
    accuracy = accuracy_score(y_test, pred)  # 정확도 계산

    return accuracy  # 정확도 반환

 

# Optuna 스터디 생성 및 최적화 실행

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

optuna 스터디 생성하고 최적화를 실행해준다.

 

(4)-3 최적의 하이퍼파라미터 출력

# 최적의 하이퍼파라미터 출력

print("Best trial:")
trial = study.best_trial

print(f" Accuracy : {trial.value}")
print(" Params: ")

for key, value in trial.params.items():
    print(f"{key}:{value}")

최적 하이퍼파라미터 출력결과

 

(5) RayTune

(5)-1 설치 및 import

!pip install ray
from ray import tune, train
from ray.tune import run

 

(5)-2 목적함수 정의

# Ray Tune의 목적 함수 정의
def train_decision_tree(config):
    # 모델 생성 및 훈련
    model = DecisionTreeClassifier(max_depth=config["max_depth"], min_samples_split=config["min_samples_split"])
    model.fit(X_train, y_train)

    # 테스트 세트에서의 정확도 측정
    accuracy = accuracy_score(y_test, model.predict(X_test)) # 정답과 예측값의 사이의 accuracy를 계산하여 변수에 할당

    # Ray Tune에 결과 보고
    metrics = {"accuracy" : accuracy}

    train.report(metrics = metrics)

 

 

(5)-3 하이퍼파라미터 설정 

# 하이퍼파라미터 설정
config = {
    "max_depth" : tune.randint(2, 8),
    "min_samples_split" : tune.randint(2, 10)
}

 

(5)-4 레이튠 실행

# Ray Tune 실행
analysis = tune.run(
    train_decision_tree,
    config= config,
    num_samples=10
)

 

(5)-5  최적 하이퍼파라미터 출력

# 최적의 하이퍼파라미터 출력
print("Best config:", analysis.get_best_config(metric="accuracy", mode="max"))

Best config: {'max_depth': 4, 'min_samples_split': 3}

 

(5)-6 모델 평가하기 - 모델 피처 중요도 추출

# feature_importances_ 를 통해 모델의 피처 중요도를 추출합니다.
best_model.feature_importances_
array([0.05288596, 0.08595844, 0.01823257, 0.05803944, 0.03404131,
       0.01090442, 0.04548358, 0.69445429])

 

 

(5)-7 피처 중요도 시각화 (sns.barplot)

# 피처 중요도를 시각화 합니다.
sns.barplot(x=best_model.feature_importances_, y=feature_names, hue=feature_names)

barplot을 이용한 피처 중요도 시각화

(5)-8 점수 측정하기

accuracy

(y_test == y_predict).mean()

 

 

옵튜나와 레이튠

옵튜나와 레이튠은 둘다 하이퍼파라미터 스페이스를 설정해야하고, 목적함수를 구현해야한다. 목적 함수 안에는 모델을 생성하고 학습을 돌려서 accuracy 계산한다음 accuracy 반환을 해야한다. 

현업에서 옵튜나와 레이튠을 많이 쓴다..

 

 

Encoding

인코딩은 범주형 데이터를 수치형 데이터로 변환하는 대표적인 방법이다. label encoding과 one-hot encoding이 있다.

 

Label Encoding

  • Label Encoding은 범주형 데이터를 연속적인 숫자 데이터로 변환하는 방법이다.
  • 이 방법은 범주 간 순서가 있는 경우에 유용하다.
  • 하지만 범주 간 순서가 없는 경우에는 모델이 범주 간 거리를 잘못 인식할 수 있다.
train["trans_Embarked"] = train["Embarked"].astype("category").cat.codes

 

라벨인코딩

One-Hot Encoding

  • 원핫 인코딩(One-Hot Encoding)은 범주형 데이터를 바이너리 형태로 변환하는 방식이다.
  • 이 방법은 범주 간 순서가 없는 경우에 유용하다.
  • 범주의 수가 많아지면 One-Hot Encoding은 데이터 차원이 늘어나는 단점이 있다. (만약에 범주개수가 너무 많다면 비추천)

원핫인코딩

# One-hot encoding
train["Embarked_S"] = train["Embarked"] =="S"
train["Embarked_C"] = train["Embarked"] =="C"
train["Embarked_Q"] = train["Embarked"] =="Q"

혹은

train = pd.get_dummies(train, columns=["Embarked"])

 

  • pandas에서는 Ordinal-Encoding을 category 타입에 대해서 cat 속성의 codes 속성으로 지원하고 있다.
  • pandas에서는 One-Hot-Encoding을 get_dummies 메서드로 지원하고 있다.
  • sklearn에서는 Ordinal-Encoding을 OrdinalEncoder 객체로 지원하고 있다.
  • sklearn에서는 One-Hot-Encoding을 OneHotEncoder 객체로 지원하고 있다.