AI

모델 경량화 2강 실습 - 작은 모델, 좋은 파라미터 찾기

쉬엄쉬엄블로그 2023. 5. 25. 18:27
728x90

이 색깔은 주석이라 무시하셔도 됩니다.

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 파일 형식으로도 표현할 수 있음

ResNet과 ResNeXt 모델 구조를 yaml 형식으로 표현
Mobilenetv2 모델의 구조를 yaml 형식으로 표현
EfficientNetB0 모델의 구조를 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