MUSTHAVE <머신러닝 • 딥러닝 문제해결 전략> 12장을 실습한 내용입니다.
4. 성능 개선
4가지 기법을 추가해 성능 개선에 도전해보겠습니다.
- 에폭 늘리기
- 스케줄러 추가
- TTA(테스트 단계 데이터 증강) 기법
- 레이블 스무딩 적용
에폭 늘리기와 스케줄러 추가는 '모델 훈련 및 성능 검증' 단계 초반에 수행합니다.
TTA와 레이블 스무딩은 무려 '예측' 단계에서 이루어지는 성능 개선 기법입니다. 모델 생성까지는 베이스라인과 똑같이 실행합니다.
① 모델 훈련 및 성능 검증
손실 함수 및 옵티마이저 설정도 베이스라인과 동일합니다. 스케줄러를 추가로 설정한 후 본격적인 모델훈련에 돌입하겠습니다.
스케줄러 설정
스케줄러는 훈련 과정에서 학습률을 조정하는 기능을 제공합니다.
훈련 초반에는 학습률이 큰 게 좋습니다. 빠르게 가중치를 갱신하기 위해서 입니다.
훈련을 진행하면서 학습률을 점차 줄이면 최적 가중치를 찾기가 더 수월합니다.
get_cosine_scedule_with_warmup() 스케줄러를 사용해 실습해보겠습니다.
해당 스케줄러는 지정한 값만큼 학습률을 증가시켰다가 코사인 그래프 모양으로 점차 감소시키는 스케줄러입니다.
지정한 학습률이란 옵티마이저에서 지정한 학습률을 일컫습니다.
스케줄러를 코드로 정의해보겠습니다.
from transformers import get_cosine_schedule_with_warmup
epochs = 39 # 총 에폭 수 늘림
# 스케줄러 생성
scheduler = get_cosine_schedule_with_warmup(optimizer,
num_warmup_steps=len(loader_train)*3,
num_training_steps=len(loader_train)*epochs)
훈련 및 성능 검증
from sklearn.metrics import roc_auc_score # ROC AUC 점수 계산 함수
from tqdm.notebook import tqdm # 진행률 표시 막대
# 총 에폭만큼 반복
for epoch in range(epochs):
# == [ 훈련 ] ==============================================
model.train() # 모델을 훈련 상태로 설정
epoch_train_loss = 0 # 에폭별 손실값 초기화 (훈련 데이터용)
# '반복 횟수'만큼 반복
for images, labels in tqdm(loader_train):
# 이미지, 레이블(타깃값) 데이터 미니배치를 장비에 할당
images = images.to(device)
labels = labels.to(device)
# 옵티마이저 내 기울기 초기화
optimizer.zero_grad()
# 순전파 : 이미지 데이터를 신경망 모델의 입력값으로 사용해 출력값 계산
outputs = model(images)
# 손실 함수를 활용해 outputs와 labels의 손실값 계산
loss = criterion(outputs, labels)
# 현재 배치에서의 손실 추가 (훈련 데이터용)
epoch_train_loss += loss.item()
loss.backward() # 역전파 수행
optimizer.step() # 가중치 갱신
scheduler.step() # 스케줄러 학습률 갱신
# 훈련 데이터 손실값 출력
print(f'에폭 [{epoch+1}/{epochs}] - 훈련 데이터 손실값 : {epoch_train_loss/len(loader_train):.4f}')
# == [ 검증 ] ==============================================
model.eval() # 모델을 평가 상태로 설정
epoch_valid_loss = 0 # 에폭별 손실값 초기화 (검증 데이터용)
preds_list = [] # 예측 확률값 저장용 리스트 초기화
true_onehot_list = [] # 실제 타깃값 저장용 리스트 초기화
with torch.no_grad(): # 기울기 계산 비활성화
# 미니배치 단위로 검증
for images, labels in loader_valid:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
epoch_valid_loss += loss.item()
preds = torch.softmax(outputs.cpu(), dim=1).numpy() # 예측 확률값
# 실제값 (원-핫 인코딩 형식)
true_onehot = torch.eye(4)[labels].cpu().numpy()
# 예측 확률값과 실제값 저장
preds_list.extend(preds)
true_onehot_list.extend(true_onehot)
# 검증 데이터 손실값 및 ROC AUC 점수 출력
print(f'에폭 [{epoch+1}/{epochs}] - 검증 데이터 손실값 : {epoch_valid_loss/len(loader_valid):.4f} / 검증 데이터 ROC AUC : {roc_auc_score(true_onehot_list, preds_list):.4f}')
② 예측
이번에는 새로운 예측 기법인 TTA와 레이블 스무딩 기법을 배워보겠습니다.
TTA(테스트 데이터 증강)
베이스라인 모델링에서는 albumentations를 활용해 훈련 데이터를 증강시켰습니다.
훈련데이터가 많으면 모델 성능이 좋아지는데, 이러한 데이터 증강 기법을 테스트 단계에서 활용하는 기법을 TTA라고 합니다.
TTA를 활용해 예측하는 절차는 3단계로 이루어집니다.
- 테스트 데이터에 여러 변환을 적용합니다.
- 변환된 테스트 데이터별로 타깃 확률값을 예측합니다.
- 타깃 예측 확률의 평균을 구합니다.
3단계에서 구한 평균 확률을 최종 제출값으로 사용하도록 하겠습니다.
이렇게 하면 앙상블 효과가 있어서 원본 데이터로 한 차례만 예측할 때보다 성능이 좋아질 가능성이 높기 때문입니다.
TTA를 실제로 적용해보도록 하겠습니다. 먼저 데이터셋과 데이터 로더를 두 벌 준비합니다.
테스트 데이터 원본용과 TTA용을 따로 만든다는 뜻입니다. 원본용 데이터셋과 로더는 베이스라인 때와 똑같으며, 테스트 데이터로만 예측할 떄 사용할 것입니다.
# 테스트 데이터 원본 데이터셋 및 데이터 로더
dataset_test = ImageDataset(test, img_dir=img_dir,
transform=transform_test, is_test=True)
loader_test = DataLoader(dataset_test, batch_size=batch_size,
shuffle=False, worker_init_fn=seed_worker,
generator=g, num_workers=2)
# TTA용 데이터셋 및 데이터 로더
dataset_TTA = ImageDataset(test, img_dir=img_dir,
transform=transform_train, is_test=True)
loader_TTA = DataLoader(dataset_TTA, batch_size=batch_size,
shuffle=False, worker_init_fn=seed_worker,
generator=g, num_workers=2)
예측
원본 테스트 데이터로 먼저 예측하고, 이어서 TTA를 적용해서 예측해보겠습니다
원본데이터로 예측하는 코드입니다. preds 변수명을 preds_test라고 한 점만 제외하면 베이스라인과 코드가 같습니다.
odel.eval() # 모델을 평가 상태로 설정
preds_test = np.zeros((len(test), 4)) # 예측값 저장용 배열 초기화
with torch.no_grad():
for i, images in enumerate(loader_test):
images = images.to(device)
outputs = model(images)
# 타깃 예측 확률
preds_part = torch.softmax(outputs.cpu(), dim=1).squeeze().numpy()
preds_test[i*batch_size:(i+1)*batch_size] += preds_part
preds_test는 테스트 데이터 원본으로 예측한 타깃값입니다. 이 값을 'submission을 복사한 submission_test에 저장해둡니다.
submission_test = submission.copy() # 제출 샘플 파일 복사
submission_test[['healthy', 'multiple_diseases', 'rust', 'scab']] = preds_test
TTA를 적용해 예측해보겠습니다.
num_TTA = 7 # TTA 횟수
preds_tta = np.zeros((len(test), 4)) # 예측값 저장용 배열 초기화 (TTA용)
# TTA를 적용해 예측
for i in range(num_TTA):
with torch.no_grad():
for i, images in enumerate(loader_TTA):
images = images.to(device)
outputs = model(images)
# 타깃 예측 확률
preds_part = torch.softmax(outputs.cpu(), dim=1).squeeze().numpy()
preds_tta[i*batch_size:(i+1)*batch_size] += preds_part
TTA를 적용한 예측확률 preds_tta를 구했습니다.
마지막으로 이 값의 평균을 내야합니다.
preds_tta를 구할 때 TTA 횟수(num_TTA)만큼 누적했으니 다시 나눠주면 됩니다.
preds_tta /= num_TTA
예측한 타깃확률을 submission_tta에 저장합니다.
submission_tta = submission.copy()
submission_tta[['healthy', 'multiple_diseases', 'rust', 'scab']] = preds_tta
제출 파일 생성
원본 테스트 데이터로 구한 예측값과 TTA를 적용해 구한 예측값을 각각 제출파일로 만들겠습니다.
submission_test.to_csv('submission_test.csv', index=False)
submission_tta.to_csv('submission_tta.csv', index=False)
여기서 성능을 조금 더 높일 수 있는 레이블 스무딩 기법을 활용해보겠습니다.
레이블 스무딩
간혹 딥러닝 모델이 과잉 확신 하는 경우가 있는데 그렇게 된다면 일반화 성능이 떨어질 우려가 있습니다.
이로 인해 최종 제출 시 평가 점수가 안좋게 나올 수 있습니다.
일반화 성능을 높이려면 과잉 확신한 예측값을 보정해줘야하는데 이때 사용하는 보정기법이 레이블 스무딩입니다.
레이블 스무딩을 적용하는 함수를 정의해보겠습니다.
def apply_label_smoothing(df, target, alpha, threshold):
# 타깃값 복사
df_target = df[target].copy()
k = len(target) # 타깃값 개수
for idx, row in df_target.iterrows():
if (row > threshold).any(): # 임계값을 넘는 타깃값인지 여부 판단
row = (1 - alpha)*row + alpha/k # 레이블 스무딩 적용
df_target.iloc[idx] = row # 레이블 스무딩을 적용한 값으로 변환
return df_target # 레이블 스무딩을 적용한 타깃값 반환
# df: 데이터프레임, target: 타깃값 이름의 리스트, alpha: 레이블스무딩강도, threshold: 레이블 스무딩을 적용할 최소값
이 함수를 이용해 결과에 레이블 스무딩을 실제로 적용해보겠습니다.
alpha = 0.001 # 레이블 스무딩 강도
threshold = 0.999 # 레이블 스무딩을 적용할 임계값
# 레이블 스무딩을 적용하기 위해 DataFrame 복사
submission_test_ls = submission_test.copy()
submission_tta_ls = submission_tta.copy()
target = ['healthy', 'multiple_diseases', 'rust', 'scab'] # 타깃값 열 이름
# 레이블 스무딩 적용
submission_test_ls[target] = apply_label_smoothing(submission_test_ls, target,
alpha, threshold)
submission_tta_ls[target] = apply_label_smoothing(submission_tta_ls, target,
alpha, threshold)
submission_test_ls.to_csv('submission_test_ls.csv', index=False)
submission_tta_ls.to_csv('submission_tta_ls.csv', index=False)
'Data > MLDL' 카테고리의 다른 글
병든 잎사귀 식별 경진대회 (0) | 2023.02.06 |
---|---|
향후 판매량 예측 경진대회 (0) | 2022.11.21 |
안전 운전자 예측 경진대회: 성능개선2(XGBoost 모델) (0) | 2022.11.14 |
안전 운전자 예측 경진대회: 성능개선1(lightGBM 모델) (0) | 2022.11.14 |
안전 운전자 예측 경진대회: 베이스라인 모델 (0) | 2022.11.14 |