목적함수는 모델 학습시 최적화되는 함수이다. 모델 학습에서는 목적함수로 사용한 오차가 최소가 되도록 결정 트리의 분기나 선형 모델의 회귀계수 추가 및 갱신 등을 수행한다. 이때 학습이 잘 진행되려면 목적함수는 미분할 수 있어야 한다는 제약이 있다. 최귀 문제에서는 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)
간단하고 올바른 모델링 시행
: 평가 지표가 RMSE나 로그 손실일 경우 모델의 목적함수도 같은 것을 지정할 수 있다. 이 경우에는 별도의 처리를 하지 않아도 단순히 모델을 학습, 예측시키는 것만으로 평가지표에 거의 최적화된다.
학습 데티러를 전처리하고 다른 평가지표를 최적화
: log 취한 뒤 원래대로 변환하기
다른 평가지표를 최적화하고 후처리
: 모델의 학습/예측을 수행한 후 평가지표의 성질에 근거하여 계산하거나, 최적화 알고리즘을 이용하여 임계값을 최적화하는 방법
사용자 정의 목적함수를 사용
다른 평가지표를 최적화하고 학습 조기 종료
- 모든 임계값을 알아내는 방법
- 최적화 알고리즘을 이용하는 방법
# 임곗값(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)
# 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 놀이터, 캐글
혹시 임곗값 초기화 부분에서 목적함수 초기화 할때 f1_score가 아니라 -f1_score 하는지 알 수 있을까요??