CNN Networks

Seohyeon Park·2025년 3월 16일

Intro.

1998년 Yann Lecun 연구팀은 "Gradient-based learning applied to document recognition” 논문에서 LeNet을 통해 CNN(Convolutional Neural Network) 구조를 세상에 처음 공개한다. 기존의 DNN(Deep Neural Network)은 1차원 형태의 데이터를 사용하다 보니, 2차원 형태의 이미지의 입력값을 처리할 때 이미지의 공간적, 지역적 정보 손실이 불가피 했다. CNN은 이러한 문제점을 해결하며, 이미지 분야에서 딥러닝을 크게 도약 하게 만든다.

1998년 이후 2012년 AlexNet, 2014년 VGGNet, 2015년 ResNet을 비롯하여 많은CNN 구조의 Networks들이 등장하였다.

CNN Concept.

1. Convolution

  • 필요성
    • 특징 추출 : 컨볼루션 연산은 이미지의 로컬적은 특징(ex. 엣지, 코너 등)을 추출한다. 이는 이미지가 가지는 공간적, 지역적 정보를 유지할 수 있게 해준다.
    • 파라미터 효율성 : 이미지와 같은 고차원의 데이터를 단순 fully connect로 처리하면 엄청난 파라미터가 필요하다. 이는 비용의 증가와 과적합을 불러일으킬 수 있다. 컨볼루션 레이어는 필터를 공유하여 파라미터의 수를 크게 줄일 수 있다.
  • 동작원리
    컨볼루션 연산은 작은 크기의 필터를 입력 이미지 위에서 슬라이딩하여 점진적으로 적용 된다. 각 위치에서 필터와 입력 이미지의 일부분이 곱해지고, 그 합이 계산되어 출력 특징 맵을 형성한다. 이를 통해 입력 이미지의 중요한 공간적 특징을 추출할 수 있다.
    (fg)(i,j)=m=0M1n=0N1f(i+m,j+n)g(m,n)(f*g)(i,j)=\sum^{M-1}_{m=0}\sum^{N-1}_{n=0}f(i+m, j+n)g(m,n)
  • ff : 입력 이미지
  • gg : 필터 이미지
  • ii, jj : 출력 위치의 인덱스
  • MM, NN : 필터의 크기

2. Pooling

  • 필요성
    • 차원 축소 : 풀링은 데이터의 공간적 크기를 줄여 계산 비용을 크게 감소시킨다.
    • 중요 특징 강조 : 로컬 영역에서 가장 중요한 특징을 강조하여 나타낸다.
    • 불변성 강화 : 작은 변형이나 왜곡에 대해 모델의 성능이 크게 영향 받지 않게 하는 기능을 한다. 작은 이동이나 변형이 발생 하더라도 추출된 특징은 크게 변하지 않으며, 이는 모델이 작은 위치 변화에 대해 불변성을 갖게 한다.
  • 동작원리
    풀링은 공간적 크기를 줄이고 이미지의 중요한 특징 정보를 유지하는 것에 목적을 가지고 있으며, 주로 일반적으로 최대 풀링(max pooling)이 사용된다. 밑에 수식과 그림을 통해 알 수 있듯이 풀링은 차원을 축소한다. 또한 로컬 영역(m,n)에서 가장 중요한 특징(가장 큰 수)을 추출 하며, 작은 왜곡(낮은 숫자)에 대해서는 pooling 과정 결과에 영향을 주지 못하므로 불변성을 가지게 해준다.

    ㅇㅇㅇ
y(i,j)=maxm,nx(i+m,j+n)y_{(i,j)}=max_{m,n}x(i+m, j+n)
  • yy : 풀링 결과
  • xx : 입력 값
  • mm, nn : 풀링 윈도우 크기

LeNet.

1. LeNet 구조

  1. Input Layer
  • 32x32 크기의 그레이 스케일 이미지를 입력 받는다. (픽셀 값은 0 ~ 255)
  1. Convolutional Layer (C1)

    C1=Conv2D(32,32,6,5,stride=1,padding=0)C1=Conv2D(32,32,6,5,stride=1,padding=0)
    • 6개의 5x5 필터를 사용하여 입력 이미지와 컨볼루션 연산을 수행한다.
    • 6개의 28x28 크기의 특징 맵이 생성된다.
  2. First Pooling Layer (S2)

    S2=AveragePool2D(2,2,stride=2)S2=AveragePool2D(2,2,stride=2)
    • 2x2 필터를 사용하여 서브 샘플링 (Avg Pooling)을 수행한다.
    • 6개의 14x14 크기의 특징 맵이 생성된다.
  3. Second Convolutional Layer (C3)

    C3=Conv2D(14,14,16,5,stride=1,padding=0)C3=Conv2D(14,14,16,5,stride=1,padding=0)
    • 16개의 5x5 필터를 사용하여 입력 특징 맵과 컨볼루션 연산을 수행한다.
    • 16개의 10x10 크기의 특징 맵이 생성된다.
  4. Second Pooling Layer (S4)

    S4=AveragePool2D(2,2,stride=2)S4=AveragePool2D(2,2,stride=2)
    • 2x2 필터를 사용하여 서브 샘플링 (Avg Pooling)을 수행한다.
    • 16개의 5x5 크기의 특징 맵이 생성된다.
  5. Third Convolutional Layer (C5)

    C5=Conv2D(5,5,120,5,stride=1,padding=0)C5=Conv2D(5,5,120,5,stride=1,padding=0)
    • 120개의 5x5 필터를 사용하여 입력 특징 맵과 컨볼루션 연산을 수행한다.
    • 필터의 크기와 입력 특징 맵의 크기가 같기 때문에 출력은 1x1 크기의 120개의 노드가 된다.
  6. Fully Connected Layer (F6)

    F6=Linear(120,84)F6=Linear(120,84)
    • 120개의 노드를 84개의 출력 노드로 연결한다.
  7. Output Layer
    - 84개의 노드를 10개의 출력 클래스 (0~9 숫자)로 연결한다.
    - 각 노드는 특정 숫자 클래스에 해당하는 확률을 나타낸다.

    Stride & Padding

    1. Stride
      Stride는 컨볼루션 필터가 입력 데이터 위를 이동하는 간격을 의미한다. 기본적으로 stride는 필터가 한 번에 이동하는 픽셀 수를 정의하며, 주로 컨볼루션에서는 기본값은 1을 사용하며, 풀링에서는 윈도우와 같은 값을 사용한다. 수가 클수록 출력 맵에 크기를 줄일 수 있으며, 계산 비용을 줄여줄 수 있다.
    2. Padding
      Padding은 입력 데이터의 경계에 값을 추가하는 것을 의미한다. 주로 컨볼루션 연산 후 출력 크기를 조정하거나, 경계 정보 손실을 방지하기 위해 사용되며, 기본값은 0을 사용한다. 출력 맵에 크기를 유지시켜 주거나 입력 데이터의 경계 정보를 보존하여 컨볼루션이 경계 영역에서도 충분한 정보를 활용할 수 있도록 한다.

2. LeNet 구현 예시

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


# LeNet-5 모델 정의
class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0)
        self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0)
        self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
        self.conv3 = nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0)
        self.fc1 = nn.Linear(120 * 1 * 1, 84)
        self.fc2 = nn.Linear(84, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = F.relu(self.conv3(x))
        x = x.view(-1, 120 * 1 * 1)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

# 데이터셋 로딩 및 변환
transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)

test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 모델, 손실 함수, 옵티마이저 정의
model = LeNet5()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for data, target in train_loader:
        optimizer.zero_grad()
        outputs = model(data)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")

# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for data, target in test_loader:
        outputs = model(data)
        _, predicted = torch.max(outputs.data, 1)
        total += target.size(0)
        correct += (predicted == target).sum().item()

print(f"Accuracy: {100 * correct / total}%")

AlexNet.

1. AlexNet 구조

Layer 이름Layer 설명
1Input Layer227 x 227 크기의 RGB 이미지 입력
2Conv1Conv1=Conv2D(227,227,96,11,stride=4,padding=0)Conv1=Conv2D(227,227,96,11,stride=4,padding=0)
3Max Pool1MaxPool1=MaxPool2D(3,3,stride=2)MaxPool1=MaxPool2D(3,3,stride=2)
4Conv2Conv2=Conv2D(27,27,256,5,stride=1,padding=2)Conv2=Conv2D(27,27,256,5,stride=1,padding=2)
5Max Pool2MaxPool1=MaxPool2D(3,3,stride=2)MaxPool1=MaxPool2D(3,3,stride=2)
6Conv3Conv3=Conv2D(13,13,384,3,stride=1,padding=1)Conv3=Conv2D(13,13,384,3,stride=1,padding=1)
7Conv4Conv4=Conv2D(13,13,384,3,stride=1,padding=1)Conv4=Conv2D(13,13,384,3,stride=1,padding=1)
8Conv5Conv5=Conv2D(13,13,256,3,stride=1,padding=1)Conv5=Conv2D(13,13,256,3,stride=1,padding=1)
9Max Pool3MaxPool3=MaxPool2D(3,3,stride=2)MaxPool3=MaxPool2D(3,3,stride=2)
10FC1FC1=Linear(25666,4096)FC1=Linear(256*6*6, 4096)
11FC2FC2=Linear(4096,4096)FC2=Linear(4096, 4096)
12FC3FC3=Linear(4096,1000)FC3=Linear(4096, 1000)
13OutPutLayer 1,000개의 출력 클래스
  • ImageNet Classification with Deep Convolutional Neural Networks 논문 내에서는 좌우 반전, RGB 채널의 색상 강도 조절과 같은 Data Augmentation 기법을 사용해 더 많은 데이터를 확보하였다.
  • AlexNet은 2개의 GPU를 사용하였으며, 3번째 레이어에서는 이전 레이어에서 연산된 2개의 GPU 결과를 모두 받아 오도록 설계되었다.
  • 3개의 Convolution Layer와 Fully Connected Layer에서 활성화 함수로 ReLU를 사용하였다.
  • 네트워크 구조가 거대한 딥러닝 모델이기 때문에 비용 절감과 과적합의 방지를 위해 Dropout 기법이 사용 되었다.
  • 출력의 크기를 계산하는 식은 다음과 같다.
    IK+2PS+1\frac{I-K+2P}{S}+1
    • II : 입력 이미지의 크기
    • KK : 커널의 너비
    • PP : padding 크기
    • SS : stride 크기

2. AlexNet 구현 예시

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


# AlexNet 모델 정의
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0)
        self.pool1 = nn.MaxPool2d(kernel_size=3, stride=2)
        self.conv2 = nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2)
        self.pool2 = nn.MaxPool2d(kernel_size=3, stride=2)
        self.conv3 = nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1)
        self.conv4 = nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1)
        self.conv5 = nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1)
        self.pool3 = nn.MaxPool2d(kernel_size=3, stride=2)
        self.fc1 = nn.Linear(256 * 6 * 6, 4096)
        self.fc2 = nn.Linear(4096, 4096)
        self.fc3 = nn.Linear(4096, 1000)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        x = F.relu(self.conv5(x))
        x = self.pool3(x)
        x = x.view(-1, 256 * 6 * 6)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, p=0.5)
        x = F.relu(self.fc2(x))
        x = F.dropout(x, p=0.5)
        x = self.fc3(x)
        return x

# 데이터셋 로딩 및 변환
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(227),
    transforms.ToTensor(),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])

train_dataset = datasets.ImageFolder(root='./data/train', transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)

test_dataset = datasets.ImageFolder(root='./data/val', transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

# 모델, 손실 함수, 옵티마이저 정의
model = AlexNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for data, target in train_loader:
        optimizer.zero_grad()
        outputs = model(data)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    print(f"Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}")

# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for data, target in test_loader:
        outputs = model(data)
        _, predicted = torch.max(outputs.data, 1)
        total += target.size(0)
        correct += (predicted == target).sum().item()

print(f"Accuracy: {100 * correct / total}%")

VGG Net.

1. VGG Net 구조

  • VGG Net은 A, A-LRN, B, C, D, E 등 다양한 모델이 존재하며, 가장 많이 쓰이는 모델은 VGG-16(D)이 높은 성능과 가장 많이 사용되고 있다.
  • VGG Net은 모델을 굉장히 깊게 쌓았다는 점을 제외한다면 여타 다른 모델과 비교하면 특별한 점은 없는 모델이다.
  • VGG16은 모든 Convolution Layer에서 3x3, stride=1로 가장 작은 필터를 사용한다. 이로 인해 깊은 모델의 구현과 파라미터 개수의 감소 그리고 ReLU의 적극적인 사용이 가능해졌다.
  • VGG Net은 풀링 또한 2x2의 일정한 Max Pooling만을 사용한다.

2. VGG Net 구현 예시

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


# VGG-16 모델 정의
class VGG16(nn.Module):
    def __init__(self):
        super(VGG16, self).__init__()
        self.conv1 = nn.Sequential(  # Block 1
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.conv2 = nn.Sequential(  # Block 2
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.conv3 = nn.Sequential(  # Block 3
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.conv4 = nn.Sequential(  # Block 4
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.conv5 = nn.Sequential(  # Block 5
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.fc_layers = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 1000)
        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        x = x.view(x.size(0), -1)  # Flatten
        x = self.fc_layers(x)
        return x


# 데이터셋 로딩 및 변환
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # VGG 입력 크기 224x224
    transforms.ToTensor(),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),  # ImageNet 평균과 표준편차
])

train_dataset = datasets.ImageFolder(root='./data/train', transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)

test_dataset = datasets.ImageFolder(root='./data/val', transform=transform)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)

# 모델, 손실 함수, 옵티마이저 정의
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = VGG16().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.0001)

# 학습 루프
num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for data, target in train_loader:
        data, target = data.to(device), target.to(device)
        
        optimizer.zero_grad()
        outputs = model(data)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")

# 모델 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for data, target in test_loader:
        data, target = data.to(device), target.to(device)
        outputs = model(data)
        _, predicted = torch.max(outputs, 1)
        total += target.size(0)
        correct += (predicted == target).sum().item()

print(f"Accuracy: {100 * correct / total:.2f}%")

Reference.

  • Gradient-based learning applied to document recognition (Yann LeCun. 1998)
  • ImageNet Classification with Deep Convolutional Neural Networks (Alex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton. 2012)
  • Very Deep Convolutional Networks for Large-Scale Image Recognition (Karen Simonyan, Andrew Zisserman. 2014)
  • Deep Residual Learning for Image Recognition (Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun. 2015)
profile
카페에서 한줄 한줄

0개의 댓글