모델 경량화 2강 실습 - 작은 모델, 좋은 파라미터 찾기
이 색깔은 주석이라 무시하셔도 됩니다.
1. Overview
1.1 Objective
- 충분히 좋은 configuration 찾기
- 어느 정도의 prior를 개입, 적은 search space를 잡고,
- 적지만, 대표성을 띄는 좋은 subset 데이터를 정하고(+ n-fold Cross validation 등의 테크닉)
- 학습 과정의 profile을 보고 early terminate 하는 기법 적용
- ASHA Scheduler, BOHB(Bayesian Optimization & Hyperband)
- 등등
- Set Hyperparameters
- Loss, Optimizer, Learning rate, Batchsize, ...)
2. 코드: Sample
2.1 이론과 코드의 연결
- Optuna API 활용(https://optuna.org)
- SOTA 알고리즘 구현, 병렬화 용이, Conditional 파라미터 구성 용이
- 과정 Overview
- 1. Optuna Study 생성(blackbox optimizer 및 관리 담당)
- 2. Study에 최적화할 목적함수 및 시도 횟수, 조건 등을 주고 Optimize
if __name__ == "__main__":
study = optuna.create_study(directions="maximize")
study.optimize(objective, n_trials=500)
print(f"Best trial {study.best_trial}")
- 구현
def objective(trial: optuna.trial.Trial) -> float:
"""Get objective score.
Args:
trial: optuna trial object
Returns:
float: Score value.
Whether to maximize, minimize will determined in optuna study setup.
"""
architecture = sample_model(trial)
hyperparams = sample_hyperparam(trial)
model = train_model(Model(architecture, verbose=True), hyperparams)
score = evaluate(model)
return score
study.optimize(objective, n_trials=500)
2.2 Architecture config
- 모델을 코드상에서 직접 구현하는 것보다 쉽게 가져오는 방법
- 논문에서 모델의 구조를 본다면 아래 그림처럼 표 형태로 표현한 것을 종종 볼 수 있음
- 표 형태로 표현된 모델의 구조를 yaml 파일 형식으로도 표현할 수 있음
sample_hyperparams에서 탐색 공간들을 정의하는 방법
- Optuna에서 주요 타입들을 정의하는 방법
# Categorical
activation = trial.suggest_categorial(name=”m2/activation”, choices=[”ReLU”, “ReLU6”, “Hardswish”])
# Continuous
lr = trial.suggest_float(name=”learning_rate”, low=1e-5, high=1e-2)
# Integer
epochs = trial.suggest_int(name=”epochs”, low=10, high=30, step=10)
- Conditional 구성
- Optuna의 큰 장점 중 하나
# 어떤 옵티마이저를 샘플링 하냐에 따라서 다른 탐색 공간을 가질 수 있음
# Sample optimizer
optimizer = trial.suggest_categorical(name="optimizer", choices= ["Adam", "SD", "Adagrad", "Adaml"])
# Optimizer args are conditional!
if optimizer=="Adam":
# More aggressive lr!
lr = trial.suggest_float(name="lr", low=1e-5, high-le-3)
# Adam only params
betal = trial.suggest_float(name="betal", low=0.8, high=0.95)
beta2 = trial.suggest_float(name="beta2", low=0.9, high=8.9999)
elif optimizer="SGD":
# Conservative lr!
lr = trial.suggest float(name="lr", low le-6, high-le-4)
# SGD only params
momentum = trial.suggest float(name="momentum", low=0.0, high=0.95)
Custom search space 구성하기: 예시
- 대부분의 논문이 모듈 block들의 조합 및 구성(macro)을 탐색하는 것이 아닌, 모듈 block(micro)를 탐색하는 것에 주요 초점을 둠
- 하지만 Application 레벨에서는 이미 나와있는 모듈 또한 충분히 좋기 때문에 NAS(Neural Architecture Search)라의 접근법은 좋은 모듈 Block을 그대로 쓰되 Macro 한 구조만 그대로 사용해서 모델을 사용하고자 함
- 다음은 위 그림에서 왼쪽에 있는 구조(Normal Cell과 Reduction Cell이 반복해서 등장하는 구조)를 사용해서 모델을 생성하는 예시
예시 1. 나의 작고 이상한 첫 번째 model: 고정된 N, NC(Normal Cell), RC(Reduction Cell)
n = trial.suggest_int("n_repeat", 1, 5)
# Sample Normal Cell (NC)
nc_args = []
nc = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
if nc == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc == "DWConv":
pass
elif nc == "Bottleneck":
pass
# Sample Reduction Cell(RC)
rc_args = []
rc = trial.suggest_categorical("reduction_cell", ["InvertedResidualv2", "InvertedResidualv3", "MaxPool", "AvgPool"])
if rc == "InvertedResidualv2":
t = trial.suggest_int("reduction_cell/t", 1, 6)
out_channel = trial.suggest_int("reduction_cell/out_channels", 16, 64)
stride = 2
rc_args = [t, out_channel, stride]
elif rc == "InvertedResidualv3":
pass
elif rc == "MaxPool":
pass
model.append([n, nc, nc_args]) # 1: nc * n
model.append([1, rc, rc_args]) # 2: rc
model.append([n, nc, nc_args]) # 3: nc * n
model.append([1, rc, rc_args]) # 4: rc
model.append([n, nc, nc_args]) # 5: nc * n
model.append([1, "GlobalAvgPool", []])
model.append([1, "Flatten", []])
model.append([1, "Linear", [10]])
예시 1. 나의 작고 이상한 두 번째 model: 고정된 N, NC(Normal Cell), RC(Reduction Cell)
- 레이어가 깊어질수록 더 많은 feature를 뽑아야 더 학습이 잘되지 않나?
- → N을 깊어질수록 더 크게 만들자
- NC, RC 각각 고정 파라미터로 sample, 모델을 구성, N은 점점 커지게!(1)
n = trial.suggest_int("n_repeat", 1, 5)
n1, n2, n3 = n, 2*n, 3*n
# Sample Normal Cell (NC)
nc_args = []
nc = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
if nc == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc == "DWConv":
pass
elif nc == "Bottleneck":
pass
# Sample Reduction Cell(RC)
rc_args = []
rc = trial.suggest_categorical("reduction_cell", ["InvertedResidualv2", "InvertedResidualv3", "MaxPool", "AvgPool"])
if rc == "InvertedResidualv2":
t = trial.suggest_int("reduction_cell/t", 1, 6)
out_channel = trial.suggest_int("reduction_cell/out_channels", 16, 64)
stride = 2
rc_args = [t, out_channel, stride]
elif rc == "InvertedResidualv3":
pass
elif rc == "MaxPool":
pass
model.append([n1, nc, nc_args]) # 1: nc * n1
model.append([1, rc, rc_args]) # 2: rc
model.append([n2, nc, nc_args]) # 3: nc * n2
model.append([1, rc, rc_args]) # 4: rc
model.append([n3, nc, nc_args]) # 5: nc * n3
model.append([1, "GlobalAvgPool", []])
model.append([1, "Flatten", []])
model.append([1, "Linear", [10]])
- NC, RC 각각 고정 파라미터로 sample, 모델을 구성, N은 점점 커지게!(2)
n1 = trial.suggest_int("n1_repeat", 1, 3)
n2 = trial.suggest_int("n2_repeat", 2, 5)
n3 = trial.suggest_int("n3_repeat", 3, 8)
# Sample Normal Cell (NC)
nc_args = []
nc = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
if nc == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc == "DWConv":
pass
elif nc == "Bottleneck":
pass
# Sample Reduction Cell(RC)
rc_args = []
rc = trial.suggest_categorical("reduction_cell", ["InvertedResidualv2", "InvertedResidualv3", "MaxPool", "AvgPool"])
if rc == "InvertedResidualv2":
t = trial.suggest_int("reduction_cell/t", 1, 6)
out_channel = trial.suggest_int("reduction_cell/out_channels", 16, 64)
stride = 2
rc_args = [t, out_channel, stride]
elif rc == "InvertedResidualv3":
pass
elif rc == "MaxPool":
pass
model.append([n1, nc, nc_args]) # 1: nc * n1
model.append([1, rc, rc_args]) # 2: rc
model.append([n2, nc, nc_args]) # 3: nc * n2
model.append([1, rc, rc_args]) # 4: rc
model.append([n3, nc, nc_args]) # 5: nc * n3
model.append([1, "GlobalAvgPool", []])
model.append([1, "Flatten", []])
model.append([1, "Linear", [10]])
예시 1. 나의 N번째 model: 가변 N, NC, RC
- 동일한 방법으로 탐색의 범위를 넓힐 수 있음
n1 = trial.suggest_int("n1_repeat", 1, 3)
n2 = trial.suggest_int("n2_repeat", 2, 5)
n3 = trial.suggest_int("n3_repeat", 3, 8)
# Sample Normal Cell (NC)
nc1_args = []
nc2_args = []
nc3_args = []
nc1 = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
nc2 = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
nc3 = trial.suggest_categoricall("normal_cell", ["Conv", "DWConv", "Bottleneck", "InvertedResidualv2", "MBConv"])
if nc1 == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc1_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc1 == "DWConv":
pass
elif nc1 == "Bottleneck":
pass
if nc2 == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc2_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc2 == "DWConv":
pass
elif nc2 == "Bottleneck":
pass
if nc3 == "Conv":
# Conv_args: [out_channel, kernel_size, stride, padding, activation]
out_channel = trial.suggest_int("normal_cell/out_channels", 16, 64)
kernel_size = trial.suggest_int("normal_cell/kernel_size", 1, 7, step=2)
activation = trial.suggest_categorical("normal_cell/activation", ["RELU", "RELU6", "Hardswish"])
nc3_args = [out_channel, kernel_size, 1, None, 1, activation]
elif nc3 == "DWConv":
pass
elif nc3 == "Bottleneck":
pass
# Sample Reduction Cell(RC)
rc1_args = []
rc2_args = []
rc1 = trial.suggest_categorical("reduction_cell", ["InvertedResidualv2", "InvertedResidualv3", "MaxPool", "AvgPool"])
rc2 = trial.suggest_categorical("reduction_cell", ["InvertedResidualv2", "InvertedResidualv3", "MaxPool", "AvgPool"])
if rc1 == "InvertedResidualv2":
t = trial.suggest_int("reduction_cell/t", 1, 6)
out_channel = trial.suggest_int("reduction_cell/out_channels", 16, 64)
stride = 2
rc1_args = [t, out_channel, stride]
elif rc1 == "InvertedResidualv3":
pass
elif rc1 == "MaxPool":
pass
if rc2 == "InvertedResidualv2":
t = trial.suggest_int("reduction_cell/t", 1, 6)
out_channel = trial.suggest_int("reduction_cell/out_channels", 16, 64)
stride = 2
rc2_args = [t, out_channel, stride]
elif rc2 == "InvertedResidualv3":
pass
elif rc2 == "MaxPool":
pass
model.append([n1, nc1, nc1_args]) # 1: nc1 * n1
model.append([1, rc1, rc_args]) # 2: rc1
model.append([n2, nc2, nc2_args]) # 3: nc2 * n2
model.append([1, rc2, rc2_args]) # 4: rc2
model.append([n3, nc3, nc3_args]) # 5: nc3 * n3
model.append([1, "GlobalAvgPool", []])
model.append([1, "Flatten", []])
model.append([1, "Linear", [10]])
예시 1. 나의 N+1번째 model
- (형식과 조건만 맞추면) 더욱 자유로운 모델도 만들 수 있음
model.append([n1, m1, m1_args])
model.append([n2, m2, m2_args])
model.append([n3, m3, m3_args])
model.append([n4, m4, m4_args])
model.append([n5, m5, m5_args])
model.append([n6, m6, m6_args])
model.append([n7, m7, m7_args])
model.append([n8, m8, m8_args])
model.append([n9, m9, m9_args])
model.append([1, "GlobalAvgPool", []])
model.append([1, "Flatten", []])
model.append([1, "Linear", [10]])
https://papers.nips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf
https://direct.mit.edu/books/book/2320/Gaussian-Processes-for-Machine-Learning
Gaussian Processes for Machine Learning
A comprehensive and self-contained introduction to Gaussian processes, which provide a principled, practical, probabilistic approach to learning in kernel machi
direct.mit.edu
https://ieeexplore.ieee.org/document/7352306
Taking the Human Out of the Loop: A Review of Bayesian Optimization
Big Data applications are typically associated with systems involving large numbers of users, massive complex software systems, and large-scale heterogeneous computing and storage architectures. The construction of such systems involves many distributed de
ieeexplore.ieee.org
https://www.boostcourse.org/ai302/joinLectures/374476
딥러닝 모델 더 작게 만들기(경량화)
부스트코스 무료 강의
www.boostcourse.org