이미지 분류를 위한 신경망
이미지 분류 : 이미지에 나타나는 객체는 무조건 하나여야 한다. (이러한 가정을 하고 분류를 한다)
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 |