반응형

<Image Classification 모델 리뷰>

 

이전글 : [AI/Self-Study] - EfficientNet 모델 구조

 

EfficientNet 모델 구조

EfficientNet - B0 baseline 네트워크 구조 EfficientNet B0 전체 모델 구조 파악 MBConv1 Block 구조 (= mobile inverted bottleneck convolution) MobileNetV2 and MobileNetV3 Depthwise Separable Convoluti..

lynnshin.tistory.com

 

1. EfficientNet 설명 (2019, 2020)

EfficientNet 성능

EfficientNet 은 Image Classification Task에 대해서 기존보다 훨씬 적은 파라미터수로 더욱 좋은 성능을 내서 State-Of-The-Art(SOTA) 달성한 모델이다.

 

기존 연구들을 보면, ConvNet 의 성능을 올리기 위해 scaling up 시도를 많이 했다는 것을 알 수 있다. 예를 들어, ResNet 은 ResNet-18 부터 ResNet-200 까지 망의 깊이(depth) = 레이어의 개수를 늘려 성능 향상을 이뤄냈다. 여러 과거 실험을 통해 우리는 망이 깊어질수록 모델은 무거워지지만, 성능은 좋아짐을 알 수 있다. 이것은 모델에 대해 depth scaling을 진행한 것이다.

 

3가지 scale-up

 

Scale-up 방법은 3가지가 있다.

  1. 망의 depth 를 늘리는 것 (= layer의 개수를 늘림) : 가장 흔한 방법
  2. channel width 를 늘리는 것 (= filter의 개수를(channel의 개수를) 늘림) : 기존의 연구에 따르면 width를 넒게 할 수록 미세한 정보들이 더 많이 담긴다고 한다.
  3. 입력 이미지의 resolution 을 올리는 것 (input image의 해상도를 높임) : 실험을 통해 더 큰 사이즈의 이미지를 넣으면 성능이 올라감을 확인했다고 한다.

 

EfficientNet은 세가지 방법에 대한 최적의 조합을 AutoML을 통해 찾은 모델이다. 그러므로 3가지를 효율적으로 조절할 수 있는 compound scaling 방법을 제안한다. 하지만 "효율적"이라는 단어를 쓴 만큼, 제한된 resource 범위에서 최적의 조합 고려한다. 이를 통해 기존보다 훨씬 적은 파라미터수로 더욱 좋은 성능을 내서 State-Of-The-Art(SOTA) 달성할 수 있었던 것이다.

 

참고 : https://norman3.github.io/papers/docs/efficient_net.html

 

2. 모델 구조

EfficientNet은 메인으로 mobile inverted bottleneck convolution (MBConV) 블럭을 사용한다. MBConV 블록을 이해하기 위해서는 아래 두 내용을 이해해야한다.

  • Depthwise separable conv (MobileNetV1에서 처음 제시)
  • Squeeze-and-excitation(SE)

 

1. MobileNetV1 (2017)

  • 모바일 기기에서 동작하는 것을 목표로한 CNN 아키텍쳐
  • Depthwise Separable Convolution 개념 제시 
    (Depthwise Conv + Pointwise Conv)

1) Depthwise Conv

모든 채널에 한번에 컨볼루션 연산을 적용하는 것 대신에 이미지 혹은 피쳐맵을 각 채널 별로 쪼개서 컨볼루션 연산을 적용 (RGB)

  •  인풋의 feature (특징)을 그냥 Conv 보다 적은 양의 파라미터로 캡쳐할 수 있음

2) Pointwise Conv

필터의 크기가 1로 고정되어 있는 1-D Convolution으로 여러 개의 채널을 하나의 새로운 채널로 합치는 역할

  • 채널의 수를 조절 (차원 감소 -> 연산량 감소)

 

보통의 convolution 연산이 input(3*3*3) -> output(1) 으로 한번에 수행할때, depthwise convolution은 intput(3*3 + 3*3 + 3*3) -> output(1 + 1 + 1), pointwise convolution은 input(1 + 1 + 1) -> output (1) 나누어서 연산이 수행한다.

 

Depthwise Separable Convolution

 

기존의 convolution 보다 MobilenetV2에서 k=3 (3 *3 depthwise seperable convolutions) 을 사용한다고 할 때 연산비용은 기존대비 8~9배 정도 감소 (약간의 정확도 감소)

 

파이토치 코드 & 모델 아키텍쳐 👇

더보기
# depthwise convolution를 pytorch에서 적용하기 위해서는,
# groups 파라미터는 number of channels로 세팅한다.
class DepthWiseConv2d(nn.Conv2d):
  def __init__(self, channels, ks=3, st=1):
    super().__init__(channels, channels, ks, stride=st,
                     padding=ks//2, groups=channels)
                     
# pointwise convolution는 1x1 커널 사이즈를 가진다.
class PointWiseConv2d(nn.Conv2d):
  def __init__(self, cin, cout):
    super().__init__(cin, cout, 1, stride=1)
    
# mobilenet block은 depthwise conv, batchnorm, relu, pointwise
# conv, batchnorm and relu로 구성된다.
class MobileNetConv(nn.Module):
  def __init__(self, cin, cout, ks=3, st=1,
               act=nn.ReLU(True)):
    super().__init__()
    self.dwconv = DepthWiseConv2d(cin, ks=ks, st=st)
    self.bn1 = nn.BatchNorm2d(cin)
    self.act = act
    self.pwconv = PointWiseConv2d(cin, cout)
    self.bn2 = nn.BatchNorm2d(cout)
    
  def forward(self, x):
    x = self.act(self.bn1(self.dwconv(x)))
    x = self.act(self.bn2(self.pwconv(x)))
    return x

 

depthwise separable  convolution, which is a 3×3 depthwise convolution layer followed by a 1×1 conv layer

 

2. MobileNetV2 (2018)

  • Linear bottleneck을 갖는 inverted residual 구조 제안 (mobile inverted bottleneck conv)

1) Linear Bottleneck

  • Depthwise Separable Convolution에서 Pointwise Convolution의 연산량이 많아지기 때문에 이러한 계산량 부담을 인식하고 이전 layer인 Depthwise Convolution에 연산 비중을 올리는 테크닉을 제안
  • Manifold 개념 ("고차원의 채널은 사실 저차원의 채널로 잘 표현 할 수 있다")을 제안해 이 가설을 가지고 1x1 Pointwise Conv을 먼저 수행해 채널 수를 늘리고 Depthwise Conv를 진행
    • Manifold의 가설
    • It has been long assumed that manifolds of interest in neural network could be embedded in low-dimensional subspaces에 따르면 ‘고차원의 정보는 사실 저차원으로 표현 가능함’
  • 그 후에 다시 input의 channel 개수로 줄이는 bottleneck block의 구조
  • =>  Expansion layer(pointwise conv)으로 채널 수를 늘리고 depthwise convolution을 수행한 후 projection layer을 통해 다시 input의 channel 개수로 줄이는 bottleneck block의 구조
    Linear Bottleneck (1)

Linear Bottleneck (2) - https://seing.tistory.com/58

 

  • ReLU6 activation
    • Projection layer를 제외한 다른 레이어에서 사용
    • 기존 ReLU에서는 positive region에 상한선이 없음 
    • ReLU6는 positive region에서 maximum value 6를 넘지 못하게 하는 activation function
    • ReLU6는 interger 의 bit를 3 bit으로 고정 가능

https://www.oreilly.com/library/view/tensorflow-machine-learning/9781789131680/b126a62b-4340-4826-9acc-2da0295412fb.xhtml

 

 

낮은 차원의 압축된 표현을 입력으로 받아 높은 차원으로 먼저 확장한 뒤, depthwise convolution 필터를 통과시킨 뒤, linear convolution을 통해 다시 낮은 차원 표현으로 바꿈으로서 => 메모리 접근을 줄임

 

 

2) inverted residual

  • Depthwise Separable Convolution과 Linear Bottleneck 을 합친 것
  • Resnet에서 나온 개념인 residual connection를 사용해서 위에 과정을 진행하면서 여러 레이어들 사이에서도 그레디언트가 잘 전파되도록 함 
    • Residual connection : 연산 후에 input x 를 더하는 것 (Residual Block). Skip connection를 통해 각각의 Layer (Block)들이 작은 정보들을 추가적으로 학습하도록 함. 즉, 기존에 학습한 정보를 보존하고 거기에 추가적으로 학습하는 정보를 의미한다.
  • 기존의 residual connection을 반대로 뒤집은 형태라서 inverted residual라고 부름 (기존은 채널 감소 → 학습(DSC) → 채널 복구 방식이지만, inverted residual은 채널 증가 → 학습(DSC) → 채널 감소 방식이다)
    • gradient vanishing 문제 해결 : layer가 깊어질수록 미분을 점점 많이 하기 때문에 output에 영향을 끼치는 weight의 정도가 학습을 할 수록 작아짐
    • ReLU는 필연적으로 정보손실을 야기하기 때문에 차원이 적을 때 ReLU를 사용하면 성능을 저하되는 문제 해결 : 채널이 많을수록 ReLU가 정보 손실을 발생시켜도 다른 채널에서 정보가 유지될 수 있음 => 먼저 input에 1x1 pointwise convolution으로 채널수를 확장하고 ReLU6가 포함된 Depthwise convolution을 진행
    • Input Data와 Output Data의 크기가 작기 때문에 메모리 효율적

ReLU 함수를 사용할 때, 비선형에 의한 정보 손실 발생 / 차원수가 충분히 큰 공간에서 ReLU 함수를 사용하게 되면 그만큼 정보 손실율이 낮아짐

 

왼쪽: residual bottleneck -> 중간 3x3 컨볼루션 이전에 채널 수를 줄임 / 오른쪽: inverted residual block ->채널 수를 확장한 다음 depthwise convolution을 적용하고, 채널 수를 원래 크기로 다시 줄여 identity path에 더해질 수 있도록 함.

 

오른쪽 : MobileNetV2 모듈

파이토치 코드 & 모델 아키텍쳐 👇

더보기
class InvertedResidualBlock(nn.Module):
  """
  strides>1 이거나 cin!=cout 이면,
  인풋은 residual 에 안더해짐
  """
  
  def __init__(self, cin, cout, stride=1,
              expansion_ratio=6, act=nn.ReLU6(True)):
    super().__init__()
    self.stride = stride
    self.same_c = cin == cout
    self.block = nn.Sequential(
        PointWiseConv2d(cin, cin*expansion_ratio),
        nn.BatchNorm2d(cin*expansion_ratio),
        act,
        DepthWiseConv2d(cin*expansion_ratio, st=stride),
        nn.BatchNorm2d(cin*expansion_ratio),
        act,
        PointWiseConv2d(cin*expansion_ratio, cout),
        nn.BatchNorm2d(cout),
    )
    
  def forward(self, x):
    residual = self.block(x)
    if self.stride != 1 or not self.same_c:
        return residual
    else:
        return x + residual

 

depthwise convolution is now in the middle. Before the depthwise layer is a 1×1 convolution known as the  expansion  layer. This increases the number of channels. After the depthwise layer is another 1×1 convolution that reduces the number of channels again, known as the projection layer or the  bottleneck  layer.

 

출처:  

https://masterzone.tistory.com/38

https://n1094.tistory.com/29

 

MobileNetV2에 대한 자세한 설명 : https://gaussian37.github.io/dl-concept-mobilenet_v2/

 

3. SENet (squeeze-and-excitation network)

  • 모델 성능 향상을 위해 더함
  • Squeeze and Excitation 이라는 이름처럼 Feature를 쥐어짜냈다가, 다시 증폭
  • Se block은 기존의 네트워크에 가져다 붙여 사용 가능 
    • Inception과 ResNet에 해당 모듈을 붙여 테스트 결과 ImageNet Dataset에 대해 기존 모델의 정확도를 0.6%가량 향상시킴

 

Squeeze (압축)

 

Excitation을 거쳐, Channel 사이의 Feature들에 대한 중요도를 Recalibaration(재조정)

 

1) Squeeze

Squeeze는 짜낸다, 즉 각 채널들의 중요한 정보만 추출해서 가져가겠다라는 뜻으로 볼 수 있다. Global average pooling (GAP)을 통해 각 2차원의 특성맵을 평균내어 하나의 값을 얻는 방식으로 스퀴즈(squeeze)를 실행한다. GAP가 Feature들을 1차원 벡터로 바꿔서 HxWxC 크기의 특성맵을 1x1xC로 압축한다. 

(논문상에선 편리상 Global Average Pooling을 사용했지만, 압축하는 방법은 정해져있지 않다.)

 

2) Excitation 활성화

이제 중요한 정보들을 압축(Squeeze)했다면 재조정(Recalibration)을 하는 과정이다. 두 개의 Fully-connected(FC) 층을 더해줘서 각 채널의 상대적 중요도를 알아낸다. (채널 간 의존성(channel-wise dependencies)을 계산)

GP -> FC -> ReLu -> FC -> Sigmoid 로 SE Block 이 이뤄져 있는데,

channel descriptor vector를 선형 변환 시키고, ReLU를 씌우고, 또 선형 변환시킨 뒤 sigmoid 함수를 pointwise로 적용한 것이다. 마지막에 Sigmoid 함수를 지남으로 excitation operation을 거친 스케일 값들이 모두0과 1사이의 값을 가지기 때문에 채널들의 중요도에 따라 스케일 된다.

 

아래 사진에서 색깔은 각 Feature의 0~1까지의 중요도를 나타냄. 이 재조정된 중요도를 담은 Feature를 원래 Feature Map에 곱해주어 중요도가 학습된 새로운 Feature Map이 탄생한다.

 

<자세한 설명 - wolfy.tistory.com/246>

추출된 1x1xC의 피처맵을 FC Layer를 거쳐 C/r (r = reduction ratio) 만큼 1차적으로 차원을 축소해줍니다.
ReLU를 거쳐 FC Layer에서 C만큼 다시 차원을 늘립니다.
이렇게 추출된 중요도 Map을 원래 Feature Map과 곱해 Feature Map을 재조정 해줍니다.

Excitation은 C에서 C/r로 축소된다음, 다시 C가 되는 과정이라고 할 수 있습니다.

 

파이토치 코드 👇

더보기
class SqueezeExcite(nn.Module):
  def __init__(self, cin, reduction_ratio=16):
    super().__init__()
    self.squeeze = nn.Sequential(
        nn.AdaptiveAvgPool2d(1),
        nn.Conv2d(cin, cin//reduction_ratio, 1),
        nn.ReLU(True),
        nn.Conv2d(cin//reduction_ratio, cin, 1),
        nn.Sigmoid()
    )
    
  def forward(self, x):
    squeeze = self.squeeze(x)
    return x * squeeze


출처: 

https://wwiiiii.tistory.com/entry/SqueezeandExcitation-Networks

jayhey.github.io/deep%20learning/2018/07/18/SENet/

*** wolfy.tistory.com/246

*** bskyvision.com/640

 

 

4. MobileNetV3 (2019)

  • NetAdapt 알고리즘이 적용된 NAS (neural architecture search) 을 사용하여 구조를 탐색하고, 탐색한 구조를 수정하여 성능을 개선한 모델
  • 새로운 비선형 함수인 h-swish를 소개 (ReLU6 대신)
    • hard swish or h-swish
    • h_swish(x) = x * ReLU6(x + 3) / 6
    • 모든 레이어에서 h-swish를 사용하는 건 아니고 처음의 반은 보통 ReLU를 사용. MobileNet 저자들이 h-swish는 deeper 레이어에서만 효과가 있다는 것을 발견 
  • MobileNet-Large는 MobileNetV2와 비교하여 20% 감소한 latency로 3.2%의 정확도 상승
  • MobileNet-Small은 동일한 latency에서 MobileNetV2보다 6.6%의 정확도 상승
  • MobileNetV2 구조를 기반으로 하는 구조이며, bottleneck 구조에 squeeze and excitation에 기반한 모듈을 제안
  • Expensive Layers Redesign  - architecture search로 찾은 모델 구조는 마지막 레이어와 초기 레이어가 연산량이 많다는 것을 발견해 이 layer들을 수정
    • MobileNet v1과 v2에서 일반적인  32 filters 의 3 x 3 convolution layer 를 사용하는데 이것이 느림
      •  320 에서 1280 으로 채널을 늘려주는 1 x 1 convolution이 MobileNetV2에서는 global average pooling layer 바로 앞에 있는데 이걸 뒤로 바꿈 > 작은 피쳐맵에서 작동함 (7x7 대신에 1x1), 그리고 빠름
      • => 3개의 레이어를 없애고 정확도는 유지
    • 32개의 필터를 16개로 감소 - 연산량 감소는 크지 않지만 빨라짐
  • MnasNet-A1과 거의 같은 구조
    • 다른점은 1) h-swish activation function 사용
    • 2) expansion layer에서 사용한 필터 개수가 다름 (NetAdapt 알고리즘을 사용해 찾은 최적의 값으로 변경)
    • 3) bottleneck layers 에서 나온 output의 채널 개수가 다를 수 있음 (NetAdapt를 사용해 찾은 값)
    • 4) SE 모듈이 채널 개수를 (ration 가) 3이나 4로 줄임
    • 5) 시그모이드 함수 대신에 SE 모듈은 대략 ReLU6(x + 3) / 6 사용
      • Note: If you’re wondering where that ReLU6(x + 3) / 6 happens, the Mul layer in the right branch multiplies by 0.16667, so that’s the division by 6. The ReLU6 is fused into the conv layer. But where is the x + 3 part? The TFLite converter was clever and actually added 3 to the bias values of the conv layer, a clever micro-optimization that saves doing the addition in a separate layer. We could do this optimization in the Core ML model too and save a layer.

MobileNetV2 의  inverted residual block에 32 filters의 3×3 convolution layer

 

파이토치 코드 & 모델 아키텍쳐 👇

더보기
class HardSwish(nn.Module):
  def forward(self, x):
    return x*(F.relu6(x+3)/6)
    
class InvertedResidualV3(nn.Module):
  """
  MobileNetV3 inverted residual block with squeeze
  excite block embedded into residual layer, after the
  depthwise conv. Uses the HardSwish activation.
  """
  def __init__(self, cin, cout, ks=3, stride=1,
              expansion_ratio=4, squeeze_ratio=None,
              act=HardSwish()):
    super().__init__()
    self.stride = stride
    self.same_c = cin == cout
    self.exp = round(cin*expansion_ratio)
    self.block = nn.Sequential(
        PointWiseConv2d(cin, self.exp),
        nn.BatchNorm2d(self.exp),
        act,
        DepthWiseConv2d(self.exp, st=stride),
        nn.BatchNorm2d(self.exp),
        act,
        SqueezeExcite(self.exp,
                      reduction_ratio=squeeze_ratio),
        PointWiseConv2d(self.exp, cout),
        nn.BatchNorm2d(cout),
    )
    
  def forward(self, x):
    residual = self.block(x)
    if self.stride != 1 or not self.same_c:
        return residual
    else:
        return x + residual
MobileNet v3 uses the same structure as MnasNet-A1

 

5. EfficientNet

[AI/Self-Study] - EfficientNet 모델 구조

 

EfficientNet 모델 구조

EfficientNet 정리 글 : [AI Research Paper Review/More] - EfficientNet 정리 EfficientNet 정리 이전글 : [AI/Self-Study] - EfficientNet 모델 구조 EfficientNet 모델 구조 EfficientNet - B0 baseline 네트..

lynnshin.tistory.com

 

 

 

그 외 참고 :

https://kau-deeperent.tistory.com/56

https://medium.com/@sunwoopark/slow-paper-mobilenetv2-inverted-residuals-and-linear-bottlenecks-6eacaa696b54

https://ttumiel.github.io/blog/mobilenet-to-efficientnet/

*** https://machinethink.net/blog/mobile-architectures/

반응형

'AI Research Paper Review > More' 카테고리의 다른 글

[논문 리뷰] Instances as Queries (Cont.)  (0) 2021.06.28