본 블로그 포스팅은 수도권 ICT 이노베이션 스퀘어에서 진행하는 인공지능 고급-시각 강의의 CNN알고리즘 강좌 내용을 필자가 다시 복기한 내용에 관한 것입니다.
MobileNet v3은 이전 포스트 26. 주요 CNN알고리즘 구현 : MobileNet v2 (1) - 인공지능 고급(시각) 강의 복습
의 MobileNet v2를 좀 더 개선한 모델로
주요 개선사항은
⭐ 개선된 아키텍쳐 설계 및 최적화에 NAS(Neural Architecture Search)를 도입
⭐ 경량화 모델에 더 적합한 Activation Function인 Hard-Swish
활성화 함수 도입
⭐ SENet의 핵심 설계모듈인 SE Block
(Squeeze-and-Excitation) 도입으로 모델 정확도 향상
⭐ 고성능 버전(MobileNet v3 - Large), 저용량 버전(MobileNet v3 - Small)으로 파생모델의 세분화
로 정리할 수 있다.
위 개선사항을 통해 MobileNet v2대비 동일 Width Multiplier
, Resolution Multiplier
일 때 전반적으로 20%의 연산복잡도 최적화를 이뤄냈으며,
동일한 연산복잡도 조건에서는 약 3~6%의 정확도 향상을 기대할 수 있다.
MobileNet v3이 성능향상 및 최적화를 하는데 가장 핵심적으로 사용된 방법론이며
신경망 구조탐색으로 번역되는 해당 방법론은 AutoML
의 한 분야에 속하는 방법론이다.
그러면 AutoML
은 무엇이냐? 머신러닝 모델의 설계, 학습, 하이퍼 파라미터 튜닝, 평가, 배포와 같은
기존에 사람이 수작업으로 수행하던 머신러닝 개발 프로세스를 자동화 하는 것을 말하며
이 중 NAS
는 더 최적화된 딥러닝 모델 설계를 위한 AutoML
이라 보면 된다.
이를 통해 사람이 설계를 진행했던 MobileNet v2과 초기 버전의 MobileNet v3를 NAS
를 통하여
레이어 크기, 채널수, 레이어 배치 순서 등을 인공지능으로 설계최적화를 꾀했다.
논문에서는 NAS
의 주요 성과로
MobileNet v3의 가장 마지막 block인 Last Stage
의 레이어 개선과
MobileNet v3를 Backbone
으로 활용한 세그멘테이션 모델 설계 시 세그멘테이션 디코더 블럭의 구조개선을 언급하고 있다.
NAS
를 적용하여 모델을 최적화하는 방법론을 도식화 한다면 초기 모델을 설계한 후
커널크기, 필터 크기, 레이어 개수 등을 탐색공간으로 정의하여
조건별로 여러 모델을 설계하고 각 파생 모델 중 가장 목적에 부합한 최적 모델을 찾아내는 식으로
머신러닝을 위한 머신러닝 : AutoML
이 동작한다.
이 AutoML
은 Google AtuoML, H2O.ai AutoML, Azure AutoML 등으로 플랫폼 형식으로 제공되는 서비스를 이용하는게 일반적인 적용 방법론인 듯 하다.
MobileNet v3는 새로운 활성화 함수를 사용하여 기존에 주로 사용되는 ReLU
(ReLU6) 함수가 갖고 있는 비선형성 문제를 해결하면서
동시에 모바일 환경에서 더 나은 연산 효율성을 확보하려 한다.
이로써 모델의 성능은 향상시키면서 동시에 모델 최적화를 수행하는데
이를 위해서는 기존 활성화 함수가 내포하는 문제점과 개선사항을 모두 숙지할 필요가 있다.
CNN계열의 모델에서 주로 사용되는 ReLU
(MobileNet계열은 Relu6
를 사용하지만 주요 문제점은 거의 동일하다)
우선 ReLU
가 갖고 있는 주요 장점은 아래와 같다.
⭐ 계산 효율성
ReLU
는 여타 다른 활성화 함수에 비해 수식이 매우 간단하기에 연산 속도 및 계산 효율성이 상당히 높은 편에 속한다.
이는 훈련 프로세스를 종료하는 수렴 단계로 도달하는 시간 단축에도 많이 영향을 끼치는데
통상 Sigmoid
함수 대비 6배 정도 수렴 단계에 도달하기에 훈련 프로세스에 소요되는 시간을 단축시키는 장점이 있다.
⭐ Vanishing Gradient(기울기 소실)에 대한 억제력이 높음
Sigmoid
함수가 초기에 사용되었으나, 활성화 함수의 주류에서 밀려난 주 이유로 기울기 소실 문제가 있으며 위 사진처럼 활성화 함수의 출력값이 양 극단에 위치하는 값(Sigmoid
는 0 or 1)일 때는 향후 역전파 연산 시 미분치(기울기값)가 0에 가까운 값이 되버려서 가중치가 거의 업데이트가 되지 않아 훈련이 중단되는 문제가 발생한다.
그에 반해 ReLU
는 미분값이 정수값(1)을 갖기에 기울기 소실문제가 꽤 억제되는 특징이 있다.
⭐ 과적합 방지 및 정규화 효과
ReLU
함수는 음수 입력값을 모두 0
으로 출력하기에 이는 뉴런을 비활성화 하는 Dropout
기법과 유사한 효과를 기대할 수 있다.
따라서 역전파 과정에서 업데이트에 참여하지 않는 뉴런집단을 Dropout
처럼 동일하게 만들어 내기에 이를 통한 과적합 방지 효과를 꾀할 수 있다.
또 가중치의 값 일부를 0으로 만드는 것은 weight에 패널티(0
)을 줘서 원하는 뉴런만을 역전파 과정에 참여시키는
L1, L2 정규화 기법과도 그 효과를 비슷하게 기대할 수 있다.
ReLU
의 장점중 '음수 입력은 0으로 출력'은 과적합 방지라는 장점을 가져오지면 동시에 Dead ReLU이라는 단점을 가져온다.
뉴런이 비활성화 하는게 장점일 수 있지만
동전의 양면처럼 단점이 되기도 하는 것이다.
이는 위 그림처럼 임의의 데이터셋 Data cloud
가 존재할 때 ReLU
함수를 통과하면 값이 음수인 지역(주황색)은 뉴런이 비활성화되는 Dead ReLU가 되버린다.
통상적으로 딥러닝을 학습시키면 위와 같이 Dead ReLU에 속하는 데이터 클라우드는 10~20% 정도가 발생하기에
흔히 Dropout
의 설정값으로 사용하는 0.2~0.5
보다 적은 뉴런이 비활성화 된다.
따라서 대다수의 CNN모델에서는 ReLU
의 단점보다는 장점이 더 많이 부각되지만
MobileNet 계열 네트워크는 단점이 더 많이 부각되는데
이는 Data Cloud
를 차원 축소 후 연산하는 과정이
Inverted Residual Bottelneck Block
에 포함되기 때문이다.
이는 Inverted Residual Bottelneck Block
가 가장 마지막 레이어는 활성화 함수를 사용하지 않는 이유와 맞닿아 있는데
초기 고차원의 Data Cloud
를 구성하는 Intent Vector은 통상적인 음수벡터 요소가 10~20% 정도 되기에
적당한 과적합 방지 효과를 기대할 수 있지만
이 Data Cloud
를 저차원으로 임베딩 하면서 Intent Vector의 벡터 구성요소가 음수지역으로 투영되는 확률이 증가하게 된다.
이는 적정선의 비활성 뉴런의 개수를 증가시키는 효과를 가져오기에
과적합 방지의 이점보다는 Dead RelU
의 단저이 더 부각되는 부작용이 발생하는 것으로 이해할 수 있다.
ReLU
의 장/단점을 공부했으니 이를 개선하고자 하는 수많은 활성화 함수들이 연구되었으며,
대표적으로 사용되는 함수는
LeakyReLU
, SiLU
(Swish), GeLU
, Mish
등이 존재한다.
MobileNet v3는 SiLU
(Swish) 계열의 활성화 함수를 사용하며,
💎 입력값이 음수 구간이어도 작은 값으로 출력이 발생함
💎 기울기(Gradien)가 0이 아닌 부드러운 비 선형성을 갖고 있음
의 장점이 있다.
그러나 다른 활성화 함수랑 비교한다면 SiLU
는 타 활성화 함수인 GeLU
, Mish
랑 그래프 상으로는 큰 차이가 없어보이는데 SiLU
가 사용되는 주요한 이유가 있다.
SiLU
(swish)를 제안한 연구팀은 Google Brain
으로 MobileNet v3은 동일 부서인 Google Brain
와 Google AI Perception
부서의 cowork으로 제안된 모델이기에
MobileNet v3의 개발에 SiLU
이 적용되는 것이 당연하다 볼 수있다.
MobileNet v3에 적용된 Activation Function은 SiLU
(swish)를 목적에 맞게 경량화 시킨 버전인 Hardswish
함수를 사용한다.
Hardswish
는 수식으로 보면 어려워 보이지만
SiLU
= Relu
X Sigmoid
인 것이랑 동일하게
Hardswish
= ReLU 6
X Sigmoid
로 함수를 정리할 수 있다.
ReLU
의 경량화 버전인 ReLU 6
에 Sigmoid
함수를 섞은 것이기에 기존 MobileNet에 적용되던 ReLU 6
의 장점과
SiLU
의 장점이 함께 적용되기를 기대한 함수라 보면 된다.
이때 MobileNet v3는 NAS
를 사용하여 전체 모델에 대한 최적화가 진행되었기에
모든 블럭의 활성화 함수로 Hardswish
를 적용하는 것이 아닌
Hardswish
와 ReLU
를 혼용하여 사용한다.
다음으로 MobileNet v3에 적용된 성능 향상 기법은 이전 포스트 23. 주요 CNN알고리즘 구현 : SENet (3) - 인공지능 고급(시각) 강의 복습
에서 언급한 SENet의 핵심 설계모듈인 SE Block
으로
위 사진처럼 채널별 Attention Score
을 산출한 값을 곱하는 과정을 통해 연산 Cost가 약간 증가하나 이를 상쇄하고도 남는 성능(Accuracay)향상을 꾀할 수 있다.
코드는 이전포스트에서 사용한 SE_Block
과는 약간의 코드개선이 있는데
1️⃣ nn.Linear
[1x1] nn.Conv2D
로 변경
Squeeze Path의 nn.AdaptiveAvgPool2d((1, 1))
메서드를 통해 (HxW) Feature가 (1x1)짜리 node로 변경되었기에
여기서부터는 [1x1]conv2d
는 위 사진처럼 Linear
연산이랑 동일해진다.
이렇게 되면 발생하는 장점이 Flatten
연산을 [1x1]conv2d
적용할 필요성이 없어지고, conv2d
가 linear
대비 GPU병렬연산에 좀 더 친화적이다.
따라서 위 과정을 통해서 코드 개선을 수행할 수 있다.
2️⃣ Attention Score
를 계산하는 마지막 활성화 함수로
Sigmoid
가 아닌 HardSigmoid
적용
HardSigmoid
는 위 사진에서 알 수 있듯이 ReLU6
처럼 임베디드 환경에서 더 간편하게 연산이 가능한 활성화 함수다
따라서 MobileNet v3에는 Attention Score
연산의 완성에 HardSigmoid
를 적용한다.
위 개선사항을 모두 적용한 MobileNet v3 Bneck
블럭의 구조는 위 그림으로 도식화 할 수 있으며
1️⃣NAS
최적화 방법에 따라 Depth-wise Conv
의 Kenal_size가 3
or 5
가 적용됨
2️⃣활성화 함수도 NAS
최적화 방법에 따라 ReLU
or H-SiLU
(Hard swish)가 적용됨
3️⃣SE block
의 도입 (NAS
에 따라 모든 블럭에 적용되는것은 아님)
이렇게 3가지의 주요 개선이 발생한다.
우선 논문에 기재된 고성능 버전(MobileNet v3 - Large), 저용량 버전(MobileNet v3 - Small)의 모델 아키텍쳐를 보면 열받는게
이전 모델 MobileNet v2의 모델 아키텍쳐 표기법을 또 갈아 엎은것도 문제지만 중대한 오류를 발생시키면서 갈아 엎은 문제가 있다.
바로 exp_size
인데 MobileNet v3의 bneck block
의 차원 확장 블럭인 Depth-wise conv block
블럭의 hid_dim
채널 개수 값이 기재되어있고
이게 골때리는게 Width multiplier = 1
인 조건에서의 채널 개수값을 기재한 것이다.
Width multiplier
는 당연히 하이퍼 파라미터에 속하는 가변인자값이고 그에 따라 hid_dim
채널 값이 조정받기에
MobileNet v2 처럼 ratio
단위로 기재를 해야지 왜 뜬금없이 특정조건에서의 채널값을 기재하는지 이해하기 어렵다.
따라서 MobileNet v3-Large, MobileNet v3-Small의 아키텍쳐 표를 올바르게 다시 기재하고자 한다.
# 인자값 순서(Kernel, t, c, SE, AF, S)
# k: 커널사이즈(3x3 or 5x5)
# t: 확장 계수 (expand ratio)
# c: 출력 채널 수
# SE : SE블럭 삽입 유/무
# AF : 적용할 활성화 함수 종류
# s: 스트라이드 (S = 1 or 2)
# BN : Batchnormal 레이어 유/무
MobileNetV3_Feature_setting = {
'Large' : [
[
[3, 1, 16, False, 'RL', 1],
[3, 4, 24, False, 'RL', 2],
[3, 3, 24, False, 'RL', 1],
[5, 3, 40, True, 'RL', 2],
[5, 3, 40, True, 'RL', 1],
[5, 3, 40, True, 'RL', 1],
[3, 6, 80, False, 'HS', 2],
[3, 2.5, 80, False, 'HS', 1],
[3, 2.3, 80, False, 'HS', 1],
[3, 2.3, 80, False, 'HS', 1],
[3, 6, 112, True, 'HS', 1],
[3, 6, 112, True, 'HS', 1],
[5, 6, 160, True, 'HS', 1],
[5, 6, 160, True, 'HS', 1],
], #k, t, c, SE, AF, s
# Efficient Last Stage 인자값
[ #i_c, o_ch BN, SE, AF
[160, 960, True, False, 'HS'],
[960, 1280, False, False, 'HS'],
[1280, -1, False, False, None],
]
],
'Small' : [
[
[3, 1, 16, True, 'RL', 2],
[3, 4.5, 24, False, 'RL', 2],
[3, 11/3, 24, False, 'RL', 1],
[5, 4, 40, True, 'HS', 2],
[5, 6, 40, True, 'HS', 1],
[5, 6, 40, True, 'HS', 1],
[5, 3, 48, True, 'HS', 1],
[5, 3, 48, True, 'HS', 1],
[5, 6, 96, True, 'HS', 2],
[5, 6, 96, True, 'HS', 1],
[5, 6, 96, True, 'HS', 1],
], #k, t, c, SE, AF, s
# Efficient Last Stage 인자값
[ #i_c, o_ch BN, SE, AF
[96, 576, True, True, 'HS'],
[576, 1024, False, False, 'HS'],
[1024, -1, False, False, None],
]
]
}
MobileNet v3-Large, MobileNet v3-Small는 모델 별로 복잡한 레이어 및 블록 세팅이 진행되니
세팅값을 위 코드처럼 딕셔너리로 따로 저장해서 사용하는게 제일 속편하다.
import torch
import torch.nn as nn
class BasicConv(nn.Module):
def __init__(self, in_ch, out_ch, kernel_size, padding, AF=None, **kwargs):
super(BasicConv, self).__init__()
# padding='same'인 경우 padding 값 자동 계산
if padding == 'same':
padding = (kernel_size - 1) // 2
layers = [
nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size,
padding=padding, bias=False, **kwargs),
nn.BatchNorm2d(out_ch)
]
# 활성화 함수는 총 3가지 조건으로 적용
# Default=None인 경우 활셩화 함수 적용 X
if AF == 'RL':
layers.append(nn.ReLU(inplace=True))
elif AF == 'R6':
layers.append(nn.ReLU6(inplace=True))
elif AF == 'HS':
layers.append(nn.Hardswish(inplace=True))
self.conv_block = nn.Sequential(*layers)
def forward(self, x):
x = self.conv_block(x)
return x
MobileNet v3의 가장 기본 모듈인 BasicConv
는 MobileNet 계열의 네트워크가 발전하면서 활성화 함수를 여러 종류를 사용하기에
아에 MobileNet v1도 호환이 가능하도록 코드를 작성했다.
class SEBlock(nn.Module):
def __init__(self, ch, reduction=4):
super(SEBlock, self).__init__()
# Squeeze 단계: 채널의 글로벌 정보 획득
self.squeeze_path = nn.AdaptiveAvgPool2d((1, 1))
# Excitation 단계: 채널 중요도 계산
# (H, W)가 모두 (1,1)되었기에 [1x1]nn.conv2D는 nn.Linear와
# 같은 연산 결과를 기대할 수 있게 됨
# 여기서 [1x1]conv2d는 flatten을 안써도 되고 병렬연산에 더 친화적
# 따라서 FC layer은 동일연산의 [1x1]conv2d로 변경
self.excitation_path = nn.Sequential(
nn.Conv2d(ch, ch//reduction, 1), # 채널 크기 감소
nn.ReLU(inplace = True), # 비선형성 추가
nn.Conv2d(ch//reduction, ch, 1), # 채널 크기 원상 복원
nn.Hardsigmoid(inplace = True) # 중요도 계산
)
def forward(self, x):
# 1. Squeeze 단계: (BS, C, H, W) -> (BS, C, 1, 1)
y = self.squeeze_path(x)
# 2. Excitation 단계: Attention Score를 적용한 (BS, C, 1, 1)
y = self.excitation_path(y)
# 3. Recalibration 단계: 채널별 중요도(Attention Score)
# 를 입력 텐서에 곱함, 여기서 expands_as로 (bs, ch, H, W) 확장
Recalibration = x * y.expand_as(x)
return Recalibration
MobileNet v3의 주요 개선사항이라 볼 수 있는 SEBlock
코드는 앞서 언급한대로 FC layer은 [1x1] Conv2D
로 구현했다.
class BNeckBlock(nn.Module):
def __init__(self, in_ch, out_ch, kernel_size, stride, expand_ratio,
AF=None, SE=False):
super(BNeckBlock, self).__init__()
# 중간 블럭의 채널개수(차원)은 확장계수로 조정됨
hidden_dim = int(in_ch * expand_ratio)
# Residual Connection이 활성화되는 조건
self.use_residual = (stride == 1 and in_ch == out_ch)
layers = [] #쌓을 레이어를 리스트에 담음
if expand_ratio != 1:
# 확장 단계 : 커널 사이즈가 1 + 채널크기가 변경되는 Point-wies Conv
layers.append(BasicConv(in_ch, hidden_dim, kernel_size=1,
stride=1, padding=0, AF = AF))
# Depthwise Conv -> groups 옵션을 사용하여 커널을 그룹별로 적용
layers.append(BasicConv(hidden_dim, hidden_dim,
kernel_size=kernel_size,
stride=stride, padding='same',
AF = AF, groups=hidden_dim))
if SE: # SE_block옵션이 True이면 SEblock를 추가한다
layers.append(SEBlock(hidden_dim))
# 축소단계 : 확장한 채널을 다시 감소시킴, 이때 활성화함수 적용X
# 이 부분 선형 Point-wise Conv이다.
layers.append(BasicConv(hidden_dim, out_ch, kernel_size=1,
stride=1, padding=0, AF = None))
self.res_block = nn.Sequential(*layers)
def forward(self, x):
if self.use_residual:
return x + self.res_block(x)
else:
return self.res_block(x)
MobileNet v3의 Bneck Block
은 사실상 MobileNet v2의
Inverted Residual Bottelneck Block
에서 SE_Block
추가 및 활성화 함수 선택폭이 확장된 수준이다.
물론 레이어 조정 조건이 늘어난 만큼 코드가 조금 복잡해졌으며, MobileNet v2에도 호환되게 코드를 작성했다.
class LastStage(nn.Module):
def __init__(self, alpha=1.0, classifier_set = None,
num_classes=1000):
super(LastStage, self).__init__()
layers = []
for in_ch, out_ch, BN, SE, AF in classifier_set:
in_ch = int(in_ch * alpha)
if out_ch > 0:
out_ch = int(out_ch * alpha)
else:
out_ch = num_classes
layers.append(nn.Conv2d(in_ch, out_ch, kernel_size=1,
stride=1, padding=0, bias=False))
if BN: #Batch_norm 이 있으면 해당 레이어 추가
layers.append(nn.BatchNorm2d(out_ch))
if SE: #SE_block이 있으면 해당 블럭 추가
layers.append(SEBlock(out_ch))
# 활성화 함수는 조건별로 삽입
if AF == 'RL':
layers.append(nn.ReLU(inplace=True))
elif AF == 'R6':
layers.append(nn.ReLU6(inplace=True))
elif AF == 'HS':
layers.append(nn.Hardswish(inplace=True))
# 완성된 Layers 리스트의 1번째 자리에 AvgPool 넣기
layers.insert(1, nn.AdaptiveAvgPool2d((1,1)))
# 가장 마지막 레이어에 Flatten 레이어 넣기
layers.append(nn.Flatten())
self.Last_stage_block = nn.Sequential(*layers)
def forward(self, x):
x = self.Last_stage_block(x)
return x
위 코드는 MobileNet v3의 NAS
방법론을 통해 개선한 블럭인
Efficient Last Stage
를 따로 클래스로 모듈화하여 구현한 코드이다.
Efficient Last Stage
는 사실상 Classifier
의 기능을 수행하는 코드이며, MobileNet v2도 원래 정석으로는 가장 마지막 블럭인 Classifer
을 위와 같이 Last Stage
코드로 작성해야 더 병렬처리에 친화적으로 코드 구동이 가능하다.
class MobileNetV3(nn.Module):
def __init__(self, width_multiplier=1.0, Feature_ext_set = None,
num_classes=1000):
super(MobileNetV3, self).__init__()
#네트워크 각 층의 필터 개수를 조정하는 인자값
self.alpha = width_multiplier
# stem의 out_ch이자 Feature_ext의 Start in_ch
in_ch = int(16*self.alpha)
self.stem = BasicConv(3, in_ch, kernel_size=3,
stride=2, padding=1, AF='HS')
blocks = []
# 레이어 설계 세팅값이 정상적으로 들어올 경우 기동
if len(Feature_ext_set[0][0]) == 6:
for k, t, c, SE, AF, s in Feature_ext_set[0]:
out_ch = int(c * self.alpha)
blocks.append(BNeckBlock(in_ch=in_ch, out_ch=out_ch,
kernel_size=k, stride=s,
expand_ratio=t, AF = AF, SE = SE))
in_ch = out_ch # 세팅된 블럭 추가 후 in_ch업데이트
self.feature_ext = nn.Sequential(*blocks)
self.classifier = LastStage(self.alpha, Feature_ext_set[1],
num_classes)
def forward(self, x):
x = self.stem(x)
x = self.feature_ext(x)
x = self.classifier(x)
return x
코드가 지저분해질만한 항목들은 전부 모듈로 분리를 했고
복잡한 아키텍쳐 블록 별 설정값은 MobileNetV3_Feature_setting
으로 한번에 관리하기에 최종 MobileNet v3부모코드는 꽤 간결하게 작성하는 것이 가능하다.
이전 포스트 26. 주요 CNN알고리즘 구현 : MobileNet v2 (1) - 인공지능 고급(시각) 강의 복습
,
23. 주요 CNN알고리즘 구현 : MobileNet (1) - 인공지능 고급(시각) 강의 복습
에서
MobileNet v2, MobileNet v1를 너무 빡세게 파생 네트워크 별로 성능평가를 진행했기에 MobileNet v3까지 동일하게 성능평가를 하는건 비효율적이라 생각한다.
절대 하기 싫어서 안하는게 아니다...
따라서 width multiplier=1
, resolution multiplier=224
으로만 간단하게 성능평가하고 턴을 종료하려 한다.
사용 데이터셋은 이전과 동일하게 Animlas-10
으로 한다.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MobileNetV3(1, MobileNetV3_Feature_setting['Large'], 1000).to(device)
# 데이터 전처리 방법론 정의
from torchvision.transforms import v2
animals_val = {'mean' : [0.5177, 0.5003, 0.4126],
'std' : [0.2133, 0.2130, 0.2149]
}
def define_transform(img_size, normal_val, augment=False):
transform_list = []
if augment:
transform_list += [ #데이터 증강은 반전, 색상밝기채도, 아핀 3가지
v2.RandomHorizontalFlip(p=0.5),
v2.ColorJitter(brightness=0.4,
contrast=0.4,
saturation=0.4,
hue=0.1),
v2.RandomAffine(degrees=(30, 70),
translate=(0.1, 0.3),
scale=(0.5, 0.75)),
]
transform_list += [
v2.Resize((img_size, img_size)), #이미지 사이즈별로 리사이징
v2.ToImage(), #이미지를 Tensor 자료형으로 변환
v2.ToDtype(torch.float32, scale=True), #텐서 자료형을 [0~1]로 정규화
v2.Normalize(mean=normal_val['mean'], std=normal_val['std']) #데이터셋 표준화
]
return v2.Compose(transform_list)
train_transform = define_transform(img_size=224, normal_val=animals_val, augment=True)
val_transform = define_transform(img_size=224, normal_val=animals_val, augment=False)
import os
from torchvision import datasets
from torch.utils.data import DataLoader
root = './Animals-10'
img_dataset = {}
img_dataloader = {}
img_dataset['train'] = datasets.ImageFolder(os.path.join(root, 'train'),
transform=train_transform)
img_dataset['val'] = datasets.ImageFolder(os.path.join(root, 'val'),
transform=val_transform)
bs = 192
img_dataloader['train'] = DataLoader(img_dataset['train'],
batch_size=bs, shuffle=True)
img_dataloader['val'] = DataLoader(img_dataset['val'],
batch_size=bs, shuffle=False)
import torch.optim as optim
#손실함수 및 옵티마이저 설정
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 사전에 모듈화 한 학습/검증용 라이브러리 import
from C_ModelTrainer import ModelTrainer
num_epoch = 10 #총 훈련/검증 epoch값
ES = 2 # 디스플레이용 에포크 스텝
# BC_mode = True(이진), False(다중)
# aux = 보조분류기 유/무
# wandb = 완디비에 연결 안하면 None
trainer = ModelTrainer(epoch_step=ES, device=device, BC_mode=False, aux=False)
for epoch in range(num_epoch):
train_loss, train_acc = trainer.model_train(
model, img_dataloader['train'],
criterion, optimizer, epoch
)
val_loss, val_acc = trainer.model_evaluate(
model, img_dataloader['val'],
criterion, epoch
)
# Epoch_Step(ES)일때 print하기
if (epoch+1) % ES == 0 or epoch == 0:
print(f"epoch {epoch+1:03d}," + "\t" +
f"훈련 [Loss: {train_loss:.3f}, " +
f"Acc: {train_acc*100:.2f}%]")
print(f"epoch {epoch+1:03d}," + "\t" +
f"검증 [Loss: {val_loss:.3f}, " +
f"Acc: {val_acc*100:.2f}%]")
MobileNet v3, MobileNet v2, MobileNet v1
3개 모델을 다 구현해보고 실험도 해봣으니 각 버전별로 모델 인스턴스화가 가능한 통합 코드를 배포하고자 한다.
https://github.com/tbvjvsladla/MobileNet_pytorch
작성한 코드는 위 깃허브에 업로드 하였으며
사용방법은 아래와 같다
from mobile_net import MobileNetV1, MobileNetV2, MobileNetV3, V3_setting, V2_setting, V1_setting, mobile_debug
# 임의의 MobileNet계열 모델 인스턴스화
exam_model = MobileNetV3(1, V3_setting['Large'], 1000)
# 모델 성능 측정
mobile_debug(exam_model, 192)
MobileNet v3, MobileNet v2, MobileNet v1 모델별로
구현 및 간단 성능측정은
mobile_net.py
의 main
구문을 참조 바란다.
종류별로 인스턴스화가 가능하게 파일을 배포한다.
버전 3개를 내리 코드실습하니까 어질어질하다;;;