🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

CNN 아키텍처 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 9. · 64 Views

CNN 아키텍처 완벽 가이드

이미지 인식의 핵심인 CNN의 내부 동작 원리를 초급 개발자를 위해 쉽게 풀어냅니다. Convolution 연산부터 PyTorch 구현까지, 실무에서 바로 활용할 수 있는 CNN의 모든 것을 담았습니다.


목차

  1. Convolution 연산 원리
  2. Pooling과 Stride
  3. 채널과 특징 맵
  4. Receptive Field
  5. CNN의 계층적 특징 추출
  6. PyTorch로 CNN 구현

1. Convolution 연산 원리

어느 날 김개발 씨가 회사에서 이미지 분류 프로젝트를 맡게 되었습니다. 선배가 "CNN을 써서 구현해 봐"라고 했지만, CNN이 정확히 어떻게 이미지를 이해하는지 감이 잡히지 않았습니다.

"도대체 Convolution이 뭐길래 이미지 인식에 이렇게 좋다는 거지?"

Convolution 연산은 이미지 위를 작은 필터가 슬라이딩하며 특징을 추출하는 핵심 연산입니다. 마치 돋보기로 사진의 일부분씩 자세히 들여다보며 중요한 패턴을 찾아내는 것과 같습니다.

이 과정을 통해 CNN은 엣지, 코너, 텍스처 같은 이미지의 핵심 특징을 자동으로 학습할 수 있습니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn

# 3x3 필터로 Convolution 연산 수행
conv_layer = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1)

# 입력 이미지: 배치 1개, 채널 1개, 28x28 크기
input_image = torch.randn(1, 1, 28, 28)

# Convolution 연산 실행 - 필터가 이미지를 스캔하며 특징 추출
output = conv_layer(input_image)

# 출력 크기 확인: [1, 16, 28, 28]
# 16개의 서로 다른 특징 맵이 생성됨
print(f"입력 크기: {input_image.shape}")
print(f"출력 크기: {output.shape}")

김개발 씨는 선배 박시니어 씨를 찾아가 물어봤습니다. "선배님, CNN이 이미지를 어떻게 이해하는 건가요?" 박시니어 씨는 웃으며 종이에 격자를 그리기 시작했습니다.

"자, 이 격자가 이미지라고 생각해 봐. 각 칸이 하나의 픽셀이지.

그리고 이 작은 3x3 크기의 틀이 필터야." 쉽게 비유하자면, Convolution 연산은 마치 창문을 통해 풍경을 보는 것과 같습니다. 큰 풍경 전체를 한 번에 보는 게 아니라, 작은 창문을 이리저리 움직이며 각 부분을 자세히 관찰하는 것입니다.

창문이 움직일 때마다 그 안에 무엇이 보이는지 기록하고, 이 정보들을 모아서 전체 풍경을 이해하게 됩니다. 전통적인 이미지 처리에서는 어땠을까요?

예전 개발자들은 이미지의 엣지를 찾기 위해 Sobel 필터, Prewitt 필터 같은 것을 수동으로 설계해야 했습니다. 어떤 이미지에는 효과가 좋지만, 다른 이미지에는 전혀 먹히지 않는 경우가 많았습니다.

더 큰 문제는 복잡한 패턴을 인식하려면 수십, 수백 개의 필터를 사람이 직접 만들어야 한다는 점이었습니다. 바로 이런 문제를 해결하기 위해 CNN의 Convolution 연산이 등장했습니다.

Convolution을 사용하면 필터의 값들을 자동으로 학습할 수 있습니다. 개발자가 직접 설계하지 않아도, 데이터를 통해 가장 효과적인 필터를 스스로 찾아냅니다.

또한 여러 개의 필터를 동시에 학습하여 다양한 특징을 동시에 포착할 수 있습니다. 무엇보다 이미지의 위치가 조금 바뀌어도 같은 특징을 인식할 수 있다는 큰 이점이 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 Conv2d를 정의하는 부분을 보면 in_channels는 입력 이미지의 채널 수를 의미합니다.

흑백 이미지는 1, 컬러 이미지는 3입니다. out_channels는 생성할 특징 맵의 개수이고, 여기서는 16개의 서로 다른 필터를 사용합니다.

kernel_size=3은 필터의 크기가 3x3이라는 뜻입니다. padding=1을 주면 출력 크기가 입력과 동일하게 유지됩니다.

다음으로 실제 연산이 일어나는 부분을 보면, 필터가 이미지의 왼쪽 위부터 시작해서 오른쪽으로, 그리고 아래로 한 칸씩 이동하며 연산을 수행합니다. 각 위치에서 필터와 이미지의 해당 영역을 곱한 후 모두 더합니다.

이 값이 출력 특징 맵의 한 픽셀이 됩니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 의료 영상 분석 시스템을 개발한다고 가정해봅시다. X-ray 이미지에서 골절을 찾아야 하는데, Convolution 연산을 활용하면 수천 장의 X-ray 이미지로부터 골절의 특징적인 패턴을 자동으로 학습할 수 있습니다.

많은 의료 AI 스타트업에서 이런 패턴을 적극적으로 사용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 kernel_size를 너무 크게 설정하는 것입니다. 7x7, 11x11 같은 큰 필터를 쓰면 한 번에 넓은 영역을 볼 수 있지만, 파라미터 수가 기하급수적으로 늘어나 학습이 느려지고 과적합 위험이 커집니다.

따라서 3x3 필터를 여러 층 쌓는 것이 더 효과적입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 CNN이 이미지를 잘 이해하는 거군요!" Convolution 연산을 제대로 이해하면 이미지 인식 모델을 더 효과적으로 설계할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 3x3 필터가 표준: 대부분의 최신 CNN은 3x3 필터를 주로 사용합니다. VGG, ResNet 모두 3x3 기반입니다.

  • padding 활용: padding=kernel_size//2로 설정하면 출력 크기가 입력과 같아져 설계가 편해집니다.
  • 필터 개수는 점진적으로 증가: 보통 32 → 64 → 128처럼 층이 깊어질수록 필터 개수를 2배씩 늘립니다.

2. Pooling과 Stride

김개발 씨가 첫 번째 Convolution 층을 성공적으로 만들었습니다. 그런데 출력 크기를 보니 입력과 똑같은 28x28이었습니다.

"이렇게 계속 가다가는 특징 맵이 너무 커지는 거 아닌가요?" 선배가 말했습니다. "좋은 질문이야.

그래서 Pooling과 Stride가 필요하지."

Pooling은 특징 맵의 크기를 줄이면서 중요한 정보만 남기는 다운샘플링 기법입니다. Stride는 필터가 이동하는 간격을 조절하는 파라미터입니다.

마치 고해상도 사진을 저해상도로 압축하면서도 핵심 내용은 그대로 유지하는 것처럼, 계산량을 줄이면서 중요한 특징은 보존합니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn

# MaxPooling: 2x2 영역에서 최댓값만 선택
maxpool = nn.MaxPool2d(kernel_size=2, stride=2)

# Stride를 사용한 Convolution
conv_with_stride = nn.Conv2d(in_channels=16, out_channels=32,
                              kernel_size=3, stride=2, padding=1)

# 입력: 16채널, 28x28 크기
x = torch.randn(1, 16, 28, 28)

# MaxPooling 적용: 크기가 절반으로 줄어듦
pooled = maxpool(x)  # [1, 16, 14, 14]

# Stride=2 Convolution: 필터가 2칸씩 이동하며 크기 감소
strided = conv_with_stride(x)  # [1, 32, 14, 14]

print(f"Pooling 후: {pooled.shape}")
print(f"Stride 후: {strided.shape}")

박시니어 씨는 다시 화이트보드로 다가갔습니다. "자, 이 28x28 특징 맵을 계속 유지하면 어떻게 될까?" 김개발 씨는 잠시 생각했습니다.

"음... 층이 10개라면 특징 맵도 계속 28x28이니까 메모리를 엄청 많이 쓸 것 같은데요?" 쉽게 비유하자면, Pooling은 마치 신문 기사를 요약하는 것과 같습니다.

긴 기사의 모든 단어를 다 읽는 게 아니라, 핵심 문장만 추려내어 짧게 만드는 것입니다. 내용의 본질은 그대로 유지하면서 분량은 확 줄어듭니다.

이처럼 Pooling도 특징 맵의 크기를 줄이면서 가장 중요한 특징만 남깁니다. Pooling이 없던 초기 신경망에서는 어땠을까요?

특징 맵의 크기가 계속 유지되다 보니 메모리 사용량이 기하급수적으로 늘어났습니다. 28x28 이미지를 10층으로 처리하면 각 층마다 수백만 개의 값을 저장해야 했습니다.

GPU 메모리가 부족해서 배치 크기를 1로 줄여야 하는 경우도 많았습니다. 더 큰 문제는 학습 시간이었습니다.

계산량이 너무 많아 하나의 모델을 학습하는 데 며칠씩 걸렸습니다. 바로 이런 문제를 해결하기 위해 Pooling과 Stride가 등장했습니다.

Pooling을 사용하면 특징 맵의 공간 크기를 절반으로 줄일 수 있습니다. 28x28이 14x14가 되면 픽셀 수는 4분의 1로 줄어듭니다.

또한 작은 위치 변화에 강건해지는 효과도 있습니다. 고양이가 이미지에서 1픽셀 옮겨져도 같은 특징으로 인식됩니다.

무엇보다 과적합을 방지하는 정규화 효과까지 얻을 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 MaxPool2d를 보면 kernel_size=2는 2x2 영역을 하나로 합친다는 뜻입니다. stride=2는 2칸씩 이동하므로 겹치지 않고 모든 영역을 커버합니다.

각 2x2 영역에서 가장 큰 값만 선택하여 출력합니다. 이것이 Max Pooling의 핵심입니다.

다음으로 Stride를 사용한 Convolution을 보면, stride=2로 설정하면 필터가 한 칸이 아니라 두 칸씩 이동합니다. 따라서 출력의 가로, 세로가 각각 절반으로 줄어듭니다.

Pooling 대신 Stride로 다운샘플링하는 방법도 최근 많이 사용됩니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 자율주행 자동차의 객체 인식 시스템을 개발한다고 가정해봅시다. 카메라에서 들어오는 1920x1080 고해상도 영상을 실시간으로 처리해야 하는데, Pooling을 활용하면 해상도를 점진적으로 줄이면서도 차량, 보행자, 신호등 같은 핵심 객체는 정확하게 감지할 수 있습니다.

Tesla, Waymo 같은 기업들이 이런 기법을 적극 활용하고 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 Pooling을 너무 많이 사용하는 것입니다. 3x3 이미지가 될 때까지 Pooling을 반복하면 중요한 공간 정보까지 손실됩니다.

이렇게 하면 객체의 정확한 위치를 파악하기 어려워집니다. 따라서 적절한 시점에서 Pooling을 멈추고 특징 맵의 크기를 유지해야 합니다.

또 다른 실수는 Max Pooling과 Average Pooling을 혼동하는 것입니다. Max Pooling은 가장 강한 특징을 선택하고, Average Pooling은 평균을 냅니다.

대부분의 경우 Max Pooling이 더 좋은 성능을 보입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 눈이 반짝였습니다. "그렇군요!

그래서 CNN이 큰 이미지도 빠르게 처리할 수 있는 거네요!" Pooling과 Stride를 제대로 이해하면 효율적이면서도 정확한 CNN 모델을 설계할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - Max Pooling이 기본: 대부분의 경우 Average Pooling보다 Max Pooling이 더 좋은 성능을 냅니다.

  • 2x2가 표준: kernel_size=2, stride=2가 가장 일반적입니다. 크기를 정확히 절반으로 줄입니다.
  • 최근 트렌드: ResNet 이후로는 Pooling 대신 stride=2인 Convolution으로 다운샘플링하는 추세입니다.

3. 채널과 특징 맵

김개발 씨는 코드에서 자꾸 등장하는 'channels'라는 단어가 궁금했습니다. "in_channels=3, out_channels=16...

이 숫자들이 정확히 뭘 의미하는 건가요?" 선배가 웃으며 답했습니다. "아, 그게 바로 CNN의 핵심 개념 중 하나야.

채널과 특징 맵에 대해 설명해줄게."

채널은 이미지나 특징 맵의 깊이 차원을 의미합니다. 컬러 이미지는 RGB 3개 채널을 가지고, CNN의 각 층은 여러 개의 특징 맵 채널을 생성합니다.

마치 같은 장면을 여러 개의 다른 렌즈로 촬영하여 각기 다른 측면을 포착하는 것처럼, 각 채널은 서로 다른 특징을 감지합니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn

# RGB 이미지 (3채널) → 32개의 특징 맵으로 변환
conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)

# 32개 특징 맵 → 64개 특징 맵으로 변환
conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)

# 입력: 컬러 이미지 (배치 4개, RGB 3채널, 224x224)
input_image = torch.randn(4, 3, 224, 224)

# 첫 번째 층: 3채널 → 32채널
features1 = conv1(input_image)  # [4, 32, 224, 224]
# 32개의 서로 다른 필터가 각각 다른 특징을 포착

# 두 번째 층: 32채널 → 64채널
features2 = conv2(features1)  # [4, 64, 224, 224]
# 이전 층의 32개 특징을 조합하여 64개의 더 복잡한 특징 생성

print(f"Layer 1 출력: {features1.shape} - 32개의 특징 맵")
print(f"Layer 2 출력: {features2.shape} - 64개의 특징 맵")

박시니어 씨는 모니터를 가리키며 설명을 시작했습니다. "자, 이 고양이 사진을 봐.

우리 눈에는 그냥 고양이로 보이지만, 컴퓨터에게는 어떻게 보일까?" 김개발 씨는 고개를 갸우뚱했습니다. "음...

픽셀 숫자들의 나열이겠죠?" 쉽게 비유하자면, 채널은 마치 서로 다른 전문가들이 같은 대상을 분석하는 것과 같습니다. 의사는 건강 상태를, 요리사는 맛을, 예술가는 미적 요소를 봅니다.

같은 사과를 보더라도 각자 다른 관점에서 정보를 추출합니다. 이처럼 CNN의 각 채널도 이미지의 서로 다른 측면, 즉 수평 엣지, 수직 엣지, 색상 변화, 텍스처 등을 각각 감지합니다.

초기 이미지 처리에서는 어땠을까요? 흑백 이미지만 다루던 시절에는 채널이라는 개념이 별로 중요하지 않았습니다.

하나의 필터로 엣지를 찾고, 다른 필터로 코너를 찾는 식이었습니다. 하지만 컬러 이미지가 보편화되면서 문제가 생겼습니다.

RGB 3개 채널을 어떻게 동시에 처리할지, 각 채널의 정보를 어떻게 조합할지가 까다로웠습니다. 바로 이런 문제를 해결하기 위해 다중 채널 Convolution이 등장했습니다.

다중 채널 Convolution을 사용하면 RGB 3개 채널을 한 번에 처리할 수 있습니다. 하나의 필터가 3개 채널 모두를 고려하여 특징을 추출합니다.

또한 여러 개의 필터를 동시에 적용하여 다양한 특징 맵을 생성할 수 있습니다. 무엇보다 층이 깊어질수록 점점 더 복잡하고 추상적인 특징을 학습할 수 있다는 큰 이점이 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 첫 번째 Convolution 층을 보면 in_channels=3은 입력이 RGB 컬러 이미지라는 뜻입니다.

out_channels=32는 32개의 서로 다른 필터를 적용한다는 의미입니다. 각 필터는 3x3x3 크기를 가집니다.

가로 3, 세로 3, 그리고 깊이(채널) 3입니다. 다음으로 두 번째 층을 보면 in_channels=32로 이전 층의 출력을 입력으로 받습니다.

이제 각 필터는 3x3x32 크기가 됩니다. 32개의 이전 특징 맵을 모두 고려하여 새로운 특징을 만듭니다.

out_channels=64로 설정하여 더 다양한 고수준 특징을 추출합니다. 여기서 중요한 점은 각 출력 채널이 모든 입력 채널을 조합하여 만들어진다는 것입니다.

예를 들어 두 번째 층의 첫 번째 출력 채널은 이전 32개 특징 맵을 모두 고려한 결과입니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 얼굴 인식 시스템을 개발한다고 가정해봅시다. 첫 번째 층의 32개 채널은 엣지, 코너, 색상 변화 같은 저수준 특징을 감지합니다.

두 번째 층의 64개 채널은 눈, 코, 입의 형태 같은 중간 수준 특징을 포착합니다. 더 깊은 층으로 가면 얼굴 전체의 구조나 표정 같은 고수준 특징을 학습합니다.

Apple의 Face ID, 은행의 얼굴 인증 시스템이 모두 이런 방식으로 작동합니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 채널 수를 무조건 많이 늘리는 것입니다. out_channels=1024처럼 과도하게 설정하면 파라미터 수가 폭발적으로 증가합니다.

3x3x512x1024 크기의 가중치 텐서는 거의 500만 개의 파라미터를 가집니다. 이렇게 하면 메모리도 부족하고 과적합 위험도 커집니다.

따라서 적절한 채널 수를 선택하는 것이 중요합니다. 또 다른 실수는 채널 차원과 공간 차원을 혼동하는 것입니다.

[4, 64, 224, 224]에서 64는 채널 수이고, 224x224는 공간 크기입니다. Pooling은 공간 크기만 줄이고 채널 수는 그대로 유지합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 감탄했습니다.

"와, 그래서 CNN이 여러 층을 거치면서 점점 더 복잡한 개념을 이해하는 거군요!" 채널과 특징 맵을 제대로 이해하면 CNN의 표현력과 계산 효율 사이의 균형을 잘 맞출 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 채널 수는 2배씩 증가: 일반적으로 32 → 64 → 128 → 256처럼 층마다 2배씩 늘립니다.

  • 공간 크기와 반비례: Pooling으로 공간 크기를 절반으로 줄일 때 채널을 2배로 늘려 정보량을 유지합니다.
  • 첫 층은 적게: 첫 번째 층은 보통 32 또는 64 채널로 시작합니다. 너무 많으면 과적합 위험이 있습니다.

4. Receptive Field

김개발 씨가 CNN 모델을 5층까지 쌓았습니다. 그런데 문득 궁금해졌습니다.

"마지막 층의 한 픽셀이 원본 이미지의 얼마나 넓은 영역을 보는 걸까요?" 선배가 고개를 끄덕이며 말했습니다. "좋은 질문이야.

그게 바로 Receptive Field라는 개념이지."

Receptive Field는 출력의 한 픽셀이 입력 이미지에서 영향을 받는 영역의 크기를 의미합니다. 3x3 필터를 여러 층 쌓으면 Receptive Field가 점진적으로 커져서, 깊은 층에서는 매우 넓은 영역을 한 번에 볼 수 있게 됩니다.

이것이 CNN이 전체 맥락을 이해할 수 있는 핵심 원리입니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn

# 간단한 3층 CNN으로 Receptive Field 이해하기
class ReceptiveFieldDemo(nn.Module):
    def __init__(self):
        super().__init__()
        # 각 층마다 3x3 필터 사용
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        # Layer 1 Receptive Field: 3x3

        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        # Layer 2 Receptive Field: 5x5

        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        # Layer 3 Receptive Field: 7x7

    def forward(self, x):
        x = self.conv1(x)  # RF: 3x3
        x = self.conv2(x)  # RF: 5x5
        x = self.conv3(x)  # RF: 7x7
        return x

model = ReceptiveFieldDemo()
x = torch.randn(1, 3, 224, 224)
output = model(x)

# 마지막 층의 한 픽셀은 입력의 7x7 영역을 본다
print(f"3개 층 후 Receptive Field: 7x7")

박시니어 씨는 다시 화이트보드를 꺼내 들었습니다. "자, 3x3 필터 하나만 사용하면 출력의 한 픽셀은 입력의 3x3 영역만 보지.

그런데 이 출력에 다시 3x3 필터를 적용하면 어떻게 될까?" 김개발 씨는 잠시 생각했습니다. "음...

그럼 원본 이미지의 더 넓은 영역을 보게 되는 건가요?" 쉽게 비유하자면, Receptive Field는 마치 망원경의 배율과 같습니다. 작은 배율로는 좁은 범위만 보이지만, 배율을 높일수록 더 넓은 하늘을 관찰할 수 있습니다.

하지만 망원경과 다른 점은, CNN은 층을 쌓으면서 점진적으로 시야를 넓혀간다는 것입니다. 첫 층은 작은 부분만 보고, 마지막 층은 이미지 전체를 아우르는 큰 그림을 봅니다.

초기 신경망 연구에서는 어땠을까요? Receptive Field의 중요성을 모르던 시절, 개발자들은 7x7이나 11x11 같은 큰 필터를 첫 층에 사용했습니다.

"한 번에 넓게 보면 되잖아?"라고 생각했던 것입니다. 하지만 이렇게 하면 파라미터 수가 너무 많아졌습니다.

11x11 필터는 3x3 필터보다 13배 이상의 파라미터를 가집니다. 게다가 학습도 잘 안 되고 과적합도 심했습니다.

바로 이런 문제를 해결하기 위해 작은 필터를 깊게 쌓는 방법이 등장했습니다. 작은 3x3 필터를 여러 층 쌓으면 적은 파라미터로도 큰 Receptive Field를 얻을 수 있습니다.

3x3 필터 3개를 쌓으면 7x7 Receptive Field를 가지지만, 파라미터는 7x7 필터 1개보다 훨씬 적습니다. 또한 층마다 활성화 함수를 적용하여 비선형성도 증가합니다.

무엇보다 깊은 네트워크는 더 복잡한 패턴을 학습할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 첫 번째 층을 통과하면 출력의 한 픽셀은 입력의 3x3 영역에서 영향을 받습니다. 이것이 Receptive Field 3x3입니다.

두 번째 층을 통과하면, 출력 픽셀 하나가 이전 층의 3x3 영역을 보고, 그 각각은 다시 원본의 3x3 영역을 봅니다. 계산하면 Receptive Field가 5x5로 늘어납니다.

세 번째 층까지 가면 Receptive Field는 7x7이 됩니다. 단순히 층 수와 필터 크기로 계산할 수 있습니다.

RF = 1 + (kernel_size - 1) × num_layers 공식을 사용하면, 1 + (3-1) × 3 = 7입니다. 만약 중간에 Pooling이 있다면 Receptive Field는 더 빠르게 증가합니다.

Stride=2인 Pooling 하나는 Receptive Field를 2배로 늘립니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 OCR(문자 인식) 시스템을 개발한다고 가정해봅시다. 작은 Receptive Field로는 점이나 획 같은 부분만 볼 수 있습니다.

하지만 층을 깊게 쌓아 Receptive Field를 키우면 글자 전체, 나아가 단어 수준의 맥락까지 이해할 수 있습니다. Google의 Cloud Vision API, Naver의 Clova OCR이 모두 이런 구조를 사용합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Receptive Field가 무조건 클수록 좋다고 생각하는 것입니다.

하지만 작은 객체를 인식할 때는 너무 큰 Receptive Field가 오히려 방해가 됩니다. 교통 표지판 같은 작은 물체는 주변 배경까지 보면 혼란스러워질 수 있습니다.

따라서 작업에 맞는 적절한 Receptive Field를 설계해야 합니다. 또 다른 실수는 이론적 Receptive Field와 실효 Receptive Field를 혼동하는 것입니다.

이론상 7x7이어도 실제로는 중심부에 가까운 픽셀들의 영향력이 훨씬 큽니다. 가장자리는 거의 영향을 주지 못합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 무릎을 쳤습니다.

"아, 그래서 VGG나 ResNet이 3x3 필터를 수십 층씩 쌓는 거군요!" Receptive Field를 제대로 이해하면 작업에 맞는 최적의 네트워크 깊이를 설계할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - 3x3 필터가 효율적: 3x3 필터 N개는 (2N+1)x(2N+1) Receptive Field를 만듭니다.

  • Pooling으로 빠르게 확장: Stride=2 Pooling은 Receptive Field를 2배로 늘립니다.
  • 입력 크기 고려: 224x224 이미지라면 최소 50x50 이상의 Receptive Field가 필요합니다.

5. CNN의 계층적 특징 추출

김개발 씨는 드디어 완전한 CNN 모델을 만들었습니다. 그런데 신기한 것은 첫 번째 층의 필터를 시각화해보니 엣지와 코너만 감지하는데, 마지막 층에서는 고양이나 자동차를 인식한다는 것이었습니다.

"어떻게 단순한 엣지에서 복잡한 객체를 인식하게 되는 거죠?" 선배가 미소 지으며 답했습니다. "그게 바로 CNN의 계층적 특징 추출 능력이야."

계층적 특징 추출은 CNN이 얕은 층에서는 단순한 패턴을, 깊은 층에서는 복잡한 개념을 학습하는 구조를 의미합니다. 마치 레고 블록을 조립하듯이, 간단한 엣지들이 모여 도형이 되고, 도형들이 모여 사물이 되는 방식으로 점진적으로 추상화됩니다.

이것이 CNN이 사람처럼 이미지를 이해할 수 있는 핵심 원리입니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn

# 계층적 특징 추출을 보여주는 CNN
class HierarchicalCNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 얕은 층: 저수준 특징 (엣지, 코너, 색상)
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        # 중간 층: 중간 수준 특징 (텍스처, 단순 도형)
        self.layer2 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        # 깊은 층: 고수준 특징 (객체 부분, 전체 구조)
        self.layer3 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

    def forward(self, x):
        # 점진적으로 추상화되는 특징들
        low_level = self.layer1(x)      # 엣지, 색상 변화
        mid_level = self.layer2(low_level)  # 텍스처, 패턴
        high_level = self.layer3(mid_level) # 객체 부분, 구조
        return high_level

model = HierarchicalCNN()
x = torch.randn(1, 3, 224, 224)
features = model(x)
print(f"계층적 특징 추출 완료: {features.shape}")

박시니어 씨는 김개발 씨를 회의실로 불러 큰 화이트보드 앞에 섰습니다. "자, 사람이 고양이를 어떻게 인식하는지 생각해 봐.

우리도 처음부터 '고양이'라는 개념을 아는 게 아니야." 김개발 씨는 고개를 갸우뚱했습니다. "그럼 어떻게 배우는 건데요?" 쉽게 비유하자면, 계층적 특징 추출은 마치 언어를 배우는 과정과 같습니다.

처음에는 자음과 모음 같은 기본 요소를 배우고, 그것들이 모여 글자가 되고, 글자들이 모여 단어가 되고, 단어들이 모여 문장이 됩니다. 각 단계는 이전 단계의 지식을 기반으로 더 복잡한 개념을 구축합니다.

이처럼 CNN도 저수준 특징부터 시작해서 점진적으로 고수준 개념을 만들어갑니다. 초기 컴퓨터 비전 연구에서는 어땠을까요?

예전에는 연구자들이 손으로 직접 특징을 설계했습니다. SIFT, HOG, SURF 같은 알고리즘들이 대표적입니다.

엣지 특징을 추출하는 알고리즘, 코너를 찾는 알고리즘, 텍스처를 분석하는 알고리즘을 각각 따로 만들었습니다. 하지만 이런 방식은 사람이 생각할 수 있는 특징에만 제한되었습니다.

더 큰 문제는 이 특징들을 어떻게 조합해서 고수준 개념을 만들지가 명확하지 않았다는 것입니다. 바로 이런 문제를 해결하기 위해 CNN의 계층적 학습이 등장했습니다.

CNN을 사용하면 특징을 자동으로 학습할 수 있습니다. 사람이 설계하지 않아도 데이터에서 가장 유용한 패턴을 스스로 찾아냅니다.

또한 각 층이 이전 층의 특징을 조합하여 새로운 특징을 만들면서 자연스럽게 계층 구조가 형성됩니다. 무엇보다 end-to-end 학습이 가능해서, 원시 픽셀에서 최종 분류까지 한 번에 학습할 수 있다는 큰 이점이 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 첫 번째 층을 보면 64개의 필터가 RGB 이미지에서 저수준 특징을 추출합니다.

실제로 이 필터들을 시각화해보면 수평선, 수직선, 대각선, 색상 변화 같은 단순한 패턴을 감지합니다. 사람의 시각 피질 V1 영역과 놀랍도록 비슷합니다.

두 번째 층에서는 128개의 필터가 이전 64개 특징을 조합합니다. 이제 좀 더 복잡한 패턴이 나타납니다.

체크무늬, 격자, 점무늬, 원형 패턴 같은 텍스처를 감지하기 시작합니다. 또한 간단한 도형의 일부도 인식합니다.

세 번째 층까지 가면 256개의 필터가 더욱 추상적인 개념을 학습합니다. 고양이의 귀, 자동차의 바퀴, 사람의 얼굴 부분 같은 객체의 구성 요소를 인식하게 됩니다.

마지막 층에서는 객체 전체를 이해합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 의류 쇼핑몰의 상품 추천 시스템을 개발한다고 가정해봅시다. 얕은 층의 특징은 옷의 색상과 패턴을 파악합니다.

중간 층은 체크무늬인지 스트라이프인지, 면 재질인지 데님인지를 구분합니다. 깊은 층은 티셔츠인지 원피스인지, 캐주얼인지 정장인지를 판단합니다.

이런 계층적 정보를 모두 활용하여 사용자가 좋아할 만한 상품을 정확하게 추천할 수 있습니다. Musinsa, 29CM 같은 패션 플랫폼이 이런 기술을 활용합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 층을 무조건 깊게 쌓으면 성능이 좋아진다고 생각하는 것입니다.

하지만 너무 깊으면 gradient vanishing 문제가 발생합니다. 초기 층까지 학습 신호가 전달되지 않아 학습이 제대로 안 됩니다.

따라서 ResNet 같은 skip connection이나 Batch Normalization 같은 기법이 필요합니다. 또 다른 실수는 각 층의 역할을 무시하고 똑같은 구조를 반복하는 것입니다.

얕은 층은 작은 필터와 적은 채널로, 깊은 층은 많은 채널로 설계하는 것이 효과적입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 감동했습니다. "와, CNN이 사람의 뇌처럼 학습하는 거네요!" 계층적 특징 추출을 제대로 이해하면 더 효과적인 CNN 아키텍처를 설계할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - Transfer Learning 활용: ImageNet으로 사전 학습된 모델의 얕은 층은 범용 특징을 학습했으므로 재사용 가능합니다.

  • Feature Visualization: 각 층의 필터를 시각화하면 네트워크가 무엇을 학습했는지 이해할 수 있습니다.
  • 층별 학습률 조정: 얕은 층은 작은 학습률, 깊은 층은 큰 학습률을 사용하면 효과적입니다.

6. PyTorch로 CNN 구현

김개발 씨는 드디어 이론을 충분히 이해했습니다. 이제 실제로 작동하는 CNN을 처음부터 끝까지 구현해볼 차례입니다.

"선배님, 이제 직접 만들어보고 싶은데 어디서부터 시작하면 될까요?" 선배가 노트북을 열며 말했습니다. "좋아, 그럼 CIFAR-10 데이터셋으로 이미지 분류 모델을 만들어보자."

PyTorch CNN 구현은 이론을 실제 코드로 옮기는 과정입니다. 모델 정의, 데이터 로딩, 학습 루프, 평가까지 전체 파이프라인을 구축합니다.

마치 설계도를 보고 실제 건물을 짓는 것처럼, CNN의 모든 구성 요소를 코드로 구현하여 실제로 작동하는 시스템을 만듭니다.

다음 코드를 살펴봅시다.

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms

# 완전한 CNN 모델 정의
class CIFAR10CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 특징 추출 부분
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1),  # 32x32x32
            nn.ReLU(),
            nn.MaxPool2d(2),                  # 16x16x32

            nn.Conv2d(32, 64, 3, padding=1),  # 16x16x64
            nn.ReLU(),
            nn.MaxPool2d(2),                  # 8x8x64

            nn.Conv2d(64, 128, 3, padding=1), # 8x8x128
            nn.ReLU(),
            nn.MaxPool2d(2),                  # 4x4x128
        )

        # 분류 부분
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(256, 10)  # 10개 클래스
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

# 모델, 손실 함수, 옵티마이저 초기화
model = CIFAR10CNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 학습 루프 (1 에폭 예시)
def train_one_epoch(model, train_loader, criterion, optimizer):
    model.train()
    for images, labels in train_loader:
        optimizer.zero_grad()          # 그래디언트 초기화
        outputs = model(images)         # 순전파
        loss = criterion(outputs, labels)  # 손실 계산
        loss.backward()                 # 역전파
        optimizer.step()                # 가중치 업데이트
    return loss.item()

박시니어 씨는 화면을 공유하며 코드를 작성하기 시작했습니다. "자, 이제 지금까지 배운 모든 걸 하나로 합쳐볼 거야." 김개발 씨는 긴장하면서도 기대에 찼습니다.

"드디어 직접 만들어보는 거네요!" 쉽게 비유하자면, CNN 구현은 마치 요리 레시피를 따라 실제 요리를 만드는 것과 같습니다. Convolution이라는 재료, Pooling이라는 조리법, 활성화 함수라는 양념을 모두 적절히 조합해야 맛있는 요리, 즉 잘 작동하는 모델이 완성됩니다.

레시피를 읽는 것과 직접 요리하는 것은 전혀 다른 경험입니다. 초기 딥러닝 개발 환경은 어땠을까요?

PyTorch가 없던 시절에는 Theano나 초기 TensorFlow를 사용했습니다. Convolution 하나를 구현하는 데도 수십 줄의 복잡한 코드가 필요했습니다.

계산 그래프를 명시적으로 정의하고, 세션을 열고, placeholder를 만들고... 코드가 길고 복잡해서 디버깅도 어려웠습니다.

더 큰 문제는 동적인 네트워크 구조를 만들기가 거의 불가능했다는 것입니다. 바로 이런 문제를 해결하기 위해 PyTorch가 등장했습니다.

PyTorch를 사용하면 파이썬처럼 직관적으로 코드를 작성할 수 있습니다. nn.Conv2d 한 줄이면 Convolution 층이 완성됩니다.

또한 define-by-run 방식으로 실행하면서 그래프를 만들어 디버깅이 쉽습니다. print 문으로 중간 결과를 확인할 수 있습니다.

무엇보다 강력한 자동 미분 기능으로 역전파를 자동으로 처리해준다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.

먼저 모델 클래스를 정의하는 부분을 보면 nn.Module을 상속받아야 합니다. 이것이 PyTorch 모델의 기본 구조입니다.

init 메서드에서 모든 층을 정의하고, forward 메서드에서 데이터 흐름을 정의합니다. features 부분은 Convolution과 Pooling을 반복하는 특징 추출기입니다.

Sequential로 묶으면 자동으로 순차 실행됩니다. 각 블록마다 Conv → ReLU → MaxPool 패턴을 사용합니다.

이것이 가장 기본적인 CNN 구조입니다. classifier 부분에서는 먼저 Flatten으로 2D 특징 맵을 1D 벡터로 펼칩니다.

그 다음 Fully Connected 층으로 최종 분류를 수행합니다. Dropout(0.5)를 추가하여 과적합을 방지합니다.

학습 루프에서는 optimizer.zero_grad()로 그래디언트를 초기화하는 것이 중요합니다. PyTorch는 그래디언트를 누적하기 때문입니다.

loss.backward()로 역전파를 수행하고, optimizer.step()으로 가중치를 업데이트합니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 제조업체의 불량품 검사 시스템을 개발한다고 가정해봅시다. 생산 라인에서 실시간으로 제품 이미지를 촬영하고, CNN 모델이 정상/불량을 판정합니다.

먼저 수천 장의 정상 및 불량 제품 이미지로 모델을 학습시킵니다. 그 다음 TorchScript로 변환하여 실시간 추론 속도를 높입니다.

마지막으로 임베디드 장치에 배포하여 현장에서 즉시 검사를 수행합니다. Samsung, LG 같은 제조사들이 이런 시스템을 적극 도입하고 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 GPU로 데이터를 옮기는 것을 잊는 것입니다.

model.to(device)와 images.to(device)를 모두 해야 합니다. 하나라도 빠뜨리면 "expected tensor on cuda but got cpu" 에러가 발생합니다.

또 다른 실수는 model.train()과 model.eval()을 제대로 사용하지 않는 것입니다. 학습할 때는 train 모드, 평가할 때는 eval 모드로 전환해야 Dropout과 BatchNorm이 올바르게 동작합니다.

마지막으로 데이터 전처리를 소홀히 하는 경우가 많습니다. transforms.Normalize로 정규화하고, transforms.RandomHorizontalFlip 같은 데이터 증강을 추가하면 성능이 크게 향상됩니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 도움을 받아 김개발 씨는 첫 CNN 모델을 성공적으로 학습시켰습니다.

"와, 정확도가 85%나 나왔어요!" "잘했어. 이제 데이터 증강, 배치 정규화, 학습률 스케줄링 같은 기법들을 추가해보면 90% 이상도 가능할 거야." PyTorch CNN 구현을 제대로 마스터하면 어떤 이미지 인식 문제든 빠르게 프로토타입을 만들고 실험할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - GPU 활용: model.to('cuda')와 data.to('cuda')로 GPU 가속을 활용하세요. 학습 속도가 10배 이상 빨라집니다.

  • 체크포인트 저장: torch.save(model.state_dict(), 'best_model.pth')로 최고 성능 모델을 저장하세요.
  • Learning Rate Finder: 적절한 학습률을 찾기 위해 작은 값부터 시작해 점진적으로 증가시키며 손실을 모니터링하세요.

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#CNN#Convolution#Pooling#DeepLearning#CNN,Deep Learning

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스