[논문 리뷰 + 코드 구현] ConvNeXt

이정민·2026년 5월 3일

[논문 리뷰]

목록 보기
9/19
post-thumbnail

Introduction

2010년대 컴퓨터 비전 분야는 ResNetr과 같은 ConvNet이 지배적이었다.

ConvNet이 시각 인식 시스템의 기본 구성 요소로 자리잡을 수 있었던 이유는 Sliding Window 방식의 도움이 컸는데, 이는 고해상도 이미지 작업에 본질적이기 때문이다.

ConvNet은 다양한 컴퓨터 비전 응용 프로그램에 매우 적합한 Inductive Bias가 있는데, 이 중에서 가장 중요한 것은 객체 탐지와 같은 작업에 알맞은 translation equivariance이다.

Inductive Bias의 세 가지 대표 요소는 ViT논문 리뷰를 참고하자.

한편, 자연어처리(NLP) 분야를 지배하던 Transformer 구조가 2020년 Vision Transformer(ViT)라는 이름으로 비전 분야에 도입이 되면서 큰 변화가 일어났다.

ViT는 이미지를 패치 단위로 나누는 초기 레이어를 제외하면 이미지 특화적인 Inductive bias를 최소화했으며, 거대한 모델 크기와 데이터셋을 바탕으로 이미지 분류에서 표준 ResNet의 성능을 크게 뛰어넘었다.

하지만, ViT의 'Global Attention' 메커니즘은 Input에 대해 Quadratic 복잡도를 가지기 때문에, 고해상도 이미지를 다루는 일반적인 비전 작업의 Backbone으로 사용하기에는 계산량이 기하급수적으로 늘어나는 한계가 有.

ViT는 이미지를 패치 단위로 쪼개어 문장 속 단어처럼 취급한다.
Self-Attention은 "어떤 패치를 이해하기 위해, 이미지 내의 '모든' 다른 패치들을 한 번씩 다 훑어보고 연관성을 계산하는 것"이다. 전체 패치를 다 본다고 해서 Global Attention이라고 부른다.

이러한 ViT의 한계를 극복하기 위해 등장한 것이 Swin Transformer와 같은 Hierarchical Transformer 모델이다.

Swin Transformer는 Local Windows 내에서만 Attention 연산을 수행하는 Sliding Window 방식을 다시 도입하여, ConvNet과 유사한 동작을 하고, 이를 통해 다양한 비전 작업에서 뛰어난 성능을 입증했다.

그럼 결국 Convolution의 특성을 다시 가져온 것인데.. 이 논문이 나옷것도, Local Operation에 강한 ConvNet(CNN)을 다듬는 것이 더 좋을 것이라는 주장에서 나온다.

본 연구는 ConvNet과 Transformer 간의 아키텍처 차이를 조사하고 네트워크 성능을 비교할 때 Confounding Variables를 식별한다고 한다.

이를 위해 표준 ResNet-50 모델을 사용하며, Attention 모듈을 전혀 사용하지 않은 채 Swin Transformer의 구조적 특징들을 적용하며 점진적으로 네트워크를 modernize 한다고 한다.


Modernizing a ConvNEt: a Roadmap

표준 ResNet-50 모델에 Attention 모듈을 하나도 추가하지 않고, Vision Transformer의 디자인 철학만 차용해서 구조를 뜯어고쳤을 때 성능은 얼마나 오르는지 살펴보자.

이 과정은 크게 6개의 Step으로 나눌 수 있다.

  • Training Techniques
  • Macro Design
  • ResNeXt-ify
  • Inverted Bottleneck
  • Large Kernel Sizes
  • Micro Design

Training Techniques

네트워크 아키텍처를 변경하기 전에, Transformer가 도입한 최신의 학습 기법을 먼저 ResNet-50에 적용한다.
Transformer와 유사하게 학습 Epoch를 90에서 300으로 늘리고, AdamW를 비롯하여 Mixup, Cutmix, RandAugemnt, RAndomErasing 등의 Augmentation 기법들을 도입했다.

이러한 Training Techniques를 적용한 것만으로 모델 성능이 76.1%에서 78.8%로 향상되었으며, 이는 기존 ConvNet과 Transformer사이의 성능 차이 상당 부분이 Training Techniques에서 비롯됨을 알 수 있다.


Macro Design

네트워크의 거시적인 뼈대를 Transformer와 비슷하게 변경했다.

Swin Transformer는 ConvNet을 따라 각 스테이지가 다른 특징 맵 해상도를 갖는 다중 스테이지 설계를 사용하는데, 이 때 두 가지를 고려한다.

- Stage Compute Ratio: 기존 ResNet-50의 각 스테이지 블록 개수는 (3, 4, 6, 3)이었으나, 이를 Swin Transformer 비율에 맞게 (3, 3, 9, 3)으로 조정했다.

- Patchify Stem으로 교체: 일반적인 ViT가 이미지를 패치화 하듯, ResNet의초기 7x7 Convolution과 Max Pool 층을 4x4 크기에 stride 4를 갖는 겹치지 않는 non-overlapping convolution)으로 단순 교체했다.


ResNeXt-ify

일반 Convolution 대신, 채널별로 따로 연산하는 Depthwise Convolusion을 도입했다.

이는 Self-Attention 연산이 Spatial 차원에서만 정보를 섞고 채널 정보는 섞지 않는 특성과 수학적으로 매우 유사하다.

이로 인해 연산량이 크게 감소하고, 손실된 용량을 보완하기 위해 네트워크의 채널 폭을 Swin Transformer와 동일하게 64에서 96으로 확장한다.

채널 폭을 늘린 이유 (Depth-wise Conv & Width)

기존 ResNet에서 쓰는 일반 Convolution은 '모든 채널'을 한 번에 섞어서 계산하기 때문에 연산량이 크다.

반면, 이 논문에서 도입한 Depth-wise Convolution은 각 채널을 독립적으로 계산한다.
이는 self-attention이 채널별로 가중합(Weighted Sum)을 수행하는 것과 수학적으로 매우 유사한 성질을 가진다.

문제는 연산 방식을 이렇게 바꾸면 연산량(FLOPs)이 훅 떨어지는 대신, 모델이 학습할 수 있는 용량(Capacity)도 떨어져서 성능이 하락한다.
그래서 ResNeXt의 핵심은 그룹을 많이 쪼개는 대신, 네트워크 폭을 넓히는 것이다.

즉, Depthwise Conv로 연산량을 크게 아꼈으니, 그 아낀 연산량을 투자해서 기본 채널 크기를 64에서 Swin Transformer와 같은 96으로 확장한 것이다.


Inverted BottleNeck

기존 ResNet의 기본 블록인 BottleNeck 구조는 다음과 같다.

[ResNet]
Input -> 3x3 Conv -> Output
연산량이 많은 3x3 Conv를 할 때, 차원을 줄여서 병목 계산을 아끼는 것이 목적이었다.

하지만, Transformer의 핵심 요소인 MLP 블록은 정반대이다. Hidden Dimension을 입력 차원보다 4배 넓게 팽창시켰다가 다시 줄이는 구조를 사용하는데,
연구진들은 이 아이디어를 ConvNet에 그대로 적용한다.

(a) 는 전통적인 병목 구조로, 기존에 우리가 알던 ResNet의 기본 형태이다.

  • 1x1 Conv (384 \rightarrow 96 축소)
  • Depthwise 3x3 Conv (96\rightarrow 96 유지
  • 1x1 Conv (96 \rightarrow 384 팽창)

연산량이 많은 3x3 Conv를 통과시키기 전에 먼저 1x1을 사용해서 채널 차원을 384 \rightarrow 96으로 연산량을 아끼고, 계산이 끝나면 다시 384로 돌려 놓는다.

(b)는 역 병목 구조(Inverted BottleNeck)을 적용한 구조이다.
Transformer의 구조를 따라한 것으로, 트랜스포머의 MLP 블록은 Hidden Dimension이 입력 차원보다 4배 더 큰 특징을 가진다.

이를 ConvNet에 적용하면 모래시계처럼 다이아몬드 모양인데, 이 아이디어는 ConvNet의 MobileNetV2 등에도 사용된다고 한다.

  • 1x1 Conv (96 \rightarrow 384 팽창)
  • Depthwise 3x3 Conv (384 \rightarrow 384 유지)
  • 1x1 Conv (384 \rightarrow 96 축소)

처음부터 좁은 96차원으로 들어와서, 중간에 384 차원으로 4배 팽창(Inverted Bottleneck) 시킨 후, 연산을 하고 다시 96 차원으로 줄여서 내보내도록 설계가 바뀌었다.

(a)에서는 무거운 연산 (Depthwise 3x3)을 96차원에서 했는데, (b)에서는 384 차원으로 했다.

그럼 연산량이 4배 늘어나야 하는 것 아닌가?

논문에 따르면, 전체 네트워크의 연산량(FLOPs)은 오히려 5.3G에서 4.6G로 크게 감소했다.
그 이유는 잔차 연결(Shortcut Connection) 때문인데,
(b) 구조로 바뀌면서 블로그이 최종 출력 차원이 384에서 96으로 크게 줄어든 결과, 해상도를 줄이는 블록 Downsampling Block의 Shortcut 역할을 하는 1x1 Conv 레이어가 처리해야 할 채널 수도 384에서 96으로 크게 줄어들면서, 여기서 아낀 연산량이 훨씬 큰 것이다.

(c): Depthwise Conv의 위치를 맨 위로

  • Depthwise 3x3 Conv (96 \rightarrow 96)
  • 1x1 Conv (96 \rightarrow 384 팽창)
  • 1x1 Conv (384 \rightarrow 96 축소)

트랜스포머 블록을 보면 항상 Spatial Info를 섞는 MSA(Attention 모듈)가 먼저 오고, 그 뒤에 차원을 넓혔다 줄이는 MLP 모듈이 따라온다.

Standard Convolution

standard_conv = nn.Conv2d(in_channels=96, out_channels=96, kernel_size=7, padding=3)
기본적으로 groups=1이 생략되어 있어, 96개의 입력 채널이 한꺼번에 연산되어 96개의 출력 채널을 만들어내기 때문에 연산량이 막대하다.

Depthwise Convolution (ConvNeXt 방식)

depthwise_conv = nn.Conv2d(in_channels=96, out_channels=96, kernel_size=7, padding=3, groups=96)

groups파라미터의 값을 입력 채널 수 (in_channels)와 같게 맞춰준다.

  • groups=96이라고 설정하면, 파이토치는 96개의 채널을 96개의 그룹으로 분할한다.
  • 결과적으로 1개의 그룹당 1개의 채널만 들어가게 된다.
  • 각각의 채널이 다른 채널과 섞이지 않고 자기 자신만의 필터(가중치)를 가지고 독립적으로 연산을 수행하게 된다.

이 순서를 그대로 ConvNet에 매핑한 것이다.
(b)에서는 팽창된 384 차원에서 Depthwise Conv를 했지만, (c)에서는 팽창하기 전인 좁은 96차원에서 먼저 Depthwise Conv를 해버린다.

덕분에 복잡한 Spatial Mixing은 가벼운 채널(96)에서 해버리고, 단순 차원 확장/축소 연산(Channel Mixing)만 1x1 Conv에 맡길 수 있다.

정리하자면, "어떻게 하면 ConvNet의 블록을 Transformer의 블록 구조\rightarrow 넓은 MLP)와 똑같이 맞추면서 연산량은 오히려 줄일 수 있을까?" 에 대한 과정이다.


Large Kernel Sizes

Transformer의 특징인 Global Receptive Field를 ConvNet에서도 모방하기 위해 Kernel Size를 키웠다.

과거 VGGNet의 등장 이후, ConvNet에서는 "큰 커널 하나를 쓰는 것보다 3x3 커널을 여러 층 깊게 쌓는 것이 연산량도 적고 효율적이다." 라는 것이 Standard로 자리잡게 되었다.

하지만 Transformer는 달랐다. ViT는 이미지를 한 번에 보는 능력이 있었고, 심지어 연산량을 줄인 Swin Trasnformer조차도 최소 7x7 크기의 Window 단위로 큼지막하게 이미지를 처리했다.

그래서 연구진들도 3x3의 틀을 깨고, 커널 크기를 키워보자는 것이다.

- Depthwise Conv 끌어올리기
앞서 (c)에서 Depthwise-Conv를 블록 맨 위로 끌어올린 것을 기억 할 것이다. 사실 이것이 '큰 커널'을 감당하기 위한 작업이었다.

  • 만약 384 차원으로 4배 확장된 곳에서 7x7처럼 큰 kernel을 돌렸더라면, 연산량이 말도 안되게 폭발했을 것이다.

    커널의 위치를 블록 맨 위로 올려서, 커널 크기를 7x7로 거대하게 키워도 FLOPs는 거의 늘어나지 않고, 4.1G로 유지될 수 있던 것이다.

연구진들은 Kernel Size를 3x3에서 시작해서 5x5, 7x7, 9x9, 11x11 까지 점차 늘려보며 성능을 측정했다.

결과: 커널을 키울수록 성능이 오르다가, 7x7 크기에 도달했을 때 80.6%로 성능 향상이 정점을 찍었다.

그 이상으로 커널을 키워도 연산량이 아주 조금 늘어날 뿐, 성능은 더 이상 좋아지지 않았다.

저자들이 실험으로 찾은 최적 커널 크기가 공교롭게도 7x7인데, Swin Transformer 역시 window크기를 7x7로 흔히 설정하는 것을 보면, 순수 ConvNet도 7x7 크기의 Depthwise Convolution kernel 을 사용하면 트랜스포머의 넓은 Receptive Field와 지역적 연산 방식을 흉내낼 수 있다는 것을 보인다.


Micro Design

레이어 구석구석의 디테일을 Transformer의 스타일과 맞추는 작업을 해보자.

이 단계에서는 총 5가지의 Micro Design이 진행된다.

  • 활성화 함수 교체: ReLU \rightarrow GELU
  • 활성화 함수 개수 최소화
  • 정규화 레이어 개수 최소화
  • BatchNorm(BN) \rightarrow LayerNorm(LM) 교체
  • 별도의 DownSampling 레이어 도입

- 활성화 함수 교체

기존 ConvNet은 오랫동안 계산이 빠르고 단순한 ReLU를 가장 기본적으로 사용해왔다.

하지만, 최신 Transformer(BERT, GPT-2, ViT 등)은 ReLU보다 곡선이 더 부드러운 GELU를 표준으로 사용한다.

그래서 ConvNeXt도 Transformer를 따라서 활성화 함수를 모두 GELU로 바꿨다. (다만 이 시점에서는 성능은 동일하게 80.6%로 같다.)

- 활성화 함수 개수 최소화

일반적인 ConvNet(ResNet)은 1x1을 포함한 모든 Conv Layer 뒤에 활성화 함수를 꼬박꼬박 붙이는 것이 Standard이다.

하지만, Transformer의 MLP블록을 보면, 선형 레이어들 사이에 활성화 함수가 딱 1개만 존재한다.

연구진들은 이 점에 착안하여 블록 내에 있던 GELU를 전부 지워버리고, 두 개의 1x1 Conv layer 사이에 딱 하나의 GELU만 남겼다.
(놀랍게도 이걸로 성능이 80.6 \rightarrow 81.3%로 향상되며 Swin Transformer와 성능이 사실상 같아졌다.)

- 정규화 레이어의 개수 최소화

활성화 함수와 마찬가지로, Transformer는 블록 내에 정규화 레이어도 훨씬 적게 사용한다.

이를 반영하여 블록 안에 붙어있던 여러 개의 Batch Norm(BN) 레이어를 삭제하고, 1x1 Conv 레이어들 앞에 딱 1개의 정규화 레이어만 남겼다.

결과적으로 81.4%로 성능이 오르며 Swin-Transformer를 뛰어 넘는다.

- BatchNorm(BN) \rightarrow LayerNorm(LN) 교체

BatchNorn(BN)은 ConvNet의 성능을 끌어올린 공신이지만, 배치 크기에 민감하게 반응하는 등 은근히 까다롭다.

반면 NLP와 Trasnformer모델들은 구조가 훨씬 단순한 LayerNorm(LN)을 주로 사용해서 뛰어난 성능을 낸다.

과거에는 Original ResNet 에 LN을 그냥 넣으면 성능이 오히려 떨어졌다. 하지만 뼈대를 Transformer 친화적으로 바꾸고 LN을 도입해보니, 아무런 문제 없이 학습이 잘 되고 성능도 81.5%로 소폭 상승했다.

- 별도의 DownSampling Layer 도입

이미지의 가로세로 해상도를 반으로 줄일 때, 기존 ResNet은 새로운 Stage가 시작되는 첫 번째 블록 안에서 3x3 Conv에 stride 2를 줘서 해상도를 줄였다.

하지만, Swin Transformer는 Stage 사이에 해상도만 전담해서 줄이는 독립적인 레이어를 두고 있다.

이를 흉내내서 ConvNeXt도 DownSampling을 블록 밖으로 분리하여 2x2 Conv (stride 2) 레이어를 별도로 만들었다.

이렇게만 설정하면 학습이 발산하는 문제가 발생하는데, Swin-T가 하는 것처럼 해상도가 변하는 곳(DownSampling 레이어 앞, 초기 패치화 직후, 마지막 풀링 직후)마다 LN 레이어를 추가해주었다.

덕분에 학습이 완벽히 안정화 되었으며, 최종 성능 82.0%를 달성하며 실험은 성공적으로 끝난다.

PyTorch LayerNorm의 한계

파이토치로 Layer Norm을 사용하여모델을 구현할 때에는 텐서 차원의 충돌을 주의해야 한다.
nn.LayerNorm은 자연어처리를 위한 함수이기 때문에 Computer Vision에서 사용하는 것과 형태가 약간 다르다.

  • 자연어 처리에서의 데이터: (B, Sequence, Embedding)
  • CV 에서의 데이터 형태: (B, C, H, W) -> (N, C, H, W)

    파이토치의 nn.LayerNorm()은 기본적으로 텐서의 가장 마지막 차원을 기준으로 데이터를 묶어서 평균과 분산을 구하도록 하드코딩 되어있다.


    permute 없이 그냥 LayerNorm을 하게 된다면 크기 불일치 분제로 에러가 발생하기 쉽다.

Empirical Evaluations on ImageNet

Model Scaling

Swin-T와 공정하게 성능을 비교하기 위해, ConvNeXt-T (Tiny)를 기본 골격으로 삼고, 각 Stage의 채널 수(C)와 블록 개수(B)를 늘려가며 ConvNeXt-S (Small), B(Base), L(Large), XL(Extra Large) 버전을 만들었다.

크기를 키우는 방식은 기존 ResNet이나 Swin-T가 모델을 확장하는 방식과 완전 동일하다.

Fine-tunning

ViT나 Swin-T는 학습할 때와 테스트할 때 이미지의 해상도가 달라지면 패치 크기를 조절하거나 Position Bias 값을 새롭게 interpolate하는 번거로움이 있다.

하지만 ConvNeXt는 Fully-convolutional 이기 때문에, 입력 해상도가 달라져도 네트워크 구조를 뜯어고칠 필요 없이 아주 단순하게 Fine-Tunning 가능하다.

ImageNet-1K Results

120만장의 이미지를 가진 ImageNet-1K의 결과이다.

  • Accuracy: 모든 스케일 (T,S,B,L)에서 ConvNeXt가 비슷한 파라미터 크기를 가진 Swin-T의 정확도를 능가했다.
  • Throughput: ConvNeXt-B 모델을 384x384 고해상도로 테스트했을 때 Swin-B 보다는 0.6% 높으면서 추론 속도는 12.5% 더 빨랐다.

    Swin은 성능을 내기 위해 Shifted Window와 ㄱ ㅏㅌ은 특수한 모듈을 써야해서 하드웨어 친화적이지 않다.

    반면, ConvNeXt는 GPU 친화적인 표준 Conv 모듈만을 사용하기 때문에 실제 속도 면에서 훨씬 유리하다.

ImageNet-22K Results

이 부분에서는 데이터가 많아지면 Transformer가 이길 것이라는 편견을 부순다.

  • ConvNet은 이미지에 특화된 Inductive Bias가 있어서 작은 데이터에서는 좋지만, 데이터가 1천만 장 단위로 넘어가면 제약이 없는 ViT가 더 성능이 좋을 것이라는 믿음이 있었다.

  • 1,400만장의 ImageNet-22K에서 ConvNeXt는 동급의 Swin Transformer와 성능이 같거나 더 우수했다.
    ConvNeXt-XL 모델은 무료 87.8% 라는 압도적인 성능을 보이기도 했다.

    연구진들은 레이어를 지날수록 해상도가 줄어드는 Hierarchical 구조에서 실험했는데, 처음부터 끝까지 해상도가 유지되는 Vanila ViT 구조에 ConvNeXt를 끼워 넣어봤다.

    실험 결과, ViT와 똑같은 형태로 뼈대를 짜고 ConvNeXt 블록을 채워도 기존 ViT와 대등한 성능을 냈다.
    즉, ConvNeXt의 블록 디자인 자체가 어떤 형태의 Architecture에 가져다 붙여도 강력한 설계라는 것이다.


    Empirical Evaluation on Downstream Tasks

    단순 이미지가 '고양이'인지, '강아지' 인지 맞추는 분류 작업도 중요하지만, 자율주행, 의료AI 등 실제 현장에서는 이미지 안의 Object Detection과 Segmentation 등 Downstream Task가 중요하다.

    Object Detection & Instance Segmentation

    연구진들은 COCO 데이터셋을 사용하여 해당 Task의 테스트를 진행했다.

    이미지 안에서 물체를 찾고 픽셀 단위로 모양을 따는 Mask R-CNN과 Cascade Mask R-CNN 모델을 가져와, 그 Backbone 역할에 ConvNeXt와 Swin Transformer를 각각 넣어 비교했다.

    모든 Scale (T, S, B, L)에서 ConvNeXt는 Swin Transformer와 동등하거나 더 높은 성능(AP)를 달성했다.


코드 구현

Augmentation

    train_transform = transforms.Compose([
    # Augmentation
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2,
                           contrast = 0.2,
                           saturation = 0.2,
                           hue = 0.1),

    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])

test_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.405], std=[0.229, 0.224, 0.225])])

Warmup

from torch.optim import lr_scheduler

class LinearWarmupSteps(lr_scheduler.LambdaLR):
    def __init__(self, optimizer, warmup_steps, total_steps, last_epoch=-1):
        def lr_lambda(current_step):
            if current_step < warmup_steps:
                return float(current_step) / float(max(1.0, warmup_steps))
            return max(0.0, float(total_steps - current_step) / float(max(1, total_steps - warmup_steps)))
        
        super().__init__(optimizer, lr_lambda, last_epoch=last_epoch)

Epoch 0인 상태에서는 가중치가 완전히 랜덤하게 설정되어 있다.
이 때, lr을 0.001로 빠르게 설정해버리면 1. 방향을 잘못 잡아 Loss를 발산하거나 NaN에러, Local Minima에 빠지게 된다.

그래서 처음에는 아주 작은 lr로 훈련하다가 서서히 높히는 것이다.

0개의 댓글