개요

22편에서 CNN이 사진을 보는 방식을 봤다. 이번 글은 그 부품들로 사람들이 실제로 무엇을 만들었는지다.

모델 이름을 외우는 글이 아니다. 나는 이 계보가 다섯 번의 질문과 다섯 번의 대답이라고 생각한다.

연도 모델 던진 질문 대답
2012 AlexNet 깊은 CNN이 진짜 되나? 된다, GPU만 있으면
2014 VGG 어떻게 쌓는 게 좋은가? 작은 필터를 깊게
2014 GoogLeNet 꼭 깊게만 가야 하나? 넓게, 대신 1×1로 조인다
2015 ResNet 더 깊게는 왜 안 되나? 지름길을 내면 된다
2017 MobileNet 꼭 이렇게 비싸야 하나? 쪼개면 9배 싸진다

교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다.


1. 판이 갈린 순간

ImageNet 에러율 변화

2010~2011년은 에러율 25~28%짜리 얕은 모델의 시대였다. 그러다 2012년 AlexNet이 16.4%로 뚝 떨어뜨린다. 그 뒤로는 전부 딥러닝이고, 2015년 ResNet이 3.6%로 사람의 오차(5.1%)를 넘는다.

그래프에서 눈여겨볼 건 에러율보다 층수 표시다. 8층 → 19층 → 22층 → 152층. 성능이 좋아진 방향과 깊어진 방향이 정확히 같다. 하지만 곧 보겠지만 “깊게만 하면 된다”는 건 사실이 아니었고, 그 벽을 뚫는 게 ResNet의 이야기다.

여기서 짚고 갈 게 두 가지 있다.

첫째, “사람의 오차 5.1%”는 인류의 능력치가 아니다. 이 숫자는 연구자 한 명(Andrej Karpathy)이 검증셋 일부를 직접 라벨링해서 측정한 값이다. ImageNet에는 개 품종만 120종이 들어 있어서, 사람이 못 맞히는 건 “보는 능력”이 부족해서가 아니라 품종 이름을 몰라서인 경우가 많다. 그러니 “2015년에 AI가 사람을 이겼다”는 문장은 ImageNet 1000종 분류라는 특정 과제에서, 특정 방식으로 측정했을 때 라는 단서를 빼면 과장이 된다.

둘째, 이 표의 에러율은 전부 Top-5다. 모델이 답 다섯 개를 냈을 때 그 안에 정답이 있으면 맞은 것으로 친다. Top-1(첫 번째 답만 인정)으로 재면 숫자가 훨씬 나쁘다. ResNet-152도 Top-1 기준으로는 20%대다. 논문 표를 비교할 때 Top-1인지 Top-5인지 확인하지 않으면 엉뚱한 결론이 나온다.


2. AlexNet — 되는 걸 증명한 모델

구조 자체는 지금 보면 소박하다.

합성곱 5개 + 완전연결 3개 = 8층, ReLU 활성화, 맥스 풀링, 입력 3×227×227

구조 말고 AlexNet이 실제로 쓴 재료도 같이 적어둔다. 구조만 보면 “그냥 CNN을 깊게 쌓은 것”인데, 당시 이걸 학습이 되게 만든 건 아래쪽이었다.

재료 왜 넣었나 지금은
ReLU 시그모이드보다 수렴이 6배 빨랐다 여전히 기본
드롭아웃 (FC층에 0.5) 파라미터 6천만 개짜리 모델의 과적합 방지 FC층이 사라지며 CNN에선 잘 안 씀
데이터 증강 (크롭·좌우반전·색상 변형) 사실상 학습 데이터를 수십 배로 여전히 기본
LRN (국소 응답 정규화) 채널 간 경쟁을 유도 배치 정규화로 완전히 대체됨
겹치는 풀링 (3×3, 스트라이드 2) 약간의 정규화 효과 잘 안 씀

LRN은 특히 흥미로운데, 이후 연구에서 효과가 거의 없다는 게 밝혀졌다. VGG 논문이 대놓고 “LRN은 성능을 올리지 않으면서 메모리와 시간만 잡아먹는다”는 취지로 적어놨다. 당시엔 맞다고 믿었던 것이 몇 년 만에 폐기되는 게 이 분야의 속도다.

계산을 손으로 따라가 보자 — Conv1

말로만 넘어가면 감이 안 온다. 첫 번째 합성곱층 하나만 직접 계산해 보자.

주어진 값

항목 값
입력 3채널 × 227 × 227
필터 96개, 크기 11×11
스트라이드 4
패딩 0

① 출력 크기

$$ W’ = \left\lfloor \frac{227 - 11 + 2\times 0}{4} \right\rfloor + 1 = 55 $$

출력 채널은 필터 개수 그대로 96. 즉 출력은 96 × 55 × 55다.

바닥 함수(⌊ ⌋)를 빼먹지 말자. 여기서는 216이 4로 딱 나눠떨어져서 있으나 없으나 같지만, 나눠떨어지지 않으면 입력 오른쪽·아래 끝의 몇 픽셀이 그냥 버려진다. 그리고 227이 아니라 224로 넣으면 (224-11)/4+1 = 54.25 → 54가 되어 논문의 55와 어긋난다.

② 메모리

출력값 개수는 96 × 55 × 55 = 290,400개. 각 값을 float32(4바이트)로 저장하면

$$ 290{,}400 \times 4 = 1{,}161{,}600\ \text{Byte} \approx 1{,}134\ \text{KB} $$

주의할 게 두 개 있다. 하나, 여기서 KB는 1,024바이트 기준이다(엄밀히는 KiB). 둘, 이건 모델 파일 크기가 아니라 사진 한 장이 이 층을 지날 때 잡아먹는 활성값 메모리다. 배치 크기 128로 학습하면 이 층 하나가 145MB를 쓴다. 게다가 역전파를 하려면 각 층의 출력을 끝까지 들고 있어야 하므로, 실제 학습 시 GPU 메모리는 여기에 배치 크기를 곱하고 전 층을 더한 값이 된다. 배치를 줄이면 OOM이 풀리는 이유가 이것이다.

③ 파라미터 개수

필터 하나가 가지는 값은 (입력 채널 × 커널 크기²) = 3 × 11 × 11. 이게 96개고, 여기에 편향 96개를 더한다.

$$ 96 \times 3 \times 11 \times 11 + 96 = 34{,}944 \approx 34.9\text{K} $$

④ 연산량(FLOPs)

출력 한 칸을 만들려면 3 × 11 × 11 = 363번의 곱하고 더하기가 필요하다. 출력 칸은 55 × 55 × 96개다.

$$ (55 \times 55 \times 96) \times (3 \times 11 \times 11) = 105{,}415{,}200 \approx 105.4\text{M} $$

층 하나가 1억 번이다. 이런 층이 여덟 개다.

여기서 한 번 짚고 갈 것 — FLOPs 세는 규약

곱셈 한 번과 덧셈 한 번은 엄밀히 따지면 부동소수점 연산 2회다. 그런데 이 바닥의 표(위 표, 강의 자료, 논문 대부분)는 관례적으로 곱하고 더하기 한 쌍을 1회로 센다. 하드웨어가 실제로 FMA(fused multiply-add) 명령 하나로 처리하기 때문이다. 정확한 이름은 FLOPs가 아니라 MACs(multiply-accumulate)인데, 다들 그냥 FLOPs라고 부른다.

그래서 같은 VGG-16을 두고 어떤 자료는 13.6 GFLOPs, 다른 자료는 그 2배로 적는다. 틀린 게 아니라 규약이 다른 것이다. 두 모델의 숫자를 비교할 땐 같은 규약으로 센 값인지부터 확인해야 한다. 이 글은 전부 MAC 기준(곱하고 더하기 = 1회)으로 통일한다.

그리고 이 숫자는 전부 순전파, 사진 1장 기준이다. 학습할 땐 역전파가 대략 순전파의 2배라, 실제 학습 비용은 여기에 약 3배를 곱하고 다시 배치 크기를 곱해야 한다.

이 계산이 왜 중요한가

층별로 다 계산해 보면 아주 흥미로운 그림이 나온다.

AlexNet의 층별 비용 분포

자원 어디에 몰리나 왜
메모리 앞쪽 합성곱층 해상도가 커서 특징 맵 자체가 크다 (Conv1 = 1,134KB)
파라미터 완전연결층 FC1 하나가 37,753K, 전체의 절반이 넘는다
연산량 합성곱층 Conv2 하나가 448M FLOPs

세 숫자의 단위가 서로 다르다는 걸 헷갈리지 말자. 메모리는 사진 1장의 활성값(순전파 기준), 파라미터는 모델이 들고 있는 가중치 개수, 연산량은 사진 1장 처리 비용이다. AlexNet의 파라미터 61M을 float32로 저장하면 모델 파일이 약 244MB, VGG-16의 138M은 약 553MB다. 아래에서 말할 활성값 메모리(1.9MB / 48.6MB)와는 완전히 다른 축이다.

이 세 줄이 이번 편에서 가장 실무적인 부분이라고 생각한다. “모델이 무겁다”는 말은 사실 세 가지 다른 문제를 뭉뚱그린 말이다.

  • 학습 중 OOM이 난다 → 앞쪽 해상도를 줄이거나 배치를 줄여야 한다
  • 모델 파일이 너무 크다 → 완전연결층을 손봐야 한다
  • 추론이 느리다 → 합성곱층을 줄여야 한다

엉뚱한 곳을 깎으면 아무것도 안 나아진다. 이후 모델들이 완전연결층을 없애고(전역 평균 풀링), 합성곱을 쪼개는(MobileNet) 방향으로 간 이유가 여기 다 들어 있다.


3. VGG — 설계에 규칙을 준 모델

VGG의 기여는 성능(ILSVRC 2014 앙상블 기준 Top-5 7.3%)보다도 규칙이다. 자료에 나온 디자인 룰이 딱 세 줄이다.

  1. 3×3 합성곱 / 스트라이드 1 / 패딩 1을 반복한다
  2. 2×2 맥스 풀링으로 크기를 절반으로 줄인다
  3. 풀링 후에는 채널 수를 2배로 늘린다

이 규칙만 알면 VGG-16이든 VGG-19든 종이에 그릴 수 있다. 그전까지 모델 설계는 감이었는데, VGG 이후로는 블록을 반복하는 문법이 됐다.

세 번째 규칙이 왜 “하필 2배”인지는 계산해 보면 바로 나온다. 합성곱 한 층의 연산량은 대략 $H \times W \times C_{in} \times C_{out} \times k^2$ 이다. 풀링으로 H와 W가 각각 절반이 되면 HW가 1/4이 되고, 채널을 2배로 늘리면 $C_{in} \times C_{out}$ 이 4배가 된다. 정확히 상쇄된다.

즉 “풀링 후 채널 2배”는 블록마다 연산량을 비슷하게 유지하려는 규칙이다. 해상도로 잃은 만큼 채널로 채워 넣는 것. 이 관례는 ResNet, MobileNet을 거쳐 지금 백본들까지 그대로 이어진다.

왜 하필 3×3인가

3x3 두 층과 5x5 한 층 비교

3×3을 두 번 연달아 쓰면 수용영역이 5×5가 된다. 22편에서 본 그 계산이다. 그런데 비용은 다르다.

방식 파라미터 FLOPs 비선형
5×5 한 층 25C² 25C²HW 1번
3×3 두 층 18C² 18C²HW 2번

(표의 전제: 입력·출력 채널이 둘 다 C이고, 패딩으로 해상도 H×W를 유지한다고 놓았다.)

같은 시야를 28% 싼값에, 게다가 비선형은 두 번 넣으면서 확보한다. 그래서 지금도 대부분의 CNN이 3×3을 기본으로 쓴다.

다만 완전히 공짜는 아니다. 층이 하나 늘어난 만큼 중간 활성값을 하나 더 저장해야 하므로, 학습 시 메모리는 오히려 늘어난다. 파라미터와 연산량을 활성값 메모리와 맞바꾼 셈이다. 앞에서 “무겁다는 말은 세 가지 다른 문제”라고 한 게 여기서도 그대로 적용된다. 하나를 줄이는 설계는 대개 다른 하나를 늘린다.

같은 논리를 한 번 더 밀면 3×3 세 층 = 7×7 시야에 27C²가 되고, 그래서 VGG는 7×7 같은 큰 필터를 아예 쓰지 않는다. 반대로 첫 층만은 예외로 큰 필터(ResNet의 7×7 스템)를 쓰는 모델이 많은데, 입력 채널이 3개뿐이라 그 층만은 큰 필터가 싸기 때문이다.

그런데 VGG는 거대하다

비교 AlexNet VGG-16 배수
메모리 1.9MB 48.6MB 약 25배
파라미터 61M 138M 약 2.3배
연산량 0.7 GFLOPs 13.6 GFLOPs 약 19.4배

성능은 확실히 좋아졌지만 연산량이 20배다. VGG의 세 번째 규칙(“풀링 후 채널 2배”)은 층별 연산량 편차를 줄여주긴 하는데, 총량 자체는 어쩔 수 없다.

그리고 표에 안 드러나는 비용이 하나 더 있다. VGG는 그냥 쌓아서는 학습이 안 됐다. 논문 저자들은 먼저 11층짜리(VGG-A)를 학습시킨 뒤, 그 가중치로 더 깊은 모델의 앞뒤 층을 초기화해서 학습을 시작했다. 층을 무작정 깊게 쌓으면 학습이 안 된다는 신호가 이미 2014년에 나와 있었던 것이다. 다음 절 ResNet 이야기의 복선이 여기다.

정리하면 VGG는 단순함과 깊이로 성능을 낸 모델이고, 구조가 단순해서 해석과 전이학습의 기준선(baseline)으로 오래 쓰였다. 대신 비싸다.


4. GoogLeNet — 같은 해의 다른 대답

VGG와 같은 2014년, 실제 ILSVRC 우승은 GoogLeNet이었다(Top-5 6.7%). 마지막 계보 표에는 들어가는데 본문에서 빠지면 흐름이 끊기니 짧게라도 짚는다. VGG와 정확히 반대 방향의 대답이라 대비가 선명하다.

  VGG GoogLeNet
전략 한 가지 블록(3×3)을 깊게 반복 여러 크기 필터를 한 층에 나란히
파라미터 138M 5M
연산량 13.6 GFLOPs 1.5 GFLOPs
Top-5 (2014) 7.3% 6.7%

파라미터가 VGG의 1/27인데 성능은 더 좋다. 어떻게 이게 되나. 두 가지 장치다.

① 인셉션 모듈 — 필터 크기를 하나로 고를 필요가 없다. 1×1, 3×3, 5×5, 풀링을 동시에 태우고 결과를 채널 방향으로 이어붙인다. 어떤 크기가 좋을지 고민하는 대신 다 넣고 학습이 고르게 하는 것이다.

② 1×1 병목 — 그런데 ①을 그냥 하면 비싸다. 그래서 비싼 3×3, 5×5 앞에 1×1을 먼저 넣어 채널을 줄인다. 이게 뒤에 나올 ResNet 보틀넥의 원형이다.

③ 전역 평균 풀링 — 파라미터 폭식범인 FC층을 없앴다. 5M이라는 숫자의 대부분이 여기서 나온다. ResNet도 이걸 그대로 가져다 쓴다.

보조 분류기(auxiliary classifier)라고, 중간층에 손실 함수를 하나 더 달아 기울기를 강제로 주입하는 장치도 있었다. 깊으면 기울기가 안 내려간다는 문제를 정면이 아니라 옆에서 우회한 방법인데, ResNet이 나오면서 필요가 없어져 사라졌다. 문제를 제대로 푼 해법이 나오면 우회로는 없어진다는 좋은 예다.

정리하면 2014년에 두 개의 답이 있었다. “단순한 걸 깊게”(VGG)와 “효율적으로 넓게”(GoogLeNet). 읽기 쉬운 쪽은 VGG가 이겼고, 실전 효율은 GoogLeNet의 아이디어가 이겼다. 1×1 병목과 전역 평균 풀링은 지금도 살아 있다.


5. ResNet — “깊게 하면 좋다”가 깨진 자리

여기가 이 계보에서 가장 흥미로운 대목이다.

배치 정규화 같은 기법 덕분에 10층 이상도 학습이 가능해졌다. 그래서 사람들은 당연히 생각했다. 더 깊게 하면 더 좋겠지. 그런데 CIFAR-10에서 20층 모델과 56층 모델을 비교했더니 결과가 이랬다(같은 구조를 층수만 바꿔서, 배치 정규화까지 다 넣고 한 실험이다).

퇴화 문제와 잔차 블록

56층 모델이 훈련 오차조차 더 높았다

이걸 처음 봤을 때 “아 과적합이네” 하고 넘어가기 쉽다. 그런데 아니다. 과적합이면 훈련 오차는 낮고 검증 오차만 높아야 한다. 여기서는 훈련 오차부터 높다. 즉 모델이 답을 외우는 데조차 실패한 것이다.

이걸 퇴화(Degradation) 문제라고 부른다.

기울기 소실과도 구분해야 한다. 배치 정규화를 넣으면 기울기는 실제로 잘 흐르고, 논문 저자들도 그걸 확인했다. 그런데도 안 됐다. 그래서 퇴화는 “신호가 못 간다”보다 “최적화가 좋은 해를 못 찾는다”에 가까운 문제다. 원인이 완전히 규명된 건 아니고, 지금도 “잔차 연결이 손실 지형(loss landscape)을 평탄하게 만든다”는 쪽 설명이 유력한 정도다. 효과는 확실하고 이유는 사후에 붙었다 — 딥러닝에선 이 순서가 흔하다.

논리적으로 이상한 상황

생각해보면 이건 말이 안 된다. 56층 모델은 앞의 20층을 그대로 쓰고 나머지 36층이 아무것도 안 하면(항등 함수) 최소한 20층 모델과 똑같은 성능이 나와야 한다. 그런데 실제로는 더 나쁘다.

즉 문제는 표현력이 아니라 “아무것도 안 하기”조차 학습으로 배우기 어렵다는 데 있었다.

그러면 배우게 하지 말고 그냥 주자

잔차 블록의 아이디어가 이거다.

$$ \text{출력} = F(x) + x $$

입력 x를 두 갈래로 보낸다. 하나는 합성곱을 통과하고(F(x)), 하나는 아무 계산 없이 그대로 간다(지름길, shortcut). 마지막에 둘을 더한다.

이러면 “아무것도 안 하기”가 공짜다. F(x)가 0이 되기만 하면 출력이 x 그대로다. 실제로 잔차 블록 마지막 배치 정규화의 γ를 0으로 초기화하는 기법(zero-init residual)이 널리 쓰이는데, 이러면 학습 시작 시점에 모든 블록이 항등 함수라 처음부터 얕은 모델과 똑같은 예측에서 출발한다. 깊은 모델일수록 초반 학습이 안정된다.

실제로 구현하면 바로 걸리는 두 가지

수식만 보면 $F(x) + x$ 가 전부인데, 코드를 짜 보면 곧장 막히는 지점이 있다.

① 모양이 안 맞으면 더할 수가 없다. 블록이 채널을 64 → 128로 늘리거나 스트라이드 2로 해상도를 절반으로 줄이면, x와 F(x)의 텐서 모양이 달라서 덧셈 자체가 불가능하다. ResNet은 이럴 때만 지름길에 1×1 합성곱(스트라이드 2)을 끼워 넣어 모양을 맞춘다(논문의 옵션 B). 모양이 같은 대부분의 블록에서는 아무 연산 없는 순수 항등 경로를 쓴다.

여기가 중요한 이유: 지름길에 1×1이 끼는 순간 더 이상 항등이 아니고, 그 블록은 “아무것도 안 하기”가 공짜가 아니다. 그래서 ResNet은 이런 블록을 스테이지가 바뀌는 지점에만 최소한으로 둔다. 지름길은 최대한 비워두는 게 원칙이다.

② ReLU를 어디에 넣느냐. 원 논문(v1)은 $\text{ReLU}(F(x) + x)$ 순서라 덧셈 결과가 ReLU를 한 번 더 통과한다. 엄밀히 말하면 이 때문에 지름길이 완전한 항등 경로는 아니다. 이듬해 같은 저자들이 낸 후속 논문(pre-activation ResNet, v2)은 순서를 $x + F(\text{ReLU}(\text{BN}(x)))$ 로 바꿔 지름길을 처음부터 끝까지 순수하게 뚫었고, 1000층까지 학습이 됐다. 요즘 프레임워크의 기본 구현은 대개 v1이지만, 아주 깊게 갈 땐 v2를 쓴다.

더 쉬운 비유로

계단 100개짜리 건물이 있다. 한 층씩 손으로 짐을 옮기라고 하면, 층이 많을수록 중간에 짐이 상하거나 잃어버릴 확률이 커진다. 그래서 엘리베이터(지름길)를 놨다. 급하면 그냥 짐을 그대로 올려보내면 되고, 필요한 층에서만 짐을 손질한다. 손질을 잘 못하는 층이 있어도 최소한 원래 짐은 도착한다.

기울기 관점에서도 같다. 덧셈 경로가 있어서 기울기가 깊은 층까지 끊기지 않고 내려간다. 10편에서 LSTM이 셀 상태를 덧셈으로 전달해 기울기 소실을 막았던 것과 정확히 같은 발상이다. 데이터 종류만 다르지 방법은 같다.

보틀넥 — 더 깊게, 더 싸게

일반 잔차 블록과 보틀넥 블록

ResNet-50 이상은 잔차 블록 안을 이렇게 바꾼다.

1×1로 채널을 줄이고 → 3×3 하고 → 1×1로 되돌린다

핵심은 1×1 합성곱이다. 공간(가로·세로)은 건드리지 않고 채널만 섞는 연산이라, 채널 수를 조절하는 밸브 역할을 한다. 256채널을 64로 줄여놓고 비싼 3×3을 시킨 다음 다시 256으로 되돌린다.

FLOPs를 비교할 때 무엇과 무엇을 비교하는지를 정확히 해야 한다. 여기서 C는 3×3이 실제로 처리하는 안쪽 채널 수다.

블록 구성 다루는 채널 FLOPs
기본 블록 3×3(C→C) + 3×3(C→C) C 18HWC²
보틀넥 1×1(4C→C) + 3×3(C→C) + 1×1(C→4C) 4C 17HWC²

같은 18 대 17이 아니다. 거의 같은 비용으로 4배 넓은 채널을 층 3개로 처리한다는 뜻이다. 실제 숫자로 확인해 보면, 안쪽이 64채널일 때 보틀넥은 바깥 256채널을 다루면서 3×3 하나가 $9 \times 64 \times 64 = 36{,}864$, 앞뒤 1×1이 각각 $256 \times 64 = 16{,}384$ 씩이다. 비싼 3×3을 좁은 데서만 돌리는 게 전부다.

ResNet-34와 ResNet-50이 GFLOPs로는 3.6 대 3.8로 거의 같은데(아래 표) 층수는 34 대 50이고 성능은 50 쪽이 좋은 이유가 이것이다. 깊이를 공짜로 산 것이 아니라, 넓이를 싸게 사서 그 돈으로 깊이를 샀다.

ResNet의 나머지 재료

요소 내용
스템(Stem) 초반에 입력 해상도를 1/4로 줄이고 시작 (비싼 초반부를 아낌)
전역 평균 풀링 완전연결층 여러 개 대신 채널별 평균 하나 → 마지막 FC 1개만
배치 정규화 모든 합성곱 뒤에 적용
He 초기화 ReLU와 궁합이 맞는 초기화 (25편에서 자세히)

전역 평균 풀링이 특히 중요하다. AlexNet의 파라미터 절반을 잡아먹던 FC1을 통째로 없애버린 셈이니까.

성능 비교

모델 구조 Top-5 에러 GFLOPs
ResNet-18 18층, 기본 블록 10.92% 1.8
ResNet-34 34층, 기본 블록 8.58% 3.6
ResNet-50 50층, 보틀넥 7.13% 3.8
VGG-16 16층, 일반 CNN 9.62% 13.6

(ImageNet 검증셋, 단일 모델·단일 크롭 기준이다. 논문 헤드라인의 3.57%는 앙상블 + 멀티크롭 결과라 이 표와 조건이 다르다. 비교할 땐 조건을 맞춰야 한다.)

ResNet-34를 보라. VGG-16보다 정확하면서 연산량은 4분의 1이다. 좋은 구조가 무엇인지 이보다 명확한 표가 없다.

한 가지만 덧붙이면, 연산량이 1/4이라고 속도가 4배가 되지는 않는다. GPU에서 실제 걸리는 시간은 층 개수(커널 실행 횟수), 메모리 접근량, 병렬화 정도에 함께 좌우된다. VGG처럼 층이 적고 뚱뚱한 모델은 오히려 GPU 효율이 좋은 편이다. 다음 절에서 이 이야기가 훨씬 심각해진다.


6. MobileNet — 휴대폰 안으로

여기서 질문이 바뀐다. 정확도가 아니라 “이걸 서버 없이 돌릴 수 있나”다.

문제 진단은 이랬다.

기존 합성곱은 공간(H×W)과 채널(C)을 한 번에 처리한다. 그래서 비싸다.

깊이별·화소별 합성곱

MobileNet은 이걸 두 단계로 쪼갠다.

  1. 깊이별 합성곱(Depthwise) — 채널마다 따로 3×3을 돌린다. 공간만 담당
  2. 화소별 합성곱(Pointwise) — 1×1로 채널을 섞는다. 채널만 담당

깊이별 합성곱은 입력 채널과 출력 채널이 1:1로 묶여 있어서 채널 간 정보가 전혀 섞이지 않는다. 그래서 반드시 1×1이 뒤따라야 한다. 둘은 한 세트고, 따로 쓰면 모델이 망가진다. 코드에서는 보통 Conv2d(C, C, 3, padding=1, groups=C) 뒤에 Conv2d(C, C_out, 1)을 붙이는 형태로 쓴다 — groups=C가 바로 “채널마다 따로”라는 뜻이다.

절감 비율을 직접 계산해보면

방식 연산량
일반 합성곱 9C²HW
깊이별 + 화소별 9CHW + C²HW

비율을 구해보자.

$$ \frac{9C^2HW}{9CHW + C^2HW} = \frac{9C}{9+C} $$

여기서 채널 C를 키우면 어떻게 되는가. C가 64, 128, 256처럼 커질수록 분모의 9는 무시할 만해지고, 값은 9에 수렴한다.

$$ \lim_{C \to \infty} \frac{9C}{9+C} = 9 $$

즉 연산량이 약 9분의 1. 이 한 줄이 딥러닝을 서버 밖으로 꺼냈다.

일반형으로 쓰면 더 명확하다. 커널 크기 $k$, 출력 채널 $N$일 때 절감 비율의 역수는 이렇다.

$$ \frac{1}{N} + \frac{1}{k^2} $$

여기서 읽어야 할 게 두 가지다.

  • 한계가 $1/k^2$에 걸려 있다. 채널을 아무리 늘려도 3×3이면 9배가 천장이다. “9배”는 도달하는 값이 아니라 상한이고, 실제로는 C=64에서 약 7.9배, C=256에서 약 8.7배다. 위 표는 입력·출력 채널이 둘 다 C라고 놓은 단순화 버전이라는 점도 같이 기억하자.
  • $1/k^2$ 쪽이 지배한다. 두 항 중 채널 항 $1/N$은 금방 작아지므로, 절감량은 사실상 커널 크기가 정한다. 5×5로 하면 이론상 25배까지 갈 수 있다는 뜻이기도 하다(MobileNetV2 이후 실제로 5×5 깊이별 합성곱을 섞어 쓴다).

더 쉬운 비유로

빨래를 한다고 하자. 원래는 세탁기 한 대에 전부 넣고 한 번에 돌렸다. 그런데 MobileNet은 색깔별로 따로 빨고(깊이별), 마지막에 한 번 모아서 정리한다(화소별). 일을 두 번 하는 것 같은데, 각 단계가 훨씬 단순해져서 총 시간은 훨씬 짧다.

그런데 9배 싸졌다고 9배 빨라지지 않는다

이번 편에서 가장 실무적인 함정이라 따로 뺀다. 깊이별 합성곱은 연산량 대비 메모리 접근이 지나치게 많다.

일반 합성곱은 한 번 실어온 데이터로 곱셈을 잔뜩 한다. 반면 깊이별 합성곱은 채널당 9번 곱하고 끝이라, 계산은 금방 끝나고 대부분의 시간을 데이터를 실어 나르는 데 쓴다. 연산 유닛은 놀고 메모리 대역폭만 꽉 차는 상태다.

그래서 MobileNet은 FLOPs가 1/9이어도 실제 지연시간은 절반 남짓 줄어드는 데 그치는 경우가 흔하다. 하드웨어와 커널 최적화 수준에 따라 편차도 크다.

여기서 얻을 교훈은 MobileNet이 별로라는 게 아니라, FLOPs는 속도의 대리 지표일 뿐이라는 것이다. 결국 실기기에서 지연시간을 직접 재야 한다. 이후 ShuffleNetV2, MnasNet, EfficientNet 같은 후속 연구들이 FLOPs 대신 실측 지연시간을 최적화 목표로 삼기 시작한 이유가 정확히 이것이다.

대가도 같이 봐야 한다

싸진 만큼 정확도는 조금 내준다. 논문의 ImageNet 비교가 이렇다.

모델 Top-1 정확도 연산량(MACs) 파라미터
VGG-16 71.5% 15,300M 138M
GoogLeNet 69.8% 1,550M 6.8M
MobileNet 70.6% 569M 4.2M

VGG 대비 정확도 0.9%p를 내주고 연산량을 27분의 1로 줄였다. 이 교환비를 받아들일지는 서버냐 휴대폰이냐에 따라 갈린다. 성능 표만 보고 모델을 고르는 게 아니라 어디서 돌릴 건지를 먼저 정해야 한다는 뜻이다.

그리고 MobileNet에는 이 교환비를 손으로 돌릴 수 있는 손잡이가 두 개 달려 있다. 구조 못지않게 실용적인 기여였다.

하이퍼파라미터 하는 일 연산량
너비 배수 α (1.0 / 0.75 / 0.5 / 0.25) 모든 층의 채널 수를 α배로 약 α²배
해상도 배수 ρ (224 / 192 / 160 / 128) 입력 이미지 크기를 줄임 약 ρ²배

모델을 다시 설계할 필요 없이 숫자 하나로 정확도-속도 곡선 위 원하는 지점을 고를 수 있다. 기기 성능에 맞춰 같은 모델을 여러 크기로 배포하는 지금의 관행이 여기서 나왔다.

이 구조는 이후 거의 모든 경량 모델의 표준 부품이 됐다.


7. 계보를 한 줄로 다시 보면

각 모델이 CNN의 한계를 하나씩 걷어낸 순서다.

모델 걷어낸 한계 남긴 유산
AlexNet (2012) “깊은 CNN은 안 된다” ReLU, 드롭아웃, GPU 학습
VGG (2014) “설계는 감으로 한다” 3×3 반복, 블록 문법
GoogLeNet (2014) “층이 넓으면 비싸다” 인셉션 모듈, 1×1 차원 축소
ResNet (2015) “깊으면 학습이 안 된다” 잔차 연결, 전역 평균 풀링
MobileNet (2017) “무거워서 못 쓴다” 깊이별·화소별 분리

확인 문제로 자주 나오는 순서도 이거다. AlexNet → VGG → GoogLeNet → ResNet → MobileNet.

2017년 이후는 어떻게 됐나

계보가 2017년에서 끊긴 것처럼 보이지 않도록 한 문단만 붙인다.

  • 잔차 연결은 살아남는 정도가 아니라 표준이 됐다. 트랜스포머의 모든 블록에도 그대로 들어가 있다. 24편에서 볼 ViT도, LLM도 안을 뜯어보면 $x + F(x)$ 다. 2015년의 덧셈 하나가 지금 모든 대형 모델의 뼈대다.
  • 깊이 경쟁은 끝났다. 152층 다음에 1000층 경쟁이 벌어지지 않았다. ResNet-50이 여전히 가장 흔한 백본이고, 이후 개선은 깊이가 아니라 너비·해상도·학습 전략으로 옮겨갔다(EfficientNet이 셋을 같이 조절하는 방법을 제안했다).
  • 학습 전략이 구조만큼 중요해졌다. 2021년에 나온 “ResNet strikes back” 류의 연구는 구조는 그대로 두고 학습 레시피만 현대화해서 ResNet-50의 정확도를 76% → 80%대로 올렸다. 구조 논문의 성능 비교 상당수가 사실 레시피 차이였던 셈이다. 25편 주제가 정확히 이거다.
  • 그리고 합성곱 자체가 도전을 받는다. 24편의 Vision Transformer 이야기다.

그리고 개인적으로 오래 남는 감각은 이쪽이다. 이 계보는 새로운 수학이 나와서 뚫린 게 아니다. “쌓아봤더니 안 되네” 하고 관찰한 사람이, 왜 안 되는지 정확히 짚고, 가장 단순한 우회로를 낸 것이다. 잔차 연결은 덧셈 하나고, 보틀넥은 1×1이고, MobileNet은 나눗셈이다. 다 초등학교 산수다.


정리

  • ImageNet 에러율은 28% → 3.6%로 떨어졌고, 그 방향은 깊이가 늘어난 방향과 같다
  • AlexNet — 8층. 메모리는 앞쪽 합성곱, 파라미터는 완전연결층, 연산량은 합성곱층에 몰린다
    • Conv1 계산: 출력 55×55×96, 파라미터 34.9K, 연산량 105.4M FLOPs
  • VGG — 3×3 반복 / 2×2 풀링 / 풀링 후 채널 2배. 3×3 두 층 = 5×5 시야를 18C²에
  • ResNet — 퇴화 문제는 과적합이 아니다. 지름길로 최소 성능을 보장하고 100층 이상을 열었다
    • 보틀넥(1×1 → 3×3 → 1×1)으로 더 깊게, 더 싸게
  • GoogLeNet — VGG와 같은 해의 반대 방향 대답. 1×1 병목과 전역 평균 풀링으로 파라미터를 VGG의 1/27로
  • MobileNet — 공간과 채널을 분리해 연산량 약 9분의 1(정확히는 상한이 $1/N + 1/k^2$의 역수)
  • “모델이 무겁다”는 말은 메모리·파라미터·연산량 중 무엇인지 먼저 물어야 한다

읽으면서 따로 메모해둔, 표에는 잘 안 적히는 것들:

  • FLOPs 숫자는 규약을 확인하고 비교하자. 곱하고 더하기를 1로 세느냐 2로 세느냐에 따라 그대로 2배 차이가 난다
  • FLOPs가 줄어도 속도는 그만큼 안 빨라진다. 특히 깊이별 합성곱은 메모리 대역폭에 막힌다. 실기기에서 재는 수밖에 없다
  • 지름길도 모양이 안 맞으면 1×1이 낀다. 그 블록에서는 “항등 함수가 공짜”라는 성질이 깨진다
  • 에러율은 Top-1인지 Top-5인지, 단일 크롭인지 앙상블인지 확인하고 비교하자

참고 자료

한줄 평

  • 퇴화 문제를 보면서 제일 놀란 건 “깊게 했더니 나빠졌다”가 아니라, 그걸 과적합으로 착각하지 않고 끝까지 파고든 사람이 있었다는 사실이다.