이진 분류 용. 모든 사람은 곡선 (AUC) 메트릭의 영역을 좋아하지만 손실 기능에서 직접 타겟팅하는 사람은 없습니다. 대신 사람들은 이진 크로스 엔트로피 (BCE)와 같은 프록시 기능을 사용합니다.
이것은 대부분의 시간에 상당히 잘 작동합니다. 그러나 우리는 잔소리 질문이 남아 있습니다. 우리는 AUC에 자연에서 손실 함수가 더 가까워서 더 높은 점수를 얻을 수 있습니까?
BCE가 실제로 AUC와 거의 관련이 없기 때문에 아마도 보일 것 같습니다. AUC를보다 직접적으로 대상으로하는 손실 함수를 찾으려는 많은 시도가있었습니다. (일반적인 전술 중 하나는 힌지 순위 손실과 같은 어떤 형태의 순위 손실 기능입니다.) 실제로는 명확한 승자가 나오지 않았습니다. BCE에 심각한 도전은 없었습니다.
성능 이외의 고려 사항도 있습니다. BCE는 본질적으로 AUC와는 다르기 때문에 BCE는 우리가 가장 높은 AUC 점수를 향해 조종하려는 최종 훈련에서 잘못 행동하는 경향이 있습니다.
AUC 최적화의 많은 부분은 실제로 하이퍼 파라미터의 튜닝에서 발생합니다. 모델이 높은 점수에서 언제라도 급격히 분기 될 수 있으므로 조기 중지는 불편한 필요성이됩니다.
우리는 더 높은 점수와 덜 문제를주는 손실 기능을 원합니다.
우리는 여기에 그러한 기능을 제시합니다.
AUC의 제가 가장 좋아하는 작업 정의는 다음과 같습니다. 바이너리 클래스 레이블을 "검은 색"(0) 및 "화이트"(1)이라고 부릅니다. 무작위로 하나의 검은 요소를 선택하고 x를 예측 값으로 보이게하십시오. 이제 값 y 인 임의의 흰색 요소를 선택하십시오. 그 다음에,
auc = 요소가 올바른 순서로있을 확률. 즉, x < y 입니다.
그게 다야. 훈련 세트와 같은 주어진 포인트 세트의 경우, 우리는 무차별적인 계산을 통해이 확률을 얻을 수 있습니다. 가능한 모든 검은 색/흰색 쌍의 세트를 스캔하고 오른쪽 주문 부분을 계산하십시오.
우리는 AUC 점수가 차별화되지 않음 (단일 x 또는 y 에 대한 부드러운 곡선)이 요소 (모든 색상)를 가져 와서 인접 요소에 부딪치지 않을 정도로 약간 움직입니다. AUC는 동일하게 유지됩니다. 포인트가 이웃을 가로 지르면 x <y 비교 중 하나를 뒤집을 가능성이 있습니다. 따라서 AUC는 원활한 전환을하지 않습니다.
그것은 우리가 차별화 가능한 손실 함수가 필요한 신경망의 문제입니다.
그래서 우리는 AUC에 가능한 한 가까운 차별화 가능한 기능을 찾기 시작했습니다.
나는 기존 문헌을 다시 파고 실제로 효과가있는 것은 발견하지 못했습니다. 마침내 나는 누군가가 tflearn 코드베이스에 체크인 한 호기심 많은 코드를 발견했습니다.
팡파르가 없다면, 새로운 손실 기능의 형태로 BCE로부터 차별화 가능한 구원을 약속했다.
( 시도하지 말고 날아갑니다 .) : http://tflearn.org/objectives/#Roc-Auc-Score
def roc_auc_score(y_pred, y_true):
"""Bad code, do not use.
ROC AUC Score.
Approximates the Area Under Curve score, using approximation based on
the Wilcoxon-Mann-Whitney U statistic.
Yan, L., Dodier, R., Mozer, M. C., & Wolniewicz, R. (2003).
Optimizing Classifier Performance via an Approximation to the Wilcoxon-Mann-Whitney Statistic.
Measures overall performance for a full range of threshold levels.
Arguments:
y_pred: `Tensor`. Predicted values.
y_true: `Tensor` . Targets (labels), a probability distribution.
"""
with tf.name_scope("RocAucScore"):
pos = tf.boolean_mask(y_pred, tf.cast(y_true, tf.bool))
neg = tf.boolean_mask(y_pred, ~tf.cast(y_true, tf.bool))
.
.
more bad code)
전혀 작동하지 않습니다. (폭발은 실제로 가장 적은 문제입니다). 그러나 그것은 그것이 기반한 논문을 언급합니다.
이 논문은 고대 이지만 2003 년으로 거슬러 올라가는데, 나는 약간의 작업으로 수학의 일부 확장과 신중한 코딩이 실제로 작동한다는 것을 알았습니다. 그것은 BCE와 비슷한 속도 (그리고 GPU/MPP의 경우 벡터 화 할 수있는 속도) * * *입니다. 내 테스트에서는 BCE보다 AUC 점수가 높을수록 학습 속도에 덜 민감하며 (테스트에서 스케줄러가 필요하지 않음) 전적으로 조기 중지의 필요성을 제거합니다.
좋아, 원본 용지로 돌아가 봅시다 : Wilcoxon -Mann -Whitney 통계에 대한 근사치를 통해 분류기 성능 최적화 .
저자, Yan et. AL , AUC 점수를 특정 형태로 작성하여 토론에 동기를 부여하십시오. 가능한 흑백 쌍의 세트에 대한 무차별적인 수를 수행하여 AUC를 계산하여 오른쪽 주문 부분을 찾으십시오. B를 검은 색 값 세트로하고 흰 값 세트로 하자 . 가능한 모든 쌍은 직교 제품 B x w 에 의해 주어집니다. 오른 주문 쌍을 계산하기 위해 우리는 다음을 작성합니다.
이것은 실제로 '오른쪽 주문 쌍을 세는'이라고 말하는 수학적 표기법입니다. 이 합계를 총 쌍 수로 나누면 | B | * | W |, 우리는 정확히 AUC 메트릭을 얻습니다. (역사적으로 이것은 정규화 된 Wilcoxon-Mann-Whitney (WMW) 통계라고합니다.)
이것으로부터 손실 함수를 만들기 위해, 우리는 x <y 비교를 x> y로 뒤집기 위해 잘못 주문한 쌍을 불쾌하게 만들 수 있습니다. 물론 문제는 x가 y를 교차 할 때 불연속적인 점프입니다.
Yan ET. Al Sigmoid 곡선과 같은 Step (Heaviside) 기능에 대한 연속 근사치를 사용하여 Al Sugve, 다음 거부 - 과거의 작업 어라운드. 그런 다음 그들은 이것을 모자에서 꺼냅니다.
Yann은 WMW에 일련의 변경 사항을 적용 하여이 포럼 라를 얻었습니다.
우리는 이것들을 차례로 진행할 것입니다. 1은 충분히 명확합니다. 눈을 만나는 것보다 2가 조금 더 있습니다. 넓은 분리가있는 잘못된 주문 쌍은 더 큰 손실을 가져야한다는 것은 직관적입니다. 그러나 분리가 0에 접근함에 따라 흥미로운 일이 일어나고 있습니다. 손실은 단계 기능이 아니라 선형으로 0으로갑니다. 그래서 우리는 불연속성을 제거했습니다.
실제로, p가 1이고 γ가 0이라면, 손실은 단순히 우리의 오랜 친구 Relu (xy) 일 것이다. 그러나이 경우 우리는 딸꾹질을 발견하여 지수 p 에 대한 필요성을 나타냅니다. Relu는 0에서 차별화 할 수 없습니다. 그것은 활성화 기능으로서 Relu의 더 익숙한 역할에 큰 문제가되지 않지만, 우리가 가장 관심이있는 것에 대해 직접 1 개의 토지의 특이점은 : 흰색과 검은 색 요소가있는 지점. 서로 통과하십시오.
다행히도, Relu를 전원으로 올리는 것은 이것을 해결합니다. p> 1과 함께 Relu^p는 어디서나 차별화 될 수 있습니다. 좋아, 그래서 p> 1.
이제 γ로 돌아 가기 : γ는 두 지점 사이에 시행되는 '패딩'을 제공합니다. 우리는 잘못 주문한 쌍뿐만 아니라 너무 가까운 오른쪽 주문 쌍을 처벌합니다. 오른쪽 주문 쌍이 너무 가깝다면, 그 요소는 확률 론적 신경망의 임의의 흔들림으로 향후 교환 될 위험이 있습니다. 아이디어는 편안한 거리에 도달 할 때까지 계속 움직이는 것입니다.
그리고 그것은 논문에 요약 된 기본 아이디어입니다. 우리는 이제 γ와 p에 관한 몇 가지 개선을 ake습니다.
여기서 우리는 종이를 조금 깨뜨립니다. Yan ET. Al은 γ와 P를 선택한다는 주제에 대해 약간 짜증나는 것처럼 보이며, p = 2 또는 p = 3만으로는 γ가 0.10에서 0.70 사이에 있어야한다는 것을 제공합니다. Yan은 본질적으로 이러한 매개 변수와 활으로 운이 좋기를 바랍니다.
먼저, 자존심이있는 손실 함수는 제곱의 합계 여야하기 때문에 p = 2를 영구적으로 수정합니다. (이에 대한 한 가지 이유는 손실 함수가 차별화 가능할뿐만 아니라 볼록한 것이기 때문입니다).
두 번째로 더 중요한 것은 γ를 살펴 보겠습니다. '0.10에서 0.70까지의 어딘가'의 휴리스틱은 그 얼굴에 이상하게 보입니다. 예측이 0 <x <1으로 정규화 되더라도,이 지침은 기본 분포에 무관심하고 이상하게 보이는 것처럼 보입니다.
우리는 훈련 세트에서 γ를 도출 할 것입니다.
훈련 세트와 그 검은 색/흰색 쌍 인 B x w 를 고려하십시오. | B || W | 이 세트의 쌍. 이 중 | B | | W | AUC는 직접 주문됩니다. 따라서 잘못 주문한 쌍의 수는 (1-auc) |입니다 B | | W |
γ가 0 인 경우, 이러한 잘못된 주문 쌍만이 움직이고 있습니다 (양의 손실이 있습니다.) 양의 γ는 이동 쌍 세트를 확장하여 오른쪽 주문이지만 너무 가까운 일부 쌍을 포함합니다. γ의 숫자 값에 대해 걱정하는 대신, 우리가 모션을 설정하려는 너무 많은 쌍의 쌍을 지정할 것입니다.
우리는 너무 클로즈 쌍의 비율을 잘못 주문한 쌍으로 고정시키는 상수 δ를 정의합니다.
우리는 훈련을 통해이 δ를 고정하고 γ를 업데이트하여이를 준수합니다. 주어진 δ에 대해, 우리는 γ를 찾습니다
실험에서 우리는 δ가 0.5에서 2.0까지, 1.0은 좋은 기본 선택임을 발견했습니다.
그래서 우리는 δ를 1, p로 2로 설정하고 γ를 완전히 잊어 버렸습니다.
우리의 손실 기능 (1)은 계산하는 데 비싸게 보입니다. 각 개별 예측에 대한 전체 교육 세트를 스캔해야합니다.
우리는 성능 조정 으로이 문제를 우회합니다.
주어진 흰색 데이터 포인트 x 에 대한 손실 함수를 계산한다고 가정 해 봅시다. (3)을 계산하려면 X를 검은 색 예측 세트와 비교해야합니다. 우리는 바로 가기를 취하고 검은 색 데이터 포인트의 임의의 하위 표본을 사용합니다. 서브 샘플의 크기를 1000으로 설정하면 실제 손실 함수에 대한 매우 근사한 근사치를 얻습니다. [1]
비슷한 추론은 흑인 데이터 포인트의 손실 함수에 적용됩니다. 우리는 모든 흰색 훈련 요소의 임의의 하위 표본을 사용합니다.
이러한 방식으로 흰색과 검은 색 서브 샘플은 GPU 메모리에 쉽게 맞습니다. 주어진 배치 전체에 걸쳐 동일한 서브 샘플을 재사용함으로써 작업을 배치로 병렬화 할 수 있습니다. 우리는 BCE에서 빠른 손실 기능으로 끝납니다.
Pytorch의 배치 손실 기능은 다음과 같습니다.
def roc_star_loss( _y_true, y_pred, gamma, _epoch_true, epoch_pred):
"""
Nearly direct loss function for AUC.
See article,
C. Reiss, "Roc-star : An objective function for ROC-AUC that actually works."
https://github.com/iridiumblue/articles/blob/master/roc_star.md
_y_true: `Tensor`. Targets (labels). Float either 0.0 or 1.0 .
y_pred: `Tensor` . Predictions.
gamma : `Float` Gamma, as derived from last epoch.
_epoch_true: `Tensor`. Targets (labels) from last epoch.
epoch_pred : `Tensor`. Predicions from last epoch.
"""
#convert labels to boolean
y_true = (_y_true>=0.50)
epoch_true = (_epoch_true>=0.50)
# if batch is either all true or false return small random stub value.
if torch.sum(y_true)==0 or torch.sum(y_true) == y_true.shape[0]: return torch.sum(y_pred)*1e-8
pos = y_pred[y_true]
neg = y_pred[~y_true]
epoch_pos = epoch_pred[epoch_true]
epoch_neg = epoch_pred[~epoch_true]
# Take random subsamples of the training set, both positive and negative.
max_pos = 1000 # Max number of positive training samples
max_neg = 1000 # Max number of positive training samples
cap_pos = epoch_pos.shape[0]
cap_neg = epoch_neg.shape[0]
epoch_pos = epoch_pos[torch.rand_like(epoch_pos) < max_pos/cap_pos]
epoch_neg = epoch_neg[torch.rand_like(epoch_neg) < max_neg/cap_pos]
ln_pos = pos.shape[0]
ln_neg = neg.shape[0]
# sum positive batch elements agaionst (subsampled) negative elements
if ln_pos>0 :
pos_expand = pos.view(-1,1).expand(-1,epoch_neg.shape[0]).reshape(-1)
neg_expand = epoch_neg.repeat(ln_pos)
diff2 = neg_expand - pos_expand + gamma
l2 = diff2[diff2>0]
m2 = l2 * l2
len2 = l2.shape[0]
else:
m2 = torch.tensor([0], dtype=torch.float).cuda()
len2 = 0
# Similarly, compare negative batch elements against (subsampled) positive elements
if ln_neg>0 :
pos_expand = epoch_pos.view(-1,1).expand(-1, ln_neg).reshape(-1)
neg_expand = neg.repeat(epoch_pos.shape[0])
diff3 = neg_expand - pos_expand + gamma
l3 = diff3[diff3>0]
m3 = l3*l3
len3 = l3.shape[0]
else:
m3 = torch.tensor([0], dtype=torch.float).cuda()
len3=0
if (torch.sum(m2)+torch.sum(m3))!=0 :
res2 = torch.sum(m2)/max_pos+torch.sum(m3)/max_neg
#code.interact(local=dict(globals(), **locals()))
else:
res2 = torch.sum(m2)+torch.sum(m3)
res2 = torch.where(torch.isnan(res2), torch.zeros_like(res2), res2)
return res2
몇 가지 추가 매개 변수가 있습니다. 우리는 마지막 시대 에서 훈련 세트를 전달하고 있습니다. 전체 훈련 세트는 하나의 시대에서 다음 에포크로 크게 변경되지 않기 때문에 손실 함수는 각 예측을 다시 한 번 외부 교육 세트와 비교할 수 있습니다. 이것은 디버깅을 단순화하고 '배경'에포크가 한 배치에서 다음 배치로 바뀌지 않기 때문에 성능에 도움이되는 것으로 보입니다.
마찬가지로, γ는 값 비싼 계산이다. 우리는 다시 하위 샘플링 트릭을 사용하지만 정확한 견적을 보장하기 위해 서브 샘플의 크기를 ~ 10,000으로 늘립니다. 성능 클리핑을 유지하기 위해이 값을 에포크 당 한 번만 재편성합니다. 다음은 다음과 같은 기능입니다.
def epoch_update_gamma(y_true,y_pred, epoch=-1,delta=2):
"""
Calculate gamma from last epoch's targets and predictions.
Gamma is updated at the end of each epoch.
y_true: `Tensor`. Targets (labels). Float either 0.0 or 1.0 .
y_pred: `Tensor` . Predictions.
"""
DELTA = delta
SUB_SAMPLE_SIZE = 2000.0
pos = y_pred[y_true==1]
neg = y_pred[y_true==0] # yo pytorch, no boolean tensors or operators? Wassap?
# subsample the training set for performance
cap_pos = pos.shape[0]
cap_neg = neg.shape[0]
pos = pos[torch.rand_like(pos) < SUB_SAMPLE_SIZE/cap_pos]
neg = neg[torch.rand_like(neg) < SUB_SAMPLE_SIZE/cap_neg]
ln_pos = pos.shape[0]
ln_neg = neg.shape[0]
pos_expand = pos.view(-1,1).expand(-1,ln_neg).reshape(-1)
neg_expand = neg.repeat(ln_pos)
diff = neg_expand - pos_expand
ln_All = diff.shape[0]
Lp = diff[diff>0] # because we're taking positive diffs, we got pos and neg flipped.
ln_Lp = Lp.shape[0]-1
diff_neg = -1.0 * diff[diff<0]
diff_neg = diff_neg.sort()[0]
ln_neg = diff_neg.shape[0]-1
ln_neg = max([ln_neg, 0])
left_wing = int(ln_Lp*DELTA)
left_wing = max([0,left_wing])
left_wing = min([ln_neg,left_wing])
default_gamma=torch.tensor(0.2, dtype=torch.float).cuda()
if diff_neg.shape[0] > 0 :
gamma = diff_neg[left_wing]
else:
gamma = default_gamma # default=torch.tensor(0.2, dtype=torch.float).cuda() #zoink
L1 = diff[diff>-1.0*gamma]
ln_L1 = L1.shape[0]
if epoch > -1 :
return gamma
else :
return default_gamma
다음은 우리가 에포크에서 루프 할 때 두 기능을 사용한 다음 배치로 사용하는 방법을 보여주는 헬리콥터보기입니다.
train_ds = CatDogDataset(train_files, transform)
train_dl = DataLoader(train_ds, batch_size=BATCH_SIZE)
#initialize last epoch with random values
last_epoch_y_pred = torch.tensor( 1.0-numpy.random.rand(len(train_ds))/2.0, dtype=torch.float).cuda()
last_epoch_y_t = torch.tensor([o for o in train_tt],dtype=torch.float).cuda()
epoch_gamma = 0.20
for epoch in range(epoches):
epoch_y_pred=[]
epoch_y_t=[]
for X, y in train_dl:
preds = model(X)
# .
# .
loss = roc_star_loss(y,preds,epoch_gamma, last_epoch_y_t, last_epoch_y_pred)
# .
# .
epoch_y_pred.extend(preds)
epoch_y_t.extend(y)
last_epoch_y_pred = torch.tensor(epoch_y_pred).cuda()
last_epoch_y_t = torch.tensor(epoch_y_t).cuda()
epoch_gamma = epoch_update_gamma(last_epoch_y_t, last_epoch_y_pred, epoch)
#...
완전한 작업 예제는 여기에서 찾을 수 있습니다. example.py 더 빠른 점프 스타의 경우 Kaggle 에서이 커널을 포크 할 수 있습니다 : 커널
아래에서는 BCE를 사용하여 동일한 모델에 대한 Roc-Star의 성능을 도표로합니다. 경험에 따르면 Roc-Star는 종종 성능이 증가 할 때 BCE를 사용하여 모든 모델로 교체 할 수 있습니다.