평가지표, 목적함수 정리

우수민·2021년 8월 7일
0

ML 관련 정리

목록 보기
2/10
post-thumbnail
post-custom-banner

평가지표와 목적함수의 차이점

  • 목적함수는 모델 학습시 최적화되는 함수이다. 모델 학습에서는 목적함수로 사용한 오차가 최소가 되도록 결정 트리의 분기나 선형 모델의 회귀계수 추가 및 갱신 등을 수행한다. 이때 학습이 잘 진행되려면 목적함수는 미분할 수 있어야 한다는 제약이 있다. 최귀 문제에서는 RMSE, 분류 문제에서는 로그 손실을 목적함수로 많이 사용한다.

  • 평가함수는 모델 또는 예측값의 성능이 좋고 나쁨을 측정하는 지표이다.

사용자 정의 평가지표와 사용자 정의 목적 함수

# xgboost에 있어, 사용자 평가지표와 목적 변수의 예
# (참조)https://github.com/dmlc/xgboost/blob/master/demo/guide-python/custom_objective.py
# -----------------------------------
import xgboost as xgb
from sklearn.metrics import log_loss

# 특징과 목적변수를 xgboost의 데이터 구조로 변환
# 학습 데이터의 특징과 목적변수는 tr_x, tr_y
# 검증 데이터의 특징과 목적변수는 va_x, va_y
dtrain = xgb.DMatrix(tr_x, label=tr_y)
dvalid = xgb.DMatrix(va_x, label=va_y)

# 사용자 정의 목적함수(이 경우는 logloss이며, xgboost의 ‘binary:logistic’과 동일)
def logregobj(preds, dtrain):
    labels = dtrain.get_label()           # 실젯값 레이블 획득
    preds = 1.0 / (1.0 + np.exp(-preds))  # 시그모이드 함수
    grad = preds - labels                 # 그래디언트
    hess = preds * (1.0 - preds)          # 시그모이드 함수 미분
    return grad, hess

# 사용자 정의 평가지표(이 경우 오류율)
def evalerror(preds, dtrain):
    labels = dtrain.get_label()           # 실젯값 레이블 획득
    return 'custom-error', float(sum(labels != (preds > 0.0))) / len(labels)

# 하이퍼 파라미터의 설정
# xgboost 버전이 하위버전의 경우, 'verbosity':0을 'silent':1로 변경 후, 실행.
# params = {'silent': 1, 'random_state': 71}
params = {'verbosity': 0, 'random_state': 71}   # xgboost 1.3.3 버전 적용
num_round = 50
watchlist = [(dtrain, 'train'), (dvalid, 'eval')]

# 모델 학습 실행
bst = xgb.train(params, dtrain, num_round, watchlist, obj=logregobj, feval=evalerror)

# 목적함수에 binary:logistic을 지정했을 때와 달리 확률로 변환하기 전 값으로
# 예측값이 출력되므로 변환이 필요
pred_val = bst.predict(dvalid)
pred = 1.0 / (1.0 + np.exp(-pred_val))
logloss = log_loss(va_y, pred)
print(logloss)

# (참고)일반적인 방법으로 학습하는 경우
# params = {'silent': 1, 'random_state': 71, 'objective': 'binary:logistic'}
params = {'verbosity': 0, 'random_state': 71, 'objective': 'binary:logistic'}   # 현 버전 1.3.0 버전

bst = xgb.train(params, dtrain, num_round, watchlist)

pred = bst.predict(dvalid)
logloss = log_loss(va_y, pred)
print(logloss)

평가 지표의 최적화

  1. 간단하고 올바른 모델링 시행
    : 평가 지표가 RMSE나 로그 손실일 경우 모델의 목적함수도 같은 것을 지정할 수 있다. 이 경우에는 별도의 처리를 하지 않아도 단순히 모델을 학습, 예측시키는 것만으로 평가지표에 거의 최적화된다.

  2. 학습 데티러를 전처리하고 다른 평가지표를 최적화
    : log 취한 뒤 원래대로 변환하기

  3. 다른 평가지표를 최적화하고 후처리
    : 모델의 학습/예측을 수행한 후 평가지표의 성질에 근거하여 계산하거나, 최적화 알고리즘을 이용하여 임계값을 최적화하는 방법

  4. 사용자 정의 목적함수를 사용

  5. 다른 평가지표를 최적화하고 학습 조기 종료

임계값 초기화

  1. 모든 임계값을 알아내는 방법
  2. 최적화 알고리즘을 이용하는 방법
# 임곗값(threshold)의 최적화
# -----------------------------------
from sklearn.metrics import f1_score
from scipy.optimize import minimize

# 행 데이터 데이터 생성 준비
rand = np.random.RandomState(seed=71)
train_y_prob = np.linspace(0, 1.0, 10000)

# 실젯값과 예측값을 다음과 같은 train_y, train_pred_prob이었다고 가정
train_y = pd.Series(rand.uniform(0.0, 1.0, train_y_prob.size) < train_y_prob)
train_pred_prob = np.clip(train_y_prob * np.exp(rand.standard_normal(train_y_prob.shape) * 0.3), 0.0, 1.0)

# 임곗값(threshold)을 0.5로 하면, F1은 0.722
init_threshold = 0.5
init_score = f1_score(train_y, train_pred_prob >= init_threshold)
print(init_threshold, init_score)

# 최적화의 목적함수를 설정
def f1_opt(x):
    return -f1_score(train_y, train_pred_prob >= x)

# scipy.optimize의 minimize 메소드에서 최적의 임곗값 구하기
# 구한 최적의 임곗값을 바탕으로 F1을 구하면 0.756이 됨
result = minimize(f1_opt, x0=np.array([0.5]), method='Nelder-Mead')
best_threshold = result['x'].item()
best_score = f1_score(train_y, train_pred_prob >= best_threshold)
print(best_threshold, best_score)
  • 아래의 코드는 OOF를 활용한 최적화
# out-of-fold에서의 임곗값(threshold)의 최적화
# -----------------------------------
from scipy.optimize import minimize
from sklearn.metrics import f1_score
from sklearn.model_selection import KFold

# 샘플 데이터 생성 준비
rand = np.random.RandomState(seed=71)
train_y_prob = np.linspace(0, 1.0, 10000)

# 실젯값과 예측값을 다음과 같은 train_y, train_pred_prob이었다고 가정
train_y = pd.Series(rand.uniform(0.0, 1.0, train_y_prob.size) < train_y_prob)
train_pred_prob = np.clip(train_y_prob * np.exp(rand.standard_normal(train_y_prob.shape) * 0.3), 0.0, 1.0)

# 교차 검증 구조로 임곗값을 구함
thresholds = []
scores_tr = []
scores_va = []

kf = KFold(n_splits=4, random_state=71, shuffle=True)
for i, (tr_idx, va_idx) in enumerate(kf.split(train_pred_prob)):
    tr_pred_prob, va_pred_prob = train_pred_prob[tr_idx], train_pred_prob[va_idx]
    tr_y, va_y = train_y.iloc[tr_idx], train_y.iloc[va_idx]

    # 최적화 목적함수를 설정
    def f1_opt(x):
        return -f1_score(tr_y, tr_pred_prob >= x)

    # 학습 데이터로 임곗값을 실시하고 검증 데이터로 평가를 수행
    result = minimize(f1_opt, x0=np.array([0.5]), method='Nelder-Mead')
    threshold = result['x'].item()
    score_tr = f1_score(tr_y, tr_pred_prob >= threshold)
    score_va = f1_score(va_y, va_pred_prob >= threshold)
    print(threshold, score_tr, score_va)

    thresholds.append(threshold)
    scores_tr.append(score_tr)
    scores_va.append(score_va)

# 각 fold의 임곗값 평균을 테스트 데이터에 적용
threshold_test = np.mean(thresholds)
print(threshold_test)

참고 : 데이터가 뛰어노는 AI 놀이터, 캐글

profile
데이터 분석하고 있습니다
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 1월 25일

혹시 임곗값 초기화 부분에서 목적함수 초기화 할때 f1_score가 아니라 -f1_score 하는지 알 수 있을까요??

답글 달기