🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈 바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의 바로가기 🙌

4 분 소요

조기 종료(Early Stopping)는 학습시 일정기간(여기서 기간은 보통 N번의 Epoch을 기준으로 합니다)동안 Loss 나 Score 기준으로 개선이 일어나지 않으면 학습을 조기에 종료해 주는 기능입니다. 만약, 20번의 Epoch 동안 학습이 진행한다고 가정했을 때, 아래 그림처럼 Epoch=5 이후에 지속해서 Loss가 상승하면서 개선이 안 일어나면 굳이 남은 학습을 지속할 이유가 없습니다.

early-stopping

이럴때 조기종료 기능으로 중간에 N번의 Epoch 동안 개선이 일어나지 않으면 종료해 주면 됩니다. 여기서, “개선이 일어나지 않은 N번의 Epoch” 을 patience 라고 합니다.

조기종료(EarlyStopping) 코드 구현

import numpy as np

class EarlyStopping:
    def __init__(self, patience=3, delta=0.0, mode='min', verbose=True):
        """
        patience (int): loss or score가 개선된 후 기다리는 기간. default: 3
        delta  (float): 개선시 인정되는 최소 변화 수치. default: 0.0
        mode     (str): 개선시 최소/최대값 기준 선정('min' or 'max'). default: 'min'.
        verbose (bool): 메시지 출력. default: True
        """
        self.early_stop = False
        self.patience = patience
        self.verbose = verbose
        self.counter = 0
        
        self.best_score = np.Inf if mode == 'min' else 0
        self.mode = mode
        self.delta = delta
        

    def __call__(self, score):

        if self.best_score is None:
            self.best_score = score
            self.counter = 0
        elif self.mode == 'min':
            if score < (self.best_score - self.delta):
                self.counter = 0
                self.best_score = score
                if self.verbose:
                    print(f'[EarlyStopping] (Update) Best Score: {self.best_score:.5f}')
            else:
                self.counter += 1
                if self.verbose:
                    print(f'[EarlyStopping] (Patience) {self.counter}/{self.patience}, ' \
                          f'Best: {self.best_score:.5f}' \
                          f', Current: {score:.5f}, Delta: {np.abs(self.best_score - score):.5f}')
                
        elif self.mode == 'max':
            if score > (self.best_score + self.delta):
                self.counter = 0
                self.best_score = score
                if self.verbose:
                    print(f'[EarlyStopping] (Update) Best Score: {self.best_score:.5f}')
            else:
                self.counter += 1
                if self.verbose:
                    print(f'[EarlyStopping] (Patience) {self.counter}/{self.patience}, ' \
                          f'Best: {self.best_score:.5f}' \
                          f', Current: {score:.5f}, Delta: {np.abs(self.best_score - score):.5f}')
                
            
        if self.counter >= self.patience:
            if self.verbose:
                print(f'[EarlyStop Triggered] Best Score: {self.best_score:.5f}')
            # Early Stop
            self.early_stop = True
        else:
            # Continue
            self.early_stop = False

예시 데이터 (Epoch 별 Loss)

losses = [
    0.1,  # save
    0.25, # patience #1
    0.33, # patience #2
    0.09, # save
    0.4,  # patience #1
    0.08, # save
    0.5,  # patience #1
    0.55, # patience #2
    0.6,  # patience #3 <== Early Stop
    0.05, 
    0.89, 
    0.99 
]
es = EarlyStopping(patience=3, 
                   delta=0, 
                   mode='min', 
                   verbose=True
                  )
for loss in losses:
    es(loss)
    # Early Stop Check
    if es.early_stop:
        break
[EarlyStopping] (Update) Best Score: 0.10000
[EarlyStopping] (Patience) 1/3, Best: 0.10000, Current: 0.25000, Delta: 0.15000
[EarlyStopping] (Patience) 2/3, Best: 0.10000, Current: 0.33000, Delta: 0.23000
[EarlyStopping] (Update) Best Score: 0.09000
[EarlyStopping] (Patience) 1/3, Best: 0.09000, Current: 0.40000, Delta: 0.31000
[EarlyStopping] (Update) Best Score: 0.08000
[EarlyStopping] (Patience) 1/3, Best: 0.08000, Current: 0.50000, Delta: 0.42000
[EarlyStopping] (Patience) 2/3, Best: 0.08000, Current: 0.55000, Delta: 0.47000
[EarlyStopping] (Patience) 3/3, Best: 0.08000, Current: 0.60000, Delta: 0.52000
[EarlyStop Triggered] Best Score: 0.08000

(옵션) patience

  • loss or score가 개선된 후 기다리는 기간

  • default: 3

예시 데이터 (Epoch 별 Loss)

losses = [
    0.1,  # save
    0.25, # patience #1
    0.33, # patience #2
    0.09, # save
    0.4,  # patience #1
    0.08, # save
    0.5,  # patience #1
    0.55, # patience #2
    0.6,  # patience #3 
    0.05, # save
    0.89, # patience #1 
    0.99  # patience #2 
]

기본 사용 방법

es = EarlyStopping(patience=5, 
                   delta=0, 
                   mode='min', 
                   verbose=True
                  )

Training Loop 안에서 es(loss) 를 실행 후 early_stop 여부를 체크합니다.

for loss in losses:
    es(loss)
    # Early Stop Check
    if es.early_stop:
        break
[EarlyStopping] (Update) Best Score: 0.10000
[EarlyStopping] (Patience) 1/5, Best: 0.10000, Current: 0.25000, Delta: 0.15000
[EarlyStopping] (Patience) 2/5, Best: 0.10000, Current: 0.33000, Delta: 0.23000
[EarlyStopping] (Update) Best Score: 0.09000
[EarlyStopping] (Patience) 1/5, Best: 0.09000, Current: 0.40000, Delta: 0.31000
[EarlyStopping] (Update) Best Score: 0.08000
[EarlyStopping] (Patience) 1/5, Best: 0.08000, Current: 0.50000, Delta: 0.42000
[EarlyStopping] (Patience) 2/5, Best: 0.08000, Current: 0.55000, Delta: 0.47000
[EarlyStopping] (Patience) 3/5, Best: 0.08000, Current: 0.60000, Delta: 0.52000
[EarlyStopping] (Update) Best Score: 0.05000
[EarlyStopping] (Patience) 1/5, Best: 0.05000, Current: 0.89000, Delta: 0.84000
[EarlyStopping] (Patience) 2/5, Best: 0.05000, Current: 0.99000, Delta: 0.94000

(옵션) delta

  • 개선시 인정되는 최소 변화 수치

  • default: 0.0

예시 데이터 (Epoch 별 Loss)

losses = [
    0.1,  # save
    0.25, # patience #1
    0.33, # patience #2
    0.09, # 개선은 되었으나 delta=0.02 이상 줄이지 못하였으므로.. Early Stop!
    0.4,  
    0.08, 
    0.5,  
    0.55, 
    0.6,  
    0.05, 
    0.89, 
    0.99  
]
es = EarlyStopping(patience=3, 
                   delta=0.02, 
                   mode='min', 
                   verbose=True
                  )
for loss in losses:
    es(loss)
    # Early Stop Check
    if es.early_stop:
        break
[EarlyStopping] (Update) Best Score: 0.10000
[EarlyStopping] (Patience) 1/3, Best: 0.10000, Current: 0.25000, Delta: 0.15000
[EarlyStopping] (Patience) 2/3, Best: 0.10000, Current: 0.33000, Delta: 0.23000
[EarlyStopping] (Patience) 3/3, Best: 0.10000, Current: 0.09000, Delta: 0.01000
[EarlyStop Triggered] Best Score: 0.10000

(옵션) mode

  • 개선시 최소/최대값 기준 선정

  • 선택 가능 옵션: 'min', 'max'

  • default: 'min'

Epoch별 Score (예시)

scores = [
    0.1,  # save
    0.25, # save
    0.33, # save
    0.09, # patience #1
    0.4,  # save
    0.08, # patience #1
    0.5,  # save
    0.55, # save
    0.6,  # save
    0.05, # patience #1
    0.89, # save
    0.99  # save
]
es = EarlyStopping(patience=3, 
                   delta=0.0, 
                   mode='max', 
                   verbose=True
                  )
for score in scores:
    es(score)
    # Early Stop Check
    if es.early_stop:
        break
[EarlyStopping] (Update) Best Score: 0.10000
[EarlyStopping] (Update) Best Score: 0.25000
[EarlyStopping] (Update) Best Score: 0.33000
[EarlyStopping] (Patience) 1/3, Best: 0.33000, Current: 0.09000, Delta: 0.24000
[EarlyStopping] (Update) Best Score: 0.40000
[EarlyStopping] (Patience) 1/3, Best: 0.40000, Current: 0.08000, Delta: 0.32000
[EarlyStopping] (Update) Best Score: 0.50000
[EarlyStopping] (Update) Best Score: 0.55000
[EarlyStopping] (Update) Best Score: 0.60000
[EarlyStopping] (Patience) 1/3, Best: 0.60000, Current: 0.05000, Delta: 0.55000
[EarlyStopping] (Update) Best Score: 0.89000
[EarlyStopping] (Update) Best Score: 0.99000

댓글남기기