[딥러닝] YOLO v12 정리 (Area Attention, FlashAttention, R-ELAN)

2025. 6. 23. 17:21·Study/Computer Vision

- 이번 포스팅에서는 실시간 객체 탐지 계열 모델중 최고 성능을 유지하고 있는 YOLO 의 최신 모델인 YOLO v12 를 정리하겠습니다.

 

원래는 YOLO 시리즈를 전부 리뷰하려고 했지만, 이전에 정리한 v4 모델 이후로 후속 모델이 너무 많으므로 그 모든 변경 사항을 전부 정리하는 것은 비효율적이라고 느꼈기에 최신 모델을 정리하려 합니다.

 

이전까지의 YOLO 시리즈 정리글로 인하여 YOLO 객체 탐지 모델이 무엇인지에 대해 배웠고,

이외의 CNN 기법이라던지 Transformer 에 대해서도 정리하였으므로 크게 어렵지 않게 내용을 이해할 수 있을 것입니다.

 

- YOLO v12 는 최신 버전 모델답게 v11 대비 정확도 1.2% 가 향상되었으며, 속도 역시 0.1ms 정도 더 빨라졌습니다.

(FlashAttention 을 지원하는 GPU 환경에서...)

v12 성능 지표
값 도출 특징 히트맵 선명도 비교

 

V12 모델의 의의로는,

전통적으로 CNN 기반으로만 최적화되던 YOLO 시리즈 모델에 Attention 매커니즘의 장점을 실시간 속도 저하 없이 도입했다는 것입니다.

 

Attention 의 정확도가 높은 것은 증명된 바이지만, 느리고 무겁다는 단점이 있죠.

YOLO v12 버전에서 어텐션을 사용하면서도 속도까지 향상시킬 수 있던 이유가 무엇인지에 대해 이번 정리글로 알아본다면,

앞으로 비전 관련 경량 Attention 을 어떤식으로 응용해야 할지에 대한 힌트를 얻을 수 있을 것입니다.

 

- 논문 : YOLOv12: Attention-Centric Real-Time Object Detectors 2025

보시다시피 올해 발표된 최신 모델입니다.

 

(YOLO v12 설명)

- 먼저, 모델 전체 구성은 아래와 같습니다.

[Input Image: 3 x H x W]
        │
        ▼
[Stem Conv (3×3, stride=2)]
        │ → H/2 × W/2
        ▼
[Conv (3×3, stride=2)]
        │ → H/4 × W/4
        ▼
[R-ELAN Block (Stage 1)]
        │ → 채널 증가, 해상도 유지
        ▼
[Conv (3×3, stride=2)]
        │ → H/8 × W/8
        ▼
[R-ELAN + A² (Stage 2)]
        ▼
[Conv (3×3, stride=2)]
        │ → H/16 × W/16
        ▼
[R-ELAN + A² (Stage 3)]
        ▼
[Conv (3×3, stride=2)]
        │ → H/32 × W/32
        ▼
[R-ELAN + A² (Stage 4)]
        ▼
[SPPF (Spatial Pyramid Pooling Fast)]
        ▼
[Neck (PAN, BiFPN, etc.)]
        ▼
[YOLO Head (Detect)]

 

위에서 백본은 Stem 에서부터 R-ELAN 과 다운 샘플링이 반복되는 구조입니다.

그리고 R-ELAN 백본에서 나온 최하위 특징맵에 Neck 에 속하는 SPPF 를 적용하고,

이후 하위 Neck -> Head 로 전달되며 실행됩니다.

 

- R-ELAN(Residual Efficient Layer Aggregation Networks)

R-ELAN

 

위에서 보이듯, R-ELAN 은 ELAN 을 개선한 백본 블록입니다.

대표적인 개선사항으로는, Residual 을 적용하여 특징 추출의 효율을 높이고 학습 안정성을 높였다는 것이 있습니다.

 

ELAN 는 위 구조도에서 보이듯, 여러 레이어의 특징들을 모아서 concat 하는 것이 핵심인 것인데, 이에 Residual 로 안정성을 높였다고 생각하면 되며,

A2 라는 Area Attention 도 백본 안에 존재합니다.

 

속도가 중요한 백본 모델에 위와 같이 어텐션이 총 6개나 들어가는 것은 굉장히 특이하네요.

 

- Area Attention

Area Attention

 

Area Attention 은 전체 영역에 행하는 무거운 Self-Attention 연산을 효율화하는 경량화 Attention 기법입니다.

 

위 figure2 는 A2 모듈을 시각적으로 나타내는 그림입니다.

 

Criss cross 방식은 수직 및 수평 방향으로 attention 을 수행하는 방식으로, 행과 열을 모두 고려하므로 넓은 수용 영역을 가지지만 계산량이 큽니다.

 

window attention 은 Swin Transformer 과 같이 고정된 윈도우 크기 내에서만 어텐션을 수행하여 계산이 적고 빠르지만 문맥 정보가 제한되는 단점이 있으며,

 

Axial Attention 은 가로방향, 세로방향으로 순차적으로 수행되는 방식으로, 수직, 수평 방향으로 전체 feature map 을 커버하여 계산 효율은 좋지만 연산 순서와 병렬화 문제가 존재합니다.

 

A2 는 위와 같은 어텐션의 단점을 해소하였으며,

그림에서 보기에는 가장 커버 범위가 넓어서 연산량이 많을 것 같지만,

사실 위에서 커버하는 영역의 처리는 픽셀이 아니라 평균값을 사용하는 것입니다.

4개의 수직 혹은 수평 영역으로 나누고, 각 영역의 평균을 Key로 사용하여, Query 와 Key 의 연산량을 대폭 줄였습니다.

이러한 방식으로 연산량을 굉장히 줄이고, 각 영역을 대표하는 값인 평균 값을 Key로 사용하여 옅지만 광범위하고 계산량 대비 높은 표현력을 지니게 되었죠.

class AreaAttention(nn.Module):
    def __init__(self, channels: int):
        super().__init__()
        self.channels = channels
        self.qkv = nn.Conv2d(channels, channels * 3, kernel_size=1, bias=False)
        self.proj = nn.Conv2d(channels, channels, kernel_size=1, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        B, C, H, W = x.shape
        qkv = self.qkv(x)  # (B, 3C, H, W)
        q, k, v = qkv.chunk(3, dim=1)  # each (B, C, H, W)

        q_flat = q.view(B, C, H * W).permute(0, 2, 1)

        h_mid = H // 2
        w_mid = W // 2
        stripes_k = [
            k[:, :, :h_mid, :],  # top half
            k[:, :, h_mid:, :],  # bottom half
            k[:, :, :, :w_mid],  # left half
            k[:, :, :, w_mid:]  # right half
        ]
        stripes_v = [
            v[:, :, :h_mid, :],
            v[:, :, h_mid:, :],
            v[:, :, :, :w_mid],
            v[:, :, :, w_mid:]
        ]

        k_list = [s.view(B, C, -1).mean(dim=2) for s in stripes_k]
        v_list = [s.view(B, C, -1).mean(dim=2) for s in stripes_v]

        k_stack = torch.stack(k_list, dim=1)
        v_stack = torch.stack(v_list, dim=1)

        attn = torch.softmax(
            q_flat @ k_stack.transpose(-1, -2) / math.sqrt(C),
            dim=-1
        )

        out_flat = attn @ v_stack
        out = out_flat.permute(0, 2, 1).view(B, C, H, W)

        return self.proj(out)

 

위와 같이 어텐션을 적용할 수 있습니다.

 

k 와 v 를 평균내서 q 와 어텐션 계산을 하죠.

여기서 특이한 점은, YOLO v12 의 A2 에서는 위치 인코딩이 없다는 것인데, stripe 영역 분할 자체가 상하 좌우의 위치 정보를 반영한다고 보기 때문이며, K, V 는 평균 값이기 때문에 공간 정보를 넣어줄 필요가 없는 것이죠.

 

또한 논문에서는 Flash Attention 을 사용했다고 하는데, 이는 해당 라이브러리를 사용하면 됩니다.

해당 라이브러리를 지원하는 CUDA 계열 GPU 하드웨어가 준비된 상태에서요.

 

- R-ELAN 구현 코드

import torch
import torch.nn as nn
import math


class ConvBNAct(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, activation=True):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False)
        self.bn = nn.BatchNorm2d(out_channels)
        self.act = nn.SiLU() if activation else nn.Identity()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))


class ResidualConv(nn.Module):
    def __init__(self, channels):
        super().__init__()
        self.conv1 = ConvBNAct(channels, channels, kernel_size=3, padding=1)
        self.conv2 = ConvBNAct(channels, channels, kernel_size=3, padding=1, activation=False)
        self.act = nn.SiLU()

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        return self.act(out + x)


class AreaAttention(nn.Module):
    def __init__(self, channels: int):
        super().__init__()
        self.channels = channels
        self.qkv = nn.Conv2d(channels, channels * 3, kernel_size=1, bias=False)
        self.proj = nn.Conv2d(channels, channels, kernel_size=1, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        B, C, H, W = x.shape
        qkv = self.qkv(x)  # (B, 3C, H, W)
        q, k, v = qkv.chunk(3, dim=1)  # each (B, C, H, W)

        q_flat = q.view(B, C, H * W).permute(0, 2, 1)

        h_mid = H // 2
        w_mid = W // 2
        stripes_k = [
            k[:, :, :h_mid, :],  # top half
            k[:, :, h_mid:, :],  # bottom half
            k[:, :, :, :w_mid],  # left half
            k[:, :, :, w_mid:]  # right half
        ]
        stripes_v = [
            v[:, :, :h_mid, :],
            v[:, :, h_mid:, :],
            v[:, :, :, :w_mid],
            v[:, :, :, w_mid:]
        ]

        k_list = [s.view(B, C, -1).mean(dim=2) for s in stripes_k]
        v_list = [s.view(B, C, -1).mean(dim=2) for s in stripes_v]

        k_stack = torch.stack(k_list, dim=1)
        v_stack = torch.stack(v_list, dim=1)

        attn = torch.softmax(
            q_flat @ k_stack.transpose(-1, -2) / math.sqrt(C),
            dim=-1
        )

        out_flat = attn @ v_stack
        out = out_flat.permute(0, 2, 1).view(B, C, H, W)

        return self.proj(out)


class R_ELANBlock(nn.Module):
    def __init__(self, in_channels, out_channels, expansion=0.5, scale=0.01):
        super().__init__()
        hidden = int(out_channels * expansion)
        self.scale = scale
        self.shortcut = ConvBNAct(in_channels, out_channels, kernel_size=1, activation=False)
        self.proj = ConvBNAct(in_channels, hidden, kernel_size=1)
        self.b1 = ResidualConv(hidden)
        self.b2 = nn.Sequential(ResidualConv(hidden), ResidualConv(hidden))
        self.b3 = nn.Sequential(ResidualConv(hidden), ResidualConv(hidden), ResidualConv(hidden))
        self.b4 = nn.Sequential(ResidualConv(hidden), ResidualConv(hidden), ResidualConv(hidden), ResidualConv(hidden))
        self.merge12 = ConvBNAct(hidden * 2, hidden, kernel_size=1)
        self.merge123 = ConvBNAct(hidden * 2, hidden, kernel_size=1)
        self.concat_conv = ConvBNAct(hidden * 4, out_channels, kernel_size=1)
        self.attn12_1 = AreaAttention(hidden)
        self.attn12_2 = AreaAttention(hidden)
        self.attn123_1 = AreaAttention(hidden)
        self.attn123_2 = AreaAttention(hidden)
        self.attn_out_1 = AreaAttention(out_channels)
        self.attn_out_2 = AreaAttention(out_channels)

    def forward(self, x):
        orig = x
        x = self.proj(x)
        b1 = self.b1(x)
        b2 = self.b2(x)
        m12 = self.merge12(torch.cat([b1, b2], dim=1))
        m12 = m12 + self.attn12_1(m12) + self.attn12_2(m12)
        b3 = self.b3(m12)
        m123 = self.merge123(torch.cat([m12, b3], dim=1))
        m123 = m123 + self.attn123_1(m123) + self.attn123_2(m123)
        b4 = self.b4(m123)
        concat = torch.cat([b1, b2, b3, b4], dim=1)
        out = self.concat_conv(concat)
        out = out + self.attn_out_1(out) + self.attn_out_2(out)
        return out + self.shortcut(orig) * self.scale


class R_ELAN(nn.Module):
    def __init__(self, in_channels, out_channels, expansion=0.5, scale=0.01):
        super().__init__()
        self.block = R_ELANBlock(in_channels, out_channels, expansion, scale)
        self.act = nn.SiLU()

    def forward(self, x):
        return self.act(self.block(x))

 

R-ELAN 전체 코드는 위와 같습니다.

 

- SPPF(Spatial Pyramid Pooling Fast)

YOLO v12 모델 백본의 마지막 블록을 설명하겠습니다.

SPPF 는 YOLO v5 에서 처음 등장한 구조로, SPP(Spatial Pyramid Pooling) 구조를 간소화하여 연산 속도와 효율을 높인 모듈입니다.

 

SPPF 의 입력은 최종 특징맵 (B, C, H, W) 입니다.

그리고 출력도 (B, C, H, W) 입니다.

Input → 1×1 Conv → [X, MP(X), MP(MP(X)), MP(MP(MP(X)))] → Concat → 1×1 Conv → Output

 

구조는 위와 같습니다.

 

1x1 conv 로 채널 수를 줄여 연산량을 절약하고,

이에 5x5 max pool 을 3번 직렬로 수행하며 중간 결과들을 저장합니다.

그러면 원본인 X 와 Max Pool 이 적용된 결과가 3개 준비되어 총 4개의 결과를 concat 하여 4배의 채널을 가진 특징맵에 1x1 conv 로 다시 처음의 채널 수로 압축하는 프로세스입니다.

class SPPF(nn.Module):
    def __init__(self, in_channels, out_channels, pool_kernel=5):
        super().__init__()
        hidden = in_channels // 2
        self.conv1 = ConvBNAct(in_channels, hidden, kernel_size=1)
        self.pool = nn.MaxPool2d(kernel_size=pool_kernel, stride=1, padding=pool_kernel // 2)
        self.conv2 = ConvBNAct(hidden * 4, out_channels, kernel_size=1)

    def forward(self, x):
        x = self.conv1(x)
        y1 = self.pool(x)
        y2 = self.pool(y1)
        y3 = self.pool(y2)
        return self.conv2(torch.cat([x, y1, y2, y3], dim=1))

 

- Neck 모델

SPPF 에서 반환된 특징맵을 다음으로 넘기기 전에,

다양한 스케일 통합, 상/하위 특징간 상호작용으로 인한 특징 강화 등의 역할을 수행하는 다음 Neck 모델에 대해 알아보겠습니다.

 

YOLO v12 의 Neck 은 일반적으로 PAN(Path Aggregation Network) 이나 BiFPN(Bidirectional Feature Pyramid Network) 중 하나를 선택하여 사용합니다. (논문 및 공식 구현은 BiFPN 사용)

그외에는 실험적으로 RepNeck 이나 BiFusion 역시 도입되기도 하였는데,

 

1. PAN: 연산량·파라미터가 가장 적어 실시간 속도에 유리하지만, BiFPN 대비 정확도(mAP)가 소폭 낮습니다.
2. BiFPN: 양방향 피라미드 구조로 PAN보다 복잡하고 느리지만, 다중 스케일 정보 융합 성능이 좋아 정확도가 더 높습니다.

 

위와 같습니다.

 

목적에 맞게 선택해 사용하면 됩니다.

 

- PAN(Path Aggregation Network)

Bottom-up, Top-down 피처 경로를 사용하는 피처 피라미드 구조로, 다단계 피처맵을 결합하는 역할을 합니다.

 

1. 백본에서 추출된 하위 3단계 레이어의 특징맵을 가져옵니다.

P3 : 80x80 256C

P4 : 40x40 512C

P5 : 20x20 1024C

 

해상도가 낮아질수록 큰 객체 + 고수준 정보가 들어있고,

커질수록 작은 객체, 저수준 정보가 들어있습니다.

 

2. P5 에서부터 업샘플링을 진행합니다.

40x40 으로 업샘플링 후 concat 한 후, 512C 로 다시 1x1 conv 를 하여 P4` 를 생성합니다.

P4` 를 업샘플링하여 P3 크기인 80x80 으로 만든 후, 다시 concat 후 1x1 conv 로 256C 로 만들어 P3` 로 만듭니다.

 

3. 이번에는 P3` 부터 아래 방향으로,

P3` 다운 샘플링 및 concat -> 1x1 conv 로 P4`` 생성

P4`` 다운 샘플링 및 concat -> 1x1 conv 로 P5`` 생성

 

4. 위와 같이 한번 정보가 섞인 최종 출력 피처맵인 P3`, P4``, P5`` 를 Head 로 전달합니다.

 

- BiFPN(Bidirectional Feature Pyramid Network)

PAN 보다 복잡하고 무겁지만 정확도가 높아지는 방법입니다.

프로세스는 PAN 과 유사한데, top-down 과 bottom-up 을 한번이 아니라 여러번 반복 하며, feature fusion 시에는 concat 대신 weighted sum 기반의 병합을 합니다.

 

피쳐맵 단위로 가중치를 부여하는 것으로,

# P4_td, P4: shape = (B, C, H, W)
# learnable weights
w1 = nn.Parameter(torch.ones(1), requires_grad=True)
w2 = nn.Parameter(torch.ones(1), requires_grad=True)

# Fusion
weight_sum = w1 + w2 + 1e-4
F_out = (w1 * P4_td + w2 * P4) / weight_sum

 

위와 같은 수식으로 피쳐맵별 가중치를 부여합니다.(위는 P4 에 가중치 적용)

 

- YOLO v12 한계

YOLO v12 는 Attention 을 통한 Detection 모델에 중점이 맞춰져 있기 때문에 Segmentation 및 추가 태스크 확장은 연구가 더 필요하다고 합니다.

 

또한 어텐션 속도 향상을 위해 도입한 FlashAttention 기술은 지원하는 GPU 가 한정적(CUDA 계열 최신 GPU)이라고 합니다.

즉, FlashAttention 를 사용한 논문 성능이 v11 보다 근소하게 향상되었으므로, 이를 사용하지 못하는 환경에서는 v11 보다 성능이 떨어질 수도 있습니다...;;

 

(결론)

- 여러모로 실험적인 YOLO 모델인 것으로 보여졌습니다.

앞으로 어떻게 발전할지에 대해서는 차기 모델이 될 v13 이나 v14 를 보면 알 수 있을 것 같은데,

개인적으로는 무거운 attention 을 도입하기 위해 특화된 GPU 를 사용하면서도 그렇게까지 극적인 성능 향상을 보이지 못한 v12 보다는, 더 안정적인 버전을 사용하는 것이 낫다고 생각되었습니다.

 

이상입니다.

저작자표시 비영리 변경금지 (새창열림)

'Study > Computer Vision' 카테고리의 다른 글

[딥러닝] YOLO v11 정리 (v5 ~ v10 내용 정리, C3K2, SPPF(Spatial Pyramid Pooling - Fast), FPN(Feature Pyramid Network), C2PSA, PAN)  (0) 2025.06.24
[딥러닝] Swin Transformer 정리 (Transformer 기반 컴퓨터 비전 백본, Window-based Self-Attention, Shifted Window)  (1) 2025.06.20
[딥러닝] EfficientNet V1, V2 정리 (Compound Scaling, NAS(Neural Architecture Search) 간단 소개, MB(Mobile Inverted Bottleneck)Conv + SE)  (1) 2025.06.20
[딥러닝] CNN Attention 기법 정리(SE(Squeeze-and-Excitation), CBAM(Convolutional Block Attention Module), ECA(Efficient Channel Attention))  (0) 2025.06.19
[딥러닝] 이미지 특징 추출 CNN 모델 기법 정리  (0) 2025.06.12
'Study/Computer Vision' 카테고리의 다른 글
  • [딥러닝] YOLO v11 정리 (v5 ~ v10 내용 정리, C3K2, SPPF(Spatial Pyramid Pooling - Fast), FPN(Feature Pyramid Network), C2PSA, PAN)
  • [딥러닝] Swin Transformer 정리 (Transformer 기반 컴퓨터 비전 백본, Window-based Self-Attention, Shifted Window)
  • [딥러닝] EfficientNet V1, V2 정리 (Compound Scaling, NAS(Neural Architecture Search) 간단 소개, MB(Mobile Inverted Bottleneck)Conv + SE)
  • [딥러닝] CNN Attention 기법 정리(SE(Squeeze-and-Excitation), CBAM(Convolutional Block Attention Module), ECA(Efficient Channel Attention))
Railly Linker
Railly Linker
IT 지식 정리 및 공유 블로그
  • Railly Linker
    Railly`s IT 정리노트
    Railly Linker
  • 전체
    오늘
    어제
  • 공지사항

    • 분류 전체보기 (108) N
      • Programming (33)
        • BackEnd (18)
        • FrontEnd (2)
        • DBMS (1)
        • ETC (12)
      • Study (74) N
        • Computer Science (21)
        • Data Science (18) N
        • Computer Vision (16)
        • NLP (15)
        • ETC (4)
      • Error Note (1)
      • ETC (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    docker compose
    논리적 삭제
    docker 배포
    kotlin mutablelist
    MacOS
    Kotlin
    지리 정보
    데이터베이스 제약
    list
    springboot 배포
    kotlin linkedlist
    jvm 메모리 누수
    network_mode: "host"
    localhost
    unique
    단축키
    kotlin arraylist
  • 링크

    • RaillyLinker Github
  • hELLO· Designed By정상우.v4.10.0
Railly Linker
[딥러닝] YOLO v12 정리 (Area Attention, FlashAttention, R-ELAN)
상단으로

티스토리툴바