컴퓨터 비전

AlexNet의 구조와 구현

kdjames0930 2026. 1. 10. 22:42

 AlexNet은 2012년에 발표된 이미지 분류 CNN 아키텍쳐이고, 그 당시에 타 모델들에 비해 뛰어난 성능으로 이미지 객체 인식 대회인 ImageNet Large Scale Visual Recognition Challenge에서 우승을 했다. 처음으로 CNN 기반 딥러닝 모델이 고전적 이미지 분류 모델들의 성능을 앞지른 사례로 딥러닝이 많은 주목을 받기 시작하는 계기가 됐다고 한다. 오늘은 AlexNet의 어떠한 구조적 특징들이 큰 성능 향상으로 이어질 수 있었는지 논문의 내용을 토대로 살펴보고 신경망을 pytorch로 구현해보겠다.

 

논문 링크: https://papers.nips.cc/paper_files/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf

 

 

 

 AlexNet 모델은 총 8개의 레이어로 구성되어 있다. (5개의 Convolutional Layer, 3개의 Fully-Connected Layer) Convolution 레이어들을 거치면서 이미지의 특징을 추출해내고, 마지막에 소프트맥스 함수를 이용해서 1000개의 클래스로 분류한다. 아래 그림과 같이 2개의 GPU를 사용해서 병렬적으로 학습과 추론을 시키는데, 이는 당시 GPU의 계산 속도의 한계를 극복하기 위함이다. 1, 2, 4, 5번째 Convolution 레이어는 각자의 GPU에서 서로의 feature map을 공유하지 않은 채 학습되고, 3번째 Convolution 레이어와 FC레이어들에서는 feature map을 공유한다. 

 

출처: ImageNet Classification with Deep Convolutional Neural Networks 논문

 

 

논문에서는 속도와 성능을 향상시킨 구조적 요인으로 GPU의 병렬적 사용구조 외에도 ReLU 활성화 함수 사용, LRN(Local Response Normalization), Overlapping Pooling 등을 꼽는다. 이제 하나씩 살펴보도록 하겠다.

 

 

  • ReLU (Rectified Linear Unit) 활성화함수 사용

 기존의 딥러닝 신경망에서는 주로 sigmoid나 tanh와 같은 활성화 함수가 사용되었다. 그러나 이러한 함수들은 몇 가지 근본적인 한계를 가지고 있다. 첫째, 입력값의 절댓값이 커질수록 출력이 포화 영역에 들어가면서 미분값(기울기)이 0에 수렴한다. 이로 인해 역전파 과정에서 기울기가 여러 층을 거치며 반복적으로 곱해질 때 점점 작아지는 기울기 소실(gradient vanishing) 문제가 발생하고, 깊은 신경망에서는 학습 속도가 현저히 느려지거나 학습이 거의 이루어지지 않는 문제가 생긴다. 둘째, sigmoid와 tanh 함수는 지수 연산을 포함하고 있어 미분 및 계산 과정이 상대적으로 복잡하며, 이는 대규모 데이터와 깊은 네트워크를 학습할 때 연산 비용을 증가시키는 요인이 된다.

 

출처) DeepLearning.ai

 

 

 반면 ReLU는 입력값이 양수일 경우 출력이 입력값과 동일하고, 이 구간에서의 기울기가 1이므로 기울기가 소실되지 않고 안정적으로 전달된다. 또한 입력값이 음수일 때는 출력이 0이 되어 계산이 단순해지며, 미분 역시 음수 영역에서는 0, 양수 영역에서는 1로 정의되어 연산 비용이 매우 낮다. 이러한 특성 덕분에 ReLU를 사용한 신경망은 학습 수렴 속도가 크게 향상되었고, 이는 GPU 기반 학습과 결합되어 ImageNet과 같은 대규모 데이터셋을 활용한 깊은 신경망 학습을 가능하게 했다.

 

  • LRN (Local Response Normalization)

 ReLU 활성화 함수는 양수 영역에서 기울기가 일정하게 유지되므로, sigmoid나 tanh와 달리 입력 정규화를 반드시 수행하지 않아도 안정적인 학습이 가능하다는 장점이 있다. 그러나 AlexNet 논문에서는, ReLU를 사용하더라도 LRN과 같은 국소 정규화를 적용할 경우 모델의 일반화 성능이 추가로 향상된다고 한다.

 

 LRN은 같은 공간 위치 (x, y)에서 서로 다른 여러 개의 convolution 필터가 생성한 activation 사이에 경쟁 관계를 도입하는 정규화 기법이다. 하나의 convolution 레이어에서는 동일한 위치 (x, y)에 대해 총 개의 필터가 각각 activation 값을 출력하며, 이 중 채널 i의 activation을 정규화하기 위해 채널 i를 중심으로 인접한 n개의 채널을 선택한다. 이후 선택된 채널들의 activation 값을 제곱한 뒤 합산하여, 해당 위치에서의 전체 활성화 강도를 계산한다. 이후 채널 i의 activation 값을 , 를 통해 조정한 합산 값으로 나눈다. 동일한 위치에서 여러 채널이 동시에 강하게 반응하는 경우에는 각 채널의 activation이 상대적으로 억제되고, 반대로 주변 채널들의 반응이 약한 상황에서 특정 채널만 강하게 반응할 경우에는 해당 activation이 덜 감소되어 상대적으로 두드러지게 유지된다.

 

  • Overlapping Pooling

 기존의 합성곱 신경망에서는 풀링(pooling) 연산을 수행할 때, 인접한 풀링 영역이 서로 겹치지 않도록 설정하는 방식이 일반적으로 사용되었다. 풀링 영역의 크기를 z×z, 풀링 간격(stride)을 s라고 할 때, 보통 s=z 인 것이다. 반면, AlexNet에서는 풀링 영역의 간격을 풀링 크기보다 작게 설정하여, 즉 s<z가 되도록 하여 overlapping pooling을 도입하였다.  

 Overlapping pooling은 feature map을 지나치게 급격하게 축소하는 것을 방지하며, 인접한 공간 정보가 완전히 분리되는 것을 막아준다. 그 결과, 작은 위치 변화에 대한 민감도가 완화되고, 풀링 과정에서 발생할 수 있는 정보 손실이 줄어든다. 이러한 특성은 모델이 특정 위치나 패턴에 과도하게 의존하는 것을 억제하여 과적합을 완화시켜준다.

 

 

 

Layer # Filters Filter Size Stride Padding Size of feature map Activation
function
input - - - - 227 x 227 x 3 -
Conv1 96 11 x 11 4 - 55 x 55 x 96 ReLU
Max Pool1 - 3 x 3 2 - 27 x 27 x 96 -
Conv2 256 5 x 5 1 2 27 x 27 x 256 ReLU
Max Pool2 - 3 x 3 2 - 13 x 13 x 256 -
Conv3 384 3 x 3 1 1 13 x 13 x 384 ReLU
Conv4 384 3 x 3 1 1 13 x 13 x 384 ReLU
Conv5 256 3 x 3 1 1 13 x 13 x 256 ReLU
Max Pool3 - 3 x 3 2 - 6 x 6 x 256 -

 

 227 x 227 x 3 크기의 이미지를 시작으로 위와 같은 레이어들을 거친다. 첫번째와 두번째 convolution 레이어를 거친 후에는 ReLU 활성화 함수, LRN, Max Pooling 레이어를 거친다. 이후 세개의 convolution 레이어를 지나고 또 하나의 Max Pooling 레이어를 거친 후 Fully Connected Layer 부분으로 넘어간다. 

 

Layer Rate Filter Size Stride Padding Size of feature map Activation
function
Dropout1 0.5 - - - 6 x 6 x 256 -
FC1 - - - - 4096 ReLU
Dropout2 0.5 - - - 4096 -
FC2 - - - - 4096 ReLU
FC3 - - - - 1000 Softmax

 

 첫 번째와 두 번째 Fully Connected Layer 앞에 Dropout 기법을 적용하여 학습 과정 중 일부 뉴런을 무작위로 비활성화한다. 이를 통해 특정 뉴런이나 경로에 대한 과도한 의존을 줄이고, 모델의 일반화 성능을 향상시킨다. 최종 Fully Connected Layer의 출력은 Softmax 함수에 입력되어, 1000개 클래스 각각에 대한 확률 분포를 계산한다.

 

 

 

 마지막으로 pytorch를 통한 알렉스넷 구현이다. 다음번에는 이 모델을 학습시키고 샘플 이미지를 분류하는 작업을 해보겠다.

class AlexNet(nn.Module):
  def __init__(self, num_classes=1000):
    super().__init__()

    self.features = nn.Sequential(
        # Conv1
        nn.Conv2d(in_channels=3, out_channels=96, kernel_size=(11, 11), stride=4, padding=0),
        nn.ReLU(inplace=True),
        nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
        nn.MaxPool2d(kernel_size=3, stride=2),

        # Conv2
        nn.Conv2d(in_channels=96, out_channels=256, kernel_size=(5, 5), stride=1, padding=2, groups=2),
        nn.ReLU(inplace=True),
        nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
        nn.MaxPool2d(kernel_size=3, stride=2),

        # Conv3
        nn.Conv2d(256, 384, kernel_size=(3, 3), stride=1, padding=1),
        nn.ReLU(inplace=True),

        # Conv4
        nn.Conv2d(384, 384, kernel_size=(3, 3), stride=1, padding=1, groups=2),
        nn.ReLU(inplace=True),

        # Conv5
        nn.Conv2d(384, 256, kernel_size=(3, 3), stride=1, padding=1, groups=2),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=3, stride=2),
    )

    self.classifier = nn.Sequential(
        nn.Dropout(p=0.5),
        nn.Linear(in_features=256*6*6, out_features=4096),
        nn.ReLU(inplace=True),

        nn.Dropout(p=0.5),
        nn.Linear(in_features=4096, out_features=4096),
        nn.ReLU(inplace=True),

        nn.Linear(in_features=4096, out_features=num_classes)
    )

    self._init_weights()

  def _init_weights(self):
    for m in self.modules():
      if isinstance(m, nn.Conv2d):
        nn.init.normal_(m.weight, mean=0.0, std=0.01)
        if m.bias is not None:
          nn.init.constant_(m.bias, 0.0)
      elif isinstance(m, nn.Linear):
        nn.init.normal_(m.weight, mean=0.0, std=0.01)
        nn.init.constant_(m.bias, 0.0)

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

    # Flatten + FC
    x = torch.flatten(x, 1)
    x = self.classifier(x)
    return x

model = AlexNet()
dummy_input = torch.randn(1, 3, 227, 227)
out = model(dummy_input)
print("classified as class ", torch.argmax(out))

 

 

반응형