카테고리 없음

Alexnet 구현하기

dldbwls0818 2025. 2. 2. 05:56

1. Alexnet

- Alexnet은 2012년  ILSVRC(ImageNet Large Scale Visual Recognition Challenge)에서 우승한 딥러닝 모델로, 딥러닝의 대중화를 이끈 중요한 CNN입니다.

- 8개의 layer(5개의 Convolution layer와 3개의 FC layer)로 구성되어 있으며, ReLU 활성화 함수, Dropout, 데이터 증강 등을 사용해 Overfitting을 방지하고 학습 성능을 향상시켰습니다.

- Alexnet은 대규모 데이터셋과 GPU 병렬 연산을 활용해 1,000개의 클래스 분류 문제에서 top-1, top-5 error rates가 각각 37.5%, 17.5%로 뛰어난 성능을 보여, 컴퓨터 비전에서 딥러닝이 표준 기법으로 자리 잡는 데에 기여했습니다.

- 이 성과는 당시 매우 뛰어난 결과였고 이전에 사용된 전통적인 머신러닝 방법론보다 훨씬 큰 차이로 성능을 끌어올리며, 딥러닝의 가능성을 보여주었습니다.

※ ImageNet LSVRC

-ImageNet LSVRC는 Large Scale Visual Recognition Challenge의 약자로, 이미지 인식 및 분류 기술을 겨루는 대회입니다.

- 2010년부터 매년 개최되었으며, ImageNet이라는 대규모 이미지 데이터셋을 기반으로 참가자들이 다양한 모델을 설계하고 경쟁했습니다.

  • ImageNet 데이터셋 : 약 1400만 장의 이미지를 포함하며, 1000개의 클래스로 분류된 데규모 이미지 데이터셋입니다.
  • 목적 : 컴퓨터 비전 및 딥러닝 기술의 발전을 촉진하고, 이미지 인식 분야에서 혁신적인 기술을 발견하는 것이 목표였습니다.

※ Error Rate

- 이미지 분류 모델의 성능을 평가하는 지표로, 모델이 이미지를 얼마나 정확히 분류했는지를 나타냅니다.

  1. Top-1 Error Rate
    • 모델이 예측 가능한 가장 높은 확률의 클래스(Top-1)가 정답이 아닐 확률입니다.
    • ex) 이미지에 '고양이'가 있고, 모델이 가장 높은확률로 '강아지'라고 예측했다면, Top-1 Error입니다.
  2. Top-2 Error Rate
    • 모델이 예측한 상위 5개의 클래스 중 하나라도 정답에 포함되지 않았을 확률입니다.
    • ex) 이미지가 '고양이'인데 모델이 '강아지','토끼','고양이','호랑이','여우'를 상위 5개로 예측했다면, 이건 정답으로 간주됩니다.
    • Top-5를 사용하는 이유 : 사람들이 보기에 유사한 클래스(ex: 치타와 표범)를 분류하는 것은 어렵기 때문에, 상위 5개 중에 정답이 있는지 확인하는 방식으로 보다 실용적인 성능을 평가합니다.

2. CIFAR 데이터셋

- CIFAR 데이터셋은 torchvision 라이브러리에서 제공하는 이미지 데이터셋으로, 주로 딥러닝 모델의 학습 및 평가에 사용됩니다.

- CIFAR-10과 CIFAR-100 두 종류가 있으며, 각각 10개와 100개의 클래스에 대해 32x32 크기의 컬러 이미지로 구성됩니다.

- CIFAR-10은 클래스당 6000개(총 60,000개)의 이미지로 이루어져 있으며, CIFAR-100은 클래스당 600개(총 60,000개)로 구성됩니다.

- PyTorch는 torchvision.datasets 모듈을 통해 이 데이터셋을 쉽게 불러올 수 있으며, 학습/테스트 데이터셋 분리, 데이터 증강, 정규화 등의 전처리를 지원합니다.

- CIFAR 데이터셋은 이미지 분류 알고리즘을 실험하고 비교하는 데에 널리 사용됩니다.

3. AlexNet 직접 구현(CIFAR 데이터셋 사용)

import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision.transforms import transforms
# 이미지에 효과를 넣어줄 때 사용하는 모듈
from torchvision.transforms.functional import to_pil_image
train_img = datasets.CIFAR10(
    root = 'data',
    train=True, # train용 데이터만 다운받기
    download=True,
    transform=transforms.ToTensor()
)

test_img = datasets.CIFAR10(
    root = 'data',
    train=False, # test용 데이터만 다운받기
    download=True,
    transform=transforms.ToTensor()
)

# Normalize 할 때 이미지에 맞는 맞춤형 값 구하는방법
mean = train_img.data.mean(axis=(0,1,2)) / 255
std = train_img.data.std(axis=(0,1,2)) / 255

transform_train = transforms.Compose([
    transforms.ToTensor(),
     # 이미지를 랜덤하게 자르기(사이즈는 정해져있음)
    transforms.RandomCrop(size = train_img.data.shape[1], padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.Normalize(mean,std)
])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean,std)
])

# 이렇게도 적용이 가능함
# 중복되어도 오류가 안남
train_img = datasets.CIFAR10(
    root = 'data',
    train = True,
    download = True,
    transform = transform_train,
)

test_img = datasets.CIFAR10(
    root = 'data',
    train = False,
    download = True,
    transform = transform_test,
)
EPOCH = 10
BATCH_SIZE = 128
LEARNING_RATE = 1e-3
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print("Using Device:", DEVICE)

train_loader = DataLoader(train_img, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(test_img, batch_size=BATCH_SIZE, shuffle=False)

train_features, train_labels = next(iter(train_loader))
print(f'Feature Batch Shape: {train_features.size()}')
print(f'Labels Batch Shape: {train_labels.size()}')

labels_map = {
    0: "plane",
    1: "car",
    2: "bird",
    3: "cat",
    4: "deer",
    5: "dog",
    6: "frog",
    7: "horse",
    8: "ship",
    9: "truck",
}

def denormalize(img, mean, std) :
    # view() : tensor의 형태를 바꿔줌
    # 3, 1, 1 -> 3 : 이미지 채널, 1 : 공간 차원(높이와 너비)
    mean = torch.tensor(mean).view(3, 1, 1)
    std = torch.tensor(std).view(3, 1, 1)
    return img * std + mean
    
figure = plt.figure(figsize = (8, 8))
cols, rows = 5, 5

for i in range(1, cols * rows +1):
    sample_idx = torch.randint(len(train_img), size=(1,)).item()
    img, label = train_img[sample_idx]
    img = denormalize(img, mean, std)  # Normalize 복원
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[label])
    plt.axis('off')
    # to_pil_image : 이미지 객체로 변환하는데 사용
    # 텐서형태의 이미지를 PIL이미지로 변환
    # 특징 : 0 ~ 255 또는 0 ~ 1 범위를 가져야 함
    plt.imshow(to_pil_image(img))
plt.show()
class AlexNet(nn.Module) :
    def __init__(self, num_classes = 10) :
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(96, 256, kernel_size = 3, padding = 1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

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

            nn.Conv2d(384, 384, kernel_size = 3, padding = 1),
            nn.ReLU(inplace=True),

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

            )

        self.classifier = nn.Sequential(
            nn.Linear(256*4*4, 4096),
            nn.Dropout(0.5),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x) :
        x = self.features(x)
        # flatten 직접 하기
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
        
model = AlexNet().to(DEVICE)
def train(train_loader, model, loss_fn, optimizer):
    model.train()

    size = len(train_loader.dataset)

    for batch, (X, y) in enumerate(train_loader):
        X, y = X.to(DEVICE), y.to(DEVICE)
        pred = model(X)

        loss = loss_fn(pred, y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f'loss: {loss:>7f}  [{current:>5d}]/{size:5d}')
            
def test(test_loader, model, loss_fn):
    model.eval()

    size = len(test_loader.dataset)
    num_batches = len(test_loader)
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, y in test_loader:
            X, y = X.to(DEVICE), y.to(DEVICE)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:8f}\n")
    
for i in range(EPOCH) :
    print(f"Epoch {i+1} \n------------------------")
    train(train_loader, model, loss, optimizer)
    test(test_loader, model, loss)
print("Done!")

4. itertools

- 파이썬 표준 라이브러리 모듈로 반복과 관련된 효율적이고 고성능의 도구를 제공합니다.

- 다양한 반복 가능한 객체나 순열, 조합을 생성하거나 조작하는 함수들을 포함하고 있습니다

from itertools import product, permutations, combinations, islice

# product 모듈 : 데카르트 곱을 해주는 모듈
color = ['red', 'blue']
size = ['S', 'M', 'L']

result = list(product(color, size))
print(result)

# permutations 모듈 : 순열 모듈
# 중복 가능
number = [1, 2, 3]
perm = list(permutations(number, 2)) # 길이가 2인 순열 생성
print(perm)

# combinations 모듈 : 조합 모듈
# 중복 없음
letter = ['A', 'B', 'C']
comb = list(combinations(letter, 2))
print(comb)

# islice 모듈 : 슬라이싱 모듈
# 반복 가능한 객체에서 특정 구간만 슬라이싱
number = range(10)

sliced = list(islice(number, 2, 8, 2)) # 2부터 7까지 간격을 2로 설정
print(sliced)
import itertools

def plot_confusion_matrix(cm, target_names=None, cmap=None, normalize=True, labels=True, title='Confusion matrix') : # cm : 행렬

    # np.trace : 혼동 행렬의 대각선 요소의 합
    accuracy = np.trace(cm) / float(np.sum(cm))
    misclass = 1 - accuracy

    if cmap is None :
        cmap = plt.get_cmap('Blues')

    if normalize :
        # [:, np.newaxis] 배열의 차원을 확장하여 열 백터로 확장
        # (3,) => (3, 1)
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

    plt.figure(figsize=(8, 6))
    # interpolation='nearest' : 픽셀간의 간격을 최소화
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar() # 범례를 추가

    # threshhold : 임계값
    thresh = cm.max() / 1.5 if normalize else cm.max() / 2

    if target_names is not None :
        tick_marks = np.arange(len(target_names))
        plt.xticks(tick_marks, target_names)
        plt.yticks(tick_marks, target_names)

    if labels:
        for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
            if normalize:
                plt.text(j, i, "{:0.4f}".format(cm[i, j]),
                         horizontalalignment="center",
                         color="white" if cm[i, j] > thresh else "black")
            else:
                plt.text(j, i, "{:,}".format(cm[i, j]),
                         horizontalalignment="center",
                         color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label\naccuracy={:0.4f};\
                         misclass={:0.4f}'.format(accuracy, misclass))
    plt.show()
from sklearn.metrics import confusion_matrix

model.eval()
ylabel = []
ypred_label = []

for batch_idx, (inputs, targets) in enumerate(test_loader):
    inputs, targets = inputs.to(DEVICE), targets.to(DEVICE)
    outputs = model(inputs)
    _, predicted = outputs.max(1)
    ylabel = np.concatenate((ylabel, targets.cpu().numpy()))
    ypred_label = np.concatenate((ypred_label, predicted.cpu().numpy()))
    
plot_confusion_matrix(cnf_matrix,  target_names=labels_map.values(),  title='Confusion matrix, trained by AlexNet')