4 분 소요

본 게시물은 W&B가 제공한 MLOps의 Model Development에 대한 내용을 기술한다.

1. Model Development

1.1 Target

  • 모델 개선 시, 개선이 될 타겟을 선정해야함
  • 앞선 prototype은 사물을 구분기 위한 Semantic Segmentation를 수행

  • 수행 결과 모델이 사람을 구분해내지 못함을 알 수 있음

    person IoU를 개선하는 목표 설정

2. Hyper Parameter

2.1 Hyper parameter optimization

  • 모델의 목적에 맞게 Hyper Parameter를 tuning하여 최적의 결과를 이끌어 내야함

    ☞ 이번 포스팅에서는 사람을 구분해내기 위한 person iou의 성능 개선의 목적을 가짐

  • Hyper parameter의 공간 탐색하는 것은 너무 많은 경우의 수가 존재하기에 수동으로 할 수 없음

    최적화를 오케스트레이션 하는 방법을 정의해야함

2.2 Sweep

  • W&B의 sweep은 하이퍼파라미터 최적화 도구
  • 학습 스크립트와 탐색할 hyper parameter의 범위에 대한 정의가 있으면 대규모 튜닝을 실행할 수 있음

2.3 Train script

## This script comes from 04_refactor_baseline.ipynb

import argparse, os
import wandb
from pathlib import Path
import torchvision.models as tvmodels
import pandas as pd
from fastai.vision.all import *
from fastai.callback.wandb import WandbCallback

import params
from utils import get_predictions, create_iou_table, MIOU, BackgroundIOU, \
                  RoadIOU, TrafficLightIOU, TrafficSignIOU, PersonIOU, VehicleIOU, BicycleIOU, t_or_f
# defaults
default_config = SimpleNamespace(
    framework="fastai",
    img_size=180, #(180, 320) in 16:9 proportions,
    batch_size=8, #8 keep small in Colab to be manageable
    augment=True, # use data augmentation
    epochs=10, # for brevity, increase for better results :)
    lr=2e-3,
    pretrained=True,  # whether to use pretrained encoder,
    mixed_precision=True, # use automatic mixed precision
    arch="resnet18",
    seed=42,
    log_preds=False,
)


def parse_args():
    "Overriding default argments"
    argparser = argparse.ArgumentParser(description='Process hyper-parameters')
    argparser.add_argument('--img_size', type=int, default=default_config.img_size, help='image size')
    argparser.add_argument('--batch_size', type=int, default=default_config.batch_size, help='batch size')
    argparser.add_argument('--epochs', type=int, default=default_config.epochs, help='number of training epochs')
    argparser.add_argument('--lr', type=float, default=default_config.lr, help='learning rate')
    argparser.add_argument('--arch', type=str, default=default_config.arch, help='timm backbone architecture')
    argparser.add_argument('--augment', type=t_or_f, default=default_config.augment, help='Use image augmentation')
    argparser.add_argument('--seed', type=int, default=default_config.seed, help='random seed')
    argparser.add_argument('--log_preds', type=t_or_f, default=default_config.log_preds, help='log model predictions')
    argparser.add_argument('--pretrained', type=t_or_f, default=default_config.pretrained, help='Use pretrained model')
    argparser.add_argument('--mixed_precision', type=t_or_f, default=default_config.mixed_precision, help='use fp16')
    args = argparser.parse_args()
    vars(default_config).update(vars(args))
    return

def download_data():
    "Grab dataset from artifact"
    processed_data_at = wandb.use_artifact(f'{params.PROCESSED_DATA_AT}:latest')
    processed_dataset_dir = Path(processed_data_at.download())
    return processed_dataset_dir

def label_func(fname):
    return (fname.parent.parent/"labels")/f"{fname.stem}_mask.png"
        
def get_df(processed_dataset_dir, is_test=False):
    df = pd.read_csv(processed_dataset_dir / 'data_split.csv')
    
    if not is_test:
        df = df[df.Stage != 'test'].reset_index(drop=True)
        df['is_valid'] = df.Stage == 'valid'
    else:
        df = df[df.Stage == 'test'].reset_index(drop=True)
        
    
    # assign paths
    df["image_fname"] = [processed_dataset_dir/f'images/{f}' for f in df.File_Name.values]
    df["label_fname"] = [label_func(f) for f in df.image_fname.values]
    return df

def get_data(df, bs=4, img_size=180, augment=True):
    block = DataBlock(blocks=(ImageBlock, MaskBlock(codes=params.BDD_CLASSES)),
                  get_x=ColReader("image_fname"),
                  get_y=ColReader("label_fname"),
                  splitter=ColSplitter(),
                  item_tfms=Resize((img_size, int(img_size * 16 / 9))),
                  batch_tfms=aug_transforms() if augment else None,
                 )
    return block.dataloaders(df, bs=bs)


def log_predictions(learn):
    "Log a Table with model predictions and metrics"
    samples, outputs, predictions = get_predictions(learn)
    table = create_iou_table(samples, outputs, predictions, params.BDD_CLASSES)
    wandb.log({"pred_table":table})
    
def final_metrics(learn):
    "Log latest metrics values"
    scores = learn.validate()
    metric_names = ['final_loss'] + [f'final_{x.name}' for x in learn.metrics]
    final_results = {metric_names[i] : scores[i] for i in range(len(scores))}
    for k,v in final_results.items(): 
        wandb.summary[k] = v

def train(config):
    set_seed(config.seed)
    run = wandb.init(project=params.WANDB_PROJECT, entity=params.ENTITY, job_type="training", config=config)
        
    # good practice to inject params using sweeps
    config = wandb.config

    # prepare data
    processed_dataset_dir = download_data()
    proc_df = get_df(processed_dataset_dir)
    dls = get_data(proc_df, bs=config.batch_size, img_size=config.img_size, augment=config.augment)

    metrics = [MIOU(), BackgroundIOU(), RoadIOU(), TrafficLightIOU(),
               TrafficSignIOU(), PersonIOU(), VehicleIOU(), BicycleIOU()]

    cbs = [WandbCallback(log_preds=False, log_model=True), 
           SaveModelCallback(fname=f'run-{wandb.run.id}-model', monitor='miou')]
    cbs += ([MixedPrecision()] if config.mixed_precision else [])

    learn = unet_learner(dls, arch=getattr(tvmodels, config.arch), pretrained=config.pretrained, 
                         metrics=metrics)

    learn.fit_one_cycle(config.epochs, config.lr, cbs=cbs)
    if config.log_preds:
        log_predictions(learn)
    final_metrics(learn)
    
    wandb.finish()

if __name__ == '__main__':
    parse_args()
    train(default_config)
  • Jupyter로 수행한 baseline 코드를 py file 형태로 script 변환
  • 파일 수행 시 parse_args 부분에 정의된 argument를 통해 다양한 파라미터로 학습 가능

2.4 Sweep file

# The program to run
program: train.py

# Method can be grid, random or bayes
method: random

# Project this sweep is part of
project: mlops-course-001
entity: av-team

# Metric to optimize
metric:
  name: miou
  goal: maximize


# Parameters space to search
parameters:
  log_preds:
    value: False
  lr:
    distribution: log_uniform_values
    min: 1e-5
    max: 1e-2
  batch_size:
    values: [4, 8]
  img_size:
    value: 240
  arch:
    values:
      - 'resnet18'
      - 'convnext_tiny'
      - 'regnet_x_400mf'
      - 'mobilenet_v3_small'
  • Method can be grid, random or bayes: hyper parameter 공간의 탐색 방식 정의
    • grid search, random, bayesian optimization
  • Metric to optimize: 모니터링할 metric 설정
  • Parameters space to search: hyper parameter의 공간 정의
    • lr: distribution을 사용하여 연속적인 파라미터 샘플링 가능
    • batch_size: 불연속 파라미터에 대한 값 지정
    • arch: backbone 정의

3. W&B Example

3.1 Sweep space

  • sweep.yaml 파일을 사용하여 wandb의 sweep 기능을 사용

  • wandb 내 sweep space 생성
  • overview에 sweep에 대한 config 내용 확인 가능

3.2 Agent

  • agent 기능을 통해 각 hyper parameter 별로 학습 수행
  • count를 주지 않으면 영원히 실행되기에 수동으로 종료해야함

  • 각 수행 별로 학습의 결과를 확인할 수 있음

3.3 Interpretation

  • 하나의 점은 각 실행을 나타냄
  • parameter가 metric에 미치는 영향을 알 수 있음

  • 평행 좌표 플롯으로 각 parameter 및 metric에 대한 결과를 확인할 수 있음

  • 이번 포스팅의 목표인 person iou가 없기에 pannel에서 해당 metric 추가

  • person iou가 높다고 miou가 높지 않은 것을 확인 할 수 있음

  • 테이블에서 아키텍쳐별 성능을 확인할 수 있음
  • 해당 지표는 각 arch별 sweep의 실행결과에 대한 평균
  • 각 행을 클릭하면 arch의 sweep 수행결과를 확인

  • 가장 성능이 좋았던 모델에 대해 명령어로 다시 수행할 수 있음
  • 사물에 대한 분류 성능이 좋아지도록 image_size의 크기를 더 키움

  • sweep을 통해 진행한 결과보다 더 좋은 성능을 얻음

4. Lesson & Learned

  • MLOps Model Development(1)은 모델의 성능 개선을 위한 이론적 바탕에 대해 학습하였음
  • MLOps Model Development(2)는 모델의 초기 프로토타입인 baseline을 작성하였음
  • MLOps Model Development(3)은 baseline에서 부족한 부분을 tuning을 통해 개선하였음
  • 3가지 과정을 통해 모델 개선을 하는 방법에 대해 이론 뿐 아니라 W&B의 기능을 사용해 수행하는 방법에 대해 배울 수 있었음
  • 향후 프로젝트 시에 모델 성능 이슈 혹은 재학습에 대한 mlops 방안을 이를 통해 개선할 수 있을 듯 함

댓글남기기