[Review] 박종훈의 AI 논문 스터디 - 2주차 후기

안녕하세요! K-DEVCON 그로스 매니저 박종훈입니다. AI 논문 스터디 두 번째 시간이 9월 7일(월)에 있었습니다. (1주차 후기 보러가기) 지난 시간에 읽은 AlexNet은 "딥러닝이 된다"를 보여줬지만 동시에 숙제를 하나 남겼습니다. 더 깊게 쌓고 싶은데 쌓을 수가 없다는 것이었습니다. 이번 3장은 그 숙제를 푼 ResNet 이야기입니다. ---

1. 깊게 쌓으면 왜 학습이 안 되나

지난 시간에 기울기 소실을 다뤘습니다. 0.9처럼 1보다 작은 값이 층마다 계속 곱해지면 앞쪽 층에 도달할 즈음에는 학습 신호가 사라진다는 이야기였죠. 숫자로 보면 이렇습니다. | 층 수 | 0.9를 그만큼 곱한 값 | | ----- | -------------------- | | 10 | 0.349 | | 50 | 0.005 | | 100 | 0.0000266 | 학습은 "가중치를 어느 쪽으로 얼마나 움직이면 오차가 줄어드는가"를 알아내는 일이고, 그 방향과 크기를 알려주는 것이 미분값, 즉 기울기입니다. 그런데 오차는 맨 끝(출력)에서 계산되니 앞쪽 층의 가중치를 고치려면 이 값을 출력에서 입력 쪽으로 되돌려 보내야 합니다. 이게 역전파입니다. 함수가 겹겹이 쌓여 있을 때 전체의 미분은 각 단계의 미분을 곱한 값이 됩니다. 층이 100개면 곱셈이 100번 일어난다는 뜻이죠. 위 표에서 0.9를 계속 곱한 이유가 여기에 있습니다. 층마다 미분값이 조금씩 1보다 작기만 해도 앞쪽 층에 도착할 즈음에는 "얼마나 움직여야 하는지"가 0에 가까워집니다. 신호가 오다가 사라지니 앞쪽 층은 학습이 되지 않습니다. ReLU가 양수 구간의 기울기를 1로 유지해주면서 AlexNet 정도의 8층은 넘겼습니다. 곱해지는 값이 1이면 아무리 곱해도 줄지 않으니까요. 그런데 층이 더 깊어지면 문제는 다시 돌아옵니다. ReLU가 지켜주는 건 활성화 함수 부분의 기울기이고, 그 사이사이의 가중치는 여전히 곱해지기 때문입니다. 여기에 더 이상한 현상이 하나 있었습니다. 단순히 "깊으면 학습이 느리다"가 아니라, 깊은 네트워크가 얕은 네트워크보다 학습 데이터에서조차 오차가 더 컸습니다. 과적합이라면 학습 오차는 줄고 테스트 오차만 늘어야 하는데 그게 아니었던 거죠. ResNet 논문은 이걸 degradation(성능 저하) 문제라고 부르며 시작합니다. 논리적으로 따져보면 이상한 일입니다. 20층짜리 네트워크가 있고 거기에 층을 36개 더 붙여 56층을 만든다고 해봅시다. 추가된 36개 층이 아무 일도 하지 않고 입력을 그대로 내보내기만 해도(항등 함수) 최소한 20층과 같은 성능은 나와야 합니다. 그런데 실제로는 그보다 나빴습니다. 즉, 신경망은 "아무것도 하지 않기"조차 제대로 학습하지 못했습니다.

2. 잔차 연결 — 아무것도 안 하기를 쉽게 만들기

ResNet의 답은 놀랍도록 단순합니다. 층의 출력에 입력을 그대로 한 번 더 더해주는 것입니다.
H(x) = F(x) + x
여기서 x가 입력, F(x)가 층이 계산한 값입니다. ① "아무것도 안 하기"가 쉬워집니다. 원래 구조에서 항등 함수를 만들려면 층이 H(x) = x가 되도록 가중치를 정교하게 맞춰야 합니다. 반면 잔차 구조에서는 F(x)만 0으로 만들면 x가 그대로 남습니다. 가중치를 0으로 밀어버리는 것은 신경망이 아주 잘하는 일입니다. 그래서 논문 제목이 "잔차(residual) 학습"입니다. 정답 자체를 학습하는 게 아니라, 입력에서 얼마나 달라져야 하는지 그 차이만 학습하는 것이죠. ② 기울기가 살아서 전달됩니다. 앞에서 본 곱셈 문제가 여기서 풀립니다. H(x) = F(x) + xx로 미분하면 이렇게 됩니다.
dH/dx = dF/dx + 1
뒤에 붙은 1이 핵심입니다. 덧셈은 미분에서 양쪽으로 그대로 갈라져 흐릅니다. 그래서 F 쪽 경로의 미분값이 아무리 0에 가까워져도 1이라는 항은 남습니다. 층을 100개 지나도 전체가 0.9 × 0.9 × ...가 아니라 (1 + 작은 값) × (1 + 작은 값) × ...이 되니 앞쪽 층까지 기울기가 최소한 원래 크기로는 도착합니다. 곱셈만 있는 경로에서는 값이 0으로 수렴하지만 덧셈으로 이어진 지름길이 하나 뚫려 있으면 그 경로를 타고 기울기가 앞쪽 층까지 그대로 흘러갑니다. 순방향으로는 입력을 그대로 넘겨주는 지름길이고, 역방향으로는 기울기를 그대로 넘겨받는 지름길입니다. 한 줄 더한 것으로 양방향이 같이 해결됩니다. 물론 곱해지는 값이 1보다 커지면 이번에는 반대로 폭발하는데, 그쪽은 뒤에 나올 배치 정규화가 맡습니다. 용어 정리 — 잔차 연결(residual connection), 스킵 커넥션(skip connection), 스킵 패스는 모두 같은 것을 가리킵니다. 책에서 표현이 섞여 나와서 스터디에서도 한 번 확인하고 넘어갔습니다. 논문에 이 이야기를 한 장으로 보여주는 그림이 있습니다. VGG-19, 34층 plain 네트워크, 그리고 같은 네트워크에 스킵 커넥션을 더한 34층 residual 네트워크 비교 출처: ResNet 논문 (He et al., Deep Residual Learning for Image Recognition, Figure 3) 가운데와 오른쪽을 비교해보시면 좋습니다. 층 구성은 완전히 같고, 오른쪽에만 옆으로 휘어 나가는 화살표가 붙어 있습니다. 저 화살표가 전부입니다. 새로운 연산도, 추가 파라미터도 없이 입력을 몇 층 건너뛰어 더해주기만 한 것이죠. 실선은 입출력 크기가 같아 그대로 더할 수 있는 구간이고, 점선은 채널 수가 바뀌는 지점이라 크기를 맞춰준 뒤 더하는 구간입니다. 왼쪽의 VGG-19와 비교해보면 깊이 차이도 눈에 들어옵니다. 34층이 19층보다 훨씬 긴데도 학습이 됐다는 게 이 그림의 요지입니다. 결과는 확실했습니다. ResNet은 152층을 쌓고도 학습이 됐고, 2015년 ImageNet 대회에서 top-5 오류율 3.57%로 우승했습니다. 사람의 오류율로 알려진 5%를 넘어선 숫자입니다. 그리고 이 아이디어는 여기서 끝나지 않았습니다. 인코더-디코더 구조에서도, 트랜스포머에서도 스킵 커넥션은 계속 쓰입니다.

3. v1과 v2 — ReLU의 위치를 옮겼더니

논문에는 버전이 둘 있습니다. 둘의 차이는 ReLU를 어디에 두느냐 하나입니다. | 버전 | 순서 | | ---- | ---------------------------------------------- | | v1 | Conv → BN → ReLU → Conv → BN → 덧셈 → ReLU | | v2 | BN → ReLU → Conv → BN → ReLU → Conv → 덧셈 | v1은 덧셈을 한 뒤에 ReLU를 통과시킵니다. v2는 ReLU를 앞쪽으로 옮겨서 덧셈 뒤에는 아무것도 씌우지 않습니다. 이걸 pre-activation이라고 부릅니다. v1 잔차 블록 — 덧셈 뒤에 ReLU가 한 번 더 걸린다 v2 잔차 블록 — BN과 ReLU가 conv 앞으로 옮겨져 덧셈 뒤에는 아무 연산도 없다 (두 그림은 ResNet 동작 원리 인터랙티브 자료에서 가져왔습니다. 직접 눌러보실 수 있습니다.) 왜 이게 차이를 만들까요. ReLU는 음수를 0으로 잘라냅니다. 그런데 덧셈 뒤에 ReLU가 있으면 어렵게 만들어둔 지름길로 넘어온 값까지 음수 구간이 잘려나갑니다. 항등 경로가 온전하지 않게 되는 거죠. 앞에서 본 미분으로 보면 더 분명합니다. 잘린 구간에서는 ReLU의 기울기가 0이라 덧셈 뒤에 ReLU를 씌우면 지름길의 미분값이 항상 1로 유지되지 않습니다. 애써 뚫어놓은 통로에 중간중간 차단기가 걸리는 셈입니다. v2는 그 차단기를 앞으로 옮겨서 지름길만큼은 끝까지 열어둔 구조입니다. 다음 층으로 전달하는 데이터를 ReLU로 한번 죽이고 넘겨버리면 이 데이터가 오염될 수 있잖아요. 그래서 다음으로 흐르는 데이터가 조금 더 깨끗하게 흐를 수 있게 한 겁니다.

4. 병목(bottleneck) 구조

합성곱에서 출력 채널 수는 필터를 몇 개 두느냐로 정해집니다. 64개 두면 64채널이 나오고, 512개 두면 512채널이 나옵니다. 압축도 확장도 여기서 결정됩니다. 이걸 실제로 어떻게 쓰는지가 병목(bottleneck) 구조입니다. 숫자로 비교해보면 효과가 분명합니다. 256채널을 처리한다고 할 때, | 구조 | 파라미터 수 | | ----------------------------------------------- | ----------- | | 3x3 (256→256) 두 개 | 약 118만 개 | | 1x1로 64까지 줄이고 → 3x3 → 다시 1x1로 256 복원 | 약 7만 개 | 17배 차이입니다. 같은 예산으로 층을 훨씬 더 깊게 쌓을 수 있습니다. 그러면 여기서 "압축을 했는데 학습이 어떻게 되지? 정보가 날아가는 것 아닌가?"라는 생각이 들 수 있는데 이렇게 정리해볼 수 있었습니다.

5. 배치 정규화

Wx + b에서 b가 실제로는 빠지고 배치 정규화 안으로 들어간다는 이야기에서 시작됐습니다. 배치 정규화가 어차피 평균을 빼고 다시 값을 더해주기 때문에 컨볼루션 층에서 bias를 따로 둘 이유가 없어집니다. 배치 정규화가 하는 일은 이렇습니다.
  1. 미니배치 안에서 평균과 표준편차를 구한다
  2. 평균 0, 표준편차 1이 되도록 값을 옮기고 나눈다
  3. 학습되는 파라미터 γ(감마)와 β(베타)로 다시 스케일과 위치를 조정한다
3번이 중요합니다. 무조건 평균 0으로 밀어버리면 층이 표현할 수 있는 범위가 좁아지니까 얼마나 되돌릴지를 모델이 직접 학습하게 열어둔 것입니다. 연산을 거듭하다 보면 값이 지나치게 커지거나 작아지는데 그걸 다시 일정한 범위로 되돌려놓는 것이죠. 특징을 버리는 게 아니라 모양은 유지한 채 크기만 조정합니다.

6. Dilated Convolution

책 3장에는 Dilated Convolution도 함께 나옵니다. 커널 크기와 파라미터 수는 그대로 두고 커널 원소 사이의 간격만 벌리는 컨볼루션입니다. 3x3 필터의 간격을 하나 벌리면 파라미터는 9개 그대로인데 커널이 걸치는 범위는 5x5로 넓어집니다. 간격을 1, 2, 4로 벌려가는 dilated convolution. 빨간 점의 개수는 그대로인데 색칠된 영역은 3x3에서 7x7, 15x15로 넓어진다 출처: Yu & Koltun, Multi-Scale Context Aggregation by Dilated Convolutions (ICLR 2016) 빨간 점이 가중치가 놓이는 자리입니다. 세 그림 모두 점이 9개로 같습니다. 간격만 1, 2, 4로 벌렸을 뿐 파라미터는 늘지 않았습니다. 색칠된 영역은 한 칸이 원본에서 참조하게 되는 범위인데 이 그림은 세 층을 차례로 쌓아가며 그린 것이라 앞 층까지 누적된 값입니다. 그래서 3x3 → 7x7 → 15x15로 커집니다. 앞에서 말한 5x5는 2-dilated 커널 하나가 걸치는 범위이고, 여기 7x7은 그 앞의 1-dilated 층까지 합친 범위라 숫자가 다릅니다. 층을 세 개 쌓았을 뿐인데 보는 범위가 지수적으로 넓어진다는 것이 이 그림의 요지입니다.

이 논문은 왜 ResNet과 같은 장에 묶여 있을까요?

재밌게도 이 논문은 ResNet을 활용한 논문은 아니었습니다. 그런데 왜 포함이 되었을까요? 이 질문을 두고 서로 의견을 나눴습니다. 첫째, 항등 사상이라는 발상이 닿아 있기 때문입니다. 컨텍스트 모듈은 보통의 무작위 초기화로는 학습이 잘 되지 않았는데 저자들이 찾은 해법이 항등 초기화였습니다. 각 필터가 자기 입력 채널을 그대로 복사하도록 초기화하는 것이죠. 그러면 학습 초기에 각 층은 입력을 그냥 통과시키고, 학습은 거기서부터의 작은 편차를 찾는 문제로 바뀝니다. 잔차 연결이 하던 일을 skip 연결 없이 가중치만으로 해낸 셈입니다. 책 58페이지가 이걸 직접 짚습니다.
stability is achieved not through explicit skip connections but through the weights themselves (안정성은 명시적인 스킵 연결이 아니라 가중치 그 자체를 통해 얻어진다)
좋은 최적화는 항등 사상 근처에서 출발한다가 이 장을 관통하는 축이고, dilated convolution 논문도 그 사례로 들어와 있는 셈입니다. 둘째, 용도가 넘어가는 지점이기 때문입니다. ResNet은 처음에 이미지 한 장에 정답 하나를 매기는 분류 모델이었습니다. 그런데 이걸 세그멘테이션처럼 픽셀마다 정답이 있는 작업으로 옮기려니 문제가 생깁니다. 해상도가 계속 줄어드는데 픽셀 단위로 답을 내려면 해상도를 유지해야 하거든요. 그렇다고 넓게 보지 않으면 주변 맥락을 놓칩니다. 해상도는 지키면서 보는 범위는 넓히는 절충안이 dilation이었습니다. 여기서 해상도를 깎는 범인은 컨볼루션 자체가 아니라 풀링과 스트라이드입니다. 3x3 스트라이드 1 컨볼루션은 해상도를 그대로 둡니다. 그래서 Yu & Koltun도 VGG-16에서 컨볼루션을 건드린 게 아니라 풀링과 스트라이드 층만 걷어내고 그 자리에 dilated convolution을 넣었습니다.

왜 이런 방식을 취한걸까요?

한 분이 이런 궁금증을 가지고 오셨습니다.
요즘은 GPU도 좋아졌는데 굳이 이렇게 띄엄띄엄 볼 필요가 있나? 그냥 촘촘하게 훑으면 되는 것 아닌가?
찾아보니 그게 아니었다고 합니다.
컴퓨팅 자원이 부족해서 쓰는 꼼수라고 생각했는데, 어떤 정보를 어떤 비용으로 보존할 것인가에 대한 구조적 선택이라고 표현하더라고요.
이 이야기를 조금 더 풀어보겠습니다. 넓게 보는 방법이 dilation만 있는 것은 아닙니다. 커널을 키워도 되고, 3x3을 계속 쌓아도 되고, 풀링으로 이미지를 줄였다가 나중에 다시 키워도 됩니다. 그런데 셋 다 무언가를 내줘야 합니다. 위 그림처럼 15x15를 보고 싶다고 해보죠. | 방법 | 파라미터 | 필요한 층 수 | 해상도 | | ----------------------------- | ------------ | ------------ | -------- | | 커널을 15x15로 키운다 | 225개 (25배) | 1층 | 유지 | | 3x3을 계속 쌓는다 | 9개 × 7층 | 7층 | 유지 | | 풀링으로 줄였다가 다시 키운다 | 적음 | 적음 | 깨짐 | | 간격을 벌린다 (dilation) | 9개 × 3층 | 3층 | 유지 | 3x3을 그냥 쌓으면 한 층 지날 때마다 보는 범위가 2씩(3 → 5 → 7 …) 늘어납니다. 선형으로 늘어나니 15x15까지 가려면 7층이 필요합니다. 반면 간격을 1, 2, 4로 벌리면 3 → 7 → 15로 두 배씩 뜁니다. 같은 범위를 절반도 안 되는 층으로 덮는 셈입니다. 문제는 세 번째 줄입니다. 사실 세그멘테이션 이전의 분류 모델들이 쓰던 방식이 이것이었습니다. 풀링으로 크기를 계속 줄여가며 넓게 보고 마지막에 정답 하나를 내면 되니까요. 그런데 픽셀마다 답을 내야 하는 작업에서는 줄이는 순간 잃은 위치 정보가 다시 키운다고 돌아오지 않습니다. dilation이 푸는 문제는 속도가 아니라 손실입니다.

7. 주변 아키텍처들 — 다들 다르게 시도했습니다

ResNet 앞뒤로 나온 모델들도 함께 봤는데 생각보다 다양한 시도가 있었다는 게 재밌었습니다.

8. 이 논문의 한계

1주차에 이어 이번에도 "그래서 한계는 뭔가"를 이야기했는데 이번에는 논문 작성 방식 자체에 대한 이야기가 나왔습니다. 이 시기 CNN 논문들은 상당 부분 실험의 결과물입니다. 여러 구성을 해보고 잘 나온 것을 택하는 식이고, 왜 잘 되는지가 엄밀하게 증명되지는 않았습니다. v2에서 ReLU 위치를 옮긴 것도 그렇습니다. 여기서 요즘 논문이 요구하는 세 가지 축을 대보는 이야기가 나왔습니다. | 축 | 내용 | ResNet | | ------------------------------ | ------------------------------ | ------ | | 정확도 (accuracy) | 주어진 문제를 얼마나 잘 맞추나 | 다룸 | | 강건성 (robustness) | 입력이 달라져도 잘 되나 | 다룸 | | 설명 가능성 (interpretability) | 왜 그런지 설명할 수 있나 | 약함 | 세 번째가 약하다는 것이 이 논문의 아쉬운 점으로 정리됐습니다. 그리고 이런 논문 뒤에 붙는 연구 방식으로 ablation study가 소개됐습니다. 실험적으로 만들어놓고 나면 무엇이 실제로 기여했는지 알기 어려우니 구성 요소를 하나씩 제거해가며 각각의 영향을 측정하는 연구입니다.

9. 그래서 지금도 쓰나요

"교과서 속 이야기인가, 지금도 쓰이는가"라는 질문이 나왔습니다. 결론은 쓰입니다. 물론 ResNet을 그대로 가져다 쓰는 것은 아닙니다. 하지만 이 개념이 채택된 모델을 사용합니다. 테슬라처럼 도로 위 물체를 실시간으로 식별해야 하는 경우에는 빠르고 가벼운 모델이 필요합니다. 그래서 임베디드 쪽에서는 여전히 많이 쓰입니다. 실무 사례도 들을 수 있었습니다. "이미지에 태깅 작업을 할 때. 항상 동일한 모델을 쓰지는 않더라고요. 속성에 따라서 기본 모델만 태울 건지, CV 모델을 태울 건지, LLM을 태울 건지 비용과 성능을 보고 조건문으로 분기합니다." 우리가 코드에서 조건에 따라 함수를 선택하여 사용할 수 있듯, 모델도 그렇게 쓴다는 이야기였습니다.

그 외에 나온 이야기들

--- 2주차 인증샷 이번 시간을 마무리하면서 나온 이야기로 후기를 맺겠습니다. 한 분이 질문을 꺼내려다 마셨습니다. 머릿속에서 정리가 덜 되셨던 것 같습니다. 그래서 모르는 게 있으면 담아두지 말고 입 밖으로 꺼내달라는 당부를 드렸습니다. 정리되지 않은 채로 꺼내도 괜찮습니다. 나만 모르는 것 같아도 대개는 옆 사람도 같이 모르고 있고, 답을 하는 쪽에서도 설명하다 보면 얻는 것이 있습니다. 저희 스터디는 그렇게 자유롭게 묻고 나누는 것을 장려합니다. 참여해주신 분들 모두 고생 많으셨습니다. 다음 시간에는 4장(RNN)에서 뵙겠습니다.