"뒤집고, 밀고, 곱하고, 더한다." 신호 스무딩부터 CNN의 특징 추출까지, 전혀 달라 보이는 문제들이 전부 이 한 연산으로 수렴합니다. 수학적 정의가 왜 그렇게 생겼는지, 그 결과가 무엇을 의미하는지를 브라우저에서 직접 실험하며 확인합니다.
두 함수 f와 g의 합성곱은 이렇게 정의됩니다.
수식이 낯설다면 g[n − m] 하나만 뜯어보면 됩니다.
m이 커질수록 n − m은 작아지므로, g를 좌우로 뒤집은 것이고,
n이 커지면 뒤집힌 g가 통째로 오른쪽으로 밀립니다.
그래서 출력의 n번째 값 하나를 계산하는 과정은 항상 네 단계입니다.
아래 슬라이더가 정의식의 n입니다. 위 그래프의 파란 막대가 입력 f, 주황 윤곽이 뒤집혀서 n 위치에 와 있는 커널 g[n − m]입니다. 가운데 보라 막대가 자리별 곱, 그 합이 아래 그래프의 출력 y[n] 한 점이 됩니다.
커널을 차분 [−1 0 +1]로 바꿔 보세요. 신호가 평평한 곳에서는 출력이 0에 가깝고, 신호가 급하게 변하는 곳에서만 출력이 튑니다 — 같은 기계가 변화 감지기가 됐습니다. 비대칭 램프 커널에서 "뒤집기"를 껐다 켜면 출력이 달라지는 것도 확인해 보세요.
합성곱은 하나의 연산이지만, 커널과 보는 관점에 따라 서로 다른 네 가지 얼굴을 가집니다. 이 노트의 나머지 절이 각각을 실험으로 다룹니다.
① 이동하는 가중평균 (필터). 커널의 합이 1이면 y[n]은 n 주변 값들의 가중평균입니다. 평평한 커널은 스무딩(저역통과), 부호가 갈리는 커널은 미분(고역통과)이 됩니다. — §2에서 이미 봤습니다.
② 시스템의 응답 (중첩 원리). 선형이고 시간에 불변인 시스템(LTI)은 "임펄스 하나에 어떻게 반응하는가"만 알면 모든 입력에 대한 출력을 알 수 있고, 그 출력이 정확히 입력과 임펄스 응답의 합성곱입니다. — §4
③ 독립인 것을 더할 때 생기는 분포. 독립 확률변수 X + Y의 분포는 각 분포의 합성곱입니다. 주사위를 여러 개 던지면 종 모양이 나오는 이유가 여기 있습니다. — §5
④ 주파수 영역의 곱셈. 시간 영역에서의 합성곱은 주파수 영역에서 단순 곱셈이 됩니다 (합성곱 정리). "커널이 어떤 주파수를 살리고 죽이는가"로 필터를 읽을 수 있게 됩니다. — §6
어떤 시스템(방의 울림, 카메라 렌즈의 흐림, 스프링의 진동…)이 선형(입력을 2배 하면 출력도 2배, 입력을 더하면 출력도 더해짐)이고 시간 불변(오늘 치나 내일 치나 반응이 같음)이라고 합시다. 임의의 입력은 "크기가 다른 임펄스들의 나열"로 쪼갤 수 있으므로, 임펄스 하나에 대한 반응 h만 알면 전체 출력은 각 임펄스가 만든 반응을 겹쳐 더한 것 — 즉 y = f ∗ h 입니다. 합성곱의 정의가 아니라 중첩 원리의 결과로 합성곱이 튀어나오는 겁니다.
아래 캔버스를 클릭해서 임펄스를 놓아 보세요 (드래그한 높이만큼 크기가 정해집니다). 각 임펄스가 메아리(임펄스 응답)를 만들고, 회색 굵은 선이 그 합 = 합성곱입니다.
독립 확률변수 X, Y에 대해 P(X + Y = n) = Σm P(X = m) · P(Y = n − m). 합이 n이 되는 모든 조합을 더한 것 — 정의 그대로 합성곱입니다. 주사위 1개의 분포는 평평하지만, 개수를 늘릴수록(= 합성곱을 반복할수록) 분포가 종 모양으로 수렴합니다. 이것이 중심극한정리의 시각적 실체입니다: 가우시안은 "합성곱을 반복했을 때의 고정점"입니다.
커널의 푸리에 변환 |ℱ(g)|는 "이 필터가 각 주파수를 몇 배로 통과시키는가"라는 주파수 응답입니다. 이동평균 커널의 주파수 응답은 고주파에서 0에 가깝습니다 — 그래서 노이즈(고주파)가 죽고 스무딩이 됩니다. 차분 커널은 반대로 저주파를 죽입니다 — 그래서 변화(고주파)만 남습니다.
아래에서 노이즈 낀 신호에 커널을 합성곱한 뒤, 세 스펙트럼을 비교합니다. 출력 스펙트럼(초록 막대)이 입력 스펙트럼 × 커널 응답(점선)과 정확히 일치하는 것을 확인하세요.
차원을 올려도 정의는 그대로입니다. 커널이 3×3 창이 되어 이미지 위를 훑으며, 각 위치에서 창 안의 픽셀 × 커널 값을 전부 곱해 더한 것이 출력 픽셀 하나가 됩니다.
출력 이미지 위에 마우스를 올려 보세요. 그 픽셀 하나가 어떤 3×3 곱셈-덧셈에서 나왔는지 오른쪽 패널에 계산 과정이 그대로 표시됩니다. 커널 값은 직접 수정할 수도 있습니다.
같은 "곱하고 더하기"인데 커널만 바꾸면 — 흐려지고(블러 = 2D 가중평균 = 저역통과), 또렷해지고(샤픈 = 원본 + 고주파), 윤곽만 남습니다(Sobel = 방향별 미분). §2~§6에서 본 1D의 의미들이 그대로 2D로 올라온 것입니다. Sobel처럼 커널 합이 0인 필터는 평평한 영역을 0(회색으로 표시)으로 보내고 변화만 남깁니다.
여기까지는 채널이 1개(흑백)인 척했습니다. 하지만 실제 입력은 채널이 여러 장입니다 — RGB 이미지는 3장, ResNet 중간층의 특징맵은 64장, 256장씩 쌓여 있습니다. "1×1 conv로 채널을 256 → 64로 줄인다"가 이해되지 않는다면, 십중팔구 아래 사실을 빠뜨렸기 때문입니다.
그럼 출력 채널 수는 어디서 오는가? 이런 벽돌(필터)을 몇 개 꽂느냐에서 옵니다. 필터를 64개 두면 출력이 64장, 256개 두면 256장. 즉 출력 채널 수 Cout은 입력에서 계산되는 값이 아니라 설계자가 고르는 필터 개수입니다. "conv가 채널 수를 바꿔 준다"는 말의 실체는 "필터 개수를 원하는 만큼 두면 그게 곧 새 채널 수"라는 뜻입니다.
K = 1로 줄이면 커널은 1×1×Cin, 즉 이웃 픽셀은 전혀 안 보고 같은 위치의 채널 벡터(길이 Cin)에 가중치를 곱해 더하기만 합니다. 필터 Cout개를 모으면 각 픽셀 위치마다 Cout×Cin 행렬 곱 하나 — 모든 픽셀에 똑같이 적용되는 작은 완전연결층입니다. 그래서 1×1 conv는 순수한 채널 수 변환기가 됩니다.
아래에서 실제로 해 보세요. RGB 이미지(Cin = 3)에 1×1 필터 1개(가중치 딱 3개)를 적용해 출력 채널 1장을 만드는 데모입니다. 슬라이더가 곧 필터의 학습 파라미터입니다 — 가중치를 어떻게 두느냐에 따라 "밝기 채널", "빨강−초록 대비 채널" 같은 새로운 채널이 합성됩니다.
이게 전부입니다. ResNet bottleneck의 1×1 conv, 64필터는 위 데모의 필터를
64개로 늘리고 Cin을 256으로 늘린 것뿐입니다:
각 픽셀에서 길이 256짜리 채널 벡터에 64×256 행렬을 곱해 길이 64짜리 벡터를 만듭니다.
채널을 "버려서" 줄이는 게 아니라, 256개 전부의 가중합으로 새 채널 64개를 합성하는 것 —
그래서 정보를 압축하면서 줄일 수 있고, 반대로 필터를 256개 두면 다시 늘릴 수도 있습니다.
비싼 3×3 conv를 좁은 채널(64)에서만 돌리고 1×1로 줄였다 늘리는 것이 bottleneck 구조이고,
지름길(shortcut)의 차원을 맞출 때 쓰는 projection도 같은 1×1 conv입니다.
→ 이 구조만 따로 다룬 자료: ResNet Bottleneck에서 1×1 conv의 역할
§7에서 우리는 Sobel 같은 커널을 사람이 설계했습니다. CNN의 아이디어는 단순합니다: 커널의 9개(또는 그 이상) 숫자를 학습 가능한 파라미터로 두고, 역전파로 데이터에서 배우게 하자. 실제로 학습된 CNN의 첫 층에서는 Sobel과 놀랍도록 닮은 엣지/색 검출 커널이 저절로 나타납니다.
합성곱을 쓰는 이유는 수학적 성질 그대로입니다:
① 파라미터 공유 — 96×96 이미지를 완전연결층으로 처리하면 위치마다 다른 가중치가 필요하지만,
합성곱은 같은 3×3 커널 하나를 모든 위치에 재사용합니다 (9,216픽셀 → 파라미터 9개).
② 평행이동 등변성(equivariance) — 시간 불변성(§4)의 공간 버전. 입력이 옆으로 밀리면
출력 특징맵도 그대로 밀립니다. "고양이는 어디에 있어도 고양이"라는 사전 지식을 구조에 심은 것.
③ 지역성 — 각 출력은 작은 창만 봅니다. 층을 쌓으면 수용 영역(receptive field)이 점점 넓어져
엣지 → 무늬 → 부위 → 물체로 추상화가 올라갑니다.
마지막 퍼즐 한 조각: 출력 크기입니다. 패딩 P를 두르고 스트라이드 S로 건너뛰며 커널 K를 밀면 —
한 가지 고백: 딥러닝 프레임워크의 Conv2d는 §1의 정의에서 뒤집기를 생략합니다
(즉 수학적으로는 교차상관). 커널을 어차피 학습하기 때문에, 뒤집힌 커널을 배우면 그만이라
결과적으로 아무 차이가 없기 때문입니다. §2에서 뒤집기 스위치를 꺼 봤다면 이미 감이 올 겁니다.
# PyTorch로 §7의 Sobel X를 그대로 재현
import torch, torch.nn.functional as F
sobel_x = torch.tensor([[-1., 0., 1.],
[-2., 0., 2.],
[-1., 0., 1.]]).view(1, 1, 3, 3)
y = F.conv2d(x, sobel_x, padding=1) # 이름은 conv지만 실은 교차상관!