본문 바로가기

Python/컴퓨터 비전

Python(55)- 이진 분류 모델, VGG19 모델

*이 글을 읽기전에 작성자 개인의견이 있으니, 다른 블로그와 교차로 읽는것을 권장합니다.*

1. Classification

  • 분류는 기계 학습과 통계학에서 시스템이 일련의 특성을 기반으로 미리 정의된 여러 범주 또는 클래스 중 하나에 주어진 입력을 할당하도록 훈련되는 과정
  • 입력 기능과 클래스 레이블 사이의 학습된 관계를 기반으로 샘플의 클래스 레이블을 예측하는 것
  • Binary Classification
    • 이진 분류: 데이터 요소를 두 클래스 중 하나로 분류
    • 질병 vs 질병이 아님
  • Multiclass Classification
    • 다중 클래스 분류: 데이터 요소를 여러 클래스 중 하나로 분류
    • 고양이, 강아지, 코끼리 ...
  • Multi-lable Classification
    • 다중 레이블 분류: 단일 데이터 요소가 여러 클래스에 속할 수 있음
    • 강아지 - 포유동일, 길들어진 동물, 잡식

2. Classification의 변천사

https://blog.naver.com/chiwoojang/223131248090

 

Classification 모델 변천사

금융 알고리즘 투자전략 Lab

blog.naver.com


3. Classification 실습

!pip install matplotlib
!pip install torch
!pip install torchvision
!pip install opencv-python
!pip install interact

import os
import cv2
import copy
import matplotlib.pyplot as plt
import torch
import warnings
warnings.filterwarnings('ignore')
from ipywidgets import interact
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, models
from torch import nn
# data_dir: Covid19-dataset/train
# sub_dir: Normal, Covid, Viral Pneumonia
def list_image_file(data_dir, sub_dir):
    image_format = ['jpeg', 'jpg', 'png']
    image_files = []
    image_dir = os.path.join(data_dir, sub_dir) # Covid19-dataset/train/Normal
    for file_path in os.listdir(image_dir):
        if file_path.split('.')[-1] in image_format: # Covid19-dataset/train/Normal/01.jpeg
            image_files.append(os.path.join(sub_dir, file_path))
    return image_files
data_dir = 'Covid19-dataset/train'
normals_list = list_image_file(data_dir, 'Normal')
covids_list = list_image_file(data_dir, 'Covid')
pneumonias_list = list_image_file(data_dir, 'Viral Pneumonia')
normals_list

print(len(normals_list))
print(len(covids_list))
print(len(pneumonias_list))

def get_RGB_image(data_dir, file_name):
    image_file = os.path.join(data_dir, file_name)
    image = cv2.imread(image_file)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    return image
min_num_files = min(len(normals_list), len(covids_list), len(pneumonias_list))
min_num_files

@interact(index=(0, min_num_files-1))
def show_samples(index=0):
    normal_image = get_RGB_image(data_dir, normals_list[index])
    covid_image = get_RGB_image(data_dir, covids_list[index])
    pneumonia_image = get_RGB_image(data_dir, pneumonias_list[index])
    
    plt.figure(figsize=(12, 8))
    plt.subplot(131)
    plt.title('Normal')
    plt.imshow(normal_image)
    plt.subplot(132)
    plt.title('Covid')
    plt.imshow(covid_image)
    plt.subplot(133)
    plt.title('Pneumonia')
    plt.imshow(pneumonia_image)
    plt.tight_layout()
train_data_dir = 'Covid19-dataset/train'
class_list = ['Normal', 'Covid', 'Viral Pneumonia']
class Chest_dataset(Dataset):
    def __init__(self, data_dir, transform=None):
        self.data_dir = data_dir
        normals = list_image_file(data_dir, 'Normal')
        covids = list_image_file(data_dir, 'Covid')
        pneumonias = list_image_file(data_dir, 'Viral Pneumonia')
        self.files_path = normals + covids + pneumonias
        self.transform = transform

    def __len__(self):
        return len(self.files_path)
    
    def __getitem__(self, index):
        image_file = os.path.join(self.data_dir, self.files_path[index])
        image = cv2.imread(image_file)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # Covid19-dataset/train/Normal/01.jpeg
        # ['Covid19-dataset', 'train', 'Normal', '01.jpeg']
        target = class_list.index(self.files_path[index].split(os.sep)[-2]) # train 뽑기
        if self.transform:
            image = self.transform(image)
            target = torch.Tensor([target]).long()
        return {'image':image, 'target':target}
dset = Chest_dataset(train_data_dir)
len(dset)

dset[100]

index = 100
plt.title(class_list[dset[index]['target']])
plt.imshow(dset[index]['image'])

transformer = transforms.Compose([
    transforms.ToTensor(),
    transforms.Resize((224, 224)),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
train_dset = Chest_dataset(train_data_dir, transformer)
index = 100
image = train_dset[index]['image']
label = train_dset[index]['target']
print(image.shape, label)

def build_dataloader(train_data_dir, val_data_dir):
    dataloaders = {}
    train_dset = Chest_dataset(train_data_dir, transformer)
    dataloaders['train'] = DataLoader(train_dset, batch_size=4, shuffle=True, drop_last = True)
    val_dset = Chest_dataset(val_data_dir, transformer)
    dataloaders['val'] = DataLoader(val_dset, batch_size=1, shuffle=False, drop_last=False)
    return dataloaders
train_data_dir = 'Covid19-dataset/train'
val_data_dir = 'Covid19-dataset/test'
dataloader = build_dataloader(train_data_dir, val_data_dir)
for i, d in enumerate(dataloader['train']):
    print(i, d)
    if i == 0: # 한바퀴만 리턴
        break

d['image'].shape

d['target'].shape


4. VGG19(Classification) 모델 불러오기

  • VGG는 Visual Geometry Group의 약자
  • 다중 레이어가 있는 표준 심층 CNN 아키텍처

VGG19의 특징:

  1. 층 수: VGG19는 19개의 학습 가능한 층을 가진 심층 신경망. 여기에는 16개의 합성곱(Convolutional) 레이어와 3개의 완전 연결(Fully Connected) 레이어가 포함.
  2. 구조:
    • 컨볼루션 레이어: VGG19는 작은 3x3 필터를 사용하는 여러 개의 컨볼루션 레이어를 연속으로 쌓아 깊이를 더하는 특징이 있습니다. 이 작은 필터를 여러 번 쌓는 방식으로, 모델은 더 깊은 수준의 특징을 학습할 수 있습니다.
    • 풀링 레이어: 2x2 크기의 맥스 풀링(Max Pooling) 레이어를 사용공간적 크기를 절반으로 줄입니다.
      • max pooling: 합성곱 신경망에서 입력 feature map의 차원을 줄여서 계산량을 줄이는 압축 방법
    • 완전 연결 레이어: 네트워크의 끝에는 3개의 완전 연결 레이어가 있으며, 마지막 레이어는 softmax 함수와 함께 사용되어 이미지의 클래스 확률을 출력합니다.
  3. 파라미터 수: VGG19는 약 1억 4천만 개의 파라미터를 가지고 있어, 매우 큰 모델입니다. 이 때문에 학습과 추론에 많은 계산 자원이 필요하지만, 매우 높은 성능을 보여줍니다.
  4. 단순한 구조: VGG19의 큰 장점 중 하나는 구조가 단순하다는 것입니다. 작은 필터(3x3)와 풀링(2x2)을 일관되게 사용함으로써 네트워크의 디자인이 직관적이고 이해하기 쉬워졌습니다.
model = models.vgg19(pretrained=True)
model

def build_vgg19_based_model(device_name='cpu'):
    device = torch.device(device_name)
    model = models.vgg19(pretrained=True)
    model.avgpool = nn.AdaptiveAvgPool2d(output_size=(1,1))
    model.classifier = nn.Sequential(
        nn.Flatten(),
        nn.Linear(512,256),
        nn.ReLU(),
        nn.Linear(256, len(class_list)),
        nn.Softmax(dim=1)
    )
    return model.to(device)
model = build_vgg19_based_model(device_name='cpu')
model

loss_func = nn.CrossEntropyLoss(reduction='mean')

optimizer = torch.optim.SGD(model.parameters(), lr=1E-3, momentum=0.9)
@torch.no_grad()
def get_accuracy(image, target, model):
    batch_size = image.shape[0]
    prediction = model(image)
    _, pred_label = torch.max(prediction, dim=1)
    is_correct = (pred_label == target)
    return is_correct.cpu().numpy().sum() / batch_size
def train_one_epoch(dataloaders, model, optimizer, loss_func, device):
    losses = {}
    accuracies = {}

    for tv in ['train', 'val']:
        running_loss = 0.0
        running_correct = 0

        if tv == 'train':
            model.train()
        else:
            model.eval()

        for index, batch in enumerate(dataloaders[tv]):
            image = batch['image'].to(device)
            target = batch['target'].squeeze(dim=1).to(device)

            with torch.set_grad_enabled(tv == 'train'):
                prediction = model(image)
                loss = loss_func(prediction, target)

                if tv == 'train':
                    optimizer.zero_grad()
                    loss.backward()
                    optimizer.step()

            running_loss += loss.item()
            running_correct += get_accuracy(image, target, model)
            
            if tv == 'train':
                if index % 10 == 0:
                    print(f"{index}/{len(dataloaders['train'])} - Running loss: {loss.item()}")
        losses[tv] = running_loss / len(dataloaders[tv])
        accuracies[tv] = running_correct / len(dataloaders[tv])
    return losses, accuracies
def save_best_model(model_state, model_name, save_dir='./trained_model'):
    os.makedirs(save_dir, exist_ok=True)
    torch.save(model_state, os.path.join(save_dir, model_name))
device = torch.device('cpu')
train_data_dir = 'Covid19-dataset/train/'
val_data_dir = 'Covid19-dataset/test/'
dataloaders = build_dataloader(train_data_dir, val_data_dir)
model = build_vgg19_based_model(device_name=device)
loss_func = nn.CrossEntropyLoss(reduction='mean')
optimizer = torch.optim.SGD(model.parameters(), lr=1E-3, momentum=0.9)

-학습-

num_epochs = 10
best_acc = 0.0
train_loss, train_accuracy = [], []
val_loss, val_accuracy = [], []

for epoch in range(num_epochs):
    losses, accuracies = train_one_epoch(dataloaders, model, optimizer, loss_func, device)
    train_loss.append(losses['train'])
    val_loss.append(losses['val'])
    train_accuracy.append(accuracies['train'])
    val_accuracy.append(accuracies['val'])
    print(f"{epoch+1}/{num_epochs} - Train_Loss:{losses['train']}, Val_Loss:{losses['val']}")
    print(f"{epoch+1}/{num_epochs} - Train_Accuracies:{accuracies['train']}, Val_Loss:{accuracies['val']}")

    if(epoch > 2) and (accuracies['val'] > best_acc):
        best_acc = accuracies['val']
        best_model = copy.deepcopy(model.state_dict())
        save_best_model(best_model, f'model_{epoch+1:02d}.pth')

print(f'Best Accuracy: {best_acc}')

plt.figure(figsize=(6, 5))
plt.subplot(211)
plt.plot(train_loss, label='train')
plt.plot(val_loss, label='val')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.grid('on')
plt.legend()
plt.subplot(212)
plt.plot(train_accuracy, label='train')
plt.plot(val_accuracy, label='val')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.grid('on')
plt.legend()
plt.tight_layout()


  • vgg19 모델을 다시 로드
  • 학습된 pth 파일을 적응
  • Covid19-dataset/test 파일로 분류
  • interact에 예측된 label을 표기
ckpt = torch.load('./trained_model/model_06.pth')
model = build_vgg19_based_model()
model.load_state_dict(ckpt)
model.eval()

data_dir = 'Covid19-dataset/test'
test_normals_list = list_image_file(data_dir, 'Normal')
test_covids_list = list_image_file(data_dir, 'Covid')
test_pneumonias_list = list_image_file(data_dir, 'Viral Pneumonia')

class_list = ['Normal', 'Covid', 'Viral Pneumonia']
min_num_files = min(len(test_normals_list), len(test_covids_list), len(test_pneumonias_list))
min_num_files
def preprocess_image(image):
    transformer = transforms.Compose([
        transforms.ToTensor(),
        transforms.Resize((224, 224)),
        transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
    ])
    tensor_image = transformer(image) # C, H, W
    tensor_image = tensor_image.unsqueeze(0) # B, C, H, W
    return tensor_image
def model_predict(image, model):
    tensor_image = preprocess_image(image)
    prediction = model(tensor_image)
    _, pred_label = torch.max(prediction.detach(), dim=1)
    pred_label = pred_label.squeeze(0)
    return pred_label.item()
@interact(index=(0, min_num_files-1))
def show_samples(index=0):
    normal_image = get_RGB_image(data_dir, test_normals_list[index])
    covid_image = get_RGB_image(data_dir, test_covids_list[index])
    pneumonia_image = get_RGB_image(data_dir, test_pneumonias_list[index])
    prediction_1 = model_predict(normal_image, model)
    prediction_2 = model_predict(covid_image, model)
    prediction_3 = model_predict(pneumonia_image, model)
    plt.figure(figsize=(12, 8))
    plt.subplot(131)
    plt.title(f'Normal, Pred:{class_list[prediction_1]}')
    plt.imshow(normal_image)
    plt.subplot(132)
    plt.title(f'Covid, Pred:{class_list[prediction_2]}')
    plt.imshow(covid_image)
    plt.subplot(133)
    plt.title(f'Pneumonia, Pred:{class_list[prediction_3]}')
    plt.imshow(pneumonia_image)
    plt.tight_layout()

toggle로 index별 사진 확인