1. 분류 ( Classification )
* 분류는 기계 학습과 통계학에서 시스템이 일련의 특성을 기반으로 미리 정의된 여러 범주 또는 클래스 중 하나에 주어진 입력을 할당하도록 훈련되는 과정
* 입력 기능과 클래스 레이블 사이의 학습된 관계를 기반으로 샘플의 클래스 레이블을 예측하는 것
1. Binary Classification |
* 이진 분류: 데이터 요소를 두 클래스 중 하나로 분류 * 질병 vs 질병이 아님 |
2. Multiclass Classification |
* 다중 클래스 분류: 데이터 요소를 여러 클래스 중 하나로 분류 * 고양이, 강아지, 코끼리 ... |
3. Multi-label Classification |
* 다중 레이블 분류 : 단일 데이터 요소가 여러 클래스에 속할 수 있음 * 강아지- 포유동일, 길들여진 동물, 잡식 |
4. Classification 변천사 |
![]() |
◼ 설치
! pip install opencv-python
! pip install matplotlib
! pip install torch
! pip install torchvision
! pip install interact
|
◼ import
import numpy as np import os 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")
|
◼ 이미지 파일 개수
print(len(normals_list))
print(len(covids_list))
print(len(pneumonias_list))
|
70 111 70 |
◼ 이미지를 읽어와 RGB 형식으로 변환하고, 'Normal', 'Covid', 'Viral Pneumonia' 이미지를 표시
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) # BGR 형식을 RGB 형식으로 변환
return image
# 'Normal', 'Covid', 'Viral Pneumonia' 디렉토리의 이미지 파일 개수 중 최소값 계산
min_num_files = min(len(normal_list), len(covids_list), len(pneumonia_list))
# 인터랙티브 슬라이더를 사용하여 이미지 샘플을 보여주는 함수
@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() # 서브플롯 간의 간격을 자동으로 조정하여 레이아웃 정리
|
![]() ![]() ![]() |
@ 실행이 안된다면 가상환경 만들기
더보기
day6_venv 가상환경 만들기python -m venv day6_venv
cd day6_venv
cd Scripts
activatepython -m ipykernel install --user --name day6_venv --display-name day6(주피터 노트북에서 표시할 이름)
◼ 커스텀 데이터셋 클래스 만들기(1)
# 데이터셋의 디렉토리 경로와 클래스 목록 정의
train_data_dir = 'Covid19-dataset/train/' # 훈련 데이터가 저장된 디렉토리 경로
class_list = ['Normal', 'Covid', 'Viral Pneumonia'] # 데이터셋의 클래스 목록
class Chest_dataset(Dataset):
def __init__(self, data_dir, transform=None):
"""
데이터셋의 초기화 함수
:param data_dir: 이미지 데이터가 저장된 디렉토리 경로
:param transform: 이미지에 적용할 변환 (선택 사항)
"""
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: 데이터셋의 이미지 수
"""
return len(self.files_path)
def __getitem__(self, index):
"""
주어진 인덱스에 해당하는 이미지와 레이블을 반환
:param index: 데이터셋에서 이미지의 인덱스
:return: {'image': 이미지 텐서, 'target': 레이블 텐서}
"""
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])
return {'image':image, 'target':target}
|
PyTorch의 Dataset 클래스를 상속하여 Chest_dataset이라는 커스텀 데이터셋 클래스를 정의하고 이미지 데이터를 로드하고, 클래스 레이블을 추출하며, 선택적으로 전처리 변환을 적용할 수 있도록 설계하기 |
◼ Chest_dataset 클래스를 사용하여 데이터셋 객체를 생성
dset = Chest_dataset(train_data_dir)
|
◼ 데이터셋의 이미지수
len(dset)
|
251 |
◼ 데이터셋에서 인덱스 100에 해당하는 데이터 샘플을 반환
dset[100]
|
{'image': array([[[ 4, 4, 4], [ 4, 4, 4], [ 5, 5, 5], ..., [ 1, 1, 1], [ 1, 1, 1], [ 1, 1, 1]], [[ 4, 4, 4], [ 4, 4, 4], [ 5, 5, 5], ..., [ 1, 1, 1], [ 1, 1, 1], [ 1, 1, 1]], [[ 4, 4, 4], [ 4, 4, 4], [ 4, 4, 4], ..., [ 1, 1, 1], [ 1, 1, 1], [ 1, 1, 1]], ..., [[230, 230, 230], [230, 230, 230], [230, 230, 230], ..., [121, 121, 121], [124, 124, 124], [127, 127, 127]], [[230, 230, 230], [230, 230, 230], [230, 230, 230], ..., [121, 121, 121], [122, 122, 122], [125, 125, 125]], [[232, 232, 232], [232, 232, 232], [232, 232, 232], ..., [121, 121, 121], [122, 122, 122], [123, 123, 123]]], dtype=uint8), 'target': 1} |
◼ index = 100으로 지정된 인덱스의 이미지를 표시
index = 100
plt.title(class_list[dset[index]['target']])
plt.imshow(dset[index]['image'])
|
![]() |
◼ 이미지 데이터를 전처리하는 transforms 모듈
# 이미지 전처리를 위한 변환 함수 정의
transformer = transforms.Compose([
transforms.ToTensor(), # 이미지를 텐서로 변환 (값 범위: [0, 1])
transforms.Resize((224, 224)), # 이미지를 224x224 크기로 리사이즈
transforms.Normalize(mean = [0.5, 0.5, 0.5], std = [0.5, 0.5, 0.5]) # 텐서를 정규화
])
|
◼ 커스텀 데이터셋 클래스 만들기(2)_transformer 적용
class Chest_dataset(Dataset):
def __init__(self, data_dir, transform=None):
"""
Chest_dataset 클래스의 초기화 함수
:param data_dir: 이미지 데이터가 저장된 디렉토리 경로
:param transform: 이미지에 적용할 변환 (선택 사항)
"""
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):
"""
주어진 인덱스에 해당하는 이미지와 레이블을 반환하는 함수
:param index: 데이터셋에서 이미지의 인덱스
:return: {'image': 이미지 텐서, 'target': 레이블 텐서}
"""
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])
# 변환이 지정된 경우 적용
if self.transform:
image = self.transform(image) # 이미지에 전처리 변환을 적용
target = torch.Tensor([target]).long() # 레이블을 텐서로 변환 (long 타입)
return {'image':image, 'target':target}
|
◼ Chest_dataset 클래스를 사용하여 데이터셋 객체를 생성
train_dset = Chest_dataset(train_data_dir, transformer)
|
◼ 인덱스 100에 해당하는 데이터 샘플을 가져와서, 이미지와 레이블을 출력하기
index = 100
image = train_ dset[index]['image']
label = train_ dset[index]['target']
print(image.shape, label)
|
torch.Size([3, 224, 224]) tensor([1]) |
◼ Dataloader 만들기
def build_dataloader(train_data_dir, val_data_dir):
"""
훈련 및 검증 데이터셋에 대한 DataLoader 객체를 생성하는 함수.
:param train_data_dir: 훈련 데이터가 저장된 디렉토리의 경로
:param val_data_dir: 검증 데이터가 저장된 디렉토리의 경로
:return: 'train' 및 'val' 키를 포함하는 딕셔너리 형태의 데이터로더
"""
# 데이터로더를 저장할 빈 딕셔너리 초기화
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
|
0 {'image': tensor([[[[-0.9508, -0.9305, -0.9012, ..., -0.3886, -0.2740, -0.7891], [-0.9508, -0.9321, -0.9042, ..., -0.4202, -0.3388, -0.7640], [-0.9514, -0.9333, -0.9059, ..., -0.7780, -0.7377, -0.9323], ..., [-0.9253, -0.9147, -0.8738, ..., -1.0000, -0.9965, -0.9062], [-0.8917, -0.4111, -0.4916, ..., -1.0000, -0.9976, -0.9518], [-0.5959, -0.1704, -0.4112, ..., -1.0000, -1.0000, -0.9999]], [[-0.9508, -0.9305, -0.9012, ..., -0.3886, -0.2740, -0.7891], [-0.9508, -0.9321, -0.9042, ..., -0.4202, -0.3388, -0.7640], [-0.9514, -0.9333, -0.9059, ..., -0.7780, -0.7377, -0.9323], ..., [-0.9253, -0.9147, -0.8738, ..., -1.0000, -0.9965, -0.9062], [-0.8917, -0.4111, -0.4916, ..., -1.0000, -0.9976, -0.9518], [-0.5959, -0.1704, -0.4112, ..., -1.0000, -1.0000, -0.9999]], [[-0.9508, -0.9305, -0.9012, ..., -0.3886, -0.2740, -0.7891], [-0.9508, -0.9321, -0.9042, ..., -0.4202, -0.3388, -0.7640], [-0.9514, -0.9333, -0.9059, ..., -0.7780, -0.7377, -0.9323], ..., [-0.9253, -0.9147, -0.8738, ..., -1.0000, -0.9965, -0.9062], [-0.8917, -0.4111, -0.4916, ..., -1.0000, -0.9976, -0.9518], [-0.5959, -0.1704, -0.4112, ..., -1.0000, -1.0000, -0.9999]]], [[[-0.8873, -0.8757, -0.8885, ..., -0.8007, -0.7912, -0.8016], [-0.8786, -0.8818, -0.8937, ..., -0.8057, -0.7991, -0.8034], [-0.8789, -0.8864, -0.8887, ..., -0.8077, -0.8040, -0.8037], ..., [-0.8967, -0.8926, -0.9065, ..., -0.7752, -0.7772, -0.7866], [-0.8993, -0.8948, -0.9038, ..., -0.7771, -0.7815, -0.7833], [-0.8855, -0.8915, -0.8956, ..., -0.7825, -0.7699, -0.7819]], [[-0.8873, -0.8757, -0.8885, ..., -0.8007, -0.7912, -0.8016], [-0.8786, -0.8818, -0.8937, ..., -0.8057, -0.7991, -0.8034], [-0.8789, -0.8864, -0.8887, ..., -0.8077, -0.8040, -0.8037], ..., [-0.8967, -0.8926, -0.9065, ..., -0.7752, -0.7772, -0.7866], [-0.8993, -0.8948, -0.9038, ..., -0.7771, -0.7815, -0.7833], [-0.8855, -0.8915, -0.8956, ..., -0.7825, -0.7699, -0.7819]], [[-0.8873, -0.8757, -0.8885, ..., -0.8007, -0.7912, -0.8016], [-0.8786, -0.8818, -0.8937, ..., -0.8057, -0.7991, -0.8034], [-0.8789, -0.8864, -0.8887, ..., -0.8077, -0.8040, -0.8037], ..., [-0.8967, -0.8926, -0.9065, ..., -0.7752, -0.7772, -0.7866], [-0.8993, -0.8948, -0.9038, ..., -0.7771, -0.7815, -0.7833], [-0.8855, -0.8915, -0.8956, ..., -0.7825, -0.7699, -0.7819]]], [[[-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], ..., [-0.6367, -0.6177, -0.5638, ..., -0.4194, -0.5029, -0.6054], [-0.6391, -0.6333, -0.5873, ..., -0.4250, -0.5218, -0.6094], [-0.6641, -0.6473, -0.5882, ..., -0.4429, -0.5267, -0.6128]], [[-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], ..., [-0.6367, -0.6177, -0.5638, ..., -0.4194, -0.5029, -0.6054], [-0.6391, -0.6333, -0.5873, ..., -0.4250, -0.5218, -0.6094], [-0.6641, -0.6473, -0.5882, ..., -0.4429, -0.5267, -0.6128]], [[-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], [-1.0000, -1.0000, -1.0000, ..., -1.0000, -1.0000, -1.0000], ..., [-0.6367, -0.6177, -0.5638, ..., -0.4194, -0.5029, -0.6054], [-0.6391, -0.6333, -0.5873, ..., -0.4250, -0.5218, -0.6094], [-0.6641, -0.6473, -0.5882, ..., -0.4429, -0.5267, -0.6128]]], [[[-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9922, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9921, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9920, -0.9899, -0.9832], ..., [-0.9922, -0.9921, -0.9921, ..., -0.3735, -0.4247, -0.4114], [-0.9922, -0.9922, -0.9922, ..., -0.3650, -0.4169, -0.4075], [-0.9929, -0.9929, -0.9929, ..., -0.4184, -0.4691, -0.4698]], [[-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9922, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9921, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9920, -0.9899, -0.9832], ..., [-0.9922, -0.9921, -0.9921, ..., -0.3735, -0.4247, -0.4114], [-0.9922, -0.9922, -0.9922, ..., -0.3650, -0.4169, -0.4075], [-0.9929, -0.9929, -0.9929, ..., -0.4184, -0.4691, -0.4698]], [[-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9922, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9922, -0.9921, -0.9922], [-0.9922, -0.9922, -0.9922, ..., -0.9920, -0.9899, -0.9832], ..., [-0.9922, -0.9921, -0.9921, ..., -0.3735, -0.4247, -0.4114], [-0.9922, -0.9922, -0.9922, ..., -0.3650, -0.4169, -0.4075], [-0.9929, -0.9929, -0.9929, ..., -0.4184, -0.4691, -0.4698]]]]), 'target': tensor([[2], [0], [0], [1]])} |
◼ 데이터 로더(DataLoader)에서 가져온 배치(batch) 딕셔너리 d의 'image' 키에 해당하는 값의 모양(shape)
d['image'].shape
|
torch.Size([4, 3, 224, 224]) |
◼ 데이터 로더에서 가져온 배치 딕셔너리 d의 'target' 키에 해당하는 값의 모양(shape)
d['target'].shape
|
torch.Size([4, 1]) |
2. VGG19(Classification) 모델
- VGG는 Visual Geometry Group의 약자
- 다중 레이어가 있는 표준 심층 CNN 아키텍쳐
◼ VGG19 모델을 로드
model = models.vgg19(pretrained = True)
model
|
VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (17): ReLU(inplace=True) (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (24): ReLU(inplace=True) (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (26): ReLU(inplace=True) (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (31): ReLU(inplace=True) (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (33): ReLU(inplace=True) (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (35): ReLU(inplace=True) (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) ) ) |
◼ 모델 만들기
def build_vgg19_based_model(device_name='cpu'):
"""
VGG19 모델을 기반으로 하여 커스텀 모델을 정의하고, 주어진 장치로 이동합니다.
:param device_name: 모델을 배치할 장치 ('cpu' 또는 'cuda' 등). 기본값은 'cpu'.
:return: 정의된 모델을 주어진 장치에 배치한 상태로 반환.
"""
# 주어진 장치 이름에 해당하는 장치 객체를 생성합니다.
device = torch.device(device_name)
# 사전 학습된 VGG19 모델을 로드합니다.
model = models.vgg19(pretrained=True)
# VGG19의 기본 평균 풀링 레이어를 AdaptiveAvgPool2d로 교체하여 출력 크기를 (1, 1)로 조정합니다.
model.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))
# 기존의 완전 연결(FC) 레이어를 사용자 정의 레이어로 교체합니다.
model.classifier = nn.Sequential(
nn.Flatten(), # 4D 텐서를 2D 텐서로 변환합니다. (배치 크기, 채널, 높이, 너비 -> 배치 크기, 채널 * 높이 * 너비)
nn.Linear(512, 256), # 512개의 입력 노드를 256개의 출력 노드로 연결하는 선형 변환 레이어입니다.
nn.ReLU(), # 비선형 활성화 함수입니다. 입력 값을 비선형적으로 변환합니다.
nn.Linear(256, len(class_list)),# 256개의 입력 노드를 데이터셋의 클래스 수(len(class_list))에 맞게 출력하는 선형 변환 레이어입니다.
nn.Softmax(dim=1) # 출력값을 확률로 변환하는 소프트맥스 함수입니다. 클래스 차원(dim=1)에서 적용됩니다.
)
# 모델을 주어진 장치로 이동시킵니다 (CPU 또는 GPU).
return model.to(device)
# 모델 정의 및 CPU 장치에 배치
model = build_vgg19_based_model(device_name = 'cpu')
model
|
![]() ![]() |
◼ 손실 함수 정의
loss_func = nn.CrossEntropyLoss(reduction='mean')
|
◼ SGD 최적화 알고리즘을 사용하여 모델의 파라미터를 업데이트하도록 설정
# 옵티마이저 정의
optimizer = torch.optim.SGD(model.parameters(),lr=1E-3, momentum=0.9)
|
lr(학습률)과 momentum(모멘텀)은 모델 학습의 효율성과 안정성을 조절하는 중요한 하이퍼파라미터 |
◼ 모델의 예측 결과와 실제 레이블을 비교하여 정확도 반환하기
@torch.no_grad() 데코레이터와 함께 제공된 get_accuracy 함수는 모델의 예측 정확도를 계산하는 데 사용됩니다.
@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
|
|
◼ 모델의 손실(loss)과 정확도(accuracy)를 계산하여 반환하기
train_one_epoch 함수는 주어진 데이터 로더(dataloaders)를 사용하여
모델을 학습하거나 평가하는 단일 에폭(epoch)을 수행합니다.
def train_one_epoch(dataloaders, model, optimizer, loss_func, device):
"""
주어진 데이터 로더를 사용하여 모델의 훈련 또는 검증 단일 에폭을 수행합니다.
:param dataloaders: 훈련 및 검증 데이터 로더를 포함하는 딕셔너리.
:param model: 학습할 모델.
:param optimizer: 모델의 파라미터를 업데이트할 최적화 알고리즘.
:param loss_func: 손실 함수.
:param device: 모델과 데이터를 이동할 장치(CPU 또는 GPU).
:return: 손실과 정확도를 포함하는 딕셔너리.
"""
losses = {} # 각 단계('train', 'val')에 대한 손실 값을 저장할 딕셔너리
accuracies = {} # 각 단계('train', 'val')에 대한 정확도 값을 저장할 딕셔너리
# 'train'과 'val' 두 단계 모두에 대해 반복합니다.
for tv in ['train', 'val']:
running_loss = 0.0 # 현재 에폭의 손실 값을 누적하기 위한 변수
running_correct = 0 # 현재 에폭의 정확도 값을 누적하기 위한 변수
# 훈련 모드 또는 평가 모드 설정
if tv == 'train':
model.train() # 훈련 모드: 드롭아웃(dropout)과 배치 정규화(batch normalization) 등이 활성화됩니다.
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) # 정확도를 계산하여 누적합니다.
# 훈련 모드일 때, 10 배치마다 손실 값을 출력합니다.
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# 각 단계('train', 'val')의 손실과 정확도 딕셔너리를 반환합니다.
|
◼ 모델의 상태를 저장하는 함수
def save_best_model(model_state, model_name, save_dir = './trained_model'):
"""
주어진 모델 상태를 파일로 저장합니다.
:param model_state: 저장할 모델의 상태 딕셔너리 (예: `model.state_dict()`).
:param model_name: 저장할 파일의 이름 (예: 'best_model.pth').
:param 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/'
# `train_data_dir`과 `val_data_dir`을 사용하여 훈련 및 검증 데이터에 대한 데이터 로더를 만듭니다.
dataloaders = build_dataloader(train_data_dir, val_data_dir)
# VGG19 기반의 모델을 생성하는 함수를 호출합니다
model = build_vgg19_based_model(device_name = device)
# 손실 함수를 정의합니다.
loss_func = nn.CrossEntropyLoss(reduction = 'mean')
# 최적화 기법을 설정합니다.
# Stochastic Gradient Descent(SGD) 알고리즘을 사용하여 모델의 파라미터를 업데이트합니다.
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']}")
# epoch 2번째 까진 정확도가 높아도 신빙성 없다는 판단.
# 에포크가 2 이상일 때만 정확도를 기준으로 모델을 저장합니다.
# 검증 정확도가 현재까지의 최고 정확도보다 높은 경우, 모델을 저장합니다
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}')
|
0/62 - Running loss: 1.093900442123413 10/62 - Running loss: 1.0953799486160278 20/62 - Running loss: 1.0953714847564697 30/62 - Running loss: 1.0796129703521729 40/62 - Running loss: 1.1127455234527588 50/62 - Running loss: 1.1119818687438965 60/62 - Running loss: 1.0700511932373047 1/10 - Train_Loss:1.0962051114728373, Val_Loss:1.0794648365540938 1/10 - Train_Accuracies:0.4032258064516129, Val_Loss:0.6818181818181818 0/62 - Running loss: 1.0641858577728271 10/62 - Running loss: 1.0683239698410034 20/62 - Running loss: 1.1290593147277832 30/62 - Running loss: 0.9145467877388 40/62 - Running loss: 1.0068436861038208 50/62 - Running loss: 0.7750745415687561 60/62 - Running loss: 0.7086907625198364 2/10 - Train_Loss:0.9750767061787267, Val_Loss:0.8264774129246221 2/10 - Train_Accuracies:0.6451612903225806, Val_Loss:0.7272727272727273 0/62 - Running loss: 0.7580134868621826 10/62 - Running loss: 0.8338483572006226 20/62 - Running loss: 0.5556808710098267 30/62 - Running loss: 0.835498571395874 40/62 - Running loss: 0.8826307058334351 50/62 - Running loss: 0.794944167137146 60/62 - Running loss: 0.6320281624794006 3/10 - Train_Loss:0.78758694567988, Val_Loss:1.0869710969202446 3/10 - Train_Accuracies:0.8145161290322581, Val_Loss:0.42424242424242425 0/62 - Running loss: 1.1429064273834229 10/62 - Running loss: 0.5522404909133911 20/62 - Running loss: 0.804008960723877 30/62 - Running loss: 0.8015300035476685 40/62 - Running loss: 0.7871673703193665 50/62 - Running loss: 0.5652523040771484 60/62 - Running loss: 1.012768030166626 4/10 - Train_Loss:0.8541934249862548, Val_Loss:0.9109374718232588 4/10 - Train_Accuracies:0.7258064516129032, Val_Loss:0.6515151515151515 0/62 - Running loss: 0.7917495965957642 10/62 - Running loss: 0.5842599868774414 20/62 - Running loss: 0.8373899459838867 30/62 - Running loss: 0.5514838695526123 40/62 - Running loss: 0.5688213109970093 50/62 - Running loss: 0.6073175668716431 60/62 - Running loss: 0.7852078676223755 5/10 - Train_Loss:0.728634137299753, Val_Loss:0.6813537595850049 5/10 - Train_Accuracies:0.8669354838709677, Val_Loss:0.9090909090909091 0/62 - Running loss: 0.5629498958587646 10/62 - Running loss: 0.5740698575973511 20/62 - Running loss: 0.8823028802871704 30/62 - Running loss: 0.741658091545105 40/62 - Running loss: 0.5614508390426636 50/62 - Running loss: 0.8062911629676819 60/62 - Running loss: 0.6302551627159119 6/10 - Train_Loss:0.6409576785179877, Val_Loss:0.7088807613560648 6/10 - Train_Accuracies:0.9354838709677419, Val_Loss:0.8333333333333334 0/62 - Running loss: 0.6321278810501099 10/62 - Running loss: 0.6091440916061401 20/62 - Running loss: 0.8005079030990601 30/62 - Running loss: 0.785637617111206 40/62 - Running loss: 0.6309155225753784 50/62 - Running loss: 0.7838208675384521 60/62 - Running loss: 0.5551669597625732 7/10 - Train_Loss:0.6466519044291589, Val_Loss:0.6311718956990675 7/10 - Train_Accuracies:0.9596774193548387, Val_Loss:0.9090909090909091 0/62 - Running loss: 0.7726355791091919 10/62 - Running loss: 0.5523151159286499 20/62 - Running loss: 0.5582350492477417 30/62 - Running loss: 0.5521222352981567 40/62 - Running loss: 0.6644344329833984 50/62 - Running loss: 0.5515321493148804 60/62 - Running loss: 0.8015098571777344 8/10 - Train_Loss:0.6385487750653298, Val_Loss:0.7524168328805403 8/10 - Train_Accuracies:0.9516129032258065, Val_Loss:0.7878787878787878 0/62 - Running loss: 0.5660336017608643 10/62 - Running loss: 0.5527616143226624 20/62 - Running loss: 0.5515630841255188 30/62 - Running loss: 0.5783404111862183 40/62 - Running loss: 0.5528625249862671 50/62 - Running loss: 0.5530462265014648 60/62 - Running loss: 0.5514616966247559 9/10 - Train_Loss:0.6024611025087295, Val_Loss:0.5913251114614082 9/10 - Train_Accuracies:0.9637096774193549, Val_Loss:0.9545454545454546 0/62 - Running loss: 0.5514703989028931 10/62 - Running loss: 0.5519660711288452 20/62 - Running loss: 0.5795284509658813 30/62 - Running loss: 0.5515310168266296 40/62 - Running loss: 0.551509439945221 50/62 - Running loss: 0.5527869462966919 60/62 - Running loss: 0.5527551770210266 10/10 - Train_Loss:0.5850859534355902, Val_Loss:0.805530337673245 10/10 - Train_Accuracies:0.9879032258064516, Val_Loss:0.7424242424242424 Best Accuracy: 0.9545454545454546 ![]() |
◼ 학습 및 검증 과정에서의 손실(loss)과 정확도(accuracy)의 변화를 시각화
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()
|
![]() |
◼ 저장된 모델의 체크포인트를 로드하고, 이를 사용하여 모델을 평가 모드로 전환
ckpt = torch.load('./trained_model/model_04.pth') # 가장 좋은 모델 path
model = build_vgg19_based_model()
model.load_state_dict(ckpt)
model.eval()
|
VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (17): ReLU(inplace=True) (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (24): ReLU(inplace=True) (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (26): ReLU(inplace=True) (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (31): ReLU(inplace=True) (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (33): ReLU(inplace=True) (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (35): ReLU(inplace=True) (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(1, 1)) (classifier): Sequential( (0): Flatten(start_dim=1, end_dim=-1) (1): Linear(in_features=512, out_features=256, bias=True) (2): ReLU() (3): Linear(in_features=256, out_features=3, bias=True) (4): Softmax(dim=1) ) ) |
◼ 테스트 데이터셋을 준비하고 클래스 목록을 정의하기
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
|
20 |
◼ 이미지를 전처리하는 함수 preprocess_image를 정의하기
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
|
◼ 주어진 이미지를 모델에 입력하고 예측된 레이블을 반환하는 함수 'model_predict' 정의하기
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):
"""
선택한 인덱스에 해당하는 이미지 샘플을 로드하고, 모델의 예측 결과를 시각화하는 함수입니다.
:param index: 슬라이더를 통해 선택한 이미지 샘플의 인덱스
"""
# 선택한 인덱스에 해당하는 이미지를 RGB 형식으로 로드합니다.
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()
|
![]() ![]() |
'AI > 컴퓨터 비전' 카테고리의 다른 글
@. 과제 (0) | 2024.08.05 |
---|---|
13. Faster R-CNN | 객체 탐지 (0) | 2024.07.25 |
11. OCR (1) | 2024.07.23 |
10. 레이블링 (2) | 2024.07.23 |
09. 모폴로지 변환 (0) | 2024.07.23 |