Conv Block (CBS)

그냥 Conv라고 하기도 하고 CBS라고 쓰는 곳도 있는 것 같다.
내가 처음 참고한 논문에서는 Conv2d > BN > SiLU를 Conv로 불렀는데 다른 논문들을 몇개 읽어보니 CBS라고 쓰기도 하더라.
Conv 블록은 특징 추출만 담당하는 것이 아닌 안정적이고 매끄럽게 다듬는 고성능 필터 역할을 수행한다.
1. Conv2d
가장 먼저 이미지나 특징맵에서 선, 질감, 형태 같은 시각적 패턴을 추출한다.
필터를 이동시키며 이미지의 가로세로 해상도는 줄이고, 대신 채널(특징의 종류) 수를 늘려 정보를 압축하는 역할을 수행한다.
2. BN : Batch Normalization
Conv2d를 거치며 데이터의 값들이 너무 커지거나 작아져서 한쪽으로 치우치는 현상을 방지해준다.
값들의 분포를 가지런하게 정규화해주기 때문에, 모델이 헷갈리지 않고 훨씬 빠르고 안정적으로 학습할 수 있도록 중심을 잡아준다.
3. SiLU

정규화까지 마친 데이터에 비선형성을 부여해서 모델이 단순한 패턴이 아닌 복잡한 곡선이나 형태를 이해할 수 있도록 한다.
특히 SiLU는 기존에 흔히 쓰던 함수들보다 꺾이는 부분이 훨씬 부드러운 곡선 형태를 띠고 있어서, 층이 깊어져도 정보의 흐름이 끊기지 않고 아주 섬세한 학습을 가능하게 해준다.
C2f 모듈

C2f 모듈은 YOLOv8의 백본에서 핵심이 되는 모듈 중 하나이다.
앞에서 만들어진 Conv 블록을 재료로 삼아, 모델이 더 효율적으로 똑똑해지도록 정보의 길을 설계하는 역할을 한다.
1. 정보의 분할과 병렬처리
들어온 특징맵을 채널 단위로 두 갈래로 쪼갠다.
한쪽은 Conv 블록 여러개로 이루어진 Bottleneck 구간을 지나가고, 다른 한쪽은 연산 없이 그대로 옆으로 우회하도록하여 Concat 한다.
2. 풍부한 정보 결합
깊은 연산을 거치며 새롭게 가공된 특징과, 우회해서 들어온 원래 특징을 마지막에 하나로 합친다.
연산량은 효율적으로 줄이면서도, 다양한 수준의 정보가 보존되게 만드는 것이다.
Conv + C2f
단순 요약과 정교한 융합이 번갈아 일어나면서, 깊고 거대한 시각적 이해력을 갖추게 된다.
1. 안정적인 깊이 확장
단순히 Conv2d만 반복하면 망이 깊어질수록 학습 신호가 약해지거나 망가질 수 있다.
하지만 BN과 SiLU가 포함된 Conv 블록을 사용했기 때문에, 네트워크를 아무리 깊게 쌓아도 기울기 소실 문제 없이 아주 튼튼하게 학습을 이어갈 수 있다.
2. 부드럽고 정교한 추상화 과정
Conv 블록으로 해상도를 부드럽게 줄이면서 핵심만 압축하고, 그 직후에 바로 C2f 모듈로 채널 간의 정보를 아주 풍부하게 섞어주는 과정을 반복한다.
3. 수용 영역의 극대화
이 과정을 거듭할수록 모델이 한 번에 바라보는 이미지의 영역이 점점 넓어짐 결과적으로 초기 레이어의 자잘한 픽셀과 색상 정보들이 깊은 레이어로 갈수록 불꽃의 전체적인 형태나 연기가 퍼지는 거시적인 흐름 같은 고차원적인 정보로 아주 매끄럽고 완벽하게 변환되는 것이다.
SPPF : Spatial Pyramid Pooling-Fast

백본의 가장 깊은 곳, 즉 특징 추출의 마지막 단계에 위치한 이 모듈은 모델이 다양한 크기의 객체를 놓치지 않고 잘 인식하도록 돕는 핵심 기술이다.
1. 주요 목적
SPPF의 가장 큰 목표는 모델의 수용 영역, 즉 네트워크가 한 번에 바라보는 시야를 효율적으로 넓히는 데 있다.
실제 환경에서는 아주 작은 불꽃이 있을 수도 있고 화면 절반을 덮는 거대한 연기가 있을 수도 있다.
SPPF는 좁은 영역부터 넓은 영역까지 다양한 크기의 공간적 맥락을 한꺼번에 뽑아내어 묶어주기 때문에, 객체의 크기나 형태가 극단적으로 변해도 모델이 안정적으로 탐지할 수 있게 해준다.
2. 작동 방식과 내부 구조
입력된 특징맵을 5x5 크기의 최대 풀링 레이어를 통과시키는데, 이를 직렬 방식으로 연결한 것이 특징이다.
특징맵을 첫 번째 5x5 풀링에 통과시켜 작은 영역의 특징을 요약하고, 그 결과물을 다시 두 번째 5x5 풀링에 통과시키면 수학적으로 9x9 크기의 넓은 풀링을 한 것과 완전히 동일한 시야를 확보하게 된다. 그 결과를 한 번 더 5x5 풀링에 통과시키면 13 x 13 크기만큼의 더 넓은 시야를 얻게 되고, 마지막으로는 통과 전의 원본특징맵과 1,2,3 번째 풀링을 거친 특징 맵들을 채널 방향으로 모두 Concat해서 다음 층으로 넘겨준다.
3. Fast라는 이름이 붙은 이유
과거의 딥러닝 모델들은 넓은 시야를 얻기 위해 실제로 5x5, 9x9, 13x13 크기의 큰 필터를 각각 따로 연산한다.(기존 SPP 방식)
이 방식은 연산량이 너무 많아 속도가 느리지만, SPPF처럼 작은 5x5 연산을 직렬로 연달아 재사용하면, 최종적으로 얻는 정보량과 시야의 넓이는 기존과 똑같으면서도 컴퓨터가 처리해야 할 계산량은 크게 줄어들게 된다. 이렇게 하면 속도와 성능을 모두 잡을 수 있다.
GAP
1. GAP의 기본적인 동작 원리
GAP는 이름 그대로 전역적으로 평균을 내는 풀링 기법이다.
특정 채널 하나에 H x W 크기의 픽셀들이 모여있다고 할 때, 이 픽셀들이 가진 모든 숫자 값을 다 더한 다음에 전체 픽셀의 개수로 나누어주는 아주 단순한 산술평균 연산이다.
수식을 표현하면 다음과 같다.
만약 어떤 채널의 크기가 3x3이고 픽셀 값이 1부터 9까지 들어있다면, 이 9개의 숫자를 모두 더한 값인 45를 9로 나눈 5라는 단 하나의 결과값을 만들어내는 것이다. 이 과정을 모든 채널마다 각각 독립적으로 수행한다.
2. GAP는 왜 등장했는가?
GAP는 원래 Network In Network라는 논문에서 처음 제안되었다. 이전까지의 전통적인 CNN 모델들은 피처맵의 특징을 추출한 뒤, 마지막에 고양이인지 강아지인지 분류하기 위해 피처맵을 일렬로 쭉 펴는 Flatten 작업을 하고 여러 층의 FC 레이어를 통과시켰다.
하지만 이 방법에는 두 가지의 큰 문제점이 존재한다.
첫째, 피처맵을 일렬로 펴버리기 때문에 이미지의 공간적인 구조 정보가 완전히 망가져버린다.
둘째, FC 레이어는 파라미터 수가 매우 많기 때문에 모델이 무거워지고 과적합이 아주 쉽게 발생한다.
이러한 문제를 해결하기 위해 등장한 것이 바로 GAP이다.
3. GAP의 작동 목적과 장점
분류해야 할 클래스가 1000개라면, 모델의 마지막 Conv 층에서 채널의 개수를 딱 1000개로 맞춘다. 그리고 각 채널에 GAP를 적용한다.
그러면 1000개의 채널은 각각 1000개의 스칼라 값으로 변하게 되고, 이 값들을 그대로 Softmax 함수에 넣어서 확률을 구하는 방식이다.
이로인하여 얻게 되는 장점은 명확하다.
첫째, 학습할 파라미터가 0개이다. GAP는 단순히 평균을 내는 연산이기 때문에 모델이 학습해야할 가중치가 전혀 없다. 당연히 과적합도 방지되고 모델도 가벼워진다.
둘째, 공간 정보를 보존할 수 있다. 일렬로 펴는 과정이 없기 때문에, 입력 이미지 안에서 객체의 위치가 조금 바뀌더라도 평균 값은 비슷하게 유지되어 훨씬 안정적으로 작동한다.
코드로 보는 GAP
import torch
import torch.nn as nn
# 입력 피처맵 정의: (배치 크기=1, 채널 수=2, 높이=3, 너비=3)
x = torch.tensor([[[[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]],
[[9.0, 8.0, 7.0],
[6.0, 5.0, 4.0],
[3.0, 2.0, 1.0]]]])
# 1x1 크기로 공간 차원을 압축하는 GAP 모듈 선언
gap = nn.AdaptiveAvgPool2d(1)
# 연산 적용
output = gap(x)
print(output)
# 첫 번째 채널 연산 결과: (1+2+3+4+5+6+7+8+9) / 9 = 5.0
# 두 번째 채널 연산 결과: (9+8+7+6+5+4+3+2+1) / 9 = 5.0
# 최종 출력 형태: tensor([[[[5.]], [[5.]]]])
정리하자면 GAP는 복잡한 가중치 연산 없이, 해당 채널이 가진 공간 전체의 정보량을 단순 평균을 통해 하나의 대푯값으로 깔끔하게 요약해주는 강력하고 가벼운 연산기법이라고 생각하면 된다.
GMP
1. GMP의 기본적인 동작 원리
GMP는 채널 내에 있는 H x W 크기의 픽셀들 중에서 가장 큰 숫자 딱 하나만 골라내는 덧셈이나 나눗셈이 없는 아주 단순한 연산이다.
수식으로 표현하면 다음과 같다.
$$y_c = \max_{i,j} x_c(i, j)$$
만약 3x3 크기의 피처맵에 숫자들이 있다면, 그 숫자들을 다 더해서 나누는게 아니라 그 중 제일 높은 Peak 값 하나만 남기고 나머지는 전부 무시해버린다.
2. GMP의 작동 목적과 특징
전체적인 평균을 구하는 GAP와 달리, GMP는 가장 두드러진 특징을 포착하는데 목적이 있다.
예를들어 넓은 산림을 촬영한 이미지에서 아주 작은 불씨나 뚜렷한 불꽃의 시작점을 찾는다고 생각해보자. 배경이 숲으로 가득 차 있기 때문에 전체를 평균 내어버리면 이 작은 불씨의 신호가 배경 정보에 묻혀서 값이 작아질 수 있다. 하지만 GMP를 사용하면 그 좁은 영역에서 발생한 가장 높은 활성화 값 하나를 그대로 살려서 가져올 수 있다.
특히 픽셀 정보가 아직 잘 보존되어 있는 네트워크의 초기 레이어에서 뚜렷한 RGB 특징이나 날카로운 패턴을 잡아낼 때, 해당 채널이 목표물에 얼마나 강하게 반응했는지를 평가하는 데 아주 효과적이다.
코드로 보는 GMP
import torch
import torch.nn as nn
# 입력 텐서 생성: 배치 크기 1, 채널 수 2, 높이 3, 너비 3
x = torch.tensor([[[[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 2.0]],
[[9.0, 1.0, 2.0],
[3.0, 4.0, 5.0],
[6.0, 7.0, 8.0]]]])
# 1x1 출력 크기로 최댓값을 추출하는 GMP 레이어 초기화
gmp = nn.AdaptiveMaxPool2d(1)
# 최댓값 풀링 연산 수행
output = gmp(x)
print(output)
# 첫 번째 채널 최댓값 추출 결과: 8.0
# 두 번째 채널 최댓값 추출 결과: 9.0
# 결과 텐서 형태: tensor([[[[8.]], [[9.]]]])
이렇게 GMP는 공간 정보 중에서 가장 강한 신호만 뽑아내어, 해당 채널이 가진 특징의 최댓값을 단 하나의 스칼라 값으로 요약해주는 연산이다.
GAP vs GMP
1. 연산 방식의 차이
GAP는 채널 안의 모든 픽셀값을 더한 뒤 픽셀 개수로 나누어 평균을 구한다. 공간 전체의 정보를 골고루 섞어버리는 느낌으로 동작한다.
GMP는 채널 안의 픽셀 값 중 가장 큰 숫자 하나만 뽑아낸다. 나머지 정보는 완전히 버리고 가장 뾰족한 특징 하나만 가져오게 되는 것이다.
2. 정보 요약의 성격
GAP는 전체적인 숲을 보는 방식으로 동작한다. 이미지 전반에 걸쳐 해당 채널의 특징이 얼마나 넓고 부드럽게 퍼져있는지 문맥을 파악한다.
GMP는 나무 하나에 집중하는 방식이다. 특징이 얼마나 좁은 영역에 있든 상관없이, 가장 강렬하게 반응한 핵심 피크 신호를 놓치지 않고 잡아낸다.
3. 노이즈에 대한 민감도
GAP는 모든 픽셀값을 반영하기 때문에, 배경 노이즈가 많거나 쓸데없는 정보가 섞여있으면 평균값이 떨어지면서 신호가 약해질 수 있다.
GMP는 가장 큰 값만 취하기 때문에 배경 노이즈를 완벽하게 무시한다. 타겟이 작더라도 강하게 빛난다면 그 신호를 온전히 유지할 수 있다.
4. 언제 사용하면 되나?
GAP는 객체가 이미지 전체에 크게 분포하거나, 이미지의 전반적인 분위기나 클래스를 파악해야 할 때 유리하다.
GMP는 객체가 아주 작고 뚜렷하거나, 특정 부위의 날카로운 패턴을 잡아내야 할 때 유리하다.