본문 바로가기
프로그램/SKT FLY AI

SKT FLY AI : 22일차

by hsloth 2023. 7. 25.

이미지 분류를 위한 신경망

이미지 분류 : 이미지에 나타나는 객체는 무조건 하나여야 한다. (이러한 가정을 하고 분류를 한다)

LeNet-5


  • 합성곱 신경망이라는 개념을 최초로 개발 한 구조.
import torch
import torchvision
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
from torch.autograd import Variable
from torch import optim
import torch.nn as nn
import torch.nn.functional as F
import os
import cv2
from PIL import Image
from tqdm import tqdm_notebook as tqdm
import random
from matplotlib import pyplot as plt

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 이미지 데이터셋 전처리
class ImageTransform():
    def __init__(self, resize, mean, std):
        self.data_transform = {
            'train': transforms.Compose([
                transforms.RandomResizedCrop(resize, scale=(0.5, 1.0)),  # 쉼표 추가
                transforms.RandomHorizontalFlip(),
                transforms.ToTensor(),
                transforms.Normalize(mean, std)
            ]),
            'val': transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(resize),
                transforms.ToTensor(),
                transforms.Normalize(mean, std)
            ])
        }

    def __call__(self, img, phase):
        return self.data_transform[phase](img)


# 이미지 데이터셋을 불러운 후 훈련, 검증, 테스트 분리

cat_directory = r'./data/dogs-vs-cats/Cat/'
dog_directory = r'./data/dogs-vs-cats/Dog/'

# os마다 경로가 다르므로 다음과 같이 설정
cat_images_filepaths = sorted([os.path.join(cat_directory, f) for f in os.listdir(cat_directory)])
dog_images_filepaths = sorted([os.path.join(dog_directory, f) for f in os.listdir(dog_directory)])  # 변수명 오타 수정

images_filepaths = [*cat_images_filepaths, *dog_images_filepaths]  # 변수명 오타 수정
correct_images_filepaths = [i for i in images_filepaths if cv2.imread(i) is not None]

random.seed(42)
random.shuffle(correct_images_filepaths)
train_images_filepaths = correct_images_filepaths[:400]
val_images_filepaths = correct_images_filepaths[400:-10]
test_images_filepaths = correct_images_filepaths[-10:]
print(len(train_images_filepaths), len(val_images_filepaths), len(test_images_filepaths))

# 테스트 데이터셋 이미지 확인 함수
def display_image_grid(images_filepaths, predicted_labels=(), cols=5):
    rows = len(images_filepaths) // cols
    figure, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(12, 6))
    for i, image_filepath in enumerate(images_filepaths):
        image = cv2.imread(image_filepath)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 변수명 오타 수정
        true_label = os.path.normpath(image_filepath).split(os.sep)[-2]
        predicted_label = predicted_labels[i] if predicted_labels else true_label
        color = "green" if true_label == predicted_label else "red"
        ax.ravel()[i].imshow(image)  # 개별 이미지 출력
        ax.ravel()[i].set_title(predicted_label, color=color)  # predicted_label을 타이틀로 사용
        ax.ravel()[i].set_axis_off()  # 이미지의 축 제거
    plt.tight_layout()  # 이미지의 여백 조정
    plt.show()

# 테스트 데이터셋 이미지를 출력
display_image_grid(test_images_filepaths)

# 이미지 데이터셋 클래스 정의
class DogvsCatDataset(Dataset):
    def __init__(self, file_list, transform=None, phase='train'):
        self.file_list = file_list
        self.transform = transform
        self.phase = phase

    def __len__(self):
        return len(self.file_list)

    def __getitem__(self, idx):  # 데이터셋에서 데이터를 가져오는 부분으로 결과는 텐서 형태가 된다.
        img_path = self.file_list[idx]
        img = Image.open(img_path)  # img_path위치에서 이미지 데이터들을 가져온다.
        img_transformed = self.transform(img, self.phase)  # 이미지에 train 전처리를 적용
        label = img_path.split('/')[-1].split('.')[0]
        if label == 'dog':
            label = 1
        elif label == 'cat':
            label = 0
        return img_transformed, label

# 이미지 데이터 셋 정의
size = 224
mean = (0.485, 0.456, 0.406)
std = (0.229, 0.224, 0.225)

train_dataset = DogvsCatDataset(train_images_filepaths, transform=ImageTransform(size, mean, std), phase='train')
val_dataset = DogvsCatDataset(val_images_filepaths, transform=ImageTransform(size, mean, std), phase='val')

index = 0
print(train_dataset.__getitem__(index)[0].size())  # 훈련 데이터의 크기 출력
print(train_dataset.__getitem__(index)[1])

# 데이터로더 정의
batch_size = 32  # batch_size 변수를 설정해주어야 합니다.
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

batch_iterator = iter(train_dataloader)
inputs, label = next(batch_iterator)
print(inputs.size())
print(label)

# 모델의 네트워크 클래스
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()

        # 2d 합성곱층 적용. 이때 입력 형태는 (3, 244, 244). 출력 형태는 (weight-kernel_size+1)/stride에 따라 (16, 220, 220)
        self.cnn1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1, padding=0)
        self.relu1 = nn.ReLU()
        self.maxpool1 = nn.MaxPool2d(kernel_size=2)  # 최대 풀링이 적용. 출력형태는 (16, 110, 110)
        self.cnn2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1, padding=0)  # 출력 형태 (32, 106, 106)
        self.relu2 = nn.ReLU()
        self.maxpool2 = nn.MaxPool2d(kernel_size=2)  # 최대 풀링이 적용되며 출력 형태는 (32, 53, 53)

        self.fc1 = nn.Linear(32*53*53, 512)  # 변수명 오타 수정
        self.relu5 = nn.ReLU()  # 변수명 오타 수정
        self.fc2 = nn.Linear(512, 2)  # 변수명 오타 수정
        self.output = nn.Softmax(dim=1)

    def forward(self, x):
        out = self.cnn1(x)
        out = self.relu1(out)
        out = self.maxpool1(out)
        out = self.cnn2(out)
        out = self.relu2(out)
        out = self.maxpool2(out)
        out = out.view(out.size(0), -1)
        out = self.fc1(out)
        out = self.relu5(out)  # 변수명 오타 수정
        out = self.fc2(out)
        out = self.output(out)
        return out


# 모델 객체 생성
model = LeNet()
print(model)

# torchsummary 라이브러리를 이용한 모델의 네트워크 구조 확인
from torchsummary import summary
summary(model, input_size=(3, 224, 224))

# 학습 가능한 파라미터 수 확인
def count_parameters(model):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

print(f'The model has {count_parameters(model):,} trainable parameters')

# 옵티마이저와 손실 함수 정의
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
criterion = nn.CrossEntropyLoss()

# 모델의 파라미터와 손실 함수를 CPU에 할당
model = model.to(device)
criterion = criterion.to(device)

# 모델 학습 함수 정의
def train_model(model, dataloader_dict, criterion, optimizer, num_epoch):
    since = time.time()
    best_acc = 0.0

    for epoch in range(num_epoch):
        print('Epoch {}/{}'.format(epoch+1, num_epoch))
        print('-'*20)

        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()
            else:
                model.eval()

            epoch_loss = 0.0
            epoch_corrects = 0

            for inputs, labels in tqdm(dataloader_dict[phase]):
                inputs = inputs.to(device)
                labels = labels.to(device)
                optimizer.zero_grad()  # 역전파 단계를 실행하기 전에 기울기를 0으로 초기화

                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)  # 손실 함수를 이용한 오차 계산

                    if phase == 'train':
                        loss.backward()  # 모델의 학습 가능한 모든 파라미터에 대해 기울기를 생산
                        optimizer.step()  # optimizer의 step 함수를 호출하면 파라미터를 갱신

                    epoch_loss += loss.item() * inputs.size(0)
                    epoch_corrects += torch.sum(preds == labels.data)

            epoch_loss = epoch_loss / len(dataloader_dict[phase].dataset)  # 최종 오차 계산(오차를 데이터셋의 길이(개수)로 나누어서 계산)
            epoch_acc = epoch_corrects.double() / len(dataloader_dict[phase].dataset)  # 최종 정확도

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            if phase == 'val' and epoch_acc > best_acc:  # 검증 데이터셋에 대한 가장 최적의 정확도를 저장
                best_acc = epoch_acc
                best_model_wts = model.state_dict()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))
    return model, best_model_wts  # best_model_wts를 반환해야 합니다.

# 모델 학습
num_epoch = 10
model, best_model_wts = train_model(model, dataloader_dict={'train': train_dataloader, 'val': val_dataloader},
                                    criterion=criterion, optimizer=optimizer, num_epoch=num_epoch)

# 모델 테스트를 위한 함수 정의
import pandas as pd

id_list = []
pred_list = []
_id = 0

with torch.no_grad():  # 역전파 중 텐서들에 대한 변화도를 계산할 필요가 없음을 나타내는 것으로, 훈련 데이터셋의 모델 학습과 가장 큰 차이점이다.
    for test_path in tqdm(test_images_filepaths):
        img = Image.open(test_path)
        _id = test_path.split('/')[-1].split('.')[0]  # 확장자 제거
        transform = ImageTransform(size, mean, std)
        img = transform(img, phase='val')  # 테스트 데이터셋 전처리 적용
        img = img.unsqueeze(0)
        img = img.to(device)

        model.eval()
        outputs = model(img)
        preds = F.softmax(outputs, dim=1)[:, 1].tolist()
        id_list.append(_id)
        pred_list.append(preds[0])

res = pd.DataFrame({
    'id': id_list,
    'label': pred_list
})

res.sort_values(by='id', inplace=True)
res.reset_index(drop=True, inplace=True)

res.to_csv('./data/LeNet.csv', index=False)  # 데이터 프레임을 CSV파일로 저장

# 테스트 데이터셋의 예측 결과 호출
res.head(10)

# 테스트 데이터셋 이미지를 출력하기 위한 함수 정의
class_ = {0: 'cat', 1: 'dog'}
def display_image_grid(images_filepaths, predicted_labels=(), cols=5):
    rows = len(images_filepaths) // cols
    figure, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(12, 6))
    for i, image_filepath in enumerate(images_filepaths):
        image = cv2.imread(image_filepath)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        a = random.choice(res['id'].values)  # 데이터 프레임의 id라는 칼럼에서 임의로 데이터를 가져옵니다.
        label = res.loc[res['id'] == a, 'label'].values[0]
        if label > 0.5:  # 레이블 값이 0.5보다 크다면 개
            label = 1
        else:
            label = 0
        ax.ravel()[i].imshow(image)
        ax.ravel()[i].set_title(class_[label])
        ax.ravel()[i].set_axis_off()
    plt.tight_layout()
    plt.show()

# 테스트 데이터셋 예측 결과 이미지 출력
display_image_grid(test_images_filepaths)

AlexNet


  • AlexNet은 합성곱층 총 5개와 완전연결층 3개로 구성되어 있으며, 맨 마지막 완전연결층은 카테고리 1000개를 분류하기 위해 softmax함수를 사용한다.
  • 전체적으로 보면 GPU 두 개를 기반으로 한 병렬 구조인 점을 제외하면 LeNet-5와 크게 다르지 않다.

import torch
import torchvision
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
from torch.autograd import Variable
from torch import optim
import torch.nn as nn
import torch.nn.functional as F
import os
import cv2
from PIL import Image
from tqdm import tqdm_notebook as tqdm
import random
from matplotlib import pyplot as plt

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 이미지 데이터셋 전처리
class ImageTransform():
    def __init__(self, resize, mean, std):
        self.data_transform = {
            'train': transforms.Compose([
                transforms.RandomResizedCrop(resize, scale=(0.5, 1.0)),  # 쉼표 추가
                transforms.RandomHorizontalFlip(),
                transforms.ToTensor(),
                transforms.Normalize(mean, std)
            ]),
            'val': transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(resize),
                transforms.ToTensor(),
                transforms.Normalize(mean, std)
            ])
        }

    def __call__(self, img, phase):
        return self.data_transform[phase](img)

# 이미지 데이터셋을 불러운 후 훈련, 검증, 테스트 분리

cat_directory = r'./data/dogs-vs-cats/Cat/'
dog_directory = r'./data/dogs-vs-cats/Dog/'

# os마다 경로가 다르므로 다음과 같이 설정
cat_images_filepaths = sorted([os.path.join(cat_directory, f) for f in os.listdir(cat_directory)])
dog_images_filepaths = sorted([os.path.join(dog_directory, f) for f in os.listdir(dog_directory)])  # 변수명 오타 수정

images_filepaths = [*cat_images_filepaths, *dog_images_filepaths]  # 변수명 오타 수정
correct_images_filepaths = [i for i in images_filepaths if cv2.imread(i) is not None]

random.seed(42)
random.shuffle(correct_images_filepaths)
train_images_filepaths = correct_images_filepaths[:400]
val_images_filepaths = correct_images_filepaths[400:-10]
test_images_filepaths = correct_images_filepaths[-10:]
print(len(train_images_filepaths), len(val_images_filepaths), len(test_images_filepaths))

# 커스텀 데이터셋 정의
class DogvsCatDataset(Dataset):
    def __init__(self, file_list, transform=None, phase='train'):
        self.file_list = file_list
        self.transform = transform
        self.phase = phase

    def __len__(self):
        return len(self.file_list)

    def __getitem__(self, idx):  # 데이터셋에서 데이터를 가져오는 부분으로 결과는 텐서 형태가 된다.
        img_path = self.file_list[idx]
        img = Image.open(img_path)  # img_path위치에서 이미지 데이터들을 가져온다.
        img_transformed = self.transform(img, self.phase)  # 이미지에 train 전처리를 적용
        label = img_path.split('/')[-1].split('.')[0]
        if label == 'dog':
            label = 1
        elif label == 'cat':
            label = 0
        return img_transformed, label

size = 224
mean = (0.485, 0.456, 0.406)
std = (0.229, 0.224, 0.225)

train_dataset = DogvsCatDataset(train_images_filepaths, transform=ImageTransform(size, mean, std), phase='train')
val_dataset = DogvsCatDataset(val_images_filepaths, transform=ImageTransform(size, mean, std), phase='val')

index = 0
print(train_dataset.__getitem__(index)[0].size()) 
print(train_dataset.__getitem__(index)[1])

batch_size = 32 
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

batch_iterator = iter(train_dataloader)
inputs, label = next(batch_iterator)
print(inputs.size())
print(label)

import torch
import torch.nn as nn
from torchsummary import summary

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

        self.layers1 = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=2), # 224 -> 54
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2),                               # 54 -> 27
        )

        self.layers2 = nn.Sequential(
            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2), # 27 -> 24
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2),                              # 24 -> 12
        )

        self.layers3 = nn.Sequential(
            nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),# 12 -> 11
            nn.ReLU(),
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),# 11 -> 10
            nn.ReLU(),
            nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1),# 10 -> 9
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2)                                 # 9 -> 4
        )

        self.layers4 = nn.Sequential(
            nn.Linear(12544, 4096),
            nn.ReLU(),
            nn.Linear(4096,4096),
            nn.ReLU(),
            nn.Linear(4096,2),
            nn.Softmax(dim=1),
        )

    def forward(self, x):
        out = self.layers1(x)
        out = self.layers2(out)
        out = self.layers3(out)
        out = out.view(out.size(0),-1)
        print(out.shape)

        out = self.layers4(out)
        return out
model = AlexNet()
summary(model, input_size=(3,256,256))

VGGNet


  • 합성곱층의 파라미터 수를 줄이고 훈련 시간을 개선
  • 네트워크를 깊게 만드는 것이 성능에 어떤 영향을 미치는지 확인하고자 나온 것이 VGG라고 한다.
import copy
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data as data
import torchvision
import torchvision.transforms as transforms
import torchvision.datasets as Datasets

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# VGG 모델 정의
class VGG(nn.Module):
    def __init__(self, features, output_dim):
        super().__init__()
        self.features = features
        self.avgpool = nn.AdaptiveAvgPool2d(7)
        self.classifier = nn.Sequential(
            nn.Linear(512*7*7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),
            nn.Linear(4096, output_dim)
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        h = x.view(x.shape[0], -1)
        x = self.classifier(h)
        return x, h

# 모델 유형 정의
vgg11_config = [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M']

vgg13_config = [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M']

vgg16_config = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 
                512, 'M']

vgg19_config = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 
                512, 512, 512, 512, 'M']

# VGG 계층 정의
def get_vgg_layers(config, batch_norm):
    layers = []
    in_channels = 3

    for c in config: # vgg11_config값들을 가져옵니다.
        assert c == 'M' or isinstance(c, int)
        if c == 'M': # 불러온 값이 M이면 최대 풀링 적용
            layers += [nn.MaxPool2d(kernel_size=2)]
        else: # 불러온 값이 숫자면 합성곱 적용
            conv2d = nn.Conv2d(in_channels, c, kernel_size=3, padding=1)
            if batch_norm: # 배치 정규화를 적용할지에 대한 코드
                layers += [conv2d, nn.BatchNorm2d(c), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)] # 배치 정규화가 적용될 경우 배치 정규화 + ReLU적용
            in_channels = c

    return nn.Sequential(*layers) # 네트워크의 모든 계층 반환

# 모델 계층 생성
vgg11_layers = get_vgg_layers(vgg11_config, batch_norm=True)

# VGG11 계층 확인
print(vgg11_layers)

# VGG11 전체에 대한 네트워크
OTPUT_DIM = 2 # 개와 고양이 두 개의 클래스 사용
model = VGG(vgg11_layers, OUTPUT_DIM)
print(model)

# 사전 훈련된 모델 사용
import torchvision.models as models
pretrained_model = models.vgg11_bn(pretrained=True)
print(pretrained_model)
pretrained_model = models.vgg11_bn(pretrained=True)

# 나만의 VGG정의
My_Vgg = [64, 64, 64, 'M', 128, 128, 128, 'M', 256, 256, 256, 'M']

# 이미지 데이터 전처리
train_transforms = transforms.Compose([
        transforms.Resize((256, 256)),
        transforms.RandomRotation(5),
        transforms.RandomHorizontalFlip(0.5),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.465, 0.406], std=[0.229, 0.224, 0.225])])

test_transforms = transforms.Compose([
        transforms.Resize((256,256)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])

# ImageFolder를 이용하여 데이터셋 불러오기
train_path = '../chap06/data/catanddog/train'
test_path = '../chap06/data/catanddog/test'

train_dataset = torchvision.datasets.ImageFolder(
    train_path,
    transform=train_transforms
)

test_dataset = torchvision.datasets.ImageFolder(
    test_path,
    transform=test_transforms
)

print(len(train_dataset)), print(len(test_dataset))

# 훈련과 검증 데이터 분할
VALID_RATIO = 0.9
n_train_examples = int(len(train_dataset) * VALID_RATIO)
n_valid_examples = len(train_dataset) - n_train_examples

train_data, valid_data = data.random_split(train_dataset, [n_train_examples, n_valid_examples])

# 검증 데이터 전처리
valid_data = copy.deepcopy(valid_data)
valid_data.dataset.transform = test_transforms

# 훈련, 검증, 테스트 데이터셋 수 확인
print(f'Number of training examples: {len(train_data)}')
print(f'Number of validation examples: {len(valid_data)}')
print(f'Number of testing examples: {len(test_dataset)}')

# 메모리로 데이터 불러오기
BATCH_SIZE=128
train_iterator = data.DataLoader(train_data, shuffle=True, batch_size=BATCH_SIZE)
valid_iterator = data.DataLoader(valid_data, batch_size=BATCH_SIZE)
test_iterator = data.DataLoader(test_dataset, batch_size=BATCH_SIZE)

# 옵티마이저와 손실 함수 정의
optimizer = potim.Adam(model.parameters(), lr=1e-7)
criterion = nn.CrossEntropyLoss()

model = model.to(device)
criterion = criterion.to(device)

# 모델 정확도 측정 함수
def calculate_accuracy(y_pred, y):
    top_pred = y_pred.argmax(1, keepdim=True)
    correct = top_pred.eq(y.view_as(top_pred)).sum()
    acc = correct.float() / y.shape[0]
    return acc

# 모델 학습 함수 정의
def train(model, iterator, optimizer, criterion, device):
    epoch_loss = 0
    epoch_acc = 0

    for (x, y) in iterator:
        x = x.to(device)
        y = y.to(device)

        optimizer.zero_grad()
        y_pred, _ = model(x)
        loss = criterion(y_pred, y)
        acc = calculate_accuracy(y_pred, y)
        loss.backward()
        optimizer.step()

        epoch_loss += loss.item()
        epoch_acc += acc.item()

    return epoch_loss /len(iterator), epoch_acc /len(iterator)

# 모델 성능 측정 함수
def dvaluate(model, iterator, criterion, device):
    epoch_loss = 0
    epoch_acc = 0 

    model.eval()
    with torch.no_grad():
        for (x, y) in iterator:
            x = x.to(device)
            y = y.to(devie)
            y_pred, _ = model(x)
            loss = criterion(y_pred, y)
            acc = calculate_accuracy(y_pred, y)
            epoch_loss += loss.item()
            epoch_acc += acc.item()

    return epoch_loss / len(iterator), epoch_acc / len(iterator)

# 학습시간 측정 함수
def epoch_time(start_time, end_time):
    elapsed_time = end_time - start_time
    elapsed_mins = int(elapsed_time / 60)
    elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
    return elapsed_mins, elapsed_secs

# 모델 학습
EPOCHS = 5
best_valid_loss = float('inf')
for epoch in range(EPOCHS):
    start_time = time.monotonic()
    train_loss, train_acc = train(model, train_iterator, optimizer, criterion, device)
    valid_loss, valid_acc = evaluate(model, valid_iterator, criterion, device)

    if valid_loss < best_valid_loss: # valid_loss가 가장 작은 값을 구하고 그 상태의 모델을 VGG-model.pt이름으로 저장
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), '../chap06/data/VGG-model.pt')

    end_time = time.monotonic()
    epoch_mins, epoch_secs = epoch_time(start_time, end_time) # 모델 훈련에 대한 시작과 종료 시간을 지정

    print(f'Epoch: {epoch+1:02} | Epoch Time: {epoch_mins}m {epoch_secs}s')
    print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
    print(f'\t Valid. Loss: {valid_loss:.3f} | Valid. Acc: {valid_acc*100:.2f}%')

# 테스트 데이터셋을 이용한 모델 성능 측정
model.load_state_dict(torch.load('../chap06/data/VGG-model.pt'))
test_loss, test_acc = evaluate(model, test_iterator, criterion, device)
print(f'Test Loss: {test_loss:.3f} | Test Acc: {test_acc*100:.2f}%')

# 테스트 데이터셋을 이용한 모델의 예측 확인 함수
def get_predictions(model, iterator):
    model.eval()
    images = []
    labels = []
    probs = []

    with torch.no_grad():
        for (x, y) in iterator:
            x = x.to(device)
            y_pred, _ = mocdel(x)
            y_prob = F.softmax(y_pred, dim=-1)
            top_pred = y_prob.argmax(1, keepdim=True)
            images.append(x.cpu())
            labels.append(y.cpu())
            probs.append(y_prob.cpu())

    images = torch.cat(images, dim=0)
    labels = torch.cat(labels, dim=0)
    probs = torch.cat(probs, dim=0)
    return images, labels, probs

# 예측 중에서 정확하게 예측한 것을 추출
images, labels, probs = get_predictions(model, test_iterator)
pred_labels = torch.argmax(probs, 1)
corrects = torch.eq(labels, pred_labels) # 예측과 정답이 같은지 비교
correct_examples = []

for images, label, prob, correct in zip(images, labels, probs, corrects):
    if correct:
        correct_examples.append((image, label, prob))

correct_examples.sort(reverse=True, key=lambda x: torch.max(x[2], dim=0).values)

# 이미지 출력을 위한 전처리
def normalize_image(image):
    image_min = image.min()
    image_max = image.max()
    image.clamp_(min=image_min, max=image_max) # torch.clamp는 주어진 최소(min), 최대(max)의 범주에 이미지가 위치하도록 한다.
    image.add_(-image_min).div_(image_max-image_min+1e-5)
    return image

# 모델이 정확하게 예측한 이미지 출력 함수
def plot_most_correct(correct, classes, n_images, normalize=True):
    rows = int(np.sqrt(n_images)) # np.sqrt는 제곱근을 계산(0.5를 거듭제곱)
    cols = int(np.sqrt(n_images))
    fig = plt.figure(figsize=(25,20))
    for i in range(rows*cols):
        ax = fig.add_subplot(rows, cols, i+1) # 출력하려는 그래프 개수만큼 subplot을 만든다.
        images, true_label, probs = correct[i]
        image = image.permute(1, 2, 0)
        true_prob = probs[true_label]
        correct_prob, correct_label = torch.max(probs, dim=0)
        true_class = classes[true_label]
        correct_class = classes[correct_label]


        in normalize: # 본래 이미지대로 출력하기 위해 normalize_image함수 호출
            image = normalize_image(image)

        ax.imshow(image.cpu().numpy())
        ax.set_title(f'true label: {true_class} ({true_prob:.3f})\n' f'pred label: {correct_class} ({correct_prob:.3f})')
        ax.axis('off')

    fig.subplots_adjust(hspace=0.4)

classes = test_dataset.classes
N_IMAGES = 5
plot_most_correct(correct_examples, classes, N_IMAGES)

GoogLeNet


  • 주어진 하드웨어 자원을 최대한 효율적으로 이용하면서 학습능력은 극대화할 수 있는 깊고 넓은 신경망
  • 깊고 넓은 신경망을 위해 인셉션 모듈을 추가
  • 인셉션 모듈의 네 가지 연산
    • 1x1 합성곱
    • 1x1 합성곱 + 3x3 합성곱
    • 1x1 합성곱 + 5x5 합성곱
    • 3x3 최대 풀링 + 1x1 합성곱
    • 3x3 최대 풀링은 입력과 출력의 높이, 너비가 같아야 하므로 패딩을 추가해야한다.
  • 빽빽하게 연결된 신경망 대신 관련성이 높은 노드끼리만 연결하는 방법 사용 (희소 연결, Sparse connectivity)

ResNet


  • ResNet의 핵심은 깊어진 신경망을 효과적으로 학습하기 위한 방법으로 Residual 개념을 고안한 것
  • 신경망의 깊이가 깊어질수록 딥러닝 성능이 좋아질 것 같지만, 그렇지 않다. 오히려 일정한 단계에 다다르면 성능이 안좋아진다.
  • ResNet은 위의 문제를 해결하기 위해 Residual block을 도입했다.
  • Residual block은 기울기가 잘 전파될 수 있도록 일종의 숏컷(skip connection)을 만들어 준다.
  • 블록 : 합성곱층을 하나의 블록으로 묶은 것
  • 레지듀얼 블록 : 연관이 되는 블록끼리 묶인 계층 (즉, 연관된 블록의 모음)
  • 계층의 깊이가 깊어질수록 파라미터의 수가 무제한으로 커지는데, 이를 해결하기 위해 병목 블록이라는 것이 존재한다.
  • identity mapping : 입력x가 어떤 함수를 통과하더라도 다시 x라는 형태로 출력되게 한다.
  • 다운샘플링 : 특징맵의 크기를 줄이기 위한 것. 풀링과 같은 역할을 한다.
  • 아이덴티티 블록 : 입력과 출력의 차원이 같은 것
  • 프로젝션 숏컷(혹은 합성곱 블럭) : 입력 및 출력 차원이 동일하지 않고 입력의 차원을 출력에 맞추어 변경해야 하는 것

'프로그램 > SKT FLY AI' 카테고리의 다른 글

SKT FLY AI : 25일차  (0) 2023.07.28
SKT FLY AI : 24일차  (0) 2023.07.27
SKT FLY AI : 21일차 - 딥러닝 파이토치 시작  (0) 2023.07.24
SKT FLY AI : 20일차  (0) 2023.07.22
SKT FLY AI : 19일차  (0) 2023.07.21