서론
본 문서는 YOLOv11 아키텍처 내 기존 컨볼루션 블록을 보다 효율적인 구조로 대체함으로써 모델의 속도와 정확도를 동시에 개선하는 방법을 다룹니다. 특히, 표준 컨볼루션과 깊이별 분리형 컨볼루션을 혼합하여 사용하는
GSConv 모듈을 활용한 경량화 전략을 실습 중심으로 설명합니다.
기본 네트워크인 YOLOv11은 높은 정밀도를 제공하지만, 일부 층에서 계산 비용이 크기 때문에 모바일 또는 엣지 장치 적용 시 최적화가 필요합니다. 이를 해결하기 위해 GSConv는 정보 손실을 최소화하면서도 연산량을 줄이는 설계를 목표로 합니다.
GSConv 아키텍처 요약
GSConv(Ghost Shuffle Convolution)는 GhostNet의 원리를 기반으로 하되, 채널 간 의존성을 강화하고 특징 재사용을 극대화합니다. 주요 구성 요소는 다음과 같습니다:
- 분할 경로(Split Path): 입력 채널을 두 그룹으로 나누어 하나는 표준 3x3 컨볼루션을 통과하고, 다른 하나는 경량화된 생성(ghost) 연산을 수행합니다.
- 채널 셔플(Channel Shuffle): 생성된 피처맵 간 채널 순서를 섞어 서로 다른 경로의 특징이 융합되도록 유도합니다.
- 깊이별 분리형 컨볼루션(Depthwise Separable Conv): 공간 정보 추출 단계에서 연산 효율을 높이기 위해 사용됩니다.
이러한 설계는 FLOPs를 크게 감소시키면서도 중요한 특징의 소실을 억제하여, 고속 추론 환경에서도 안정적인 정확도를 유지합니다.
Ultralytics 코드베이스에 모듈 통합
현재 사용 중인 ultralytics 버전:
8.3.127
모듈을 YOLOv11에 추가하기 위한 절차는 아래와 같습니다.
1. 커스텀 컨볼루션 디렉터리 생성
프로젝트 내
ultralytics/nn/ 경로에
modules 폴더를 생성하고, 그 안에 GSConv 관련 파일을 저장합니다.
mkdir -p ultralytics/nn/modules
touch ultralytics/nn/modules/__init__.py
touch ultralytics/nn/modules/gsconv.py
2. GSConv 클래스 구현
ultralytics/nn/modules/gsconv.py 파일 내에 다음 코드를 삽입합니다:
import torch
import torch.nn as nn
class GSConv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, g=1, act=True):
super().__init__()
c_hidden = int(c2 / 2)
self.conv_standard = nn.Conv2d(c1, c_hidden, k, s, k//2, groups=g, bias=False)
self.conv_ghost = nn.Sequential(
nn.Conv2d(c_hidden, c_hidden, 1, 1, 0, bias=False),
nn.BatchNorm2d(c_hidden),
nn.ReLU(inplace=True),
nn.Conv2d(c_hidden, c_hidden, 3, 1, 1, groups=c_hidden, bias=False)
)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.ReLU(inplace=True) if act else nn.Identity()
self.shuffle = ChannelShuffle(g)
def forward(self, x):
x_main = self.conv_standard(x)
x_ghost = self.conv_ghost(x_main)
out = torch.cat([x_main, x_ghost], dim=1)
out = self.bn(out)
out = self.act(out)
return self.shuffle(out)
class ChannelShuffle(nn.Module):
def __init__(self, groups):
super().__init__()
self.groups = groups
def forward(self, x):
b, c, h, w = x.size()
g = self.groups
x = x.view(b, g, c // g, h, w).permute(0, 2, 1, 3, 4).contiguous()
return x.view(b, c, h, w)
3. 초기화 파일 업데이트
ultralytics/nn/modules/__init__.py 에서 모듈을 외부에서 임포트 가능하도록 등록:
from .gsconv import GSConv
__all__ = ['GSConv']
4. tasks.py 수정 – 모듈 파싱 지원
ultralytics/nn/tasks.py 파일 내부에서
parse_model 함수를 찾은 후, 기본 모듈 집합에
GSConv를 추가합니다:
# parse_model 함수 내 base_modules 정의 부분 예시:
base_modules = {
'Conv', 'Bottleneck', 'C3k2', 'SPPF', 'C2PSA', 'Detect',
'GSConv' # 이 줄을 추가
}
이 과정을 통해 YAML 설정 파일에서
GSConv를 레이어로 인식하고 정상적으로 로드할 수 있게 됩니다.
모델 아키텍처 재정의
새로운 구성 파일을
ultralytics/cfg/models/v11/ 위치에 생성합니다. 예:
yolo11-GSConv.yaml
nc: 80
scales:
n: [0.50, 0.25, 1024]
s: [0.50, 0.50, 1024]
backbone:
- [-1, 1, Conv, [64, 3, 2]]
- [-1, 1, Conv, [128, 3, 2]]
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, GSConv, [256, 3, 2]]
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, GSConv, [512, 3, 2]]
- [-1, 2, C3k2, [512, True]]
- [-1, 1, GSConv, [1024, 3, 2]]
- [-1, 2, C3k2, [1024, True]]
- [-1, 1, SPPF, [1024, 5]]
- [-1, 2, C2PSA, [1024]]
head:
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 6], 1, Concat, [1]]
- [-1, 2, C3k2, [512, False]]
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 4], 1, Concat, [1]]
- [-1, 2, C3k2, [256, False]]
- [-1, 1, GSConv, [256, 3, 2]]
- [[-1, 13], 1, Concat, [1]]
- [-1, 2, C3k2, [512, False]]
- [-1, 1, GSConv, [512, 3, 2]]
- [[-1, 10], 1, Concat, [1]]
- [-1, 2, C3k2, [1024, True]]
- [[16, 19, 22], 1, Detect, [nc]]
주요 변경점은 다음과 같습니다:
- P3, P4, P5 다운샘플링 단계에서 기존
Conv를 GSConv로 교체
- FPN 상단 병합 경로에서도 하위 샘플링 시 GSConv 사용
학습 스크립트 실행
모델 학습을 위한 파이썬 코드 예시:
from ultralytics import YOLO
if __name__ == "__main__":
model_config = "ultralytics/cfg/models/v11/yolo11-GSConv.yaml"
dataset_config = "datasets/mydata/data.yaml"
model = YOLO(model_config)
results = model.train(
data=dataset_config,
epochs=150,
batch=16,
imgsz=640,
name="yolo11_gsconv_exp",
optimizer="AdamW",
lr0=0.001
)
실행 후 출력되는 레이어 요약 정보에서
GSConv 모듈이 정상 포함되었는지 확인할 수 있습니다. 일반적으로 GFLOPs는 기존 대비 10~15% 감소하며, mAP 변화는 ±0.3% 이내로 유지되는 경향이 관찰됩니다.